diff --git a/releasenotes.md b/releasenotes.md index e500b9f1c..6bdd4ce13 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -27,6 +27,7 @@ - Casting a distinct type based on a pointer to an `any` would accidentally be permitted. #2575 - `overflow_*` vector ops now correctly return a bool vector. - Regression vector ABI: npot vectors would load incorrectly from pointers and other things. #2576 +- Using `defer catch` with a (void), would cause an assertion. #2580 ### Stdlib changes diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 24c90b89c..cdc61c05b 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -1310,6 +1310,7 @@ typedef struct Expr *expr; // May be NULL AstId cleanup; AstId cleanup_fail; + bool cleanup_catch; BlockExit** block_exit_ref; // For block exits } AstReturnStmt; diff --git a/src/compiler/llvm_codegen_stmt.c b/src/compiler/llvm_codegen_stmt.c index 0e8b0341d..a6323cdb6 100644 --- a/src/compiler/llvm_codegen_stmt.c +++ b/src/compiler/llvm_codegen_stmt.c @@ -307,9 +307,16 @@ static inline void llvm_emit_return(GenContext *c, Ast *ast) static inline void llvm_emit_block_exit_return(GenContext *c, Ast *ast) { - BlockExit *exit = *ast->return_stmt.block_exit_ref; + // In the (void) case, we might not have a variable to handle the defer catch, if so we create it here. + BEValue error_out_ref = { .value = NULL }; + if (!exit->block_error_var && ast->return_stmt.cleanup_catch) + { + error_out_ref = llvm_emit_alloca_b(c, type_fault, "defer_block_fault"); + exit->block_error_var = error_out_ref.value; + } + PUSH_CATCH_VAR_BLOCK(exit->block_error_var, exit->block_optional_exit); LLVMBasicBlockRef err_cleanup_block = NULL; @@ -337,6 +344,7 @@ static inline void llvm_emit_block_exit_return(GenContext *c, Ast *ast) { llvm_store_to_ptr_aligned(c, exit->block_return_out, &return_value, type_alloca_alignment(return_value.type)); } + llvm_emit_statement_chain(c, cleanup); if (err_cleanup_block) @@ -352,6 +360,10 @@ static inline void llvm_emit_block_exit_return(GenContext *c, Ast *ast) { llvm_emit_jmp(c, exit->block_return_exit); } + if (error_out_ref.value) + { + exit->block_error_var = NULL; + } } diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index e2c752220..2a141d065 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -24,7 +24,7 @@ static inline bool sema_analyse_return_stmt(SemaContext *context, Ast *statement static inline bool sema_analyse_switch_stmt(SemaContext *context, Ast *statement); static inline bool sema_check_return_matches_opt_returns(SemaContext *context, Expr *ret_expr); -static inline bool sema_defer_has_try_or_catch(AstId defer_top, AstId defer_bottom); +static inline bool sema_defer_has_try_or_catch(AstId defer_top, AstId defer_bottom, bool *has_catch_ref); static inline bool sema_analyse_block_exit_stmt(SemaContext *context, Ast *statement); static inline bool sema_analyse_defer_stmt_body(SemaContext *context, Ast *statement); static inline bool sema_analyse_for_cond(SemaContext *context, ExprId *cond_ref, bool *infinite); @@ -440,17 +440,27 @@ static inline bool assert_create_from_contract(SemaContext *context, Ast *direct } // Check whether a defer chain contains a try or a catch. -static inline bool sema_defer_has_try_or_catch(AstId defer_top, AstId defer_bottom) +static inline bool sema_defer_has_try_or_catch(AstId defer_top, AstId defer_bottom, bool *has_catch_ref) { + bool has_try = false; while (defer_bottom != defer_top) { Ast *defer = astptr(defer_top); - if (defer->defer_stmt.is_catch || defer->defer_stmt.is_try) return true; + if (defer->defer_stmt.is_catch) + { + if (has_catch_ref) *has_catch_ref = true; + return true; + } + if (defer->defer_stmt.is_try) + { + has_try = true; + } defer_top = defer->defer_stmt.prev_defer; } - return false; + return has_try; } + // Print defers at return (from macro/block or from function) static inline void sema_inline_return_defers(SemaContext *context, Ast *stmt, AstId defer_bottom) { @@ -458,9 +468,11 @@ static inline void sema_inline_return_defers(SemaContext *context, Ast *stmt, As stmt->return_stmt.cleanup = context_get_defers(context, defer_bottom, true); // If we have an optional return, then we create a cleanup_fail + bool has_catch = false; if (stmt->return_stmt.expr && IS_OPTIONAL(stmt->return_stmt.expr) - && sema_defer_has_try_or_catch(context->active_scope.defer_last, context->block_return_defer)) + && sema_defer_has_try_or_catch(context->active_scope.defer_last, context->block_return_defer, &has_catch)) { + stmt->return_stmt.cleanup_catch = has_catch; stmt->return_stmt.cleanup_fail = context_get_defers(context, context->block_return_defer, false); return; } diff --git a/test/test_suite/defer/defer_void.c3t b/test/test_suite/defer/defer_void.c3t new file mode 100644 index 000000000..505165097 --- /dev/null +++ b/test/test_suite/defer/defer_void.c3t @@ -0,0 +1,133 @@ +// #target: macos-x64 +module test; +import std::io; + +fn void main() +{ + (void)works(); + (void)also_works(); + (void)doesnt_work(); + (void)also_doesnt_work(); +} +macro works() +{ + defer (catch err) io::printfn("got error when using something: %s", err); + return "hi".to_int() ?? NOT_FOUND?!; +} +macro also_works() +{ + defer (catch err) io::printfn("got error when using something: %s", err); + return NOT_FOUND?!; +} +macro doesnt_work() +{ + defer (catch err) io::printfn("got error when using something: %s", err); + return "hi".to_int() ?? NOT_FOUND?; +} +macro also_doesnt_work() +{ + defer (catch err) io::printfn("got error when using something: %s", err); + return NOT_FOUND?; +} + +/* #expect: test.ll + +define void @test.main() #0 { +entry: + %retparam = alloca i32, align 4 + %error_var = alloca i64, align 8 + %err = alloca i64, align 8 + %varargslots = alloca [1 x %any], align 16 + %retparam1 = alloca i64, align 8 + %error_var4 = alloca i64, align 8 + %err6 = alloca i64, align 8 + %varargslots7 = alloca [1 x %any], align 16 + %retparam8 = alloca i64, align 8 + %blockret = alloca i32, align 4 + %defer_block_fault = alloca i64, align 8 + %retparam11 = alloca i32, align 4 + %err16 = alloca i64, align 8 + %varargslots17 = alloca [1 x %any], align 16 + %retparam18 = alloca i64, align 8 + %defer_block_fault22 = alloca i64, align 8 + %err24 = alloca i64, align 8 + %varargslots25 = alloca [1 x %any], align 16 + %retparam26 = alloca i64, align 8 + %0 = call i64 @String.to_int(ptr %retparam, ptr @.str, i64 2, i32 10) + %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 %else_block + +after_check: ; preds = %entry + %2 = load i32, ptr %retparam, align 4 + br label %phi_block + +else_block: ; preds = %entry + store i64 ptrtoint (ptr @std.core.builtin.NOT_FOUND to i64), ptr %error_var, align 8 + br label %guard_block + +guard_block: ; preds = %else_block + %3 = load i64, ptr %error_var, align 8 + store i64 %3, ptr %err, align 8 + %4 = insertvalue %any undef, ptr %err, 0 + %5 = insertvalue %any %4, i64 ptrtoint (ptr @"$ct.fault" to i64), 1 + store %any %5, ptr %varargslots, align 16 + %6 = call i64 @std.io.printfn(ptr %retparam1, ptr @.str.1, i64 34, ptr %varargslots, i64 1) + br label %phi_block + +phi_block: ; preds = %guard_block, %after_check + store i64 ptrtoint (ptr @std.core.builtin.NOT_FOUND to i64), ptr %error_var4, align 8 + br label %guard_block5 + +guard_block5: ; preds = %phi_block + %7 = load i64, ptr %error_var4, align 8 + store i64 %7, ptr %err6, align 8 + %8 = insertvalue %any undef, ptr %err6, 0 + %9 = insertvalue %any %8, i64 ptrtoint (ptr @"$ct.fault" to i64), 1 + store %any %9, ptr %varargslots7, align 16 + %10 = call i64 @std.io.printfn(ptr %retparam8, ptr @.str.2, i64 34, ptr %varargslots7, i64 1) + br label %unreachable + +unreachable: ; preds = %guard_block5 + %11 = call i64 @String.to_int(ptr %retparam11, ptr @.str.3, i64 2, i32 10) + %not_err12 = icmp eq i64 %11, 0 + %12 = call i1 @llvm.expect.i1(i1 %not_err12, i1 true) + br i1 %12, label %after_check13, label %else_block14 + +after_check13: ; preds = %unreachable + %13 = load i32, ptr %retparam11, align 4 + br label %phi_block15 + +else_block14: ; preds = %unreachable + store i64 ptrtoint (ptr @std.core.builtin.NOT_FOUND to i64), ptr %defer_block_fault, align 8 + br label %opt_block_cleanup + +phi_block15: ; preds = %after_check13 + store i32 %13, ptr %blockret, align 4 + br label %expr_block.exit + +opt_block_cleanup: ; preds = %else_block14 + %14 = load i64, ptr %defer_block_fault, align 8 + store i64 %14, ptr %err16, align 8 + %15 = insertvalue %any undef, ptr %err16, 0 + %16 = insertvalue %any %15, i64 ptrtoint (ptr @"$ct.fault" to i64), 1 + store %any %16, ptr %varargslots17, align 16 + %17 = call i64 @std.io.printfn(ptr %retparam18, ptr @.str.4, i64 34, ptr %varargslots17, i64 1) + br label %expr_block.exit + +expr_block.exit: ; preds = %opt_block_cleanup, %phi_block15 + store i64 ptrtoint (ptr @std.core.builtin.NOT_FOUND to i64), ptr %defer_block_fault22, align 8 + br label %opt_block_cleanup23 + +opt_block_cleanup23: ; preds = %expr_block.exit + %18 = load i64, ptr %defer_block_fault22, align 8 + store i64 %18, ptr %err24, align 8 + %19 = insertvalue %any undef, ptr %err24, 0 + %20 = insertvalue %any %19, i64 ptrtoint (ptr @"$ct.fault" to i64), 1 + store %any %20, ptr %varargslots25, align 16 + %21 = call i64 @std.io.printfn(ptr %retparam26, ptr @.str.5, i64 34, ptr %varargslots25, i64 1) + br label %unreachable29 + +unreachable29: ; preds = %opt_block_cleanup23 + ret void +} diff --git a/test/test_suite/vector/vector_consts.c3t b/test/test_suite/vector/vector_consts.c3t index 930a7204c..09f830041 100644 --- a/test/test_suite/vector/vector_consts.c3t +++ b/test/test_suite/vector/vector_consts.c3t @@ -10,11 +10,6 @@ fn int x(Char8 a, Char8 b) /* #expect: foo.ll -; ModuleID = 'foo' -source_filename = "foo" -target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" -target triple = "x86_64-apple-macosx10.13.0" - %.introspect = type { i8, i64, ptr, i64, i64, i64, [0 x i64] } @"$ct.foo.Char8" = linkonce global %.introspect { i8 18, i64 ptrtoint (ptr @"$ct.siv8$char" to i64), ptr null, i64 8, i64 ptrtoint (ptr @"$ct.siv8$char" to i64), i64 0, [0 x i64] zeroinitializer }, align 8