From 0fdd6bdc811650da35c7bfa16897db7ba18f8e1d Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Sun, 25 Jan 2026 00:52:57 +0100 Subject: [PATCH] - Early exit in macro call crashes codegen #2820 --- releasenotes.md | 1 + src/compiler/llvm_codegen.c | 1 + src/compiler/llvm_codegen_expr.c | 9 ++- .../assert/unreachable_macro_fn.c3t | 4 -- .../test_suite/macros/macro_with_exit_arg.c3t | 63 +++++++++++++++++++ 5 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 test/test_suite/macros/macro_with_exit_arg.c3t diff --git a/releasenotes.md b/releasenotes.md index b13d0f18b..ec7de38a0 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -123,6 +123,7 @@ - Compile time dereference of a constant slice was too generous #2821 - Constant deref of subscript had inserted checks #2818 - Raw vaargs with optional return not lowered correctly #2819 +- Early exit in macro call crashes codegen #2820 ### Stdlib changes - Add `ThreadPool` join function to wait for all threads to finish in the pool without destroying the threads. diff --git a/src/compiler/llvm_codegen.c b/src/compiler/llvm_codegen.c index 6c379c4f9..698781883 100644 --- a/src/compiler/llvm_codegen.c +++ b/src/compiler/llvm_codegen.c @@ -735,6 +735,7 @@ void gencontext_print_llvm_ir(GenContext *context) INLINE LLVMValueRef llvm_emit_alloca_internal(GenContext *c, LLVMTypeRef type, unsigned alignment, const char *name) { + ASSERT(c->current_block); ASSERT(LLVMGetTypeKind(type) != LLVMVoidTypeKind); ASSERT(!llvm_is_global_eval(c)); ASSERT(alignment > 0); diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index cbd6732ca..3f9d3bfff 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -1383,7 +1383,7 @@ void llvm_emit_ignored_expr(GenContext *c, Expr *expr) llvm_emit_expr(c, &value, expr); EMIT_EXPR_LOC(c, expr); // We only optimize if there is no instruction the current block - if (!LLVMGetFirstInstruction(c->current_block)) + if (c->current_block && !LLVMGetFirstInstruction(c->current_block)) { llvm_prune_optional(c, discard_fail); } @@ -6011,6 +6011,11 @@ static void llvm_emit_call_expr(GenContext *c, BEValue *result_value, Expr *expr llvm_emit_statement_chain(c, expr->call_expr.function_contracts); } + if (!llvm_get_current_block_if_in_use(c)) + { + llvm_value_set(result_value, NULL, type_void); + return; + } // 1. Dynamic dispatch. if (expr->call_expr.is_dynamic_dispatch) { @@ -6278,6 +6283,7 @@ static inline void llvm_emit_macro_block(GenContext *c, BEValue *be_value, Expr BEValue value; c->debug.block_stack = old_inline_location; llvm_emit_expr(c, &value, init_expr); + if (!c->current_block) goto EARLY_EXIT; if (!val->alignment) val->alignment = type_abi_alignment(val->type); if (llvm_value_is_addr(&value) || val->var.is_written || val->var.is_addr || llvm_use_accurate_debug_info(c)) { @@ -6297,6 +6303,7 @@ static inline void llvm_emit_macro_block(GenContext *c, BEValue *be_value, Expr { llvm_emit_unreachable(c); } +EARLY_EXIT: if (!c->current_block) { llvm_emit_block(c, llvm_basic_block_new(c, "after_macro")); diff --git a/test/test_suite/assert/unreachable_macro_fn.c3t b/test/test_suite/assert/unreachable_macro_fn.c3t index 113d36c3e..d5f8e0dd6 100644 --- a/test/test_suite/assert/unreachable_macro_fn.c3t +++ b/test/test_suite/assert/unreachable_macro_fn.c3t @@ -62,8 +62,4 @@ define void @test.tester_test2() #0 { entry: %blockret = alloca i32, align 4 unreachable - -after_macro: ; No predecessors! - call void @test.tester(i32 poison) - ret void } \ No newline at end of file diff --git a/test/test_suite/macros/macro_with_exit_arg.c3t b/test/test_suite/macros/macro_with_exit_arg.c3t new file mode 100644 index 000000000..3704064e0 --- /dev/null +++ b/test/test_suite/macros/macro_with_exit_arg.c3t @@ -0,0 +1,63 @@ +// #target: macos-x64 +module test; +faultdef OH_NO; +const C = 1; +fn void main() => do_thing()!!; + +fn void? do_thing() +{ + int* a_value = mem::alloc_array(int, may_fault()!); +} +macro may_fault() +{ + return OH_NO~; +} + +/* #expect: test.ll + +define void @test.main() #0 { +entry: + %error_var = alloca i64, align 8 + %varargslots = alloca [1 x %any], align 16 + %indirectarg = alloca %"any[]", align 8 + %0 = call i64 @test.do_thing() + %not_err = icmp eq i64 %0, 0 + %1 = call i1 @llvm.expect.i1(i1 %not_err, i1 true) + br i1 %1, label %after_check, label %assign_optional + +assign_optional: ; preds = %entry + store i64 %0, ptr %error_var, align 8 + br label %panic_block + +after_check: ; preds = %entry + br label %noerr_block + +panic_block: ; preds = %assign_optional + %2 = insertvalue %any undef, ptr %error_var, 0 + %3 = insertvalue %any %2, i64 ptrtoint (ptr @"$ct.fault" to i64), 1 + store %any %3, ptr %varargslots, align 16 + %4 = insertvalue %"any[]" undef, ptr %varargslots, 0 + %"$$temp" = insertvalue %"any[]" %4, i64 1, 1 + store %"any[]" %"$$temp", ptr %indirectarg, align 8 + call void @std.core.builtin.panicf(ptr @.panic_msg, i64 36, ptr @.file, i64 22, ptr @.func, i64 4, i32 4, ptr byval(%"any[]") align 8 %indirectarg) #2 + unreachable + +noerr_block: ; preds = %after_check + ret void +} + +define i64 @test.do_thing() #0 { +entry: + %a_value = alloca ptr, align 8 + %error_var = alloca i64, align 8 + store i64 ptrtoint (ptr @test.OH_NO to i64), ptr %error_var, align 8 + br label %guard_block + +guard_block: ; preds = %entry + %0 = load i64, ptr %error_var, align 8 + ret i64 %0 + +after_macro: ; No predecessors! + store ptr poison, ptr %a_value, align 8 + ret i64 0 +}