diff --git a/src/Makefile b/src/Makefile index 832f0b0ae71e4..b77627d8e5b85 100644 --- a/src/Makefile +++ b/src/Makefile @@ -112,6 +112,14 @@ ifeq ($(OS),WINNT) SRCS += win32_ucontext endif +# Assembly sources for minimal setjmp (x86_64 Linux only for now) +ASM_SRCS := +ifeq ($(OS),Linux) +ifeq ($(ARCH),x86_64) +ASM_SRCS += _jlsetjmp +endif +endif + ifeq ($(WITH_DTRACE),1) DTRACE_HEADERS := uprobes.h.gen ifneq ($(OS),Darwin) @@ -229,6 +237,12 @@ CG_RELEASE_LIBS := $(COMMON_LIBPATHS) $(CG_LIBS) -ljulia -ljulia-internal OBJS := $(SRCS:%=$(BUILDDIR)/%.o) DOBJS := $(SRCS:%=$(BUILDDIR)/%.dbg.obj) +# Assembly object files +ASM_OBJS := $(ASM_SRCS:%=$(BUILDDIR)/%.o) +ASM_DOBJS := $(ASM_SRCS:%=$(BUILDDIR)/%.dbg.obj) +OBJS += $(ASM_OBJS) +DOBJS += $(ASM_DOBJS) + CODEGEN_OBJS := $(CODEGEN_SRCS:%=$(BUILDDIR)/%.o) CODEGEN_DOBJS := $(CODEGEN_SRCS:%=$(BUILDDIR)/%.dbg.obj) @@ -313,6 +327,11 @@ $(BUILDDIR)/%.o: $(SRCDIR)/%.cpp $(SRCDIR)/llvm-version.h $(HEADERS) $(LLVM_CONF @$(call PRINT_CC, $(CXX) $(LLVM_CXXFLAGS) $(SHIPFLAGS) $(JCPPFLAGS) $(JCXXFLAGS) $(CXX_DISABLE_ASSERTION) -c $< -o $@) $(BUILDDIR)/%.dbg.obj: $(SRCDIR)/%.cpp $(SRCDIR)/llvm-version.h $(HEADERS) $(LLVM_CONFIG_ABSOLUTE) | $(BUILDDIR) @$(call PRINT_CC, $(CXX) $(LLVM_CXXFLAGS) $(DEBUGFLAGS) $(JCPPFLAGS) $(JCXXFLAGS) -c $< -o $@) +# Assembly source file rules +$(BUILDDIR)/%.o: $(SRCDIR)/%.S | $(BUILDDIR) + @$(call PRINT_CC, $(CC) $(JCPPFLAGS) $(SHIPFLAGS) -c $< -o $@) +$(BUILDDIR)/%.dbg.obj: $(SRCDIR)/%.S | $(BUILDDIR) + @$(call PRINT_CC, $(CC) $(JCPPFLAGS) $(DEBUGFLAGS) -c $< -o $@) $(BUILDDIR)/%.o : $(SRCDIR)/%.d @$(call PRINT_DTRACE, $(DTRACE) -G -s $< -o $@) $(BUILDDIR)/%.dbg.obj : $(SRCDIR)/%.d diff --git a/src/_jlsetjmp.S b/src/_jlsetjmp.S new file mode 100644 index 0000000000000..88698f7e80e85 --- /dev/null +++ b/src/_jlsetjmp.S @@ -0,0 +1,67 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +// Minimal setjmp/longjmp implementation for x86_64 Linux +// These are designed to work with the preserve_none calling convention, +// where all registers are caller-saved. This means we only need to save +// the stack pointer - the compiler will spill all live registers before +// calling setjmp. +// +// IMPORTANT: These functions ONLY work correctly when called with the +// preserve_none calling convention. Without preserve_none, callee-saved +// registers won't be restored properly after longjmp. + +#if defined(__x86_64__) && defined(__linux__) + +// Mark stack as non-executable +.section .note.GNU-stack,"",@progbits + +.text + +// ============================================================================ +// jl_minimal_setjmp / ijl_minimal_setjmp +// ============================================================================ +.p2align 4,0x90 +.globl ijl_minimal_setjmp +.globl jl_minimal_setjmp +.type ijl_minimal_setjmp,@function +.type jl_minimal_setjmp,@function +ijl_minimal_setjmp: +jl_minimal_setjmp: + // N.B: In preserve_none ABI, r12 is the first argument. + // r12 = pointer to buffer (three pointer-sized slots: rbp, rsp, rip) + // Save RSP as it was *before* the call instruction pushed the return address. + lea 8(%rsp), %rax // rax = original RSP (before call) + mov %rbp, 0(%r12) // save RBP to buffer[0] + mov %rax, 8(%r12) // save RSP to buffer[1] + mov (%rsp), %rax // rax = return address + mov %rax, 16(%r12) // save return address to buffer[2] + xor %eax, %eax // return 0 (setjmp returns 0 on first call) + ret +.size ijl_minimal_setjmp, . - ijl_minimal_setjmp +.size jl_minimal_setjmp, . - jl_minimal_setjmp + + +// ============================================================================ +// jl_minimal_longjmp / ijl_minimal_longjmp +// ============================================================================ +.p2align 4,0x90 +.globl ijl_minimal_longjmp +.globl jl_minimal_longjmp +.type ijl_minimal_longjmp,@function +.type jl_minimal_longjmp,@function +ijl_minimal_longjmp: +jl_minimal_longjmp: + // rdi = pointer to buffer (two pointer-sized slots: rsp, rip) + // esi = return value (passed to setjmp caller) + mov %esi, %eax // set return value + test %eax, %eax // longjmp must return non-zero + jne 1f + inc %eax // if val was 0, return 1 +1: + mov 0(%rdi), %rbp // restore RBP + mov 8(%rdi), %rsp // restore RSP (to pre-call value) + jmp *16(%rdi) // jump to saved return address +.size ijl_minimal_longjmp, . - ijl_minimal_longjmp +.size jl_minimal_longjmp, . - jl_minimal_longjmp + +#endif diff --git a/src/codegen.cpp b/src/codegen.cpp index ed427f999e8e3..a37e920f1e239 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1033,6 +1033,14 @@ static const auto jlenter_func = new JuliaFunction<>{ {T_pjlvalue, getPointerTy(C)}, false); }, nullptr, }; +static const auto jlentermin_func = new JuliaFunction<>{ + XSTR(jl_enter_min_handler), + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + return FunctionType::get(getVoidTy(C), + {T_pjlvalue, getPointerTy(C)}, false); }, + nullptr, +}; static const auto jl_current_exception_func = new JuliaFunction<>{ XSTR(jl_current_exception), [](LLVMContext &C) { return FunctionType::get(JuliaType::get_prjlvalue_ty(C), {JuliaType::get_pjlvalue_ty(C)}, false); }, @@ -1239,6 +1247,17 @@ static const auto jl_object_id__func = new JuliaFunction{ {T_size, PointerType::get(C, AddressSpace::Derived)}, false); }, nullptr, }; +static const auto setjmp_min_func = new JuliaFunction{ + XSTR(jl_minimal_setjmp), + [](LLVMContext &C, const Triple &T) { + return FunctionType::get(getInt32Ty(C), + {getPointerTy(C)}, false); + }, + [](LLVMContext &C) { return AttributeList::get(C, + Attributes(C, {Attribute::ReturnsTwice}), + AttributeSet(), + None); }, +}; static const auto setjmp_func = new JuliaFunction{ jl_setjmp_name, [](LLVMContext &C, const Triple &T) { @@ -6151,8 +6170,13 @@ static void emit_stmtpos(jl_codectx_t &ctx, jl_value_t *expr, int ssaval_result) } } ctx.builder.CreateCall(prepare_call(jlleave_noexcept_func), {get_current_task(ctx), ConstantInt::get(getInt32Ty(ctx.builder.getContext()), handler_to_end.size())}); +#ifdef JL_HAVE_MIN_SETJMP + auto *handler_sz64 = ConstantInt::get(Type::getInt64Ty(ctx.builder.getContext()), + sizeof(struct _jl_handler_min_setjmp)); +#else auto *handler_sz64 = ConstantInt::get(Type::getInt64Ty(ctx.builder.getContext()), - sizeof(jl_handler_t)); + sizeof(struct _jl_handler_setjmp)); +#endif for (AllocaInst *handler : handler_to_end) { ctx.builder.CreateLifetimeEnd(handler, handler_sz64); } @@ -9355,17 +9379,31 @@ static jl_llvm_functions_t ctx.ssavalue_assigned[cursor] = true; // Actually enter the exception frame auto ct = get_current_task(ctx); +#if JL_HAVE_MIN_SETJMP + auto *handler_sz64 = ConstantInt::get(Type::getInt64Ty(ctx.builder.getContext()), + sizeof(struct _jl_handler_min_setjmp)); + AllocaInst* ehbuff = emit_static_alloca(ctx, sizeof(struct _jl_handler_min_setjmp), Align(16)); +#else auto *handler_sz64 = ConstantInt::get(Type::getInt64Ty(ctx.builder.getContext()), - sizeof(jl_handler_t)); - AllocaInst* ehbuff = emit_static_alloca(ctx, sizeof(jl_handler_t), Align(16)); + sizeof(struct _jl_handler_setjmp)); + AllocaInst* ehbuff = emit_static_alloca(ctx, sizeof(struct _jl_handler_setjmp), Align(16)); +#endif ctx.eh_buffers[stmt] = ehbuff; ctx.builder.CreateLifetimeStart(ehbuff, handler_sz64); - ctx.builder.CreateCall(prepare_call(jlenter_func), {ct, ehbuff}); CallInst *sj; +#if JL_HAVE_MIN_SETJMP + ctx.builder.CreateCall(prepare_call(jlentermin_func), {ct, ehbuff}); + Value *jmpbuf = emit_ptrgep(ctx, ehbuff, offsetof(struct _jl_handler_min_setjmp, min_eh_ctx)); + sj = ctx.builder.CreateCall(prepare_call(setjmp_min_func), {jmpbuf}); + sj->setCallingConv(CallingConv::PreserveNone); +#else + Value *jmpbuf = emit_ptrgep(ctx, ehbuff, offsetof(struct _jl_handler_setjmp, eh_ctx)); + ctx.builder.CreateCall(prepare_call(jlenter_func), {ct, ehbuff}); if (ctx.emission_context.TargetTriple.isOSWindows()) - sj = ctx.builder.CreateCall(prepare_call(setjmp_func), {ehbuff}); + sj = ctx.builder.CreateCall(prepare_call(setjmp_func), {jmpbuf}); else - sj = ctx.builder.CreateCall(prepare_call(setjmp_func), {ehbuff, ConstantInt::get(Type::getInt32Ty(ctx.builder.getContext()), 0)}); + sj = ctx.builder.CreateCall(prepare_call(setjmp_func), {jmpbuf, ConstantInt::get(Type::getInt32Ty(ctx.builder.getContext()), 0)}); +#endif // We need to mark this on the call site as well. See issue #6757 sj->setCanReturnTwice(); Value *isz = ctx.builder.CreateICmpEQ(sj, ConstantInt::get(getInt32Ty(ctx.builder.getContext()), 0)); @@ -9994,6 +10032,9 @@ static void init_jit_functions(void) add_named_global(jlnew_func, &jl_new_structv); add_named_global(jlsplatnew_func, &jl_new_structt); add_named_global(setjmp_func, &jl_setjmp_f); +#if JL_HAVE_MIN_SETJMP + add_named_global(setjmp_min_func, &jl_minimal_setjmp); +#endif add_named_global(memcmp_func, &memcmp); add_named_global(jltypeerror_func, &jl_type_error); add_named_global(jlcheckassign_func, &jl_checked_assignment); @@ -10009,6 +10050,7 @@ static void init_jit_functions(void) add_named_global(jlmethod_func, &jl_method_def); add_named_global(jlgenericfunction_func, &jl_declare_const_gf); add_named_global(jlenter_func, &jl_enter_handler); + add_named_global(jlentermin_func, &jl_enter_min_handler); add_named_global(jl_current_exception_func, &jl_current_exception); add_named_global(jlleave_noexcept_func, &jl_pop_handler_noexcept); add_named_global(jlleave_func, &jl_pop_handler); diff --git a/src/interpreter.c b/src/interpreter.c index 6c0ea1bc16ab1..e862c2729c92a 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -469,7 +469,7 @@ static size_t eval_phi(jl_array_t *stmts, interpreter_state *s, size_t ns, size_ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, int toplevel) { - jl_handler_t __eh; + jl_handler_preferred_t __eh; size_t ns = jl_array_nrows(stmts); jl_task_t *ct = jl_current_task; @@ -506,7 +506,7 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, s->locals[jl_source_nslots(s->src) + id] = val; } else if (jl_is_enternode(stmt)) { - jl_enter_handler(ct, &__eh); + jl_enter_handler(ct, &__eh._handler); // This is a bit tricky, but supports the implementation of PhiC nodes. // They are conceptually slots, but the slot to store to doesn't get explicitly // mentioned in the store (aka the "UpsilonNode") (this makes them integrate more @@ -545,29 +545,29 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, // replaced later JL_GC_PUSH1(&scope); ct->scope = scope; - if (!jl_setjmp(__eh.eh_ctx, 0)) { - ct->eh = &__eh; + if (!JL_EH_SETJMP(__eh)) { + ct->eh = &__eh._handler; eval_body(stmts, s, next_ip, toplevel); jl_unreachable(); } JL_GC_POP(); } else { - if (!jl_setjmp(__eh.eh_ctx, 0)) { - ct->eh = &__eh; + if (!JL_EH_SETJMP(__eh)) { + ct->eh = &__eh._handler; eval_body(stmts, s, next_ip, toplevel); jl_unreachable(); } } if (s->continue_at) { // means we reached a :leave expression - jl_eh_restore_state_noexcept(ct, &__eh); + jl_eh_restore_state_noexcept(ct, &__eh._handler); ip = s->continue_at; s->continue_at = 0; continue; } else { // a real exception - jl_eh_restore_state(ct, &__eh); + jl_eh_restore_state(ct, &__eh._handler); ip = catch_ip; assert(jl_enternode_catch_dest(stmt) != 0); continue; @@ -617,8 +617,8 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, // leave happens during normal control flow, but we must // longjmp to pop the eval_body call for each enter. s->continue_at = next_ip; - asan_unpoison_task_stack(ct, &eh->eh_ctx); - jl_longjmp(eh->eh_ctx, 1); + asan_unpoison_eh_task_stack(ct, eh); + jl_eh_longjmp(eh); } } else if (head == jl_pop_exception_sym) { diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index d0cfb16062b7d..6b0015b23653e 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -106,6 +106,7 @@ XX(jl_egal) \ XX(jl_egal__bits) \ XX(jl_egal__bitstag) \ + XX(jl_eh_longjmp) \ XX(jl_eh_restore_state) \ XX(jl_eh_restore_state_noexcept) \ XX(jl_enter_handler) \ @@ -301,6 +302,8 @@ XX(jl_maxrss) \ XX(jl_method_def) \ XX(jl_method_instance_add_backedge) \ + XX(jl_minimal_longjmp) \ + XX(jl_minimal_setjmp) \ XX(jl_method_table_add_backedge) \ XX(jl_method_table_disable) \ XX(jl_method_table_for) \ diff --git a/src/julia.h b/src/julia.h index 9513dfb47941d..bcfd0b8331d97 100644 --- a/src/julia.h +++ b/src/julia.h @@ -35,6 +35,22 @@ #define jl_jmp_buf jmp_buf #endif +// Minimal setjmp buffer: stores stack pointer and return address. +// Used with preserve_none calling convention where the compiler spills all +// live registers before the call. +typedef struct { + void *rbp; // Base pointer + void *rsp; // Stack pointer (as it was before the call to setjmp) + void *rip; // Return address (where to resume after longjmp) +} jl_min_jmp_buf; + +// Check if minimal setjmp is available for this platform +#if defined(__x86_64__) && defined(__linux__) && !defined(_OS_WINDOWS_) +#define JL_HAVE_MIN_SETJMP 1 +#else +#define JL_HAVE_MIN_SETJMP 0 +#endif + // Define the largest size (bytes) of a properly aligned object that the // processor family (MAX_ATOMIC_SIZE) and compiler (MAX_POINTERATOMIC_SIZE) // typically supports without a lock (assumed to be at least a pointer size) @@ -2304,16 +2320,30 @@ JL_DLLEXPORT void jl_sigatomic_end(void); // tasks and exceptions ------------------------------------------------------- // info describing an exception handler +// The eh_ctx field can hold either a full jl_jmp_buf or a minimal jl_min_jmp_buf. +// When using minimal setjmp, the low bit of the buffer pointer (when passed to +// longjmp wrapper) indicates which type is in use. struct _jl_handler_t { - jl_jmp_buf eh_ctx; jl_gcframe_t *gcstack; jl_value_t *scope; struct _jl_handler_t *prev; int8_t gc_state; + int8_t min_jmp; // 1 if using minimal setjmp buffer, 0 otherwise size_t locks_len; sig_atomic_t defer_signal; jl_timing_block_t *timing_stack; size_t world_age; + // eh_ctx follows +}; + +struct _jl_handler_setjmp { + struct _jl_handler_t _handler; + jl_jmp_buf eh_ctx; // Full setjmp buffer +}; + +struct _jl_handler_min_setjmp { + struct _jl_handler_t _handler; + jl_min_jmp_buf min_eh_ctx; // Minimal setjmp buffer (stack pointer only) }; #define JL_TASK_STATE_RUNNABLE 0 @@ -2350,6 +2380,7 @@ JL_DLLEXPORT jl_value_t *jl_exception_occurred(void); JL_DLLEXPORT void jl_exception_clear(void) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_enter_handler(jl_task_t *ct, jl_handler_t *eh) JL_NOTSAFEPOINT ; +JL_DLLEXPORT void jl_enter_min_handler(jl_task_t *ct, jl_handler_t *eh) JL_NOTSAFEPOINT ; JL_DLLEXPORT void jl_eh_restore_state(jl_task_t *ct, jl_handler_t *eh); JL_DLLEXPORT void jl_eh_restore_state_noexcept(jl_task_t *ct, jl_handler_t *eh) JL_NOTSAFEPOINT; JL_DLLEXPORT void jl_pop_handler(jl_task_t *ct, int n) JL_NOTSAFEPOINT; @@ -2403,6 +2434,43 @@ extern void (*real_siglongjmp)(jmp_buf _Buf, int _Value); #endif #endif +// Minimal setjmp/longjmp declarations for x86_64 Linux +// These work with preserve_none calling convention where the compiler +// spills all live registers, so we only need to save the stack pointer. +#if JL_HAVE_MIN_SETJMP + +// Check if preserve_none calling convention is supported. +// This calling convention makes ALL registers caller-saved, which means the +// caller will spill any live registers before calling. This is essential for +// the minimal setjmp to work correctly. +#if defined(__has_attribute) && __has_attribute(preserve_none) +#define JL_HAVE_PRESERVE_NONE 1 +#define JL_PRESERVE_NONE __attribute__((preserve_none)) +typedef struct _jl_handler_min_setjmp jl_handler_preferred_t; +JL_DLLEXPORT int __attribute__((__nothrow__, __returns_twice__)) + jl_minimal_setjmp(jl_min_jmp_buf *buf) JL_PRESERVE_NONE; +#define JL_EH_SETJMP(eh) (eh._handler.min_jmp = 1,\ + jl_minimal_setjmp(&eh.min_eh_ctx)) +#else +typedef struct _jl_handler_setjmp jl_handler_preferred_t; +#define JL_HAVE_PRESERVE_NONE 0 +static inline int jl_minimal_setjmp(jl_min_jmp_buf *buf) +{ + abort(); // Not supported +} +#define JL_EH_SETJMP(eh) (eh._handler.min_jmp = 0,\ + jl_setjmp(&eh.eh_ctx, 0)) +#endif + +JL_DLLEXPORT void __attribute__((__nothrow__, __noreturn__)) + jl_minimal_longjmp(jl_min_jmp_buf *buf, int val); +#endif // JL_HAVE_MIN_SETJMP + +// Unified longjmp wrapper that handles both minimal and full buffers. +// Declared in task.c +JL_DLLEXPORT void __attribute__((__nothrow__, __noreturn__)) + jl_eh_longjmp(jl_handler_t *eh); + #ifdef __clang_gcanalyzer__ @@ -2426,17 +2494,20 @@ extern int had_exception; #else +// Use minimal setjmp when available and the compiler supports preserve_none. +// This saves memory by having the compiler spill only live registers instead +// of saving all callee-saved registers like traditional setjmp. #define JL_TRY \ - int i__try, i__catch; jl_handler_t __eh; jl_task_t *__eh_ct; \ + int i__try, i__catch; jl_handler_preferred_t __eh; jl_task_t *__eh_ct; \ __eh_ct = jl_current_task; \ size_t __excstack_state = jl_excstack_state(__eh_ct); \ - jl_enter_handler(__eh_ct, &__eh); \ - if (!jl_setjmp(__eh.eh_ctx, 0)) \ - for (i__try=1, __eh_ct->eh = &__eh; i__try; i__try=0, /* TRY BLOCK; */ jl_eh_restore_state_noexcept(__eh_ct, &__eh)) + jl_enter_handler(__eh_ct, &__eh._handler); \ + if (!JL_EH_SETJMP(__eh)) \ + for (i__try=1, __eh_ct->eh = &__eh._handler; i__try; i__try=0, /* TRY BLOCK; */ jl_eh_restore_state_noexcept(__eh_ct, &__eh._handler)) #define JL_CATCH \ else \ - for (i__catch=1, jl_eh_restore_state(__eh_ct, &__eh); i__catch; i__catch=0, /* CATCH BLOCK; */ jl_restore_excstack(__eh_ct, __excstack_state)) + for (i__catch=1, jl_eh_restore_state(__eh_ct, &__eh._handler); i__catch; i__catch=0, /* CATCH BLOCK; */ jl_restore_excstack(__eh_ct, __excstack_state)) #endif diff --git a/src/julia_internal.h b/src/julia_internal.h index 94036981f950a..87301f9f9b169 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -79,6 +79,7 @@ static inline void asan_unpoison_stack_memory(uintptr_t addr, size_t size) { } #else static inline void asan_unpoison_task_stack(jl_task_t *ct, jl_jmp_buf *buf) JL_NOTSAFEPOINT {} +static inline void asan_unpoison_eh_task_stack(jl_task_t *ct, jl_handler_t *eh) JL_NOTSAFEPOINT {} static inline void asan_unpoison_stack_memory(uintptr_t addr, size_t size) JL_NOTSAFEPOINT {} #endif #ifdef _COMPILER_MSAN_ENABLED_ diff --git a/src/rtutils.c b/src/rtutils.c index e73d0de6c69aa..f0c2bee8200e4 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -268,6 +268,23 @@ JL_DLLEXPORT void jl_enter_handler(jl_task_t *ct, jl_handler_t *eh) eh->gcstack = ct->gcstack; eh->scope = ct->scope; eh->gc_state = jl_atomic_load_relaxed(&ct->ptls->gc_state); + eh->min_jmp = 0; // Default to traditional setjmp; caller may override if using minimal setjmp + eh->locks_len = ct->ptls->locks.len; + eh->defer_signal = ct->ptls->defer_signal; + eh->world_age = ct->world_age; +#ifdef ENABLE_TIMINGS + eh->timing_stack = ct->ptls->timing_stack; +#endif +} + +JL_DLLEXPORT void jl_enter_min_handler(jl_task_t *ct, jl_handler_t *eh) +{ + // Must have no safepoint + eh->prev = ct->eh; + eh->gcstack = ct->gcstack; + eh->scope = ct->scope; + eh->gc_state = jl_atomic_load_relaxed(&ct->ptls->gc_state); + eh->min_jmp = 1; eh->locks_len = ct->ptls->locks.len; eh->defer_signal = ct->ptls->defer_signal; eh->world_age = ct->world_age; diff --git a/src/signals-unix.c b/src/signals-unix.c index 16e70ef0f764e..4a7cc74eba222 100644 --- a/src/signals-unix.c +++ b/src/signals-unix.c @@ -67,6 +67,8 @@ static int thread0_exit_count = 0; static void jl_exit_thread0(int signo, jl_bt_element_t *bt_data, size_t bt_size); int jl_simulate_longjmp(jl_jmp_buf mctx, bt_context_t *c) JL_NOTSAFEPOINT; +int jl_simulate_min_longjmp(jl_min_jmp_buf *buf, bt_context_t *c) JL_NOTSAFEPOINT; +static void jl_eh_longjmp_in_ctx(int sig, void *_ctx, jl_handler_t *eh); static void jl_longjmp_in_ctx(int sig, void *_ctx, jl_jmp_buf jmpbuf); #if !defined(_OS_DARWIN_) @@ -210,8 +212,8 @@ static void jl_throw_in_ctx(jl_task_t *ct, jl_value_t *e, int sig, void *sigctx) ptls->io_wait = 0; jl_handler_t *eh = ct->eh; if (eh != NULL) { - asan_unpoison_task_stack(ct, &eh->eh_ctx); - jl_longjmp_in_ctx(sig, sigctx, eh->eh_ctx); + asan_unpoison_eh_task_stack(ct, eh); + jl_eh_longjmp_in_ctx(sig, sigctx, eh); } else { jl_no_exc_handler(e, ct); @@ -1154,6 +1156,20 @@ static void fpe_handler(int sig, siginfo_t *info, void *context) jl_throw_in_ctx(ct, jl_diverror_exception, sig, context); } +static void jl_eh_longjmp_in_ctx(int sig, void *_ctx, jl_handler_t *eh) +{ + bt_context_t *ctx = jl_to_bt_context(_ctx); + if (eh->min_jmp ? + jl_simulate_min_longjmp(&((struct _jl_handler_min_setjmp*)eh)->min_eh_ctx, ctx) : + jl_simulate_longjmp(((struct _jl_handler_setjmp*)eh)->eh_ctx, ctx)) + return; + sigset_t sset; + sigemptyset(&sset); + sigaddset(&sset, sig); + pthread_sigmask(SIG_UNBLOCK, &sset, NULL); + jl_eh_longjmp(eh); +} + static void jl_longjmp_in_ctx(int sig, void *_ctx, jl_jmp_buf jmpbuf) { #if defined(_OS_DARWIN_) diff --git a/src/stackwalk.c b/src/stackwalk.c index 96f263dfd7f68..a5af88d41dd01 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -1358,6 +1358,20 @@ return 0; #endif } +int jl_simulate_min_longjmp(jl_min_jmp_buf *buf, bt_context_t *c) JL_NOTSAFEPOINT +{ +#if defined(_CPU_X86_64_) + // minimal longjmp for libunwind on x86_64 + mcontext_t *mc = &c->uc_mcontext; + mc->gregs[REG_RBP] = ptr_demangle((uintptr_t)buf->rbp); + mc->gregs[REG_RSP] = ptr_demangle((uintptr_t)buf->rsp); + mc->gregs[REG_RIP] = ptr_demangle((uintptr_t)buf->rip); + return 1; +#endif +return 0; + +} + JL_DLLEXPORT size_t jl_try_record_thread_backtrace(jl_ptls_t ptls2, jl_bt_element_t *bt_data, size_t max_bt_size) JL_NOTSAFEPOINT { int16_t tid = ptls2->tid; diff --git a/src/task.c b/src/task.c index 18d21b2343053..8e7a7281b1c36 100644 --- a/src/task.c +++ b/src/task.c @@ -773,6 +773,18 @@ JL_DLLEXPORT JL_NORETURN void jl_no_exc_handler(jl_value_t *e, jl_task_t *ct) #define pop_timings_stack() /* Nothing */ #endif +// Unified longjmp wrapper that handles both minimal and full buffers. +// The min_jmp field in the handler indicates which type of buffer is in use. +JL_DLLEXPORT void JL_NORETURN jl_eh_longjmp(jl_handler_t *eh) +{ +#if JL_HAVE_MIN_SETJMP + if (eh->min_jmp) { + jl_minimal_longjmp(&((struct _jl_handler_min_setjmp*)eh)->min_eh_ctx, 1); + } +#endif + jl_longjmp(((struct _jl_handler_setjmp*)eh)->eh_ctx, 1); +} + static void JL_NORETURN throw_internal(jl_task_t *ct, jl_value_t *exception JL_MAYBE_UNROOTED) { JL_GC_PUSH1(&exception); @@ -791,8 +803,8 @@ static void JL_NORETURN throw_internal(jl_task_t *ct, jl_value_t *exception JL_M jl_handler_t *eh = ct->eh; if (eh != NULL) { pop_timings_stack() - asan_unpoison_task_stack(ct, &eh->eh_ctx); - jl_longjmp(eh->eh_ctx, 1); + asan_unpoison_eh_task_stack(ct, eh); + jl_eh_longjmp(eh); } else { jl_no_exc_handler(exception, ct);