From 6ab8b2aab4b146a7d1d882686199eace19989011 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 30 Aug 2019 20:06:02 -0400 Subject: support recursive async and non-async functions which heap allocate their own frames related: #1006 --- src/codegen.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/codegen.cpp') diff --git a/src/codegen.cpp b/src/codegen.cpp index 491ddcd4ea..d87b5d0aeb 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -6340,9 +6340,12 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val, const c ZigType *type_entry = const_val->type; assert(type_has_bits(type_entry)); - switch (const_val->special) { +check: switch (const_val->special) { case ConstValSpecialLazy: - zig_unreachable(); + if ((err = ir_resolve_lazy(g, nullptr, const_val))) { + report_errors_and_exit(g); + } + goto check; case ConstValSpecialRuntime: zig_unreachable(); case ConstValSpecialUndef: -- cgit v1.2.3 From a2230639232c069e4052a2e994dd5c0bd4e2517f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 31 Aug 2019 10:38:18 -0400 Subject: `@typeOf` now guarantees no runtime side effects related: #1627 --- doc/langref.html.in | 16 ++++++++++++++++ src/all_types.hpp | 8 ++++++++ src/analyze.cpp | 22 ++++++++++++++++++++++ src/analyze.hpp | 2 ++ src/codegen.cpp | 7 +++++++ src/ir.cpp | 10 +++++++++- test/stage1/behavior/sizeof_and_typeof.zig | 26 ++++++++++++++++++++++++++ 7 files changed, 90 insertions(+), 1 deletion(-) (limited to 'src/codegen.cpp') diff --git a/doc/langref.html.in b/doc/langref.html.in index c1fe08ddb6..44b2256813 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -8114,7 +8114,23 @@ pub const TypeInfo = union(TypeId) { This function returns a compile-time constant, which is the type of the expression passed as an argument. The expression is evaluated.

+

{#syntax#}@typeOf{#endsyntax#} guarantees no run-time side-effects within the expression:

+ {#code_begin|test#} +const std = @import("std"); +const assert = std.debug.assert; + +test "no runtime side effects" { + var data: i32 = 0; + const T = @typeOf(foo(i32, &data)); + comptime assert(T == i32); + assert(data == 0); +} +fn foo(comptime T: type, ptr: *T) T { + ptr.* += 1; + return ptr.*; +} + {#code_end#} {#header_close#} {#header_open|@unionInit#} diff --git a/src/all_types.hpp b/src/all_types.hpp index 87756d338f..aee6d3994f 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -2104,6 +2104,7 @@ enum ScopeId { ScopeIdFnDef, ScopeIdCompTime, ScopeIdRuntime, + ScopeIdTypeOf, }; struct Scope { @@ -2244,6 +2245,13 @@ struct ScopeFnDef { ZigFn *fn_entry; }; +// This scope is created for a @typeOf. +// All runtime side-effects are elided within it. +// NodeTypeFnCallExpr +struct ScopeTypeOf { + Scope base; +}; + // synchronized with code in define_builtin_compile_vars enum AtomicOrder { AtomicOrderUnordered, diff --git a/src/analyze.cpp b/src/analyze.cpp index 43c8d499db..df5b27784a 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -197,6 +197,12 @@ Scope *create_comptime_scope(CodeGen *g, AstNode *node, Scope *parent) { return &scope->base; } +Scope *create_typeof_scope(CodeGen *g, AstNode *node, Scope *parent) { + ScopeTypeOf *scope = allocate(1); + init_scope(g, &scope->base, ScopeIdTypeOf, node, parent); + return &scope->base; +} + ZigType *get_scope_import(Scope *scope) { while (scope) { if (scope->id == ScopeIdDecls) { @@ -209,6 +215,22 @@ ZigType *get_scope_import(Scope *scope) { zig_unreachable(); } +ScopeTypeOf *get_scope_typeof(Scope *scope) { + while (scope) { + switch (scope->id) { + case ScopeIdTypeOf: + return reinterpret_cast(scope); + case ScopeIdFnDef: + case ScopeIdDecls: + return nullptr; + default: + scope = scope->parent; + continue; + } + } + zig_unreachable(); +} + static ZigType *new_container_type_entry(CodeGen *g, ZigTypeId id, AstNode *source_node, Scope *parent_scope, Buf *bare_name) { diff --git a/src/analyze.hpp b/src/analyze.hpp index 6e8897bf82..dc702167e7 100644 --- a/src/analyze.hpp +++ b/src/analyze.hpp @@ -85,6 +85,7 @@ void scan_decls(CodeGen *g, ScopeDecls *decls_scope, AstNode *node); ZigFn *scope_fn_entry(Scope *scope); ZigPackage *scope_package(Scope *scope); ZigType *get_scope_import(Scope *scope); +ScopeTypeOf *get_scope_typeof(Scope *scope); void init_tld(Tld *tld, TldId id, Buf *name, VisibMod visib_mod, AstNode *source_node, Scope *parent_scope); ZigVar *add_variable(CodeGen *g, AstNode *source_node, Scope *parent_scope, Buf *name, bool is_const, ConstExprValue *init_value, Tld *src_tld, ZigType *var_type); @@ -112,6 +113,7 @@ ScopeSuspend *create_suspend_scope(CodeGen *g, AstNode *node, Scope *parent); ScopeFnDef *create_fndef_scope(CodeGen *g, AstNode *node, Scope *parent, ZigFn *fn_entry); Scope *create_comptime_scope(CodeGen *g, AstNode *node, Scope *parent); Scope *create_runtime_scope(CodeGen *g, AstNode *node, Scope *parent, IrInstruction *is_comptime); +Scope *create_typeof_scope(CodeGen *g, AstNode *node, Scope *parent); void init_const_str_lit(CodeGen *g, ConstExprValue *const_val, Buf *str); ConstExprValue *create_const_str_lit(CodeGen *g, Buf *str); diff --git a/src/codegen.cpp b/src/codegen.cpp index d87b5d0aeb..33713a9b30 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -645,6 +645,7 @@ static ZigLLVMDIScope *get_di_scope(CodeGen *g, Scope *scope) { case ScopeIdSuspend: case ScopeIdCompTime: case ScopeIdRuntime: + case ScopeIdTypeOf: return get_di_scope(g, scope->parent); } zig_unreachable(); @@ -3757,6 +3758,7 @@ static void render_async_var_decls(CodeGen *g, Scope *scope) { case ScopeIdSuspend: case ScopeIdCompTime: case ScopeIdRuntime: + case ScopeIdTypeOf: scope = scope->parent; continue; } @@ -5942,12 +5944,17 @@ static void ir_render(CodeGen *g, ZigFn *fn_entry) { for (size_t block_i = 0; block_i < executable->basic_block_list.length; block_i += 1) { IrBasicBlock *current_block = executable->basic_block_list.at(block_i); + if (get_scope_typeof(current_block->scope) != nullptr) { + LLVMBuildBr(g->builder, current_block->llvm_block); + } assert(current_block->llvm_block); LLVMPositionBuilderAtEnd(g->builder, current_block->llvm_block); for (size_t instr_i = 0; instr_i < current_block->instruction_list.length; instr_i += 1) { IrInstruction *instruction = current_block->instruction_list.at(instr_i); if (instruction->ref_count == 0 && !ir_has_side_effects(instruction)) continue; + if (get_scope_typeof(instruction->scope) != nullptr) + continue; if (!g->strip_debug_symbols) { set_debug_location(g, instruction); diff --git a/src/ir.cpp b/src/ir.cpp index 02a134c62e..ad81b27a93 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -3344,6 +3344,7 @@ static void ir_count_defers(IrBuilder *irb, Scope *inner_scope, Scope *outer_sco case ScopeIdSuspend: case ScopeIdCompTime: case ScopeIdRuntime: + case ScopeIdTypeOf: scope = scope->parent; continue; case ScopeIdDeferExpr: @@ -3399,6 +3400,7 @@ static bool ir_gen_defers_for_block(IrBuilder *irb, Scope *inner_scope, Scope *o case ScopeIdSuspend: case ScopeIdCompTime: case ScopeIdRuntime: + case ScopeIdTypeOf: scope = scope->parent; continue; case ScopeIdDeferExpr: @@ -4379,8 +4381,10 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo zig_unreachable(); case BuiltinFnIdTypeof: { + Scope *sub_scope = create_typeof_scope(irb->codegen, node, scope); + AstNode *arg_node = node->data.fn_call_expr.params.at(0); - IrInstruction *arg = ir_gen_node(irb, arg_node, scope); + IrInstruction *arg = ir_gen_node(irb, arg_node, sub_scope); if (arg == irb->codegen->invalid_instruction) return arg; @@ -8269,6 +8273,10 @@ static ConstExprValue *ir_exec_const_result(CodeGen *codegen, IrExecutable *exec break; } } + if (get_scope_typeof(instruction->scope) != nullptr) { + // doesn't count, it's inside a @typeOf() + continue; + } exec_add_error_node(codegen, exec, instruction->source_node, buf_sprintf("unable to evaluate constant expression")); return &codegen->invalid_instruction->value; diff --git a/test/stage1/behavior/sizeof_and_typeof.zig b/test/stage1/behavior/sizeof_and_typeof.zig index da79c3a270..6f57bfedd5 100644 --- a/test/stage1/behavior/sizeof_and_typeof.zig +++ b/test/stage1/behavior/sizeof_and_typeof.zig @@ -89,3 +89,29 @@ test "@sizeOf(T) == 0 doesn't force resolving struct size" { expect(@sizeOf(S.Foo) == 4); expect(@sizeOf(S.Bar) == 8); } + +test "@typeOf() has no runtime side effects" { + const S = struct { + fn foo(comptime T: type, ptr: *T) T { + ptr.* += 1; + return ptr.*; + } + }; + var data: i32 = 0; + const T = @typeOf(S.foo(i32, &data)); + comptime expect(T == i32); + expect(data == 0); +} + +test "branching logic inside @typeOf" { + const S = struct { + var data: i32 = 0; + fn foo() anyerror!i32 { + data += 1; + return undefined; + } + }; + const T = @typeOf(S.foo() catch undefined); + comptime expect(T == i32); + expect(S.data == 0); +} -- cgit v1.2.3 From 5c3a9a1a3eef82ffad17bc295da05ecccd9006a5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 31 Aug 2019 18:50:16 -0400 Subject: improvements to `@asyncCall` * `await @asyncCall` generates better code. See #3065 * `@asyncCall` works with a real `@Frame(func)` in addition to a byte slice. Closes #3072 * `@asyncCall` allows passing `{}` (a void value) as the result pointer, which uses the result location inside the frame. Closes #3068 * support `await @asyncCall` on a non-async function. This is in preparation for safe recursion (#1006). --- src/all_types.hpp | 2 + src/analyze.cpp | 4 + src/codegen.cpp | 61 +++++++---- src/ir.cpp | 225 ++++++++++++++++++++++++-------------- test/compile_errors.zig | 16 +++ test/stage1/behavior/async_fn.zig | 105 +++++++++++++++++- 6 files changed, 308 insertions(+), 105 deletions(-) (limited to 'src/codegen.cpp') diff --git a/src/all_types.hpp b/src/all_types.hpp index aee6d3994f..d9e1dc44ca 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -2719,6 +2719,7 @@ struct IrInstructionCallSrc { IrInstruction *new_stack; FnInline fn_inline; bool is_async; + bool is_async_call_builtin; bool is_comptime; }; @@ -2735,6 +2736,7 @@ struct IrInstructionCallGen { IrInstruction *new_stack; FnInline fn_inline; bool is_async; + bool is_async_call_builtin; }; struct IrInstructionConst { diff --git a/src/analyze.cpp b/src/analyze.cpp index df5b27784a..dfdf06aa5a 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -5727,6 +5727,10 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { for (size_t i = 0; i < fn->call_list.length; i += 1) { IrInstructionCallGen *call = fn->call_list.at(i); + if (call->new_stack != nullptr) { + // don't need to allocate a frame for this + continue; + } ZigFn *callee = call->fn_entry; if (callee == nullptr) { add_node_error(g, call->base.source_node, diff --git a/src/codegen.cpp b/src/codegen.cpp index 33713a9b30..890724d950 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3826,17 +3826,18 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr LLVMValueRef awaiter_init_val; LLVMValueRef ret_ptr; if (callee_is_async) { - if (instruction->is_async) { - if (instruction->new_stack == nullptr) { - awaiter_init_val = zero; + if (instruction->new_stack == nullptr) { + if (instruction->is_async) { frame_result_loc = result_loc; - - if (ret_has_bits) { - // Use the result location which is inside the frame if this is an async call. - ret_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_ret_start + 2, ""); - } - } else if (cc == CallingConventionAsync) { - awaiter_init_val = zero; + } else { + frame_result_loc = ir_llvm_value(g, instruction->frame_result_loc); + } + } else { + if (instruction->new_stack->value.type->id == ZigTypeIdPointer && + instruction->new_stack->value.type->data.pointer.child_type->id == ZigTypeIdFnFrame) + { + frame_result_loc = ir_llvm_value(g, instruction->new_stack); + } else { LLVMValueRef frame_slice_ptr = ir_llvm_value(g, instruction->new_stack); if (ir_want_runtime_safety(g, &instruction->base)) { LLVMValueRef given_len_ptr = LLVMBuildStructGEP(g->builder, frame_slice_ptr, slice_len_index, ""); @@ -3856,15 +3857,37 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr } LLVMValueRef frame_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_slice_ptr, slice_ptr_index, ""); LLVMValueRef frame_ptr = LLVMBuildLoad(g->builder, frame_ptr_ptr, ""); - frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr, - get_llvm_type(g, instruction->base.value.type), ""); + if (instruction->fn_entry == nullptr) { + ZigType *anyframe_type = get_any_frame_type(g, src_return_type); + frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr, get_llvm_type(g, anyframe_type), ""); + } else { + ZigType *ptr_frame_type = get_pointer_to_type(g, + get_fn_frame_type(g, instruction->fn_entry), false); + frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr, + get_llvm_type(g, ptr_frame_type), ""); + } + } + } + if (instruction->is_async) { + if (instruction->new_stack == nullptr) { + awaiter_init_val = zero; if (ret_has_bits) { - // Use the result location provided to the @asyncCall builtin - ret_ptr = result_loc; + // Use the result location which is inside the frame if this is an async call. + ret_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_ret_start + 2, ""); } } else { - zig_unreachable(); + awaiter_init_val = zero; + + if (ret_has_bits) { + if (result_loc != nullptr) { + // Use the result location provided to the @asyncCall builtin + ret_ptr = result_loc; + } else { + // no result location provided to @asyncCall - use the one inside the frame. + ret_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_ret_start + 2, ""); + } + } } // even if prefix_arg_err_ret_stack is true, let the async function do its own @@ -3872,7 +3895,6 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr } else { // async function called as a normal function - frame_result_loc = ir_llvm_value(g, instruction->frame_result_loc); awaiter_init_val = LLVMBuildPtrToInt(g->builder, g->cur_frame_ptr, usize_type_ref, ""); // caller's own frame pointer if (ret_has_bits) { if (result_loc == nullptr) { @@ -3988,7 +4010,7 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr uint32_t arg_start_i = frame_index_arg(g, fn_type->data.fn.fn_type_id.return_type); LLVMValueRef casted_frame; - if (instruction->new_stack != nullptr) { + if (instruction->new_stack != nullptr && instruction->fn_entry == nullptr) { // We need the frame type to be a pointer to a struct that includes the args size_t field_count = arg_start_i + gen_param_values.length; LLVMTypeRef *field_types = allocate_nonzero(field_count); @@ -4014,7 +4036,8 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr if (instruction->is_async) { gen_resume(g, fn_val, frame_result_loc, ResumeIdCall); if (instruction->new_stack != nullptr) { - return frame_result_loc; + return LLVMBuildBitCast(g->builder, frame_result_loc, + get_llvm_type(g, instruction->base.value.type), ""); } return nullptr; } else { @@ -4041,7 +4064,7 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr } } - if (instruction->new_stack == nullptr) { + if (instruction->new_stack == nullptr || instruction->is_async_call_builtin) { result = ZigLLVMBuildCall(g->builder, fn_val, gen_param_values.items, (unsigned)gen_param_values.length, llvm_cc, fn_inline, ""); } else if (instruction->is_async) { diff --git a/src/ir.cpp b/src/ir.cpp index ad81b27a93..b8a81ba5c9 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -1382,7 +1382,7 @@ static IrInstruction *ir_build_union_field_ptr(IrBuilder *irb, Scope *scope, Ast static IrInstruction *ir_build_call_src(IrBuilder *irb, Scope *scope, AstNode *source_node, ZigFn *fn_entry, IrInstruction *fn_ref, size_t arg_count, IrInstruction **args, - bool is_comptime, FnInline fn_inline, bool is_async, + bool is_comptime, FnInline fn_inline, bool is_async, bool is_async_call_builtin, IrInstruction *new_stack, ResultLoc *result_loc) { IrInstructionCallSrc *call_instruction = ir_build_instruction(irb, scope, source_node); @@ -1393,6 +1393,7 @@ static IrInstruction *ir_build_call_src(IrBuilder *irb, Scope *scope, AstNode *s call_instruction->args = args; call_instruction->arg_count = arg_count; call_instruction->is_async = is_async; + call_instruction->is_async_call_builtin = is_async_call_builtin; call_instruction->new_stack = new_stack; call_instruction->result_loc = result_loc; @@ -1410,7 +1411,7 @@ static IrInstruction *ir_build_call_src(IrBuilder *irb, Scope *scope, AstNode *s static IrInstructionCallGen *ir_build_call_gen(IrAnalyze *ira, IrInstruction *source_instruction, ZigFn *fn_entry, IrInstruction *fn_ref, size_t arg_count, IrInstruction **args, - FnInline fn_inline, bool is_async, IrInstruction *new_stack, + FnInline fn_inline, bool is_async, IrInstruction *new_stack, bool is_async_call_builtin, IrInstruction *result_loc, ZigType *return_type) { IrInstructionCallGen *call_instruction = ir_build_instruction(&ira->new_irb, @@ -1422,6 +1423,7 @@ static IrInstructionCallGen *ir_build_call_gen(IrAnalyze *ira, IrInstruction *so call_instruction->args = args; call_instruction->arg_count = arg_count; call_instruction->is_async = is_async; + call_instruction->is_async_call_builtin = is_async_call_builtin; call_instruction->new_stack = new_stack; call_instruction->result_loc = result_loc; @@ -4351,6 +4353,54 @@ static IrInstruction *ir_gen_this(IrBuilder *irb, Scope *orig_scope, AstNode *no zig_unreachable(); } +static IrInstruction *ir_gen_async_call(IrBuilder *irb, Scope *scope, AstNode *await_node, AstNode *call_node, + LVal lval, ResultLoc *result_loc) +{ + size_t arg_offset = 3; + if (call_node->data.fn_call_expr.params.length < arg_offset) { + add_node_error(irb->codegen, call_node, + buf_sprintf("expected at least %" ZIG_PRI_usize " arguments, found %" ZIG_PRI_usize, + arg_offset, call_node->data.fn_call_expr.params.length)); + return irb->codegen->invalid_instruction; + } + + AstNode *bytes_node = call_node->data.fn_call_expr.params.at(0); + IrInstruction *bytes = ir_gen_node(irb, bytes_node, scope); + if (bytes == irb->codegen->invalid_instruction) + return bytes; + + AstNode *ret_ptr_node = call_node->data.fn_call_expr.params.at(1); + IrInstruction *ret_ptr = ir_gen_node(irb, ret_ptr_node, scope); + if (ret_ptr == irb->codegen->invalid_instruction) + return ret_ptr; + + AstNode *fn_ref_node = call_node->data.fn_call_expr.params.at(2); + IrInstruction *fn_ref = ir_gen_node(irb, fn_ref_node, scope); + if (fn_ref == irb->codegen->invalid_instruction) + return fn_ref; + + size_t arg_count = call_node->data.fn_call_expr.params.length - arg_offset; + + // last "arg" is return pointer + IrInstruction **args = allocate(arg_count + 1); + + for (size_t i = 0; i < arg_count; i += 1) { + AstNode *arg_node = call_node->data.fn_call_expr.params.at(i + arg_offset); + IrInstruction *arg = ir_gen_node(irb, arg_node, scope); + if (arg == irb->codegen->invalid_instruction) + return arg; + args[i] = arg; + } + + args[arg_count] = ret_ptr; + + bool is_async = await_node == nullptr; + bool is_async_call_builtin = true; + IrInstruction *call = ir_build_call_src(irb, scope, call_node, nullptr, fn_ref, arg_count, args, false, + FnInlineAuto, is_async, is_async_call_builtin, bytes, result_loc); + return ir_lval_wrap(irb, scope, call, lval, result_loc); +} + static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNode *node, LVal lval, ResultLoc *result_loc) { @@ -4360,7 +4410,7 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo Buf *name = fn_ref_expr->data.symbol_expr.symbol; auto entry = irb->codegen->builtin_fn_table.maybe_get(name); - if (!entry) { // new built in not found + if (!entry) { add_node_error(irb->codegen, node, buf_sprintf("invalid builtin function: '%s'", buf_ptr(name))); return irb->codegen->invalid_instruction; @@ -5224,7 +5274,7 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo FnInline fn_inline = (builtin_fn->id == BuiltinFnIdInlineCall) ? FnInlineAlways : FnInlineNever; IrInstruction *call = ir_build_call_src(irb, scope, node, nullptr, fn_ref, arg_count, args, false, - fn_inline, false, nullptr, result_loc); + fn_inline, false, false, nullptr, result_loc); return ir_lval_wrap(irb, scope, call, lval, result_loc); } case BuiltinFnIdNewStackCall: @@ -5257,53 +5307,11 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo } IrInstruction *call = ir_build_call_src(irb, scope, node, nullptr, fn_ref, arg_count, args, false, - FnInlineAuto, false, new_stack, result_loc); + FnInlineAuto, false, false, new_stack, result_loc); return ir_lval_wrap(irb, scope, call, lval, result_loc); } case BuiltinFnIdAsyncCall: - { - size_t arg_offset = 3; - if (node->data.fn_call_expr.params.length < arg_offset) { - add_node_error(irb->codegen, node, - buf_sprintf("expected at least %" ZIG_PRI_usize " arguments, found %" ZIG_PRI_usize, - arg_offset, node->data.fn_call_expr.params.length)); - return irb->codegen->invalid_instruction; - } - - AstNode *bytes_node = node->data.fn_call_expr.params.at(0); - IrInstruction *bytes = ir_gen_node(irb, bytes_node, scope); - if (bytes == irb->codegen->invalid_instruction) - return bytes; - - AstNode *ret_ptr_node = node->data.fn_call_expr.params.at(1); - IrInstruction *ret_ptr = ir_gen_node(irb, ret_ptr_node, scope); - if (ret_ptr == irb->codegen->invalid_instruction) - return ret_ptr; - - AstNode *fn_ref_node = node->data.fn_call_expr.params.at(2); - IrInstruction *fn_ref = ir_gen_node(irb, fn_ref_node, scope); - if (fn_ref == irb->codegen->invalid_instruction) - return fn_ref; - - size_t arg_count = node->data.fn_call_expr.params.length - arg_offset; - - // last "arg" is return pointer - IrInstruction **args = allocate(arg_count + 1); - - for (size_t i = 0; i < arg_count; i += 1) { - AstNode *arg_node = node->data.fn_call_expr.params.at(i + arg_offset); - IrInstruction *arg = ir_gen_node(irb, arg_node, scope); - if (arg == irb->codegen->invalid_instruction) - return arg; - args[i] = arg; - } - - args[arg_count] = ret_ptr; - - IrInstruction *call = ir_build_call_src(irb, scope, node, nullptr, fn_ref, arg_count, args, false, - FnInlineAuto, true, bytes, result_loc); - return ir_lval_wrap(irb, scope, call, lval, result_loc); - } + return ir_gen_async_call(irb, scope, nullptr, node, lval, result_loc); case BuiltinFnIdTypeId: { AstNode *arg0_node = node->data.fn_call_expr.params.at(0); @@ -5607,7 +5615,7 @@ static IrInstruction *ir_gen_fn_call(IrBuilder *irb, Scope *scope, AstNode *node bool is_async = node->data.fn_call_expr.is_async; IrInstruction *fn_call = ir_build_call_src(irb, scope, node, nullptr, fn_ref, arg_count, args, false, - FnInlineAuto, is_async, nullptr, result_loc); + FnInlineAuto, is_async, false, nullptr, result_loc); return ir_lval_wrap(irb, scope, fn_call, lval, result_loc); } @@ -7900,6 +7908,19 @@ static IrInstruction *ir_gen_await_expr(IrBuilder *irb, Scope *scope, AstNode *n { assert(node->type == NodeTypeAwaitExpr); + AstNode *expr_node = node->data.await_expr.expr; + if (expr_node->type == NodeTypeFnCallExpr && expr_node->data.fn_call_expr.is_builtin) { + AstNode *fn_ref_expr = expr_node->data.fn_call_expr.fn_ref_expr; + Buf *name = fn_ref_expr->data.symbol_expr.symbol; + auto entry = irb->codegen->builtin_fn_table.maybe_get(name); + if (entry != nullptr) { + BuiltinFnEntry *builtin_fn = entry->value; + if (builtin_fn->id == BuiltinFnIdAsyncCall) { + return ir_gen_async_call(irb, scope, node, expr_node, lval, result_loc); + } + } + } + ZigFn *fn_entry = exec_fn_entry(irb->exec); if (!fn_entry) { add_node_error(irb->codegen, node, buf_sprintf("await outside function definition")); @@ -7915,7 +7936,7 @@ static IrInstruction *ir_gen_await_expr(IrBuilder *irb, Scope *scope, AstNode *n return irb->codegen->invalid_instruction; } - IrInstruction *target_inst = ir_gen_node_extra(irb, node->data.await_expr.expr, scope, LValPtr, nullptr); + IrInstruction *target_inst = ir_gen_node_extra(irb, expr_node, scope, LValPtr, nullptr); if (target_inst == irb->codegen->invalid_instruction) return irb->codegen->invalid_instruction; @@ -15244,44 +15265,61 @@ static IrInstruction *ir_analyze_instruction_reset_result(IrAnalyze *ira, IrInst return ir_const_void(ira, &instruction->base); } +static IrInstruction *get_async_call_result_loc(IrAnalyze *ira, IrInstructionCallSrc *call_instruction, + ZigType *fn_ret_type) +{ + ir_assert(call_instruction->is_async_call_builtin, &call_instruction->base); + IrInstruction *ret_ptr_uncasted = call_instruction->args[call_instruction->arg_count]->child; + if (type_is_invalid(ret_ptr_uncasted->value.type)) + return ira->codegen->invalid_instruction; + if (ret_ptr_uncasted->value.type->id == ZigTypeIdVoid) { + // Result location will be inside the async frame. + return nullptr; + } + return ir_implicit_cast(ira, ret_ptr_uncasted, get_pointer_to_type(ira->codegen, fn_ret_type, false)); +} + static IrInstruction *ir_analyze_async_call(IrAnalyze *ira, IrInstructionCallSrc *call_instruction, ZigFn *fn_entry, ZigType *fn_type, IrInstruction *fn_ref, IrInstruction **casted_args, size_t arg_count, IrInstruction *casted_new_stack) { - if (casted_new_stack != nullptr) { - // this is an @asyncCall - + if (fn_entry == nullptr) { if (fn_type->data.fn.fn_type_id.cc != CallingConventionAsync) { ir_add_error(ira, fn_ref, buf_sprintf("expected async function, found '%s'", buf_ptr(&fn_type->name))); return ira->codegen->invalid_instruction; } - - IrInstruction *ret_ptr = call_instruction->args[call_instruction->arg_count]->child; - if (type_is_invalid(ret_ptr->value.type)) + if (casted_new_stack == nullptr) { + ir_add_error(ira, fn_ref, buf_sprintf("function is not comptime-known; @asyncCall required")); + return ira->codegen->invalid_instruction; + } + } + if (casted_new_stack != nullptr) { + ZigType *fn_ret_type = fn_type->data.fn.fn_type_id.return_type; + IrInstruction *ret_ptr = get_async_call_result_loc(ira, call_instruction, fn_ret_type); + if (ret_ptr != nullptr && type_is_invalid(ret_ptr->value.type)) return ira->codegen->invalid_instruction; - ZigType *anyframe_type = get_any_frame_type(ira->codegen, fn_type->data.fn.fn_type_id.return_type); + ZigType *anyframe_type = get_any_frame_type(ira->codegen, fn_ret_type); - IrInstructionCallGen *call_gen = ir_build_call_gen(ira, &call_instruction->base, nullptr, fn_ref, - arg_count, casted_args, FnInlineAuto, true, casted_new_stack, ret_ptr, anyframe_type); + IrInstructionCallGen *call_gen = ir_build_call_gen(ira, &call_instruction->base, fn_entry, fn_ref, + arg_count, casted_args, FnInlineAuto, true, casted_new_stack, + call_instruction->is_async_call_builtin, ret_ptr, anyframe_type); return &call_gen->base; - } else if (fn_entry == nullptr) { - ir_add_error(ira, fn_ref, buf_sprintf("function is not comptime-known; @asyncCall required")); - return ira->codegen->invalid_instruction; - } - - ZigType *frame_type = get_fn_frame_type(ira->codegen, fn_entry); - IrInstruction *result_loc = ir_resolve_result(ira, &call_instruction->base, call_instruction->result_loc, - frame_type, nullptr, true, true, false); - if (type_is_invalid(result_loc->value.type) || instr_is_unreachable(result_loc)) { - return result_loc; + } else { + ZigType *frame_type = get_fn_frame_type(ira->codegen, fn_entry); + IrInstruction *result_loc = ir_resolve_result(ira, &call_instruction->base, call_instruction->result_loc, + frame_type, nullptr, true, true, false); + if (type_is_invalid(result_loc->value.type) || instr_is_unreachable(result_loc)) { + return result_loc; + } + result_loc = ir_implicit_cast(ira, result_loc, get_pointer_to_type(ira->codegen, frame_type, false)); + if (type_is_invalid(result_loc->value.type)) + return ira->codegen->invalid_instruction; + return &ir_build_call_gen(ira, &call_instruction->base, fn_entry, fn_ref, arg_count, + casted_args, FnInlineAuto, true, casted_new_stack, call_instruction->is_async_call_builtin, + result_loc, frame_type)->base; } - result_loc = ir_implicit_cast(ira, result_loc, get_pointer_to_type(ira->codegen, frame_type, false)); - if (type_is_invalid(result_loc->value.type)) - return ira->codegen->invalid_instruction; - return &ir_build_call_gen(ira, &call_instruction->base, fn_entry, fn_ref, arg_count, - casted_args, FnInlineAuto, true, nullptr, result_loc, frame_type)->base; } static bool ir_analyze_fn_call_inline_arg(IrAnalyze *ira, AstNode *fn_proto_node, IrInstruction *arg, Scope **exec_scope, size_t *next_proto_i) @@ -15790,16 +15828,27 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c IrInstruction *casted_new_stack = nullptr; if (call_instruction->new_stack != nullptr) { - ZigType *u8_ptr = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8, - false, false, PtrLenUnknown, target_fn_align(ira->codegen->zig_target), 0, 0, false); - ZigType *u8_slice = get_slice_type(ira->codegen, u8_ptr); IrInstruction *new_stack = call_instruction->new_stack->child; if (type_is_invalid(new_stack->value.type)) return ira->codegen->invalid_instruction; - casted_new_stack = ir_implicit_cast(ira, new_stack, u8_slice); - if (type_is_invalid(casted_new_stack->value.type)) - return ira->codegen->invalid_instruction; + if (call_instruction->is_async_call_builtin && + fn_entry != nullptr && new_stack->value.type->id == ZigTypeIdPointer && + new_stack->value.type->data.pointer.child_type->id == ZigTypeIdFnFrame) + { + ZigType *needed_frame_type = get_pointer_to_type(ira->codegen, + get_fn_frame_type(ira->codegen, fn_entry), false); + casted_new_stack = ir_implicit_cast(ira, new_stack, needed_frame_type); + if (type_is_invalid(casted_new_stack->value.type)) + return ira->codegen->invalid_instruction; + } else { + ZigType *u8_ptr = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8, + false, false, PtrLenUnknown, target_fn_align(ira->codegen->zig_target), 0, 0, false); + ZigType *u8_slice = get_slice_type(ira->codegen, u8_ptr); + casted_new_stack = ir_implicit_cast(ira, new_stack, u8_slice); + if (type_is_invalid(casted_new_stack->value.type)) + return ira->codegen->invalid_instruction; + } } if (fn_type->data.fn.is_generic) { @@ -16010,7 +16059,11 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c FnTypeId *impl_fn_type_id = &impl_fn->type_entry->data.fn.fn_type_id; IrInstruction *result_loc; - if (handle_is_ptr(impl_fn_type_id->return_type)) { + if (call_instruction->is_async_call_builtin) { + result_loc = get_async_call_result_loc(ira, call_instruction, impl_fn_type_id->return_type); + if (result_loc != nullptr && type_is_invalid(result_loc->value.type)) + return ira->codegen->invalid_instruction; + } else if (handle_is_ptr(impl_fn_type_id->return_type)) { result_loc = ir_resolve_result(ira, &call_instruction->base, call_instruction->result_loc, impl_fn_type_id->return_type, nullptr, true, true, false); if (result_loc != nullptr) { @@ -16044,7 +16097,7 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c IrInstructionCallGen *new_call_instruction = ir_build_call_gen(ira, &call_instruction->base, impl_fn, nullptr, impl_param_count, casted_args, fn_inline, - false, casted_new_stack, result_loc, + false, casted_new_stack, call_instruction->is_async_call_builtin, result_loc, impl_fn_type_id->return_type); parent_fn_entry->call_list.append(new_call_instruction); @@ -16167,7 +16220,11 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c } IrInstruction *result_loc; - if (handle_is_ptr(return_type)) { + if (call_instruction->is_async_call_builtin) { + result_loc = get_async_call_result_loc(ira, call_instruction, return_type); + if (result_loc != nullptr && type_is_invalid(result_loc->value.type)) + return ira->codegen->invalid_instruction; + } else if (handle_is_ptr(return_type)) { result_loc = ir_resolve_result(ira, &call_instruction->base, call_instruction->result_loc, return_type, nullptr, true, true, false); if (result_loc != nullptr) { @@ -16185,7 +16242,7 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c IrInstructionCallGen *new_call_instruction = ir_build_call_gen(ira, &call_instruction->base, fn_entry, fn_ref, call_param_count, casted_args, fn_inline, false, casted_new_stack, - result_loc, return_type); + call_instruction->is_async_call_builtin, result_loc, return_type); parent_fn_entry->call_list.append(new_call_instruction); return ir_finish_anal(ira, &new_call_instruction->base); } diff --git a/test/compile_errors.zig b/test/compile_errors.zig index a9e99f4799..12f17ec790 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2,6 +2,22 @@ const tests = @import("tests.zig"); const builtin = @import("builtin"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.add( + "wrong type for result ptr to @asyncCall", + \\export fn entry() void { + \\ _ = async amain(); + \\} + \\fn amain() i32 { + \\ var frame: @Frame(foo) = undefined; + \\ return await @asyncCall(&frame, false, foo); + \\} + \\fn foo() i32 { + \\ return 1234; + \\} + , + "tmp.zig:6:37: error: expected type '*i32', found 'bool'", + ); + cases.add( "struct depends on itself via optional field", \\const LhsExpr = struct { diff --git a/test/stage1/behavior/async_fn.zig b/test/stage1/behavior/async_fn.zig index 76a2780737..28a9ade1b3 100644 --- a/test/stage1/behavior/async_fn.zig +++ b/test/stage1/behavior/async_fn.zig @@ -331,8 +331,9 @@ test "async fn with inferred error set" { fn doTheTest() void { var frame: [1]@Frame(middle) = undefined; - var result: anyerror!void = undefined; - _ = @asyncCall(@sliceToBytes(frame[0..]), &result, middle); + var fn_ptr = middle; + var result: @typeOf(fn_ptr).ReturnType.ErrorSet!void = undefined; + _ = @asyncCall(@sliceToBytes(frame[0..]), &result, fn_ptr); resume global_frame; std.testing.expectError(error.Fail, result); } @@ -819,6 +820,34 @@ test "struct parameter to async function is copied to the frame" { } test "cast fn to async fn when it is inferred to be async" { + const S = struct { + var frame: anyframe = undefined; + var ok = false; + + fn doTheTest() void { + var ptr: async fn () i32 = undefined; + ptr = func; + var buf: [100]u8 align(16) = undefined; + var result: i32 = undefined; + const f = @asyncCall(&buf, &result, ptr); + _ = await f; + expect(result == 1234); + ok = true; + } + + fn func() i32 { + suspend { + frame = @frame(); + } + return 1234; + } + }; + _ = async S.doTheTest(); + resume S.frame; + expect(S.ok); +} + +test "cast fn to async fn when it is inferred to be async, awaited directly" { const S = struct { var frame: anyframe = undefined; var ok = false; @@ -919,3 +948,75 @@ fn recursiveAsyncFunctionTest(comptime suspending_implementation: bool) type { } }; } + +test "@asyncCall with comptime-known function, but not awaited directly" { + const S = struct { + var global_frame: anyframe = undefined; + + fn doTheTest() void { + var frame: [1]@Frame(middle) = undefined; + var result: @typeOf(middle).ReturnType.ErrorSet!void = undefined; + _ = @asyncCall(@sliceToBytes(frame[0..]), &result, middle); + resume global_frame; + std.testing.expectError(error.Fail, result); + } + + async fn middle() !void { + var f = async middle2(); + return await f; + } + + fn middle2() !void { + return failing(); + } + + fn failing() !void { + global_frame = @frame(); + suspend; + return error.Fail; + } + }; + S.doTheTest(); +} + +test "@asyncCall with actual frame instead of byte buffer" { + const S = struct { + fn func() i32 { + suspend; + return 1234; + } + }; + var frame: @Frame(S.func) = undefined; + var result: i32 = undefined; + const ptr = @asyncCall(&frame, &result, S.func); + resume ptr; + expect(result == 1234); +} + +test "@asyncCall using the result location inside the frame" { + const S = struct { + async fn simple2(y: *i32) i32 { + defer y.* += 2; + y.* += 1; + suspend; + return 1234; + } + fn getAnswer(f: anyframe->i32, out: *i32) void { + var res = await f; // TODO https://github.com/ziglang/zig/issues/3077 + out.* = res; + } + }; + var data: i32 = 1; + const Foo = struct { + bar: async fn (*i32) i32, + }; + var foo = Foo{ .bar = S.simple2 }; + var bytes: [64]u8 align(16) = undefined; + const f = @asyncCall(&bytes, {}, foo.bar, &data); + comptime expect(@typeOf(f) == anyframe->i32); + expect(data == 2); + resume f; + expect(data == 4); + _ = async S.getAnswer(f, &data); + expect(data == 1234); +} -- cgit v1.2.3