From e8f0275d8eb62a4960bdfe3668aef06863d23f47 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Mon, 25 Mar 2024 11:35:16 +0100 Subject: [PATCH] 0.5.6 Add `defer (catch err)` feature. --- .github/workflows/main.yml | 4 +- releasenotes.md | 2 +- src/compiler/copying.c | 1 + src/compiler/enums.h | 1 + src/compiler/expr.c | 3 + src/compiler/llvm_codegen_expr.c | 10 +- src/compiler/llvm_codegen_internal.h | 1 + src/compiler/llvm_codegen_stmt.c | 2 + src/compiler/parse_stmt.c | 20 +++- src/compiler/sema_expr.c | 6 ++ src/compiler/sema_liveness.c | 1 + src/compiler/sema_stmts.c | 4 +- test/test_suite/defer/defer_catch_err.c3t | 114 ++++++++++++++++++++++ 13 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 test/test_suite/defer/defer_catch_err.c3t diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a64dcb5d1..069640f5d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -112,8 +112,8 @@ jobs: install: git binutils mingw-w64-x86_64-clang mingw-w64-x86_64-ninja mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-python - shell: msys2 {0} run: | - pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-18.2.1-1-any.pkg.tar.zst - pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-18.2.1-1-any.pkg.tar.zst + pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-llvm-18.1.2-1-any.pkg.tar.zst + pacman --noconfirm -U https://mirror.msys2.org/mingw/mingw64/mingw-w64-x86_64-lld-18.1.2-1-any.pkg.tar.zst - name: CMake run: | cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} diff --git a/releasenotes.md b/releasenotes.md index 68dce1397..b7ba215aa 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -3,7 +3,7 @@ ## 0.5.6 Change list ### Changes / improvements -None +- Support `defer (catch err)` ### Fixes None diff --git a/src/compiler/copying.c b/src/compiler/copying.c index 2cfcb79d6..38f382390 100644 --- a/src/compiler/copying.c +++ b/src/compiler/copying.c @@ -388,6 +388,7 @@ Expr *copy_expr(CopyStruct *c, Expr *source_expr) case EXPR_BENCHMARK_HOOK: case EXPR_TEST_HOOK: case EXPR_COMPILER_CONST: + case EXPR_LAST_FAULT: return expr; case EXPR_DESIGNATOR: expr->designator_expr.path = macro_copy_designator_list(c, expr->designator_expr.path); diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 8673551fd..530329493 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -243,6 +243,7 @@ typedef enum EXPR_IDENTIFIER, EXPR_INITIALIZER_LIST, EXPR_LAMBDA, + EXPR_LAST_FAULT, EXPR_MACRO_BLOCK, EXPR_MACRO_BODY_EXPANSION, EXPR_NOP, diff --git a/src/compiler/expr.c b/src/compiler/expr.c index 7286c5134..2da569cc3 100644 --- a/src/compiler/expr.c +++ b/src/compiler/expr.c @@ -127,6 +127,7 @@ bool expr_may_addr(Expr *expr) case EXPR_GENERIC_IDENT: case EXPR_EMBED: case EXPR_MACRO_BODY: + case EXPR_LAST_FAULT: return false; } UNREACHABLE @@ -289,6 +290,7 @@ bool expr_is_constant_eval(Expr *expr, ConstantEvalKind eval_kind) assert(!exprid_is_constant_eval(expr->ternary_expr.cond, eval_kind)); return false; case EXPR_FORCE_UNWRAP: + case EXPR_LAST_FAULT: return false; case EXPR_TYPEID: return eval_kind != CONSTANT_EVAL_CONSTANT_VALUE; @@ -693,6 +695,7 @@ bool expr_is_pure(Expr *expr) case EXPR_STRINGIFY: case EXPR_TYPEID: case EXPR_TYPEINFO: + case EXPR_LAST_FAULT: return true; case EXPR_VASPLAT: return true; diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index 46ac64c09..627dadb63 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -6803,7 +6803,12 @@ static LLVMValueRef llvm_get_benchmark_hook_global(GenContext *c, Expr *expr) return global; } -static void llmv_emit_benchmark_hook(GenContext *c, BEValue *value, Expr *expr) +INLINE void llvm_emit_last_fault(GenContext *c, BEValue *value) +{ + llvm_value_set_address_abi_aligned(value, c->defer_error_var, type_anyfault); +} + +INLINE void llmv_emit_benchmark_hook(GenContext *c, BEValue *value, Expr *expr) { LLVMValueRef get_global = llvm_get_benchmark_hook_global(c, expr); llvm_value_set_address_abi_aligned(value, get_global, expr->type); @@ -6886,6 +6891,9 @@ void llvm_emit_expr(GenContext *c, BEValue *value, Expr *expr) case EXPR_BENCHMARK_HOOK: llmv_emit_benchmark_hook(c, value, expr); return; + case EXPR_LAST_FAULT: + llvm_emit_last_fault(c, value); + return; case EXPR_TEST_HOOK: llmv_emit_test_hook(c, value, expr); return; diff --git a/src/compiler/llvm_codegen_internal.h b/src/compiler/llvm_codegen_internal.h index 24f5c8a9e..955f951d7 100644 --- a/src/compiler/llvm_codegen_internal.h +++ b/src/compiler/llvm_codegen_internal.h @@ -81,6 +81,7 @@ typedef struct GenContext_ LLVMValueRef function; LLVMValueRef alloca_point; LLVMBuilderRef builder; + LLVMValueRef defer_error_var; LLVMBasicBlockRef current_block; LLVMBasicBlockRef catch_block; LLVMBasicBlockRef *panic_blocks; diff --git a/src/compiler/llvm_codegen_stmt.c b/src/compiler/llvm_codegen_stmt.c index 2e4d87d16..0eb2545b2 100644 --- a/src/compiler/llvm_codegen_stmt.c +++ b/src/compiler/llvm_codegen_stmt.c @@ -211,6 +211,7 @@ static inline void llvm_emit_return(GenContext *c, Ast *ast) if (error_return_block && LLVMGetFirstUse(LLVMBasicBlockAsValue(error_return_block))) { llvm_emit_block(c, error_return_block); + c->defer_error_var = error_out; llvm_emit_statement_chain(c, ast->return_stmt.cleanup_fail); BEValue value; llvm_value_set_address_abi_aligned(&value, error_out, type_anyfault); @@ -263,6 +264,7 @@ static inline void llvm_emit_block_exit_return(GenContext *c, Ast *ast) { llvm_emit_br(c, exit->block_return_exit); llvm_emit_block(c, err_cleanup_block); + c->defer_error_var = exit->block_error_var; llvm_emit_statement_chain(c, err_cleanup); llvm_emit_jmp(c, exit->block_optional_exit); } diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index 246b19a7e..5f528fd4a 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -500,7 +500,7 @@ static inline Ast *parse_case_stmts(ParseContext *c, TokenType case_type, TokenT /** - * defer_stmt ::= DEFER (TRY | CATCH)? statement + * defer_stmt ::= DEFER (TRY | CATCH | '(' CATCH var ')')? statement */ static inline Ast* parse_defer_stmt(ParseContext *c) { @@ -514,6 +514,24 @@ static inline Ast* parse_defer_stmt(ParseContext *c) { defer_stmt->defer_stmt.is_catch = true; } + else if (tok_is(c, TOKEN_LPAREN) && peek(c) == TOKEN_CATCH) + { + advance_and_verify(c, TOKEN_LPAREN); + CONSUME_OR_RET(TOKEN_CATCH, poisoned_ast); + if (!expect_ident(c, "identifier")) return poisoned_ast; + Ast *compound = ast_new_curr(c, AST_COMPOUND_STMT); + Ast *first = ast_new_curr(c, AST_DECLARE_STMT); + Decl *decl = decl_new_var(c->data.string, c->span, type_info_new_base(type_anyfault, c->span), VARDECL_LOCAL); + defer_stmt->defer_stmt.is_catch = true; + decl->var.init_expr = expr_new(EXPR_LAST_FAULT, decl->span); + first->declare_stmt = decl; + advance_and_verify(c, TOKEN_IDENT); + CONSUME_OR_RET(TOKEN_RPAREN, poisoned_ast); + ASSIGN_ASTID_OR_RET(first->next, parse_stmt(c), poisoned_ast); + compound->compound_stmt.first_stmt = astid(first); + defer_stmt->defer_stmt.body = astid(compound); + return defer_stmt; + } ASSIGN_ASTID_OR_RET(defer_stmt->defer_stmt.body, parse_stmt(c), poisoned_ast); return defer_stmt; } diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 9712047b9..951052618 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -528,6 +528,7 @@ static bool sema_binary_is_expr_lvalue(Expr *top_expr, Expr *expr) case EXPR_TEST_HOOK: case EXPR_GENERIC_IDENT: case EXPR_MACRO_BODY: + case EXPR_LAST_FAULT: goto ERR; } UNREACHABLE @@ -644,6 +645,7 @@ static bool expr_may_ref(Expr *expr) case EXPR_TEST_HOOK: case EXPR_GENERIC_IDENT: case EXPR_MACRO_BODY: + case EXPR_LAST_FAULT: return false; } UNREACHABLE @@ -7841,6 +7843,7 @@ static inline bool sema_expr_analyse_ct_defined(SemaContext *context, Expr *expr case EXPR_MACRO_BODY_EXPANSION: case EXPR_BUILTIN_ACCESS: case EXPR_DECL: + case EXPR_LAST_FAULT: UNREACHABLE case EXPR_CT_ARG: FALLTHROUGH; @@ -8270,6 +8273,9 @@ static inline bool sema_analyse_expr_dispatch(SemaContext *context, Expr *expr) if (!sema_analyse_var_decl(context, expr->decl_expr, true)) return false; expr->type = expr->decl_expr->type; return true; + case EXPR_LAST_FAULT: + expr->type = type_anyfault; + return true; case EXPR_RETVAL: return sema_expr_analyse_retval(context, expr); case EXPR_BUILTIN: diff --git a/src/compiler/sema_liveness.c b/src/compiler/sema_liveness.c index a6d5325f9..84ef81101 100644 --- a/src/compiler/sema_liveness.c +++ b/src/compiler/sema_liveness.c @@ -491,6 +491,7 @@ RETRY: sema_trace_expr_list_liveness(expr->try_unwrap_chain_expr); return; case EXPR_TYPEID: + case EXPR_LAST_FAULT: return; } UNREACHABLE diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index a0297e94e..889a62ad5 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -315,7 +315,6 @@ static inline bool assert_create_from_contract(SemaContext *context, Ast *direct static inline bool sema_defer_by_result(AstId defer_top, AstId defer_bottom) { AstId first = 0; - AstId *next = &first; while (defer_bottom != defer_top) { Ast *defer = astptr(defer_top); @@ -1145,7 +1144,7 @@ bool sema_analyse_defer_stmt_body(SemaContext *context, Ast *statement, Ast *bod SEMA_ERROR(body, "A defer may not have a body consisting of a raw 'defer', this looks like a mistake."); return false; } - bool success; + bool success = true; SCOPE_START context->active_scope.defer_last = 0; @@ -1172,6 +1171,7 @@ bool sema_analyse_defer_stmt_body(SemaContext *context, Ast *statement, Ast *bod } static inline bool sema_analyse_defer_stmt(SemaContext *context, Ast *statement) { + if (!sema_analyse_defer_stmt_body(context, statement, astptr(statement->defer_stmt.body))) return false; statement->defer_stmt.prev_defer = context->active_scope.defer_last; diff --git a/test/test_suite/defer/defer_catch_err.c3t b/test/test_suite/defer/defer_catch_err.c3t new file mode 100644 index 000000000..192f061ef --- /dev/null +++ b/test/test_suite/defer/defer_catch_err.c3t @@ -0,0 +1,114 @@ +module baba; +// #target: macos-x64 +module foo; +import std::io; + +fault Test { FOO } +fn int! test() +{ + int! z = {| + const ABC = 4; + int! x = SearchResult.MISSING?; + defer (catch err) io::printfn("Hello %s", err); + defer (catch err) { io::printfn("Bye %s", err); err = Test.FOO; } + return x; + |}; + const ABC = 4; + int! x = SearchResult.MISSING?; + defer (catch err) io::printfn("Hello %s", err); + defer (catch err) { io::printfn("Bye %s", err); err = Test.FOO; } + return x; +} + +fn void main() +{ + (void)test(); +} + + +/* #expect: foo.ll + +define i64 @foo.test(ptr %0) #0 { +entry: + %z = alloca i32, align 4 + %z.f = alloca i64, align 8 + %blockret = alloca i32, align 4 + %x = alloca i32, align 4 + %x.f = alloca i64, align 8 + %err = alloca i64, align 8 + %varargslots = alloca [1 x %"any*"], align 16 + %retparam = alloca i64, align 8 + %err3 = alloca i64, align 8 + %varargslots4 = alloca [1 x %"any*"], align 16 + %retparam5 = alloca i64, align 8 + %x8 = alloca i32, align 4 + %x.f9 = alloca i64, align 8 + %reterr = alloca i64, align 8 + %err14 = alloca i64, align 8 + %varargslots15 = alloca [1 x %"any*"], align 16 + %retparam16 = alloca i64, align 8 + %err19 = alloca i64, align 8 + %varargslots20 = alloca [1 x %"any*"], align 16 + %retparam21 = alloca i64, align 8 + store i64 ptrtoint (ptr @"std.core.builtin.SearchResult$MISSING" to i64), ptr %x.f, align 8 + %optval = load i64, ptr %x.f, align 8 + %not_err = icmp eq i64 %optval, 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 %optval, ptr %z.f, align 8 + br label %opt_block_cleanup +after_check: ; preds = %entry + %2 = load i32, ptr %x, align 4 + store i32 %2, ptr %blockret, align 4 + br label %expr_block.exit +opt_block_cleanup: ; preds = %assign_optional + %3 = load i64, ptr %z.f, align 8 + store i64 %3, ptr %err, align 8 + %4 = insertvalue %"any*" undef, ptr %err, 0 + %5 = insertvalue %"any*" %4, i64 ptrtoint (ptr @"$ct.anyfault" to i64), 1 + store %"any*" %5, ptr %varargslots, align 16 + %6 = call i64 @std.io.printfn(ptr %retparam, ptr @.str, i64 6, ptr %varargslots, i64 1) + store i64 ptrtoint (ptr @"foo.Test$FOO" to i64), ptr %err, align 8 + %7 = load i64, ptr %z.f, align 8 + store i64 %7, ptr %err3, align 8 + %8 = insertvalue %"any*" undef, ptr %err3, 0 + %9 = insertvalue %"any*" %8, i64 ptrtoint (ptr @"$ct.anyfault" to i64), 1 + store %"any*" %9, ptr %varargslots4, align 16 + %10 = call i64 @std.io.printfn(ptr %retparam5, ptr @.str.2, i64 8, ptr %varargslots4, i64 1) + br label %after_assign +expr_block.exit: ; preds = %after_check + %11 = load i32, ptr %blockret, align 4 + store i32 %11, ptr %z, align 4 + store i64 0, ptr %z.f, align 8 + br label %after_assign +after_assign: ; preds = %expr_block.exit, %opt_block_cleanup + store i64 ptrtoint (ptr @"std.core.builtin.SearchResult$MISSING" to i64), ptr %x.f9, align 8 + %optval10 = load i64, ptr %x.f9, align 8 + %not_err11 = icmp eq i64 %optval10, 0 + %12 = call i1 @llvm.expect.i1(i1 %not_err11, i1 true) + br i1 %12, label %after_check13, label %assign_optional12 +assign_optional12: ; preds = %after_assign + store i64 %optval10, ptr %reterr, align 8 + br label %err_retblock +after_check13: ; preds = %after_assign + %13 = load i32, ptr %x8, align 4 + store i32 %13, ptr %0, align 4 + ret i64 0 +err_retblock: ; preds = %assign_optional12 + %14 = load i64, ptr %reterr, align 8 + store i64 %14, ptr %err14, align 8 + %15 = insertvalue %"any*" undef, ptr %err14, 0 + %16 = insertvalue %"any*" %15, i64 ptrtoint (ptr @"$ct.anyfault" to i64), 1 + store %"any*" %16, ptr %varargslots15, align 16 + %17 = call i64 @std.io.printfn(ptr %retparam16, ptr @.str.4, i64 6, ptr %varargslots15, i64 1) + store i64 ptrtoint (ptr @"foo.Test$FOO" to i64), ptr %err14, align 8 + %18 = load i64, ptr %reterr, align 8 + store i64 %18, ptr %err19, align 8 + %19 = insertvalue %"any*" undef, ptr %err19, 0 + %20 = insertvalue %"any*" %19, i64 ptrtoint (ptr @"$ct.anyfault" to i64), 1 + store %"any*" %20, ptr %varargslots20, align 16 + %21 = call i64 @std.io.printfn(ptr %retparam21, ptr @.str.5, i64 8, ptr %varargslots20, i64 1) + %22 = load i64, ptr %reterr, align 8 + ret i64 %22 +} \ No newline at end of file