| author | |
| committer | |
| log | b48948d6e805cdee7f33243098af9a862c152df1 |
| tree | 8b1b4b37670f7444e8fe12748a8db99ac7498cce |
| parent | f804310d9f953c9d78a4271ba8d75133341840e6 |
| parent | 9bd8b01650f9cf21e601117951711b21aa5fd216 |
32 files changed, 366 insertions(+), 176 deletions(-)
.gitignore+11| ... | ... | @@ -1,3 +1,14 @@ |
| 1 | # This file is for zig-specific build artifacts. | |
| 2 | # If you have OS-specific or editor-specific files to ignore, | |
| 3 | # such as *.swp or .DS_Store, put those in your global | |
| 4 | # ~/.gitignore and put this in your ~/.gitconfig: | |
| 5 | # | |
| 6 | # [core] | |
| 7 | # excludesfile = ~/.gitignore | |
| 8 | # | |
| 9 | # Cheers! | |
| 10 | # -andrewrk | |
| 11 | ||
| 1 | 12 | zig-cache/ |
| 2 | 13 | build/ |
| 3 | 14 | build-*/ |
doc/langref.html.in+18-8| ... | ... | @@ -4690,9 +4690,9 @@ test "coroutine suspend with block" { |
| 4690 | 4690 | var a_promise: promise = undefined; |
| 4691 | 4691 | var result = false; |
| 4692 | 4692 | async fn testSuspendBlock() void { |
| 4693 | suspend |p| { | |
| 4694 | comptime assert(@typeOf(p) == promise->void); | |
| 4695 | a_promise = p; | |
| 4693 | suspend { | |
| 4694 | comptime assert(@typeOf(@handle()) == promise->void); | |
| 4695 | a_promise = @handle(); | |
| 4696 | 4696 | } |
| 4697 | 4697 | result = true; |
| 4698 | 4698 | } |
| ... | ... | @@ -4733,8 +4733,8 @@ test "resume from suspend" { |
| 4733 | 4733 | std.debug.assert(my_result == 2); |
| 4734 | 4734 | } |
| 4735 | 4735 | async fn testResumeFromSuspend(my_result: *i32) void { |
| 4736 | suspend |p| { | |
| 4737 | resume p; | |
| 4736 | suspend { | |
| 4737 | resume @handle(); | |
| 4738 | 4738 | } |
| 4739 | 4739 | my_result.* += 1; |
| 4740 | 4740 | suspend; |
| ... | ... | @@ -4791,9 +4791,9 @@ async fn amain() void { |
| 4791 | 4791 | } |
| 4792 | 4792 | async fn another() i32 { |
| 4793 | 4793 | seq('c'); |
| 4794 | suspend |p| { | |
| 4794 | suspend { | |
| 4795 | 4795 | seq('d'); |
| 4796 | a_promise = p; | |
| 4796 | a_promise = @handle(); | |
| 4797 | 4797 | } |
| 4798 | 4798 | seq('g'); |
| 4799 | 4799 | return 1234; |
| ... | ... | @@ -5383,6 +5383,16 @@ test "main" { |
| 5383 | 5383 | This function is only valid within function scope. |
| 5384 | 5384 | </p> |
| 5385 | 5385 | {#header_close#} |
| 5386 | {#header_open|@handle#} | |
| 5387 | <pre><code class="zig">@handle()</code></pre> | |
| 5388 | <p> | |
| 5389 | This function returns a <code>promise->T</code> type, where <code>T</code> | |
| 5390 | is the return type of the async function in scope. | |
| 5391 | </p> | |
| 5392 | <p> | |
| 5393 | This function is only valid within an async function scope. | |
| 5394 | </p> | |
| 5395 | {#header_close#} | |
| 5386 | 5396 | {#header_open|@import#} |
| 5387 | 5397 | <pre><code class="zig">@import(comptime path: []u8) (namespace)</code></pre> |
| 5388 | 5398 | <p> |
| ... | ... | @@ -7388,7 +7398,7 @@ Defer(body) = ("defer" | "deferror") body |
| 7388 | 7398 | |
| 7389 | 7399 | IfExpression(body) = "if" "(" Expression ")" body option("else" BlockExpression(body)) |
| 7390 | 7400 | |
| 7391 | SuspendExpression(body) = "suspend" option(("|" Symbol "|" body)) | |
| 7401 | SuspendExpression(body) = "suspend" option( body ) | |
| 7392 | 7402 | |
| 7393 | 7403 | IfErrorExpression(body) = "if" "(" Expression ")" option("|" option("*") Symbol "|") body "else" "|" Symbol "|" BlockExpression(body) |
| 7394 | 7404 |
src/all_types.hpp+7-1| ... | ... | @@ -899,7 +899,6 @@ struct AstNodeAwaitExpr { |
| 899 | 899 | |
| 900 | 900 | struct AstNodeSuspend { |
| 901 | 901 | AstNode *block; |
| 902 | AstNode *promise_symbol; | |
| 903 | 902 | }; |
| 904 | 903 | |
| 905 | 904 | struct AstNodePromiseType { |
| ... | ... | @@ -1358,6 +1357,7 @@ enum BuiltinFnId { |
| 1358 | 1357 | BuiltinFnIdBreakpoint, |
| 1359 | 1358 | BuiltinFnIdReturnAddress, |
| 1360 | 1359 | BuiltinFnIdFrameAddress, |
| 1360 | BuiltinFnIdHandle, | |
| 1361 | 1361 | BuiltinFnIdEmbedFile, |
| 1362 | 1362 | BuiltinFnIdCmpxchgWeak, |
| 1363 | 1363 | BuiltinFnIdCmpxchgStrong, |
| ... | ... | @@ -1714,6 +1714,7 @@ struct CodeGen { |
| 1714 | 1714 | LLVMValueRef coro_save_fn_val; |
| 1715 | 1715 | LLVMValueRef coro_promise_fn_val; |
| 1716 | 1716 | LLVMValueRef coro_alloc_helper_fn_val; |
| 1717 | LLVMValueRef coro_frame_fn_val; | |
| 1717 | 1718 | LLVMValueRef merge_err_ret_traces_fn_val; |
| 1718 | 1719 | LLVMValueRef add_error_return_trace_addr_fn_val; |
| 1719 | 1720 | LLVMValueRef stacksave_fn_val; |
| ... | ... | @@ -2074,6 +2075,7 @@ enum IrInstructionId { |
| 2074 | 2075 | IrInstructionIdBreakpoint, |
| 2075 | 2076 | IrInstructionIdReturnAddress, |
| 2076 | 2077 | IrInstructionIdFrameAddress, |
| 2078 | IrInstructionIdHandle, | |
| 2077 | 2079 | IrInstructionIdAlignOf, |
| 2078 | 2080 | IrInstructionIdOverflowOp, |
| 2079 | 2081 | IrInstructionIdTestErr, |
| ... | ... | @@ -2791,6 +2793,10 @@ struct IrInstructionFrameAddress { |
| 2791 | 2793 | IrInstruction base; |
| 2792 | 2794 | }; |
| 2793 | 2795 | |
| 2796 | struct IrInstructionHandle { | |
| 2797 | IrInstruction base; | |
| 2798 | }; | |
| 2799 | ||
| 2794 | 2800 | enum IrOverflowOp { |
| 2795 | 2801 | IrOverflowOpAdd, |
| 2796 | 2802 | IrOverflowOpSub, |
src/ast_render.cpp-3| ... | ... | @@ -1112,9 +1112,6 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { |
| 1112 | 1112 | { |
| 1113 | 1113 | fprintf(ar->f, "suspend"); |
| 1114 | 1114 | if (node->data.suspend.block != nullptr) { |
| 1115 | fprintf(ar->f, " |"); | |
| 1116 | render_node_grouped(ar, node->data.suspend.promise_symbol); | |
| 1117 | fprintf(ar->f, "| "); | |
| 1118 | 1115 | render_node_grouped(ar, node->data.suspend.block); |
| 1119 | 1116 | } |
| 1120 | 1117 | break; |
src/codegen.cpp+24| ... | ... | @@ -4057,6 +4057,26 @@ static LLVMValueRef ir_render_frame_address(CodeGen *g, IrExecutable *executable |
| 4057 | 4057 | return LLVMBuildCall(g->builder, get_frame_address_fn_val(g), &zero, 1, ""); |
| 4058 | 4058 | } |
| 4059 | 4059 | |
| 4060 | static LLVMValueRef get_handle_fn_val(CodeGen *g) { | |
| 4061 | if (g->coro_frame_fn_val) | |
| 4062 | return g->coro_frame_fn_val; | |
| 4063 | ||
| 4064 | LLVMTypeRef fn_type = LLVMFunctionType( LLVMPointerType(LLVMInt8Type(), 0) | |
| 4065 | , nullptr, 0, false); | |
| 4066 | Buf *name = buf_sprintf("llvm.coro.frame"); | |
| 4067 | g->coro_frame_fn_val = LLVMAddFunction(g->module, buf_ptr(name), fn_type); | |
| 4068 | assert(LLVMGetIntrinsicID(g->coro_frame_fn_val)); | |
| 4069 | ||
| 4070 | return g->coro_frame_fn_val; | |
| 4071 | } | |
| 4072 | ||
| 4073 | static LLVMValueRef ir_render_handle(CodeGen *g, IrExecutable *executable, | |
| 4074 | IrInstructionHandle *instruction) | |
| 4075 | { | |
| 4076 | LLVMValueRef zero = LLVMConstNull(g->builtin_types.entry_promise->type_ref); | |
| 4077 | return LLVMBuildCall(g->builder, get_handle_fn_val(g), &zero, 0, ""); | |
| 4078 | } | |
| 4079 | ||
| 4060 | 4080 | static LLVMValueRef render_shl_with_overflow(CodeGen *g, IrInstructionOverflowOp *instruction) { |
| 4061 | 4081 | TypeTableEntry *int_type = instruction->result_ptr_type; |
| 4062 | 4082 | assert(int_type->id == TypeTableEntryIdInt); |
| ... | ... | @@ -4821,6 +4841,8 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, |
| 4821 | 4841 | return ir_render_return_address(g, executable, (IrInstructionReturnAddress *)instruction); |
| 4822 | 4842 | case IrInstructionIdFrameAddress: |
| 4823 | 4843 | return ir_render_frame_address(g, executable, (IrInstructionFrameAddress *)instruction); |
| 4844 | case IrInstructionIdHandle: | |
| 4845 | return ir_render_handle(g, executable, (IrInstructionHandle *)instruction); | |
| 4824 | 4846 | case IrInstructionIdOverflowOp: |
| 4825 | 4847 | return ir_render_overflow_op(g, executable, (IrInstructionOverflowOp *)instruction); |
| 4826 | 4848 | case IrInstructionIdTestErr: |
| ... | ... | @@ -5916,6 +5938,7 @@ static void do_code_gen(CodeGen *g) { |
| 5916 | 5938 | ir_render(g, fn_table_entry); |
| 5917 | 5939 | |
| 5918 | 5940 | } |
| 5941 | ||
| 5919 | 5942 | assert(!g->errors.length); |
| 5920 | 5943 | |
| 5921 | 5944 | if (buf_len(&g->global_asm) != 0) { |
| ... | ... | @@ -6255,6 +6278,7 @@ static void define_builtin_fns(CodeGen *g) { |
| 6255 | 6278 | create_builtin_fn(g, BuiltinFnIdBreakpoint, "breakpoint", 0); |
| 6256 | 6279 | create_builtin_fn(g, BuiltinFnIdReturnAddress, "returnAddress", 0); |
| 6257 | 6280 | create_builtin_fn(g, BuiltinFnIdFrameAddress, "frameAddress", 0); |
| 6281 | create_builtin_fn(g, BuiltinFnIdHandle, "handle", 0); | |
| 6258 | 6282 | create_builtin_fn(g, BuiltinFnIdMemcpy, "memcpy", 3); |
| 6259 | 6283 | create_builtin_fn(g, BuiltinFnIdMemset, "memset", 3); |
| 6260 | 6284 | create_builtin_fn(g, BuiltinFnIdSizeof, "sizeOf", 1); |
src/ir.cpp+64-14| ... | ... | @@ -580,6 +580,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionFrameAddress *) |
| 580 | 580 | return IrInstructionIdFrameAddress; |
| 581 | 581 | } |
| 582 | 582 | |
| 583 | static constexpr IrInstructionId ir_instruction_id(IrInstructionHandle *) { | |
| 584 | return IrInstructionIdHandle; | |
| 585 | } | |
| 586 | ||
| 583 | 587 | static constexpr IrInstructionId ir_instruction_id(IrInstructionAlignOf *) { |
| 584 | 588 | return IrInstructionIdAlignOf; |
| 585 | 589 | } |
| ... | ... | @@ -2240,6 +2244,17 @@ static IrInstruction *ir_build_frame_address_from(IrBuilder *irb, IrInstruction |
| 2240 | 2244 | return new_instruction; |
| 2241 | 2245 | } |
| 2242 | 2246 | |
| 2247 | static IrInstruction *ir_build_handle(IrBuilder *irb, Scope *scope, AstNode *source_node) { | |
| 2248 | IrInstructionHandle *instruction = ir_build_instruction<IrInstructionHandle>(irb, scope, source_node); | |
| 2249 | return &instruction->base; | |
| 2250 | } | |
| 2251 | ||
| 2252 | static IrInstruction *ir_build_handle_from(IrBuilder *irb, IrInstruction *old_instruction) { | |
| 2253 | IrInstruction *new_instruction = ir_build_handle(irb, old_instruction->scope, old_instruction->source_node); | |
| 2254 | ir_link_new_instruction(new_instruction, old_instruction); | |
| 2255 | return new_instruction; | |
| 2256 | } | |
| 2257 | ||
| 2243 | 2258 | static IrInstruction *ir_build_overflow_op(IrBuilder *irb, Scope *scope, AstNode *source_node, |
| 2244 | 2259 | IrOverflowOp op, IrInstruction *type_value, IrInstruction *op1, IrInstruction *op2, |
| 2245 | 2260 | IrInstruction *result_ptr, TypeTableEntry *result_ptr_type) |
| ... | ... | @@ -3317,7 +3332,15 @@ static VariableTableEntry *create_local_var(CodeGen *codegen, AstNode *node, Sco |
| 3317 | 3332 | static VariableTableEntry *ir_create_var(IrBuilder *irb, AstNode *node, Scope *scope, Buf *name, |
| 3318 | 3333 | bool src_is_const, bool gen_is_const, bool is_shadowable, IrInstruction *is_comptime) |
| 3319 | 3334 | { |
| 3320 | VariableTableEntry *var = create_local_var(irb->codegen, node, scope, name, src_is_const, gen_is_const, is_shadowable, is_comptime); | |
| 3335 | bool is_underscored = name ? buf_eql_str(name, "_") : false; | |
| 3336 | VariableTableEntry *var = create_local_var( irb->codegen | |
| 3337 | , node | |
| 3338 | , scope | |
| 3339 | , (is_underscored ? nullptr : name) | |
| 3340 | , src_is_const | |
| 3341 | , gen_is_const | |
| 3342 | , (is_underscored ? true : is_shadowable) | |
| 3343 | , is_comptime ); | |
| 3321 | 3344 | if (is_comptime != nullptr || gen_is_const) { |
| 3322 | 3345 | var->mem_slot_index = exec_next_mem_slot(irb->exec); |
| 3323 | 3346 | var->owner_exec = irb->exec; |
| ... | ... | @@ -3843,6 +3866,8 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo |
| 3843 | 3866 | return irb->codegen->invalid_instruction; |
| 3844 | 3867 | } |
| 3845 | 3868 | |
| 3869 | bool is_async = exec_is_async(irb->exec); | |
| 3870 | ||
| 3846 | 3871 | switch (builtin_fn->id) { |
| 3847 | 3872 | case BuiltinFnIdInvalid: |
| 3848 | 3873 | zig_unreachable(); |
| ... | ... | @@ -4475,6 +4500,16 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo |
| 4475 | 4500 | return ir_lval_wrap(irb, scope, ir_build_return_address(irb, scope, node), lval); |
| 4476 | 4501 | case BuiltinFnIdFrameAddress: |
| 4477 | 4502 | return ir_lval_wrap(irb, scope, ir_build_frame_address(irb, scope, node), lval); |
| 4503 | case BuiltinFnIdHandle: | |
| 4504 | if (!irb->exec->fn_entry) { | |
| 4505 | add_node_error(irb->codegen, node, buf_sprintf("@handle() called outside of function definition")); | |
| 4506 | return irb->codegen->invalid_instruction; | |
| 4507 | } | |
| 4508 | if (!is_async) { | |
| 4509 | add_node_error(irb->codegen, node, buf_sprintf("@handle() in non-async function")); | |
| 4510 | return irb->codegen->invalid_instruction; | |
| 4511 | } | |
| 4512 | return ir_lval_wrap(irb, scope, ir_build_handle(irb, scope, node), lval); | |
| 4478 | 4513 | case BuiltinFnIdAlignOf: |
| 4479 | 4514 | { |
| 4480 | 4515 | AstNode *arg0_node = node->data.fn_call_expr.params.at(0); |
| ... | ... | @@ -5159,6 +5194,11 @@ static IrInstruction *ir_gen_var_decl(IrBuilder *irb, Scope *scope, AstNode *nod |
| 5159 | 5194 | |
| 5160 | 5195 | AstNodeVariableDeclaration *variable_declaration = &node->data.variable_declaration; |
| 5161 | 5196 | |
| 5197 | if (buf_eql_str(variable_declaration->symbol, "_")) { | |
| 5198 | add_node_error(irb->codegen, node, buf_sprintf("`_` is not a declarable symbol")); | |
| 5199 | return irb->codegen->invalid_instruction; | |
| 5200 | } | |
| 5201 | ||
| 5162 | 5202 | IrInstruction *type_instruction; |
| 5163 | 5203 | if (variable_declaration->type != nullptr) { |
| 5164 | 5204 | type_instruction = ir_gen_node(irb, variable_declaration->type, scope); |
| ... | ... | @@ -5171,6 +5211,7 @@ static IrInstruction *ir_gen_var_decl(IrBuilder *irb, Scope *scope, AstNode *nod |
| 5171 | 5211 | bool is_shadowable = false; |
| 5172 | 5212 | bool is_const = variable_declaration->is_const; |
| 5173 | 5213 | bool is_extern = variable_declaration->is_extern; |
| 5214 | ||
| 5174 | 5215 | IrInstruction *is_comptime = ir_build_const_bool(irb, scope, node, |
| 5175 | 5216 | ir_should_inline(irb->exec, scope) || variable_declaration->is_comptime); |
| 5176 | 5217 | VariableTableEntry *var = ir_create_var(irb, node, scope, variable_declaration->symbol, |
| ... | ... | @@ -7069,19 +7110,8 @@ static IrInstruction *ir_gen_suspend(IrBuilder *irb, Scope *parent_scope, AstNod |
| 7069 | 7110 | if (node->data.suspend.block == nullptr) { |
| 7070 | 7111 | suspend_code = ir_build_coro_suspend(irb, parent_scope, node, nullptr, const_bool_false); |
| 7071 | 7112 | } else { |
| 7072 | assert(node->data.suspend.promise_symbol != nullptr); | |
| 7073 | assert(node->data.suspend.promise_symbol->type == NodeTypeSymbol); | |
| 7074 | Buf *promise_symbol_name = node->data.suspend.promise_symbol->data.symbol_expr.symbol; | |
| 7075 | 7113 | Scope *child_scope; |
| 7076 | if (!buf_eql_str(promise_symbol_name, "_")) { | |
| 7077 | VariableTableEntry *promise_var = ir_create_var(irb, node, parent_scope, promise_symbol_name, | |
| 7078 | true, true, false, const_bool_false); | |
| 7079 | ir_build_var_decl(irb, parent_scope, node, promise_var, nullptr, nullptr, irb->exec->coro_handle); | |
| 7080 | child_scope = promise_var->child_scope; | |
| 7081 | } else { | |
| 7082 | child_scope = parent_scope; | |
| 7083 | } | |
| 7084 | ScopeSuspend *suspend_scope = create_suspend_scope(node, child_scope); | |
| 7114 | ScopeSuspend *suspend_scope = create_suspend_scope(node, parent_scope); | |
| 7085 | 7115 | suspend_scope->resume_block = resume_block; |
| 7086 | 7116 | child_scope = &suspend_scope->base; |
| 7087 | 7117 | IrInstruction *save_token = ir_build_coro_save(irb, child_scope, node, irb->exec->coro_handle); |
| ... | ... | @@ -9598,6 +9628,9 @@ static ConstExprValue *ir_resolve_const(IrAnalyze *ira, IrInstruction *value, Un |
| 9598 | 9628 | case ConstValSpecialStatic: |
| 9599 | 9629 | return &value->value; |
| 9600 | 9630 | case ConstValSpecialRuntime: |
| 9631 | if (!type_has_bits(value->value.type)) { | |
| 9632 | return &value->value; | |
| 9633 | } | |
| 9601 | 9634 | ir_add_error(ira, value, buf_sprintf("unable to evaluate constant expression")); |
| 9602 | 9635 | return nullptr; |
| 9603 | 9636 | case ConstValSpecialUndef: |
| ... | ... | @@ -16099,8 +16132,14 @@ static TypeTableEntry *ir_analyze_container_init_fields_union(IrAnalyze *ira, Ir |
| 16099 | 16132 | if (casted_field_value == ira->codegen->invalid_instruction) |
| 16100 | 16133 | return ira->codegen->builtin_types.entry_invalid; |
| 16101 | 16134 | |
| 16135 | type_ensure_zero_bits_known(ira->codegen, casted_field_value->value.type); | |
| 16136 | if (type_is_invalid(casted_field_value->value.type)) | |
| 16137 | return ira->codegen->builtin_types.entry_invalid; | |
| 16138 | ||
| 16102 | 16139 | bool is_comptime = ir_should_inline(ira->new_irb.exec, instruction->scope); |
| 16103 | if (is_comptime || casted_field_value->value.special != ConstValSpecialRuntime) { | |
| 16140 | if (is_comptime || casted_field_value->value.special != ConstValSpecialRuntime || | |
| 16141 | !type_has_bits(casted_field_value->value.type)) | |
| 16142 | { | |
| 16104 | 16143 | ConstExprValue *field_val = ir_resolve_const(ira, casted_field_value, UndefOk); |
| 16105 | 16144 | if (!field_val) |
| 16106 | 16145 | return ira->codegen->builtin_types.entry_invalid; |
| ... | ... | @@ -19007,6 +19046,14 @@ static TypeTableEntry *ir_analyze_instruction_frame_address(IrAnalyze *ira, IrIn |
| 19007 | 19046 | return u8_ptr_const; |
| 19008 | 19047 | } |
| 19009 | 19048 | |
| 19049 | static TypeTableEntry *ir_analyze_instruction_handle(IrAnalyze *ira, IrInstructionHandle *instruction) { | |
| 19050 | ir_build_handle_from(&ira->new_irb, &instruction->base); | |
| 19051 | ||
| 19052 | FnTableEntry *fn_entry = exec_fn_entry(ira->new_irb.exec); | |
| 19053 | assert(fn_entry != nullptr); | |
| 19054 | return get_promise_type(ira->codegen, fn_entry->type_entry->data.fn.fn_type_id.return_type); | |
| 19055 | } | |
| 19056 | ||
| 19010 | 19057 | static TypeTableEntry *ir_analyze_instruction_align_of(IrAnalyze *ira, IrInstructionAlignOf *instruction) { |
| 19011 | 19058 | IrInstruction *type_value = instruction->type_value->other; |
| 19012 | 19059 | if (type_is_invalid(type_value->value.type)) |
| ... | ... | @@ -20982,6 +21029,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi |
| 20982 | 21029 | return ir_analyze_instruction_return_address(ira, (IrInstructionReturnAddress *)instruction); |
| 20983 | 21030 | case IrInstructionIdFrameAddress: |
| 20984 | 21031 | return ir_analyze_instruction_frame_address(ira, (IrInstructionFrameAddress *)instruction); |
| 21032 | case IrInstructionIdHandle: | |
| 21033 | return ir_analyze_instruction_handle(ira, (IrInstructionHandle *)instruction); | |
| 20985 | 21034 | case IrInstructionIdAlignOf: |
| 20986 | 21035 | return ir_analyze_instruction_align_of(ira, (IrInstructionAlignOf *)instruction); |
| 20987 | 21036 | case IrInstructionIdOverflowOp: |
| ... | ... | @@ -21274,6 +21323,7 @@ bool ir_has_side_effects(IrInstruction *instruction) { |
| 21274 | 21323 | case IrInstructionIdAlignOf: |
| 21275 | 21324 | case IrInstructionIdReturnAddress: |
| 21276 | 21325 | case IrInstructionIdFrameAddress: |
| 21326 | case IrInstructionIdHandle: | |
| 21277 | 21327 | case IrInstructionIdTestErr: |
| 21278 | 21328 | case IrInstructionIdUnwrapErrCode: |
| 21279 | 21329 | case IrInstructionIdOptionalWrap: |
src/ir_print.cpp+7| ... | ... | @@ -791,6 +791,10 @@ static void ir_print_frame_address(IrPrint *irp, IrInstructionFrameAddress *inst |
| 791 | 791 | fprintf(irp->f, "@frameAddress()"); |
| 792 | 792 | } |
| 793 | 793 | |
| 794 | static void ir_print_handle(IrPrint *irp, IrInstructionHandle *instruction) { | |
| 795 | fprintf(irp->f, "@handle()"); | |
| 796 | } | |
| 797 | ||
| 794 | 798 | static void ir_print_return_address(IrPrint *irp, IrInstructionReturnAddress *instruction) { |
| 795 | 799 | fprintf(irp->f, "@returnAddress()"); |
| 796 | 800 | } |
| ... | ... | @@ -1556,6 +1560,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { |
| 1556 | 1560 | case IrInstructionIdFrameAddress: |
| 1557 | 1561 | ir_print_frame_address(irp, (IrInstructionFrameAddress *)instruction); |
| 1558 | 1562 | break; |
| 1563 | case IrInstructionIdHandle: | |
| 1564 | ir_print_handle(irp, (IrInstructionHandle *)instruction); | |
| 1565 | break; | |
| 1559 | 1566 | case IrInstructionIdAlignOf: |
| 1560 | 1567 | ir_print_align_of(irp, (IrInstructionAlignOf *)instruction); |
| 1561 | 1568 | break; |
src/parser.cpp+9-16| ... | ... | @@ -648,12 +648,11 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc, size_t *token_index, bool m |
| 648 | 648 | } |
| 649 | 649 | |
| 650 | 650 | /* |
| 651 | SuspendExpression(body) = "suspend" option(("|" Symbol "|" body)) | |
| 651 | SuspendExpression(body) = "suspend" option( body ) | |
| 652 | 652 | */ |
| 653 | 653 | static AstNode *ast_parse_suspend_block(ParseContext *pc, size_t *token_index, bool mandatory) { |
| 654 | size_t orig_token_index = *token_index; | |
| 655 | ||
| 656 | 654 | Token *suspend_token = &pc->tokens->at(*token_index); |
| 655 | ||
| 657 | 656 | if (suspend_token->id == TokenIdKeywordSuspend) { |
| 658 | 657 | *token_index += 1; |
| 659 | 658 | } else if (mandatory) { |
| ... | ... | @@ -663,23 +662,18 @@ static AstNode *ast_parse_suspend_block(ParseContext *pc, size_t *token_index, b |
| 663 | 662 | return nullptr; |
| 664 | 663 | } |
| 665 | 664 | |
| 666 | Token *bar_token = &pc->tokens->at(*token_index); | |
| 667 | if (bar_token->id == TokenIdBinOr) { | |
| 668 | *token_index += 1; | |
| 665 | Token *lbrace = &pc->tokens->at(*token_index); | |
| 666 | if (lbrace->id == TokenIdLBrace) { | |
| 667 | AstNode *node = ast_create_node(pc, NodeTypeSuspend, suspend_token); | |
| 668 | node->data.suspend.block = ast_parse_block(pc, token_index, true); | |
| 669 | return node; | |
| 669 | 670 | } else if (mandatory) { |
| 670 | ast_expect_token(pc, suspend_token, TokenIdBinOr); | |
| 671 | ast_expect_token(pc, lbrace, TokenIdLBrace); | |
| 671 | 672 | zig_unreachable(); |
| 672 | 673 | } else { |
| 673 | *token_index = orig_token_index; | |
| 674 | *token_index -= 1; | |
| 674 | 675 | return nullptr; |
| 675 | 676 | } |
| 676 | ||
| 677 | AstNode *node = ast_create_node(pc, NodeTypeSuspend, suspend_token); | |
| 678 | node->data.suspend.promise_symbol = ast_parse_symbol(pc, token_index); | |
| 679 | ast_eat_token(pc, token_index, TokenIdBinOr); | |
| 680 | node->data.suspend.block = ast_parse_block(pc, token_index, true); | |
| 681 | ||
| 682 | return node; | |
| 683 | 677 | } |
| 684 | 678 | |
| 685 | 679 | /* |
| ... | ... | @@ -3134,7 +3128,6 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont |
| 3134 | 3128 | visit_field(&node->data.await_expr.expr, visit, context); |
| 3135 | 3129 | break; |
| 3136 | 3130 | case NodeTypeSuspend: |
| 3137 | visit_field(&node->data.suspend.promise_symbol, visit, context); | |
| 3138 | 3131 | visit_field(&node->data.suspend.block, visit, context); |
| 3139 | 3132 | break; |
| 3140 | 3133 | } |
std/event/channel.zig+4-4| ... | ... | @@ -71,10 +71,10 @@ pub fn Channel(comptime T: type) type { |
| 71 | 71 | /// puts a data item in the channel. The promise completes when the value has been added to the |
| 72 | 72 | /// buffer, or in the case of a zero size buffer, when the item has been retrieved by a getter. |
| 73 | 73 | pub async fn put(self: *SelfChannel, data: T) void { |
| 74 | suspend |handle| { | |
| 74 | suspend { | |
| 75 | 75 | var my_tick_node = Loop.NextTickNode{ |
| 76 | 76 | .next = undefined, |
| 77 | .data = handle, | |
| 77 | .data = @handle(), | |
| 78 | 78 | }; |
| 79 | 79 | var queue_node = std.atomic.Queue(PutNode).Node{ |
| 80 | 80 | .data = PutNode{ |
| ... | ... | @@ -96,10 +96,10 @@ pub fn Channel(comptime T: type) type { |
| 96 | 96 | // TODO integrate this function with named return values |
| 97 | 97 | // so we can get rid of this extra result copy |
| 98 | 98 | var result: T = undefined; |
| 99 | suspend |handle| { | |
| 99 | suspend { | |
| 100 | 100 | var my_tick_node = Loop.NextTickNode{ |
| 101 | 101 | .next = undefined, |
| 102 | .data = handle, | |
| 102 | .data = @handle(), | |
| 103 | 103 | }; |
| 104 | 104 | var queue_node = std.atomic.Queue(GetNode).Node{ |
| 105 | 105 | .data = GetNode{ |
std/event/future.zig+6-6| ... | ... | @@ -100,8 +100,8 @@ test "std.event.Future" { |
| 100 | 100 | } |
| 101 | 101 | |
| 102 | 102 | async fn testFuture(loop: *Loop) void { |
| 103 | suspend |p| { | |
| 104 | resume p; | |
| 103 | suspend { | |
| 104 | resume @handle(); | |
| 105 | 105 | } |
| 106 | 106 | var future = Future(i32).init(loop); |
| 107 | 107 | |
| ... | ... | @@ -115,15 +115,15 @@ async fn testFuture(loop: *Loop) void { |
| 115 | 115 | } |
| 116 | 116 | |
| 117 | 117 | async fn waitOnFuture(future: *Future(i32)) i32 { |
| 118 | suspend |p| { | |
| 119 | resume p; | |
| 118 | suspend { | |
| 119 | resume @handle(); | |
| 120 | 120 | } |
| 121 | 121 | return (await (async future.get() catch @panic("memory"))).*; |
| 122 | 122 | } |
| 123 | 123 | |
| 124 | 124 | async fn resolveFuture(future: *Future(i32)) void { |
| 125 | suspend |p| { | |
| 126 | resume p; | |
| 125 | suspend { | |
| 126 | resume @handle(); | |
| 127 | 127 | } |
| 128 | 128 | future.data = 6; |
| 129 | 129 | future.resolve(); |
std/event/group.zig+2-2| ... | ... | @@ -54,10 +54,10 @@ pub fn Group(comptime ReturnType: type) type { |
| 54 | 54 | const S = struct { |
| 55 | 55 | async fn asyncFunc(node: **Stack.Node, args2: ...) ReturnType { |
| 56 | 56 | // TODO this is a hack to make the memory following be inside the coro frame |
| 57 | suspend |p| { | |
| 57 | suspend { | |
| 58 | 58 | var my_node: Stack.Node = undefined; |
| 59 | 59 | node.* = &my_node; |
| 60 | resume p; | |
| 60 | resume @handle(); | |
| 61 | 61 | } |
| 62 | 62 | |
| 63 | 63 | // TODO this allocation elision should be guaranteed because we await it in |
std/event/lock.zig+6-29| ... | ... | @@ -90,10 +90,10 @@ pub const Lock = struct { |
| 90 | 90 | } |
| 91 | 91 | |
| 92 | 92 | pub async fn acquire(self: *Lock) Held { |
| 93 | suspend |handle| { | |
| 93 | suspend { | |
| 94 | 94 | // TODO explicitly put this memory in the coroutine frame #1194 |
| 95 | 95 | var my_tick_node = Loop.NextTickNode{ |
| 96 | .data = handle, | |
| 96 | .data = @handle(), | |
| 97 | 97 | .next = undefined, |
| 98 | 98 | }; |
| 99 | 99 | |
| ... | ... | @@ -106,35 +106,12 @@ pub const Lock = struct { |
| 106 | 106 | // will attempt to grab the lock. |
| 107 | 107 | _ = @atomicRmw(u8, &self.queue_empty_bit, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); |
| 108 | 108 | |
| 109 | while (true) { | |
| 110 | const old_bit = @atomicRmw(u8, &self.shared_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | |
| 111 | if (old_bit != 0) { | |
| 112 | // We did not obtain the lock. Trust that our queue entry will resume us, and allow | |
| 113 | // suspend to complete. | |
| 114 | break; | |
| 115 | } | |
| 116 | // We got the lock. However we might have already been resumed from the queue. | |
| 109 | const old_bit = @atomicRmw(u8, &self.shared_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | |
| 110 | if (old_bit == 0) { | |
| 117 | 111 | if (self.queue.get()) |node| { |
| 118 | 112 | // Whether this node is us or someone else, we tail resume it. |
| 119 | 113 | resume node.data; |
| 120 | break; | |
| 121 | } else { | |
| 122 | // We already got resumed, and there are none left in the queue, which means that | |
| 123 | // we aren't even supposed to hold the lock right now. | |
| 124 | _ = @atomicRmw(u8, &self.queue_empty_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); | |
| 125 | _ = @atomicRmw(u8, &self.shared_bit, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); | |
| 126 | ||
| 127 | // There might be a queue item. If we know the queue is empty, we can be done, | |
| 128 | // because the other actor will try to obtain the lock. | |
| 129 | // But if there's a queue item, we are the actor which must loop and attempt | |
| 130 | // to grab the lock again. | |
| 131 | if (@atomicLoad(u8, &self.queue_empty_bit, AtomicOrder.SeqCst) == 1) { | |
| 132 | break; | |
| 133 | } else { | |
| 134 | continue; | |
| 135 | } | |
| 136 | 114 | } |
| 137 | unreachable; | |
| 138 | 115 | } |
| 139 | 116 | } |
| 140 | 117 | |
| ... | ... | @@ -164,8 +141,8 @@ test "std.event.Lock" { |
| 164 | 141 | |
| 165 | 142 | async fn testLock(loop: *Loop, lock: *Lock) void { |
| 166 | 143 | // TODO explicitly put next tick node memory in the coroutine frame #1194 |
| 167 | suspend |p| { | |
| 168 | resume p; | |
| 144 | suspend { | |
| 145 | resume @handle(); | |
| 169 | 146 | } |
| 170 | 147 | const handle1 = async lockRunner(lock) catch @panic("out of memory"); |
| 171 | 148 | var tick_node1 = Loop.NextTickNode{ |
std/event/loop.zig+7-7| ... | ... | @@ -331,11 +331,11 @@ pub const Loop = struct { |
| 331 | 331 | |
| 332 | 332 | pub async fn waitFd(self: *Loop, fd: i32) !void { |
| 333 | 333 | defer self.removeFd(fd); |
| 334 | suspend |p| { | |
| 334 | suspend { | |
| 335 | 335 | // TODO explicitly put this memory in the coroutine frame #1194 |
| 336 | 336 | var resume_node = ResumeNode{ |
| 337 | 337 | .id = ResumeNode.Id.Basic, |
| 338 | .handle = p, | |
| 338 | .handle = @handle(), | |
| 339 | 339 | }; |
| 340 | 340 | try self.addFd(fd, &resume_node); |
| 341 | 341 | } |
| ... | ... | @@ -417,11 +417,11 @@ pub const Loop = struct { |
| 417 | 417 | pub fn call(self: *Loop, comptime func: var, args: ...) !(promise->@typeOf(func).ReturnType) { |
| 418 | 418 | const S = struct { |
| 419 | 419 | async fn asyncFunc(loop: *Loop, handle: *promise->@typeOf(func).ReturnType, args2: ...) @typeOf(func).ReturnType { |
| 420 | suspend |p| { | |
| 421 | handle.* = p; | |
| 420 | suspend { | |
| 421 | handle.* = @handle(); | |
| 422 | 422 | var my_tick_node = Loop.NextTickNode{ |
| 423 | 423 | .next = undefined, |
| 424 | .data = p, | |
| 424 | .data = @handle(), | |
| 425 | 425 | }; |
| 426 | 426 | loop.onNextTick(&my_tick_node); |
| 427 | 427 | } |
| ... | ... | @@ -439,10 +439,10 @@ pub const Loop = struct { |
| 439 | 439 | /// CPU bound tasks would be waiting in the event loop but never get started because no async I/O |
| 440 | 440 | /// is performed. |
| 441 | 441 | pub async fn yield(self: *Loop) void { |
| 442 | suspend |p| { | |
| 442 | suspend { | |
| 443 | 443 | var my_tick_node = Loop.NextTickNode{ |
| 444 | 444 | .next = undefined, |
| 445 | .data = p, | |
| 445 | .data = @handle(), | |
| 446 | 446 | }; |
| 447 | 447 | self.onNextTick(&my_tick_node); |
| 448 | 448 | } |
std/event/tcp.zig+4-4| ... | ... | @@ -88,8 +88,8 @@ pub const Server = struct { |
| 88 | 88 | }, |
| 89 | 89 | error.ProcessFdQuotaExceeded => { |
| 90 | 90 | errdefer std.os.emfile_promise_queue.remove(&self.waiting_for_emfile_node); |
| 91 | suspend |p| { | |
| 92 | self.waiting_for_emfile_node = PromiseNode.init(p); | |
| 91 | suspend { | |
| 92 | self.waiting_for_emfile_node = PromiseNode.init( @handle() ); | |
| 93 | 93 | std.os.emfile_promise_queue.append(&self.waiting_for_emfile_node); |
| 94 | 94 | } |
| 95 | 95 | continue; |
| ... | ... | @@ -141,8 +141,8 @@ test "listen on a port, send bytes, receive bytes" { |
| 141 | 141 | (await next_handler) catch |err| { |
| 142 | 142 | std.debug.panic("unable to handle connection: {}\n", err); |
| 143 | 143 | }; |
| 144 | suspend |p| { | |
| 145 | cancel p; | |
| 144 | suspend { | |
| 145 | cancel @handle(); | |
| 146 | 146 | } |
| 147 | 147 | } |
| 148 | 148 | async fn errorableHandler(self: *Self, _addr: *const std.net.Address, _socket: *const std.os.File) !void { |
std/fmt/index.zig+9| ... | ... | @@ -248,6 +248,11 @@ pub fn formatIntValue( |
| 248 | 248 | return formatAsciiChar(value, context, Errors, output); |
| 249 | 249 | } |
| 250 | 250 | }, |
| 251 | 'b' => { | |
| 252 | radix = 2; | |
| 253 | uppercase = false; | |
| 254 | width = 0; | |
| 255 | }, | |
| 251 | 256 | 'd' => { |
| 252 | 257 | radix = 10; |
| 253 | 258 | uppercase = false; |
| ... | ... | @@ -874,6 +879,10 @@ test "fmt.format" { |
| 874 | 879 | const value: u8 = 'a'; |
| 875 | 880 | try testFmt("u8: a\n", "u8: {c}\n", value); |
| 876 | 881 | } |
| 882 | { | |
| 883 | const value: u8 = 0b1100; | |
| 884 | try testFmt("u8: 0b1100\n", "u8: 0b{b}\n", value); | |
| 885 | } | |
| 877 | 886 | { |
| 878 | 887 | const value: [3]u8 = "abc"; |
| 879 | 888 | try testFmt("array: abc\n", "array: {}\n", value); |
std/os/index.zig+13-13| ... | ... | @@ -130,16 +130,10 @@ pub fn getRandomBytes(buf: []u8) !void { |
| 130 | 130 | try posixRead(fd, buf); |
| 131 | 131 | }, |
| 132 | 132 | Os.windows => { |
| 133 | var hCryptProv: windows.HCRYPTPROV = undefined; | |
| 134 | if (windows.CryptAcquireContextA(&hCryptProv, null, null, windows.PROV_RSA_FULL, 0) == 0) { | |
| 135 | const err = windows.GetLastError(); | |
| 136 | return switch (err) { | |
| 137 | else => unexpectedErrorWindows(err), | |
| 138 | }; | |
| 139 | } | |
| 140 | defer _ = windows.CryptReleaseContext(hCryptProv, 0); | |
| 141 | ||
| 142 | if (windows.CryptGenRandom(hCryptProv, @intCast(windows.DWORD, buf.len), buf.ptr) == 0) { | |
| 133 | // Call RtlGenRandom() instead of CryptGetRandom() on Windows | |
| 134 | // https://github.com/rust-lang-nursery/rand/issues/111 | |
| 135 | // https://bugzilla.mozilla.org/show_bug.cgi?id=504270 | |
| 136 | if (windows.RtlGenRandom(buf.ptr, buf.len) == 0) { | |
| 143 | 137 | const err = windows.GetLastError(); |
| 144 | 138 | return switch (err) { |
| 145 | 139 | else => unexpectedErrorWindows(err), |
| ... | ... | @@ -159,8 +153,14 @@ pub fn getRandomBytes(buf: []u8) !void { |
| 159 | 153 | } |
| 160 | 154 | |
| 161 | 155 | test "os.getRandomBytes" { |
| 162 | var buf: [50]u8 = undefined; | |
| 163 | try getRandomBytes(buf[0..]); | |
| 156 | var buf_a: [50]u8 = undefined; | |
| 157 | var buf_b: [50]u8 = undefined; | |
| 158 | // Call Twice | |
| 159 | try getRandomBytes(buf_a[0..]); | |
| 160 | try getRandomBytes(buf_b[0..]); | |
| 161 | ||
| 162 | // Check if random (not 100% conclusive) | |
| 163 | assert( !mem.eql(u8, buf_a, buf_b) ); | |
| 164 | 164 | } |
| 165 | 165 | |
| 166 | 166 | /// Raises a signal in the current kernel thread, ending its execution. |
| ... | ... | @@ -2790,7 +2790,7 @@ pub fn cpuCount(fallback_allocator: *mem.Allocator) CpuCountError!usize { |
| 2790 | 2790 | builtin.Os.macosx => { |
| 2791 | 2791 | var count: c_int = undefined; |
| 2792 | 2792 | var count_len: usize = @sizeOf(c_int); |
| 2793 | const rc = posix.sysctlbyname(c"hw.ncpu", @ptrCast(*c_void, &count), &count_len, null, 0); | |
| 2793 | const rc = posix.sysctlbyname(c"hw.logicalcpu", @ptrCast(*c_void, &count), &count_len, null, 0); | |
| 2794 | 2794 | const err = posix.getErrno(rc); |
| 2795 | 2795 | switch (err) { |
| 2796 | 2796 | 0 => return @intCast(usize, count), |
std/os/linux/index.zig+1-1| ... | ... | @@ -944,7 +944,7 @@ pub fn setgroups(size: usize, list: *const u32) usize { |
| 944 | 944 | } |
| 945 | 945 | |
| 946 | 946 | pub fn getpid() i32 { |
| 947 | return @bitCast(i32, u32(syscall0(SYS_getpid))); | |
| 947 | return @bitCast(i32, @truncate(u32, syscall0(SYS_getpid))); | |
| 948 | 948 | } |
| 949 | 949 | |
| 950 | 950 | pub fn sigprocmask(flags: u32, noalias set: *const sigset_t, noalias oldset: ?*sigset_t) usize { |
std/os/linux/test.zig+4| ... | ... | @@ -3,6 +3,10 @@ const builtin = @import("builtin"); |
| 3 | 3 | const linux = std.os.linux; |
| 4 | 4 | const assert = std.debug.assert; |
| 5 | 5 | |
| 6 | test "getpid" { | |
| 7 | assert(linux.getpid() != 0); | |
| 8 | } | |
| 9 | ||
| 6 | 10 | test "timer" { |
| 7 | 11 | const epoll_fd = linux.epoll_create(); |
| 8 | 12 | var err = linux.getErrno(epoll_fd); |
std/os/windows/advapi32.zig+5| ... | ... | @@ -28,3 +28,8 @@ pub extern "advapi32" stdcallcc fn RegOpenKeyExW(hKey: HKEY, lpSubKey: LPCWSTR, |
| 28 | 28 | |
| 29 | 29 | pub extern "advapi32" stdcallcc fn RegQueryValueExW(hKey: HKEY, lpValueName: LPCWSTR, lpReserved: LPDWORD, |
| 30 | 30 | lpType: LPDWORD, lpData: LPBYTE, lpcbData: LPDWORD,) LSTATUS; |
| 31 | ||
| 32 | // RtlGenRandom is known as SystemFunction036 under advapi32 | |
| 33 | // http://msdn.microsoft.com/en-us/library/windows/desktop/aa387694.aspx */ | |
| 34 | pub extern "advapi32" stdcallcc fn SystemFunction036(output: [*]u8, length: usize) BOOL; | |
| 35 | pub const RtlGenRandom = SystemFunction036; |
std/os/windows/util.zig+1-1| ... | ... | @@ -166,7 +166,7 @@ pub fn windowsUnloadDll(hModule: windows.HMODULE) void { |
| 166 | 166 | } |
| 167 | 167 | |
| 168 | 168 | test "InvalidDll" { |
| 169 | if (builtin.os != builtin.Os.windows) return; | |
| 169 | if (builtin.os != builtin.Os.windows) return error.SkipZigTest; | |
| 170 | 170 | |
| 171 | 171 | const DllName = "asdf.dll"; |
| 172 | 172 | const allocator = std.debug.global_allocator; |
std/rand/index.zig+5-4| ... | ... | @@ -30,7 +30,7 @@ pub const DefaultCsprng = Isaac64; |
| 30 | 30 | pub const Random = struct { |
| 31 | 31 | fillFn: fn (r: *Random, buf: []u8) void, |
| 32 | 32 | |
| 33 | /// Read random bytes into the specified buffer until fill. | |
| 33 | /// Read random bytes into the specified buffer until full. | |
| 34 | 34 | pub fn bytes(r: *Random, buf: []u8) void { |
| 35 | 35 | r.fillFn(r, buf); |
| 36 | 36 | } |
| ... | ... | @@ -48,10 +48,10 @@ pub const Random = struct { |
| 48 | 48 | } |
| 49 | 49 | } |
| 50 | 50 | |
| 51 | /// Get a random unsigned integer with even distribution between `start` | |
| 52 | /// inclusive and `end` exclusive. | |
| 51 | /// Return a random integer with even distribution between `start` | |
| 52 | /// inclusive and `end` exclusive. `start` must be less than `end`. | |
| 53 | 53 | pub fn range(r: *Random, comptime T: type, start: T, end: T) T { |
| 54 | assert(start <= end); | |
| 54 | assert(start < end); | |
| 55 | 55 | if (T.is_signed) { |
| 56 | 56 | const uint = @IntType(false, T.bit_count); |
| 57 | 57 | if (start >= 0 and end >= 0) { |
| ... | ... | @@ -664,6 +664,7 @@ test "Random range" { |
| 664 | 664 | testRange(&prng.random, -4, 3); |
| 665 | 665 | testRange(&prng.random, -4, -1); |
| 666 | 666 | testRange(&prng.random, 10, 14); |
| 667 | // TODO: test that prng.random.range(1, 1) causes an assertion error | |
| 667 | 668 | } |
| 668 | 669 | |
| 669 | 670 | fn testRange(r: *Random, start: i32, end: i32) void { |
std/zig/ast.zig-12| ... | ... | @@ -1778,19 +1778,12 @@ pub const Node = struct { |
| 1778 | 1778 | |
| 1779 | 1779 | pub const Suspend = struct { |
| 1780 | 1780 | base: Node, |
| 1781 | label: ?TokenIndex, | |
| 1782 | 1781 | suspend_token: TokenIndex, |
| 1783 | payload: ?*Node, | |
| 1784 | 1782 | body: ?*Node, |
| 1785 | 1783 | |
| 1786 | 1784 | pub fn iterate(self: *Suspend, index: usize) ?*Node { |
| 1787 | 1785 | var i = index; |
| 1788 | 1786 | |
| 1789 | if (self.payload) |payload| { | |
| 1790 | if (i < 1) return payload; | |
| 1791 | i -= 1; | |
| 1792 | } | |
| 1793 | ||
| 1794 | 1787 | if (self.body) |body| { |
| 1795 | 1788 | if (i < 1) return body; |
| 1796 | 1789 | i -= 1; |
| ... | ... | @@ -1800,7 +1793,6 @@ pub const Node = struct { |
| 1800 | 1793 | } |
| 1801 | 1794 | |
| 1802 | 1795 | pub fn firstToken(self: *Suspend) TokenIndex { |
| 1803 | if (self.label) |label| return label; | |
| 1804 | 1796 | return self.suspend_token; |
| 1805 | 1797 | } |
| 1806 | 1798 | |
| ... | ... | @@ -1809,10 +1801,6 @@ pub const Node = struct { |
| 1809 | 1801 | return body.lastToken(); |
| 1810 | 1802 | } |
| 1811 | 1803 | |
| 1812 | if (self.payload) |payload| { | |
| 1813 | return payload.lastToken(); | |
| 1814 | } | |
| 1815 | ||
| 1816 | 1804 | return self.suspend_token; |
| 1817 | 1805 | } |
| 1818 | 1806 | }; |
std/zig/parse.zig+14-19| ... | ... | @@ -852,19 +852,6 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { |
| 852 | 852 | }) catch unreachable; |
| 853 | 853 | continue; |
| 854 | 854 | }, |
| 855 | Token.Id.Keyword_suspend => { | |
| 856 | const node = try arena.create(ast.Node.Suspend{ | |
| 857 | .base = ast.Node{ .id = ast.Node.Id.Suspend }, | |
| 858 | .label = ctx.label, | |
| 859 | .suspend_token = token_index, | |
| 860 | .payload = null, | |
| 861 | .body = null, | |
| 862 | }); | |
| 863 | ctx.opt_ctx.store(&node.base); | |
| 864 | stack.append(State{ .SuspendBody = node }) catch unreachable; | |
| 865 | try stack.append(State{ .Payload = OptionalCtx{ .Optional = &node.payload } }); | |
| 866 | continue; | |
| 867 | }, | |
| 868 | 855 | Token.Id.Keyword_inline => { |
| 869 | 856 | stack.append(State{ |
| 870 | 857 | .Inline = InlineCtx{ |
| ... | ... | @@ -1415,10 +1402,21 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { |
| 1415 | 1402 | }, |
| 1416 | 1403 | |
| 1417 | 1404 | State.SuspendBody => |suspend_node| { |
| 1418 | if (suspend_node.payload != null) { | |
| 1419 | try stack.append(State{ .AssignmentExpressionBegin = OptionalCtx{ .RequiredNull = &suspend_node.body } }); | |
| 1405 | const token = nextToken(&tok_it, &tree); | |
| 1406 | switch (token.ptr.id) { | |
| 1407 | Token.Id.Semicolon => { | |
| 1408 | prevToken(&tok_it, &tree); | |
| 1409 | continue; | |
| 1410 | }, | |
| 1411 | Token.Id.LBrace => { | |
| 1412 | prevToken(&tok_it, &tree); | |
| 1413 | try stack.append(State{ .AssignmentExpressionBegin = OptionalCtx{ .RequiredNull = &suspend_node.body } }); | |
| 1414 | continue; | |
| 1415 | }, | |
| 1416 | else => { | |
| 1417 | ((try tree.errors.addOne())).* = Error{ .InvalidToken = Error.InvalidToken{ .token = token.index } }; | |
| 1418 | }, | |
| 1420 | 1419 | } |
| 1421 | continue; | |
| 1422 | 1420 | }, |
| 1423 | 1421 | State.AsyncAllocator => |async_node| { |
| 1424 | 1422 | if (eatToken(&tok_it, &tree, Token.Id.AngleBracketLeft) == null) { |
| ... | ... | @@ -3086,15 +3084,12 @@ fn parseBlockExpr(stack: *std.ArrayList(State), arena: *mem.Allocator, ctx: *con |
| 3086 | 3084 | Token.Id.Keyword_suspend => { |
| 3087 | 3085 | const node = try arena.create(ast.Node.Suspend{ |
| 3088 | 3086 | .base = ast.Node{ .id = ast.Node.Id.Suspend }, |
| 3089 | .label = null, | |
| 3090 | 3087 | .suspend_token = token_index, |
| 3091 | .payload = null, | |
| 3092 | 3088 | .body = null, |
| 3093 | 3089 | }); |
| 3094 | 3090 | ctx.store(&node.base); |
| 3095 | 3091 | |
| 3096 | 3092 | stack.append(State{ .SuspendBody = node }) catch unreachable; |
| 3097 | try stack.append(State{ .Payload = OptionalCtx{ .Optional = &node.payload } }); | |
| 3098 | 3093 | return true; |
| 3099 | 3094 | }, |
| 3100 | 3095 | Token.Id.Keyword_if => { |
std/zig/parser_test.zig+4-4| ... | ... | @@ -898,11 +898,11 @@ test "zig fmt: union(enum(u32)) with assigned enum values" { |
| 898 | 898 | ); |
| 899 | 899 | } |
| 900 | 900 | |
| 901 | test "zig fmt: labeled suspend" { | |
| 901 | test "zig fmt: resume from suspend block" { | |
| 902 | 902 | try testCanonical( |
| 903 | 903 | \\fn foo() void { |
| 904 | \\ s: suspend |p| { | |
| 905 | \\ break :s; | |
| 904 | \\ suspend { | |
| 905 | \\ resume @handle(); | |
| 906 | 906 | \\ } |
| 907 | 907 | \\} |
| 908 | 908 | \\ |
| ... | ... | @@ -1784,7 +1784,7 @@ test "zig fmt: coroutines" { |
| 1784 | 1784 | \\ x += 1; |
| 1785 | 1785 | \\ suspend; |
| 1786 | 1786 | \\ x += 1; |
| 1787 | \\ suspend |p| {} | |
| 1787 | \\ suspend; | |
| 1788 | 1788 | \\ const p: promise->void = async simpleAsyncFn() catch unreachable; |
| 1789 | 1789 | \\ await p; |
| 1790 | 1790 | \\} |
std/zig/render.zig+1-15| ... | ... | @@ -323,21 +323,7 @@ fn renderExpression( |
| 323 | 323 | ast.Node.Id.Suspend => { |
| 324 | 324 | const suspend_node = @fieldParentPtr(ast.Node.Suspend, "base", base); |
| 325 | 325 | |
| 326 | if (suspend_node.label) |label| { | |
| 327 | try renderToken(tree, stream, label, indent, start_col, Space.None); | |
| 328 | try renderToken(tree, stream, tree.nextToken(label), indent, start_col, Space.Space); | |
| 329 | } | |
| 330 | ||
| 331 | if (suspend_node.payload) |payload| { | |
| 332 | if (suspend_node.body) |body| { | |
| 333 | try renderToken(tree, stream, suspend_node.suspend_token, indent, start_col, Space.Space); | |
| 334 | try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Space); | |
| 335 | return renderExpression(allocator, stream, tree, indent, start_col, body, space); | |
| 336 | } else { | |
| 337 | try renderToken(tree, stream, suspend_node.suspend_token, indent, start_col, Space.Space); | |
| 338 | return renderExpression(allocator, stream, tree, indent, start_col, payload, space); | |
| 339 | } | |
| 340 | } else if (suspend_node.body) |body| { | |
| 326 | if (suspend_node.body) |body| { | |
| 341 | 327 | try renderToken(tree, stream, suspend_node.suspend_token, indent, start_col, Space.Space); |
| 342 | 328 | return renderExpression(allocator, stream, tree, indent, start_col, body, space); |
| 343 | 329 | } else { |
test/behavior.zig+1| ... | ... | @@ -60,6 +60,7 @@ comptime { |
| 60 | 60 | _ = @import("cases/try.zig"); |
| 61 | 61 | _ = @import("cases/type_info.zig"); |
| 62 | 62 | _ = @import("cases/undefined.zig"); |
| 63 | _ = @import("cases/underscore.zig"); | |
| 63 | 64 | _ = @import("cases/union.zig"); |
| 64 | 65 | _ = @import("cases/var_args.zig"); |
| 65 | 66 | _ = @import("cases/void.zig"); |
test/cases/cancel.zig+2-2| ... | ... | @@ -85,8 +85,8 @@ async fn b4() void { |
| 85 | 85 | defer { |
| 86 | 86 | defer_b4 = true; |
| 87 | 87 | } |
| 88 | suspend |p| { | |
| 89 | b4_handle = p; | |
| 88 | suspend { | |
| 89 | b4_handle = @handle(); | |
| 90 | 90 | } |
| 91 | 91 | suspend; |
| 92 | 92 | } |
test/cases/coroutine_await_struct.zig+2-2| ... | ... | @@ -30,9 +30,9 @@ async fn await_amain() void { |
| 30 | 30 | } |
| 31 | 31 | async fn await_another() Foo { |
| 32 | 32 | await_seq('c'); |
| 33 | suspend |p| { | |
| 33 | suspend { | |
| 34 | 34 | await_seq('d'); |
| 35 | await_a_promise = p; | |
| 35 | await_a_promise = @handle(); | |
| 36 | 36 | } |
| 37 | 37 | await_seq('g'); |
| 38 | 38 | return Foo{ .x = 1234 }; |
test/cases/coroutines.zig+12-7| ... | ... | @@ -62,10 +62,15 @@ test "coroutine suspend with block" { |
| 62 | 62 | var a_promise: promise = undefined; |
| 63 | 63 | var result = false; |
| 64 | 64 | async fn testSuspendBlock() void { |
| 65 | suspend |p| { | |
| 66 | comptime assert(@typeOf(p) == promise->void); | |
| 67 | a_promise = p; | |
| 65 | suspend { | |
| 66 | comptime assert(@typeOf(@handle()) == promise->void); | |
| 67 | a_promise = @handle(); | |
| 68 | 68 | } |
| 69 | ||
| 70 | //Test to make sure that @handle() works as advertised (issue #1296) | |
| 71 | //var our_handle: promise = @handle(); | |
| 72 | assert( a_promise == @handle() ); | |
| 73 | ||
| 69 | 74 | result = true; |
| 70 | 75 | } |
| 71 | 76 | |
| ... | ... | @@ -93,9 +98,9 @@ async fn await_amain() void { |
| 93 | 98 | } |
| 94 | 99 | async fn await_another() i32 { |
| 95 | 100 | await_seq('c'); |
| 96 | suspend |p| { | |
| 101 | suspend { | |
| 97 | 102 | await_seq('d'); |
| 98 | await_a_promise = p; | |
| 103 | await_a_promise = @handle(); | |
| 99 | 104 | } |
| 100 | 105 | await_seq('g'); |
| 101 | 106 | return 1234; |
| ... | ... | @@ -244,8 +249,8 @@ test "break from suspend" { |
| 244 | 249 | std.debug.assert(my_result == 2); |
| 245 | 250 | } |
| 246 | 251 | async fn testBreakFromSuspend(my_result: *i32) void { |
| 247 | suspend |p| { | |
| 248 | resume p; | |
| 252 | suspend { | |
| 253 | resume @handle(); | |
| 249 | 254 | } |
| 250 | 255 | my_result.* += 1; |
| 251 | 256 | suspend; |
test/cases/underscore.zig created+28| ... | ... | @@ -0,0 +1,28 @@ |
| 1 | const std = @import("std"); | |
| 2 | const assert = std.debug.assert; | |
| 3 | ||
| 4 | test "ignore lval with underscore" { | |
| 5 | _ = false; | |
| 6 | } | |
| 7 | ||
| 8 | test "ignore lval with underscore (for loop)" { | |
| 9 | for ([]void{}) |_, i| { | |
| 10 | for ([]void{}) |_, j| { | |
| 11 | break; | |
| 12 | } | |
| 13 | break; | |
| 14 | } | |
| 15 | } | |
| 16 | ||
| 17 | test "ignore lval with underscore (while loop)" { | |
| 18 | while (optionalReturnError()) |_| { | |
| 19 | while (optionalReturnError()) |_| { | |
| 20 | break; | |
| 21 | } else |_| { } | |
| 22 | break; | |
| 23 | } else |_| { } | |
| 24 | } | |
| 25 | ||
| 26 | fn optionalReturnError() !?u32 { | |
| 27 | return error.optionalReturnError; | |
| 28 | } |
test/cases/union.zig+14| ... | ... | @@ -297,3 +297,17 @@ test "access a member of tagged union with conflicting enum tag name" { |
| 297 | 297 | |
| 298 | 298 | comptime assert(Bar.A == u8); |
| 299 | 299 | } |
| 300 | ||
| 301 | test "tagged union initialization with runtime void" { | |
| 302 | assert(testTaggedUnionInit({})); | |
| 303 | } | |
| 304 | ||
| 305 | const TaggedUnionWithAVoid = union(enum) { | |
| 306 | A, | |
| 307 | B: i32, | |
| 308 | }; | |
| 309 | ||
| 310 | fn testTaggedUnionInit(x: var) bool { | |
| 311 | const y = TaggedUnionWithAVoid{ .A = x }; | |
| 312 | return @TagType(TaggedUnionWithAVoid)(y) == TaggedUnionWithAVoid.A; | |
| 313 | } |
test/compile_errors.zig+81-2| ... | ... | @@ -1,6 +1,85 @@ |
| 1 | 1 | const tests = @import("tests.zig"); |
| 2 | 2 | |
| 3 | 3 | pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 4 | cases.add( | |
| 5 | "@handle() called outside of function definition", | |
| 6 | \\var handle_undef: promise = undefined; | |
| 7 | \\var handle_dummy: promise = @handle(); | |
| 8 | \\export fn entry() bool { | |
| 9 | \\ return handle_undef == handle_dummy; | |
| 10 | \\} | |
| 11 | , | |
| 12 | ".tmp_source.zig:2:29: error: @handle() called outside of function definition", | |
| 13 | ); | |
| 14 | ||
| 15 | cases.add( | |
| 16 | "@handle() in non-async function", | |
| 17 | \\export fn entry() bool { | |
| 18 | \\ var handle_undef: promise = undefined; | |
| 19 | \\ return handle_undef == @handle(); | |
| 20 | \\} | |
| 21 | , | |
| 22 | ".tmp_source.zig:3:28: error: @handle() in non-async function", | |
| 23 | ); | |
| 24 | ||
| 25 | cases.add( | |
| 26 | "`_` is not a declarable symbol", | |
| 27 | \\export fn f1() usize { | |
| 28 | \\ var _: usize = 2; | |
| 29 | \\ return _; | |
| 30 | \\} | |
| 31 | , | |
| 32 | ".tmp_source.zig:2:5: error: `_` is not a declarable symbol", | |
| 33 | ".tmp_source.zig:3:12: error: use of undeclared identifier '_'", | |
| 34 | ); | |
| 35 | ||
| 36 | cases.add( | |
| 37 | "`_` should not be usable inside for", | |
| 38 | \\export fn returns() void { | |
| 39 | \\ for ([]void{}) |_, i| { | |
| 40 | \\ for ([]void{}) |_, j| { | |
| 41 | \\ return _; | |
| 42 | \\ } | |
| 43 | \\ } | |
| 44 | \\} | |
| 45 | , | |
| 46 | ".tmp_source.zig:4:20: error: use of undeclared identifier '_'", | |
| 47 | ); | |
| 48 | ||
| 49 | cases.add( | |
| 50 | "`_` should not be usable inside while", | |
| 51 | \\export fn returns() void { | |
| 52 | \\ while (optionalReturn()) |_| { | |
| 53 | \\ while (optionalReturn()) |_| { | |
| 54 | \\ return _; | |
| 55 | \\ } | |
| 56 | \\ } | |
| 57 | \\} | |
| 58 | \\fn optionalReturn() ?u32 { | |
| 59 | \\ return 1; | |
| 60 | \\} | |
| 61 | , | |
| 62 | ".tmp_source.zig:4:20: error: use of undeclared identifier '_'", | |
| 63 | ); | |
| 64 | ||
| 65 | cases.add( | |
| 66 | "`_` should not be usable inside while else", | |
| 67 | \\export fn returns() void { | |
| 68 | \\ while (optionalReturnError()) |_| { | |
| 69 | \\ while (optionalReturnError()) |_| { | |
| 70 | \\ return; | |
| 71 | \\ } else |_| { | |
| 72 | \\ if (_ == error.optionalReturnError) return; | |
| 73 | \\ } | |
| 74 | \\ } | |
| 75 | \\} | |
| 76 | \\fn optionalReturnError() !?u32 { | |
| 77 | \\ return error.optionalReturnError; | |
| 78 | \\} | |
| 79 | , | |
| 80 | ".tmp_source.zig:6:17: error: use of undeclared identifier '_'", | |
| 81 | ); | |
| 82 | ||
| 4 | 83 | cases.add( |
| 5 | 84 | "while loop body expression ignored", |
| 6 | 85 | \\fn returns() usize { |
| ... | ... | @@ -367,8 +446,8 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 367 | 446 | \\} |
| 368 | 447 | \\ |
| 369 | 448 | \\async fn foo() void { |
| 370 | \\ suspend |p| { | |
| 371 | \\ suspend |p1| { | |
| 449 | \\ suspend { | |
| 450 | \\ suspend { | |
| 372 | 451 | \\ } |
| 373 | 452 | \\ } |
| 374 | 453 | \\} |