| author | |
| committer | |
| log | 06909ceaab8ecb33d1f41049870797a3ae721610 |
| tree | 921bdc2b695512725e60e028521cef2d386c93da |
| parent | ca4341f7ba845e7af3c6f2be52cd60c51ec6d68f |
* you can label suspend blocks
* labeled break supports suspend blocks
See #8039 files changed, 136 insertions(+), 13 deletions(-)
doc/langref.html.in+1-1| ... | ... | @@ -5918,7 +5918,7 @@ Defer(body) = ("defer" | "deferror") body |
| 5918 | 5918 | |
| 5919 | 5919 | IfExpression(body) = "if" "(" Expression ")" body option("else" BlockExpression(body)) |
| 5920 | 5920 | |
| 5921 | SuspendExpression(body) = "suspend" option(("|" Symbol "|" body)) | |
| 5921 | SuspendExpression(body) = option(Symbol ":") "suspend" option(("|" Symbol "|" body)) | |
| 5922 | 5922 | |
| 5923 | 5923 | IfErrorExpression(body) = "if" "(" Expression ")" option("|" option("*") Symbol "|") body "else" "|" Symbol "|" BlockExpression(body) |
| 5924 | 5924 |
src/all_types.hpp+13| ... | ... | @@ -867,6 +867,7 @@ struct AstNodeAwaitExpr { |
| 867 | 867 | }; |
| 868 | 868 | |
| 869 | 869 | struct AstNodeSuspend { |
| 870 | Buf *name; | |
| 870 | 871 | AstNode *block; |
| 871 | 872 | AstNode *promise_symbol; |
| 872 | 873 | }; |
| ... | ... | @@ -1757,6 +1758,7 @@ enum ScopeId { |
| 1757 | 1758 | ScopeIdVarDecl, |
| 1758 | 1759 | ScopeIdCImport, |
| 1759 | 1760 | ScopeIdLoop, |
| 1761 | ScopeIdSuspend, | |
| 1760 | 1762 | ScopeIdFnDef, |
| 1761 | 1763 | ScopeIdCompTime, |
| 1762 | 1764 | ScopeIdCoroPrelude, |
| ... | ... | @@ -1852,6 +1854,17 @@ struct ScopeLoop { |
| 1852 | 1854 | ZigList<IrBasicBlock *> *incoming_blocks; |
| 1853 | 1855 | }; |
| 1854 | 1856 | |
| 1857 | // This scope is created for a suspend block in order to have labeled | |
| 1858 | // suspend for breaking out of a suspend and for detecting if a suspend | |
| 1859 | // block is inside a suspend block. | |
| 1860 | struct ScopeSuspend { | |
| 1861 | Scope base; | |
| 1862 | ||
| 1863 | Buf *name; | |
| 1864 | IrBasicBlock *resume_block; | |
| 1865 | bool reported_err; | |
| 1866 | }; | |
| 1867 | ||
| 1855 | 1868 | // This scope is created for a comptime expression. |
| 1856 | 1869 | // NodeTypeCompTime, NodeTypeSwitchExpr |
| 1857 | 1870 | struct ScopeCompTime { |
src/analyze.cpp+9| ... | ... | @@ -156,6 +156,14 @@ ScopeLoop *create_loop_scope(AstNode *node, Scope *parent) { |
| 156 | 156 | return scope; |
| 157 | 157 | } |
| 158 | 158 | |
| 159 | ScopeSuspend *create_suspend_scope(AstNode *node, Scope *parent) { | |
| 160 | assert(node->type == NodeTypeSuspend); | |
| 161 | ScopeSuspend *scope = allocate<ScopeSuspend>(1); | |
| 162 | init_scope(&scope->base, ScopeIdSuspend, node, parent); | |
| 163 | scope->name = node->data.suspend.name; | |
| 164 | return scope; | |
| 165 | } | |
| 166 | ||
| 159 | 167 | ScopeFnDef *create_fndef_scope(AstNode *node, Scope *parent, FnTableEntry *fn_entry) { |
| 160 | 168 | ScopeFnDef *scope = allocate<ScopeFnDef>(1); |
| 161 | 169 | init_scope(&scope->base, ScopeIdFnDef, node, parent); |
| ... | ... | @@ -3616,6 +3624,7 @@ FnTableEntry *scope_get_fn_if_root(Scope *scope) { |
| 3616 | 3624 | case ScopeIdVarDecl: |
| 3617 | 3625 | case ScopeIdCImport: |
| 3618 | 3626 | case ScopeIdLoop: |
| 3627 | case ScopeIdSuspend: | |
| 3619 | 3628 | case ScopeIdCompTime: |
| 3620 | 3629 | case ScopeIdCoroPrelude: |
| 3621 | 3630 | scope = scope->parent; |
src/analyze.hpp+1| ... | ... | @@ -104,6 +104,7 @@ ScopeDeferExpr *create_defer_expr_scope(AstNode *node, Scope *parent); |
| 104 | 104 | Scope *create_var_scope(AstNode *node, Scope *parent, VariableTableEntry *var); |
| 105 | 105 | ScopeCImport *create_cimport_scope(AstNode *node, Scope *parent); |
| 106 | 106 | ScopeLoop *create_loop_scope(AstNode *node, Scope *parent); |
| 107 | ScopeSuspend *create_suspend_scope(AstNode *node, Scope *parent); | |
| 107 | 108 | ScopeFnDef *create_fndef_scope(AstNode *node, Scope *parent, FnTableEntry *fn_entry); |
| 108 | 109 | ScopeDecls *create_decls_scope(AstNode *node, Scope *parent, TypeTableEntry *container_type, ImportTableEntry *import); |
| 109 | 110 | Scope *create_comptime_scope(AstNode *node, Scope *parent); |
src/codegen.cpp+1| ... | ... | @@ -654,6 +654,7 @@ static ZigLLVMDIScope *get_di_scope(CodeGen *g, Scope *scope) { |
| 654 | 654 | } |
| 655 | 655 | case ScopeIdDeferExpr: |
| 656 | 656 | case ScopeIdLoop: |
| 657 | case ScopeIdSuspend: | |
| 657 | 658 | case ScopeIdCompTime: |
| 658 | 659 | case ScopeIdCoroPrelude: |
| 659 | 660 | return get_di_scope(g, scope->parent); |
src/ir.cpp+50-10| ... | ... | @@ -2829,6 +2829,18 @@ static void ir_set_cursor_at_end_and_append_block(IrBuilder *irb, IrBasicBlock * |
| 2829 | 2829 | ir_set_cursor_at_end(irb, basic_block); |
| 2830 | 2830 | } |
| 2831 | 2831 | |
| 2832 | static ScopeSuspend *get_scope_suspend(Scope *scope) { | |
| 2833 | while (scope) { | |
| 2834 | if (scope->id == ScopeIdSuspend) | |
| 2835 | return (ScopeSuspend *)scope; | |
| 2836 | if (scope->id == ScopeIdFnDef) | |
| 2837 | return nullptr; | |
| 2838 | ||
| 2839 | scope = scope->parent; | |
| 2840 | } | |
| 2841 | return nullptr; | |
| 2842 | } | |
| 2843 | ||
| 2832 | 2844 | static ScopeDeferExpr *get_scope_defer_expr(Scope *scope) { |
| 2833 | 2845 | while (scope) { |
| 2834 | 2846 | if (scope->id == ScopeIdDeferExpr) |
| ... | ... | @@ -5665,6 +5677,15 @@ static IrInstruction *ir_gen_return_from_block(IrBuilder *irb, Scope *break_scop |
| 5665 | 5677 | return ir_build_br(irb, break_scope, node, dest_block, is_comptime); |
| 5666 | 5678 | } |
| 5667 | 5679 | |
| 5680 | static IrInstruction *ir_gen_break_from_suspend(IrBuilder *irb, Scope *break_scope, AstNode *node, ScopeSuspend *suspend_scope) { | |
| 5681 | IrInstruction *is_comptime = ir_build_const_bool(irb, break_scope, node, false); | |
| 5682 | ||
| 5683 | IrBasicBlock *dest_block = suspend_scope->resume_block; | |
| 5684 | ir_gen_defers_for_block(irb, break_scope, dest_block->scope, false); | |
| 5685 | ||
| 5686 | return ir_build_br(irb, break_scope, node, dest_block, is_comptime); | |
| 5687 | } | |
| 5688 | ||
| 5668 | 5689 | static IrInstruction *ir_gen_break(IrBuilder *irb, Scope *break_scope, AstNode *node) { |
| 5669 | 5690 | assert(node->type == NodeTypeBreak); |
| 5670 | 5691 | |
| ... | ... | @@ -5704,6 +5725,13 @@ static IrInstruction *ir_gen_break(IrBuilder *irb, Scope *break_scope, AstNode * |
| 5704 | 5725 | assert(this_block_scope->end_block != nullptr); |
| 5705 | 5726 | return ir_gen_return_from_block(irb, break_scope, node, this_block_scope); |
| 5706 | 5727 | } |
| 5728 | } else if (search_scope->id == ScopeIdSuspend) { | |
| 5729 | ScopeSuspend *this_suspend_scope = (ScopeSuspend *)search_scope; | |
| 5730 | if (node->data.break_expr.name != nullptr && | |
| 5731 | (this_suspend_scope->name != nullptr && buf_eql_buf(node->data.break_expr.name, this_suspend_scope->name))) | |
| 5732 | { | |
| 5733 | return ir_gen_break_from_suspend(irb, break_scope, node, this_suspend_scope); | |
| 5734 | } | |
| 5707 | 5735 | } |
| 5708 | 5736 | search_scope = search_scope->parent; |
| 5709 | 5737 | } |
| ... | ... | @@ -6290,14 +6318,26 @@ static IrInstruction *ir_gen_suspend(IrBuilder *irb, Scope *parent_scope, AstNod |
| 6290 | 6318 | ScopeDeferExpr *scope_defer_expr = get_scope_defer_expr(parent_scope); |
| 6291 | 6319 | if (scope_defer_expr) { |
| 6292 | 6320 | if (!scope_defer_expr->reported_err) { |
| 6293 | add_node_error(irb->codegen, node, buf_sprintf("cannot suspend inside defer expression")); | |
| 6321 | ErrorMsg *msg = add_node_error(irb->codegen, node, buf_sprintf("cannot suspend inside defer expression")); | |
| 6322 | add_error_note(irb->codegen, msg, scope_defer_expr->base.source_node, buf_sprintf("defer here")); | |
| 6294 | 6323 | scope_defer_expr->reported_err = true; |
| 6295 | 6324 | } |
| 6296 | 6325 | return irb->codegen->invalid_instruction; |
| 6297 | 6326 | } |
| 6327 | ScopeSuspend *existing_suspend_scope = get_scope_suspend(parent_scope); | |
| 6328 | if (existing_suspend_scope) { | |
| 6329 | if (!existing_suspend_scope->reported_err) { | |
| 6330 | ErrorMsg *msg = add_node_error(irb->codegen, node, buf_sprintf("cannot suspend inside suspend block")); | |
| 6331 | add_error_note(irb->codegen, msg, existing_suspend_scope->base.source_node, buf_sprintf("other suspend block here")); | |
| 6332 | existing_suspend_scope->reported_err = true; | |
| 6333 | } | |
| 6334 | return irb->codegen->invalid_instruction; | |
| 6335 | } | |
| 6298 | 6336 | |
| 6299 | 6337 | Scope *outer_scope = irb->exec->begin_scope; |
| 6300 | 6338 | |
| 6339 | IrBasicBlock *cleanup_block = ir_create_basic_block(irb, parent_scope, "SuspendCleanup"); | |
| 6340 | IrBasicBlock *resume_block = ir_create_basic_block(irb, parent_scope, "SuspendResume"); | |
| 6301 | 6341 | |
| 6302 | 6342 | IrInstruction *suspend_code; |
| 6303 | 6343 | IrInstruction *const_bool_false = ir_build_const_bool(irb, parent_scope, node, false); |
| ... | ... | @@ -6316,28 +6356,28 @@ static IrInstruction *ir_gen_suspend(IrBuilder *irb, Scope *parent_scope, AstNod |
| 6316 | 6356 | } else { |
| 6317 | 6357 | child_scope = parent_scope; |
| 6318 | 6358 | } |
| 6359 | ScopeSuspend *suspend_scope = create_suspend_scope(node, child_scope); | |
| 6360 | suspend_scope->resume_block = resume_block; | |
| 6361 | child_scope = &suspend_scope->base; | |
| 6319 | 6362 | IrInstruction *save_token = ir_build_coro_save(irb, child_scope, node, irb->exec->coro_handle); |
| 6320 | 6363 | ir_gen_node(irb, node->data.suspend.block, child_scope); |
| 6321 | suspend_code = ir_build_coro_suspend(irb, parent_scope, node, save_token, const_bool_false); | |
| 6364 | suspend_code = ir_mark_gen(ir_build_coro_suspend(irb, parent_scope, node, save_token, const_bool_false)); | |
| 6322 | 6365 | } |
| 6323 | 6366 | |
| 6324 | IrBasicBlock *cleanup_block = ir_create_basic_block(irb, parent_scope, "SuspendCleanup"); | |
| 6325 | IrBasicBlock *resume_block = ir_create_basic_block(irb, parent_scope, "SuspendResume"); | |
| 6326 | ||
| 6327 | 6367 | IrInstructionSwitchBrCase *cases = allocate<IrInstructionSwitchBrCase>(2); |
| 6328 | cases[0].value = ir_build_const_u8(irb, parent_scope, node, 0); | |
| 6368 | cases[0].value = ir_mark_gen(ir_build_const_u8(irb, parent_scope, node, 0)); | |
| 6329 | 6369 | cases[0].block = resume_block; |
| 6330 | cases[1].value = ir_build_const_u8(irb, parent_scope, node, 1); | |
| 6370 | cases[1].value = ir_mark_gen(ir_build_const_u8(irb, parent_scope, node, 1)); | |
| 6331 | 6371 | cases[1].block = cleanup_block; |
| 6332 | ir_build_switch_br(irb, parent_scope, node, suspend_code, irb->exec->coro_suspend_block, | |
| 6333 | 2, cases, const_bool_false); | |
| 6372 | ir_mark_gen(ir_build_switch_br(irb, parent_scope, node, suspend_code, irb->exec->coro_suspend_block, | |
| 6373 | 2, cases, const_bool_false)); | |
| 6334 | 6374 | |
| 6335 | 6375 | ir_set_cursor_at_end_and_append_block(irb, cleanup_block); |
| 6336 | 6376 | ir_gen_defers_for_block(irb, parent_scope, outer_scope, true); |
| 6337 | 6377 | ir_mark_gen(ir_build_br(irb, parent_scope, node, irb->exec->coro_final_cleanup_block, const_bool_false)); |
| 6338 | 6378 | |
| 6339 | 6379 | ir_set_cursor_at_end_and_append_block(irb, resume_block); |
| 6340 | return ir_build_const_void(irb, parent_scope, node); | |
| 6380 | return ir_mark_gen(ir_build_const_void(irb, parent_scope, node)); | |
| 6341 | 6381 | } |
| 6342 | 6382 | |
| 6343 | 6383 | static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scope, |
src/parser.cpp+23-2| ... | ... | @@ -648,12 +648,30 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc, size_t *token_index, bool m |
| 648 | 648 | } |
| 649 | 649 | |
| 650 | 650 | /* |
| 651 | SuspendExpression(body) = "suspend" "|" Symbol "|" body | |
| 651 | SuspendExpression(body) = option(Symbol ":") "suspend" option(("|" Symbol "|" body)) | |
| 652 | 652 | */ |
| 653 | 653 | static AstNode *ast_parse_suspend_block(ParseContext *pc, size_t *token_index, bool mandatory) { |
| 654 | 654 | size_t orig_token_index = *token_index; |
| 655 | 655 | |
| 656 | Token *suspend_token = &pc->tokens->at(*token_index); | |
| 656 | Token *name_token = nullptr; | |
| 657 | Token *token = &pc->tokens->at(*token_index); | |
| 658 | if (token->id == TokenIdSymbol) { | |
| 659 | *token_index += 1; | |
| 660 | Token *colon_token = &pc->tokens->at(*token_index); | |
| 661 | if (colon_token->id == TokenIdColon) { | |
| 662 | *token_index += 1; | |
| 663 | name_token = token; | |
| 664 | token = &pc->tokens->at(*token_index); | |
| 665 | } else if (mandatory) { | |
| 666 | ast_expect_token(pc, colon_token, TokenIdColon); | |
| 667 | zig_unreachable(); | |
| 668 | } else { | |
| 669 | *token_index = orig_token_index; | |
| 670 | return nullptr; | |
| 671 | } | |
| 672 | } | |
| 673 | ||
| 674 | Token *suspend_token = token; | |
| 657 | 675 | if (suspend_token->id == TokenIdKeywordSuspend) { |
| 658 | 676 | *token_index += 1; |
| 659 | 677 | } else if (mandatory) { |
| ... | ... | @@ -675,6 +693,9 @@ static AstNode *ast_parse_suspend_block(ParseContext *pc, size_t *token_index, b |
| 675 | 693 | } |
| 676 | 694 | |
| 677 | 695 | AstNode *node = ast_create_node(pc, NodeTypeSuspend, suspend_token); |
| 696 | if (name_token != nullptr) { | |
| 697 | node->data.suspend.name = token_buf(name_token); | |
| 698 | } | |
| 678 | 699 | node->data.suspend.promise_symbol = ast_parse_symbol(pc, token_index); |
| 679 | 700 | ast_eat_token(pc, token_index, TokenIdBinOr); |
| 680 | 701 | node->data.suspend.block = ast_parse_block(pc, token_index, true); |
test/cases/coroutines.zig+18| ... | ... | @@ -224,3 +224,21 @@ async fn printTrace(p: promise->error!void) void { |
| 224 | 224 | } |
| 225 | 225 | }; |
| 226 | 226 | } |
| 227 | ||
| 228 | test "break from suspend" { | |
| 229 | var buf: [500]u8 = undefined; | |
| 230 | var a = &std.heap.FixedBufferAllocator.init(buf[0..]).allocator; | |
| 231 | var my_result: i32 = 1; | |
| 232 | const p = try async<a> testBreakFromSuspend(&my_result); | |
| 233 | cancel p; | |
| 234 | std.debug.assert(my_result == 2); | |
| 235 | } | |
| 236 | ||
| 237 | async fn testBreakFromSuspend(my_result: &i32) void { | |
| 238 | s: suspend |p| { | |
| 239 | break :s; | |
| 240 | } | |
| 241 | *my_result += 1; | |
| 242 | suspend; | |
| 243 | *my_result += 1; | |
| 244 | } |
test/compile_errors.zig+20| ... | ... | @@ -1,6 +1,26 @@ |
| 1 | 1 | const tests = @import("tests.zig"); |
| 2 | 2 | |
| 3 | 3 | pub fn addCases(cases: &tests.CompileErrorContext) void { |
| 4 | cases.add("suspend inside suspend block", | |
| 5 | \\const std = @import("std"); | |
| 6 | \\ | |
| 7 | \\export fn entry() void { | |
| 8 | \\ var buf: [500]u8 = undefined; | |
| 9 | \\ var a = &std.heap.FixedBufferAllocator.init(buf[0..]).allocator; | |
| 10 | \\ const p = (async<a> foo()) catch unreachable; | |
| 11 | \\ cancel p; | |
| 12 | \\} | |
| 13 | \\ | |
| 14 | \\async fn foo() void { | |
| 15 | \\ suspend |p| { | |
| 16 | \\ suspend |p1| { | |
| 17 | \\ } | |
| 18 | \\ } | |
| 19 | \\} | |
| 20 | , | |
| 21 | ".tmp_source.zig:12:9: error: cannot suspend inside suspend block", | |
| 22 | ".tmp_source.zig:11:5: note: other suspend block here"); | |
| 23 | ||
| 4 | 24 | cases.add("assign inline fn to non-comptime var", |
| 5 | 25 | \\export fn entry() void { |
| 6 | 26 | \\ var a = b; |