From 3e76b7ff1cfa082406f650c9990bc294a1797294 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Mon, 26 Jan 2026 04:10:38 +0100 Subject: [PATCH] Fixes to optional rethrow in expressions. --- src/compiler/llvm_codegen_expr.c | 35 +++-- src/compiler/llvm_codegen_internal.h | 1 + src/compiler/llvm_codegen_stmt.c | 5 +- .../expressions/optional_in_cmp.c3t | 144 +++++++++++++++++ .../expressions/optional_in_expr.c3t | 146 ++++++++++++++++++ 5 files changed, 317 insertions(+), 14 deletions(-) create mode 100644 test/test_suite/expressions/optional_in_cmp.c3t create mode 100644 test/test_suite/expressions/optional_in_expr.c3t diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index f13c522ad..f18816db0 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -3317,6 +3317,7 @@ static void llvm_emit_ptr_comparison(GenContext *c, BEValue *result, BEValue *lh { llvm_value_rvalue(c, lhs); llvm_value_rvalue(c, rhs); + RETURN_ON_EMPTY_BLOCK(result); LLVMValueRef lhs_value = lhs->value; LLVMValueRef rhs_value = rhs->value; LLVMValueRef val; @@ -3545,7 +3546,7 @@ static inline void llvm_emit_fp_vector_compare(GenContext *c, BEValue *be_value, llvm_value_set(be_value, cmp, type_bool); } -static inline void llvm_emit_bool_vector_compare(GenContext *c, BEValue *be_value, BEValue *lhs, BEValue *rhs, BinaryOp binary_op, unsigned len) +static inline void llvm_emit_bool_vector_compare(GenContext *c, BEValue *result, BEValue *lhs, BEValue *rhs, BinaryOp binary_op, unsigned len) { LLVMTypeRef bool_vec = LLVMVectorType(c->bool_type, len); LLVMTypeRef load_vec = LLVMVectorType(c->byte_type, len); @@ -3564,10 +3565,10 @@ static inline void llvm_emit_bool_vector_compare(GenContext *c, BEValue *be_valu { cmp = llvm_emit_call_intrinsic(c, intrinsic_id.vector_reduce_or, &bool_vec, 1, &cmp, 1); } - llvm_value_set(be_value, cmp, type_bool); + llvm_value_set(result, cmp, type_bool); } -static void llvm_emit_array_comp(GenContext *c, BEValue *be_value, BEValue *lhs, BEValue *rhs, BinaryOp binary_op) +static void llvm_emit_array_comp(GenContext *c, BEValue *result, BEValue *lhs, BEValue *rhs, BinaryOp binary_op) { Type *array_base = type_flatten(lhs->type->array.base); switch (array_base->type_kind) @@ -3585,8 +3586,9 @@ static void llvm_emit_array_comp(GenContext *c, BEValue *be_value, BEValue *lhs, MEMCMP: llvm_value_addr(c, lhs); llvm_value_addr(c, rhs); - llvm_emit_memcmp(c, be_value, lhs->value, rhs->value, llvm_const_int(c, type_usz, type_size(lhs->type))); - llvm_emit_int_comp_zero(c, be_value, be_value, binary_op); + RETURN_ON_EMPTY_BLOCK(result); + llvm_emit_memcmp(c, result, lhs->value, rhs->value, llvm_const_int(c, type_usz, type_size(lhs->type))); + llvm_emit_int_comp_zero(c, result, result, binary_op); return; case VECTORS: if (is_power_of_two(array_base->array.len) && !type_flat_is_floatlike(array_base->array.base)) goto MEMCMP; @@ -3625,12 +3627,12 @@ MEMCMP: { if (array_base_type == type_bool) { - llvm_emit_bool_vector_compare(c, be_value, lhs, rhs, binary_op, len); + llvm_emit_bool_vector_compare(c, result, lhs, rhs, binary_op, len); return; } if (type_is_float(array_base_type)) { - llvm_emit_fp_vector_compare(c, be_value, lhs, rhs, binary_op, array_base_type, len); + llvm_emit_fp_vector_compare(c, result, lhs, rhs, binary_op, array_base_type, len); return; } LLVMBasicBlockRef blocks[17]; @@ -3661,7 +3663,7 @@ MEMCMP: blocks[len] = ok_block; LLVMValueRef phi = LLVMBuildPhi(c->builder, c->bool_type, "array_cmp_phi"); LLVMAddIncoming(phi, value_block, blocks, len + 1); - llvm_value_set(be_value, phi, type_bool); + llvm_value_set(result, phi, type_bool); return; } @@ -3695,13 +3697,14 @@ MEMCMP: llvm_emit_block(c, exit); LLVMValueRef success = LLVMConstInt(c->bool_type, want_match ? 1 : 0, false); LLVMValueRef failure = LLVMConstInt(c->bool_type, want_match ? 0 : 1, false); - llvm_new_phi(c, be_value, "array_cmp_phi", type_bool, success, comparison_phi, failure, loop_begin_phi); + llvm_new_phi(c, result, "array_cmp_phi", type_bool, success, comparison_phi, failure, loop_begin_phi); } -static void llvm_emit_float_comp(GenContext *c, BEValue *be_value, BEValue *lhs, BEValue *rhs, BinaryOp binary_op, Type *vector_type) +static void llvm_emit_float_comp(GenContext *c, BEValue *result, BEValue *lhs, BEValue *rhs, BinaryOp binary_op, Type *vector_type) { llvm_value_rvalue(c, lhs); llvm_value_rvalue(c, rhs); + RETURN_ON_EMPTY_BLOCK(result); LLVMValueRef lhs_value = lhs->value; LLVMValueRef rhs_value = rhs->value; LLVMValueRef val; @@ -3732,10 +3735,10 @@ static void llvm_emit_float_comp(GenContext *c, BEValue *be_value, BEValue *lhs, } if (vector_type) { - llvm_convert_vector_comparison(c, be_value, val, vector_type, BINARYOP_EQ == binary_op); + llvm_convert_vector_comparison(c, result, val, vector_type, BINARYOP_EQ == binary_op); return; } - llvm_value_set(be_value, val, type_bool); + llvm_value_set(result, val, type_bool); } void llvm_emit_lhs_is_subtype(GenContext *c, BEValue *result, BEValue *lhs, BEValue *rhs) @@ -3779,6 +3782,7 @@ void llvm_emit_comp(GenContext *c, BEValue *result, BEValue *lhs, BEValue *rhs, case ALL_INTS: llvm_value_rvalue(c, lhs); llvm_value_rvalue(c, rhs); + RETURN_ON_EMPTY_BLOCK(result); llvm_emit_int_comp_raw(c, result, lhs->type, rhs->type, lhs->value, rhs->value, binary_op); return; case ALL_FLOATS: @@ -4092,13 +4096,20 @@ void llvm_emit_binary(GenContext *c, BEValue *be_value, Expr *expr, BEValue *lhs } llvm_emit_expr(c, &lhs, exprptr(expr->binary_expr.left)); } + RETURN_ON_EMPTY_BLOCK(be_value); + // We need the rvalue. llvm_fold_for_compare(c, &lhs); + RETURN_ON_EMPTY_BLOCK(be_value); // Evaluate rhs BEValue rhs; llvm_emit_expr(c, &rhs, exprptr(expr->binary_expr.right)); + RETURN_ON_EMPTY_BLOCK(be_value); + llvm_fold_for_compare(c, &rhs); + RETURN_ON_EMPTY_BLOCK(be_value); + EMIT_EXPR_LOC(c, expr); // Comparison <=> if (binary_op >= BINARYOP_GT && binary_op <= BINARYOP_EQ) diff --git a/src/compiler/llvm_codegen_internal.h b/src/compiler/llvm_codegen_internal.h index ad4a78ab4..626178e5e 100644 --- a/src/compiler/llvm_codegen_internal.h +++ b/src/compiler/llvm_codegen_internal.h @@ -595,6 +595,7 @@ void llvm_emit_debug_local_var(GenContext *c, Decl *var); #define PUSH_DEFER_ERROR(val__) LLVMValueRef def_err__ = c->defer_error_var; c->defer_error_var = val__ #define POP_DEFER_ERROR() c->defer_error_var = def_err__ #define RETURN_ON_EMPTY_BLOCK(value__) do { if (!c->current_block) { llvm_value_set_empty(value__); return; }} while(0) +#define RETURN_ON_EMPTY_BLOCK_VOID() do { if (!c->current_block) { return; }} while(0) LLVMAtomicOrdering llvm_atomic_ordering(Atomicity atomicity); diff --git a/src/compiler/llvm_codegen_stmt.c b/src/compiler/llvm_codegen_stmt.c index c08e5efd4..ec20f323b 100644 --- a/src/compiler/llvm_codegen_stmt.c +++ b/src/compiler/llvm_codegen_stmt.c @@ -724,10 +724,11 @@ static void llvm_emit_switch_body_if_chain(GenContext *c, else { llvm_emit_comp(c, &equals, &be_value, switch_value, BINARYOP_EQ); + RETURN_ON_EMPTY_BLOCK_VOID(); } } next = llvm_basic_block_new(c, "next_if"); - llvm_emit_cond_br(c, &equals, block, next); + if (c->current_block) llvm_emit_cond_br(c, &equals, block, next); if (case_stmt->case_stmt.body) { llvm_emit_block(c, block); @@ -748,7 +749,6 @@ static void llvm_emit_switch_body_if_chain(GenContext *c, llvm_emit_br(c, exit_block); } llvm_emit_block(c, exit_block); - return; } static LLVMValueRef llvm_emit_switch_jump_stmt(GenContext *c, @@ -1069,6 +1069,7 @@ void llvm_emit_switch(GenContext *c, Ast *ast) { // Regular switch llvm_emit_cond(c, &switch_value, expr, false); + RETURN_ON_EMPTY_BLOCK_VOID(); } else { diff --git a/test/test_suite/expressions/optional_in_cmp.c3t b/test/test_suite/expressions/optional_in_cmp.c3t new file mode 100644 index 000000000..7535b04e3 --- /dev/null +++ b/test/test_suite/expressions/optional_in_cmp.c3t @@ -0,0 +1,144 @@ +// #target: macos-x64 +module test; +faultdef FOO_FAULT; +macro bool? maybe_throw() => FOO_FAULT~; +macro int? maybe_throw_int() => FOO_FAULT~; +macro int[2]? maybe_throw_array() => FOO_FAULT~; +macro int[<2>]? maybe_throw_vec() => FOO_FAULT~; +macro int[]? maybe_throw_slice() => FOO_FAULT~; +macro float? maybe_throw_float() => FOO_FAULT~; +macro void*? maybe_throw_ptr() => FOO_FAULT~; + +fn void? throwme1() +{ + bool x = maybe_throw()! == true; +} + +fn void? throwme2() +{ + bool x = maybe_throw_int()! == 1; +} + +fn void? throwme3() +{ + bool x = maybe_throw_array()! == (int[2]){}; +} + +fn void? throwme4() +{ + bool x = maybe_throw_slice()! == (int[]) {}; +} + +fn void? throwme5() +{ + bool x = maybe_throw_vec()! == (int[<2>]){}; +} + +fn void? throwme6() +{ + bool x = maybe_throw_float()! == 1.0; +} + +fn void? throwme7() +{ + bool x = maybe_throw_ptr()! == null; +} + +fn int main() +{ + (void)throwme1(); + (void)throwme2(); + (void)throwme3(); + (void)throwme4(); + (void)throwme5(); + (void)throwme6(); + (void)throwme7(); + return 0; +} + +/* #expect: test.ll + +define i64 @test.throwme1() #0 { +entry: + %x = alloca i8, align 1 + %error_var = alloca i64, align 8 + store i64 ptrtoint (ptr @test.FOO_FAULT 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 +} + +define i64 @test.throwme2() #0 { +entry: + %x = alloca i8, align 1 + %error_var = alloca i64, align 8 + store i64 ptrtoint (ptr @test.FOO_FAULT 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 +} + +define i64 @test.throwme3() #0 { +entry: + %x = alloca i8, align 1 + %error_var = alloca i64, align 8 + store i64 ptrtoint (ptr @test.FOO_FAULT 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 +} + +define i64 @test.throwme4() #0 { +entry: + %x = alloca i8, align 1 + %error_var = alloca i64, align 8 + store i64 ptrtoint (ptr @test.FOO_FAULT 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 +} + +define i64 @test.throwme5() #0 { +entry: + %x = alloca i8, align 1 + %error_var = alloca i64, align 8 + store i64 ptrtoint (ptr @test.FOO_FAULT 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 +} + +define i64 @test.throwme6() #0 { +entry: + %x = alloca i8, align 1 + %error_var = alloca i64, align 8 + store i64 ptrtoint (ptr @test.FOO_FAULT 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 +} + +define i64 @test.throwme7() #0 { +entry: + %x = alloca i8, align 1 + %error_var = alloca i64, align 8 + store i64 ptrtoint (ptr @test.FOO_FAULT 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 +} + diff --git a/test/test_suite/expressions/optional_in_expr.c3t b/test/test_suite/expressions/optional_in_expr.c3t new file mode 100644 index 000000000..c3cf5e1ef --- /dev/null +++ b/test/test_suite/expressions/optional_in_expr.c3t @@ -0,0 +1,146 @@ +// #target: macos-x64 +module test; +faultdef FOO_FAULT; +macro bool? maybe_throw() => FOO_FAULT~; +macro int? maybe_throw_int() => FOO_FAULT~; + +fn void? throwme1() +{ + if (maybe_throw()!); +} + +fn void? throwme2() +{ + switch (maybe_throw_int()!) { case 0: break; }; +} + +fn void? throwme3() +{ + switch { case maybe_throw()!: break; }; +} +fn void? throwme4() +{ + while (maybe_throw()!); +} + +fn void? throwme5() +{ + for (maybe_throw()!;;); +} + +fn void? throwme6() +{ + for (;maybe_throw()!, true;); +} + +fn void? throwme7() +{ + for (;;maybe_throw()!); +} + +fn int main() +{ + (void)throwme1(); + (void)throwme2(); + (void)throwme3(); + (void)throwme4(); + (void)throwme5(); + (void)throwme6(); + (void)throwme7(); + return 0; +} + +/* #expect: test.ll + +define i64 @test.throwme1() #0 { +entry: + %error_var = alloca i64, align 8 + store i64 ptrtoint (ptr @test.FOO_FAULT 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 +} + +define i64 @test.throwme2() #0 { +entry: + %error_var = alloca i64, align 8 + store i64 ptrtoint (ptr @test.FOO_FAULT 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 +} + +define i64 @test.throwme3() #0 { +entry: + %switch = alloca i8, align 1 + %error_var = alloca i64, align 8 + store i8 1, ptr %switch, align 1 + br label %switch.entry + +switch.entry: ; preds = %entry + %0 = load i8, ptr %switch, align 1 + %1 = trunc i8 %0 to i1 + store i64 ptrtoint (ptr @test.FOO_FAULT to i64), ptr %error_var, align 8 + br label %guard_block + +guard_block: ; preds = %switch.entry + %2 = load i64, ptr %error_var, align 8 + ret i64 %2 +} + +define i64 @test.throwme4() #0 { +entry: + %error_var = alloca i64, align 8 + br label %loop.cond + +loop.cond: ; preds = %entry + store i64 ptrtoint (ptr @test.FOO_FAULT to i64), ptr %error_var, align 8 + br label %guard_block + +guard_block: ; preds = %loop.cond + %0 = load i64, ptr %error_var, align 8 + ret i64 %0 +} + +define i64 @test.throwme5() #0 { +entry: + %error_var = alloca i64, align 8 + store i64 ptrtoint (ptr @test.FOO_FAULT 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 +} + +define i64 @test.throwme6() #0 { +entry: + %error_var = alloca i64, align 8 + br label %loop.cond + +loop.cond: ; preds = %entry + store i64 ptrtoint (ptr @test.FOO_FAULT to i64), ptr %error_var, align 8 + br label %guard_block + +guard_block: ; preds = %loop.cond + %0 = load i64, ptr %error_var, align 8 + ret i64 %0 +} + +define i64 @test.throwme7() #0 { +entry: + %error_var = alloca i64, align 8 + br label %loop.body + +loop.body: ; preds = %entry + store i64 ptrtoint (ptr @test.FOO_FAULT to i64), ptr %error_var, align 8 + br label %guard_block + +guard_block: ; preds = %loop.body + %0 = load i64, ptr %error_var, align 8 + ret i64 %0 +} \ No newline at end of file