diff --git a/BRANCH_TODO b/BRANCH_TODO index a9bc5f3666..0ac1062b43 100644 --- a/BRANCH_TODO +++ b/BRANCH_TODO @@ -1,9 +1,8 @@ + * @asyncCall with an async function pointer * compile error for error: expected anyframe->T, found 'anyframe' * compile error for error: expected anyframe->T, found 'i32' * await of a non async function - * await in single-threaded mode * async call on a non async function - * @asyncCall with an async function pointer * cancel * defer and errdefer * safety for double await @@ -21,3 +20,16 @@ * compile error for copying a frame * compile error for resuming a const frame pointer * runtime safety enabling/disabling scope has to be coordinated across resume/await/calls/return + * await in single-threaded mode + * calling a generic function which is async + * make sure `await @asyncCall` and `await async` are handled correctly. + * allow @asyncCall with a real @Frame(func) (the point of this is result pointer) + * documentation + - @asyncCall + - @frame + - @Frame + - @frameSize + - coroutines section + - suspend + - resume + - anyframe, anyframe->T diff --git a/src/all_types.hpp b/src/all_types.hpp index 0f8cce1376..87db8edf8d 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1503,6 +1503,7 @@ enum BuiltinFnId { BuiltinFnIdInlineCall, BuiltinFnIdNoInlineCall, BuiltinFnIdNewStackCall, + BuiltinFnIdAsyncCall, BuiltinFnIdTypeId, BuiltinFnIdShlExact, BuiltinFnIdShrExact, @@ -1553,6 +1554,7 @@ enum PanicMsgId { PanicMsgIdBadAwait, PanicMsgIdBadReturn, PanicMsgIdResumedAnAwaitingFn, + PanicMsgIdFrameTooSmall, PanicMsgIdCount, }; @@ -3699,6 +3701,7 @@ static const size_t maybe_null_index = 1; static const size_t err_union_err_index = 0; static const size_t err_union_payload_index = 1; +// label (grep this): [coro_frame_struct_layout] static const size_t coro_fn_ptr_index = 0; static const size_t coro_awaiter_index = 1; static const size_t coro_arg_start = 2; diff --git a/src/analyze.cpp b/src/analyze.cpp index 5eb70d6717..cd8f981ff3 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -5205,6 +5205,7 @@ static Error resolve_coro_frame(CodeGen *g, ZigType *frame_type) { call->frame_result_loc = &alloca_gen->base; } + // label (grep this): [coro_frame_struct_layout] ZigList field_types = {}; ZigList field_names = {}; @@ -7525,6 +7526,7 @@ static void resolve_llvm_types_any_frame(CodeGen *g, ZigType *any_frame_type, Re if (result_type == nullptr) { g->anyframe_fn_type = ptr_result_type; } + // label (grep this): [coro_frame_struct_layout] LLVMTypeRef field_types[] = { ptr_result_type, // fn_ptr usize_type_ref, // awaiter @@ -7558,6 +7560,7 @@ static void resolve_llvm_types_any_frame(CodeGen *g, ZigType *any_frame_type, Re ZigLLVMReplaceTemporary(g->dbuilder, frame_header_di_type, replacement_di_type); } else { ZigType *ptr_result_type = get_pointer_to_type(g, result_type, false); + // label (grep this): [coro_frame_struct_layout] LLVMTypeRef field_types[] = { LLVMPointerType(fn_type, 0), // fn_ptr usize_type_ref, // awaiter diff --git a/src/codegen.cpp b/src/codegen.cpp index db617e636a..ebdd9e6120 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -879,6 +879,8 @@ static Buf *panic_msg_buf(PanicMsgId msg_id) { return buf_create_from_str("async function returned twice"); case PanicMsgIdResumedAnAwaitingFn: return buf_create_from_str("awaiting function resumed"); + case PanicMsgIdFrameTooSmall: + return buf_create_from_str("frame too small"); } zig_unreachable(); } @@ -3479,7 +3481,18 @@ static void render_async_var_decls(CodeGen *g, Scope *scope) { } } +static LLVMValueRef gen_frame_size(CodeGen *g, LLVMValueRef fn_val) { + LLVMTypeRef usize_llvm_type = g->builtin_types.entry_usize->llvm_type; + LLVMTypeRef ptr_usize_llvm_type = LLVMPointerType(usize_llvm_type, 0); + LLVMValueRef casted_fn_val = LLVMBuildBitCast(g->builder, fn_val, ptr_usize_llvm_type, ""); + LLVMValueRef negative_one = LLVMConstInt(LLVMInt32Type(), -1, true); + LLVMValueRef prefix_ptr = LLVMBuildInBoundsGEP(g->builder, casted_fn_val, &negative_one, 1, ""); + return LLVMBuildLoad(g->builder, prefix_ptr, ""); +} + static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstructionCallGen *instruction) { + LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type; + LLVMValueRef fn_val; ZigType *fn_type; bool callee_is_async; @@ -3511,34 +3524,54 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr LLVMValueRef awaiter_init_val; LLVMValueRef ret_ptr; if (instruction->is_async) { - frame_result_loc = result_loc; awaiter_init_val = zero; - if (ret_has_bits) { - ret_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, coro_arg_start + 1, ""); - } - // Use the result location which is inside the frame if this is an async call. - if (ret_has_bits) { - LLVMValueRef ret_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, coro_arg_start, ""); - LLVMBuildStore(g->builder, ret_ptr, ret_ptr_ptr); + if (instruction->new_stack == nullptr) { + 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, coro_arg_start + 1, ""); + } + } 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, ""); + LLVMValueRef given_frame_len = LLVMBuildLoad(g->builder, given_len_ptr, ""); + LLVMValueRef actual_frame_len = gen_frame_size(g, fn_val); + + LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "FrameSizeCheckFail"); + LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "FrameSizeCheckOk"); + + LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntUGE, given_frame_len, actual_frame_len, ""); + LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); + + LLVMPositionBuilderAtEnd(g->builder, fail_block); + gen_safety_crash(g, PanicMsgIdFrameTooSmall); + + LLVMPositionBuilderAtEnd(g->builder, ok_block); + } + 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 (ret_has_bits) { + // Use the result location provided to the @asyncCall builtin + ret_ptr = result_loc; + } } } else if (callee_is_async) { frame_result_loc = ir_llvm_value(g, instruction->frame_result_loc); awaiter_init_val = LLVMBuildPtrToInt(g->builder, g->cur_ret_ptr, g->builtin_types.entry_usize->llvm_type, ""); // caller's own frame pointer if (ret_has_bits) { + // Use the call instruction's result location. ret_ptr = result_loc; } - - // Use the call instruction's result location. - if (ret_has_bits) { - LLVMValueRef ret_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, coro_arg_start, ""); - LLVMBuildStore(g->builder, result_loc, ret_ptr_ptr); - } } if (instruction->is_async || callee_is_async) { assert(frame_result_loc != nullptr); - assert(instruction->fn_entry != nullptr); if (prefix_arg_err_ret_stack) { zig_panic("TODO"); @@ -3547,6 +3580,10 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr LLVMValueRef awaiter_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, coro_awaiter_index, ""); LLVMBuildStore(g->builder, awaiter_init_val, awaiter_ptr); + if (ret_has_bits) { + LLVMValueRef ret_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, coro_arg_start, ""); + LLVMBuildStore(g->builder, ret_ptr, ret_ptr_ptr); + } } if (!instruction->is_async && !callee_is_async) { if (first_arg_ret) { @@ -3581,16 +3618,37 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr if (instruction->is_async || callee_is_async) { size_t ret_2_or_0 = type_has_bits(fn_type->data.fn.fn_type_id.return_type) ? 2 : 0; + size_t arg_start_i = coro_arg_start + ret_2_or_0; + + LLVMValueRef casted_frame; + if (instruction->new_stack != nullptr) { + // We need the frame type to be a pointer to a struct that includes the args + // label (grep this): [coro_frame_struct_layout] + size_t field_count = arg_start_i + gen_param_values.length; + LLVMTypeRef *field_types = allocate_nonzero(field_count); + LLVMGetStructElementTypes(LLVMGetElementType(LLVMTypeOf(frame_result_loc)), field_types); + for (size_t arg_i = 0; arg_i < gen_param_values.length; arg_i += 1) { + field_types[arg_start_i + arg_i] = LLVMTypeOf(gen_param_values.at(arg_i)); + } + LLVMTypeRef frame_with_args_type = LLVMStructType(field_types, field_count, false); + LLVMTypeRef ptr_frame_with_args_type = LLVMPointerType(frame_with_args_type, 0); + + casted_frame = LLVMBuildBitCast(g->builder, frame_result_loc, ptr_frame_with_args_type, ""); + } else { + casted_frame = frame_result_loc; + } + for (size_t arg_i = 0; arg_i < gen_param_values.length; arg_i += 1) { - LLVMValueRef arg_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, - coro_arg_start + ret_2_or_0 + arg_i, ""); + LLVMValueRef arg_ptr = LLVMBuildStructGEP(g->builder, casted_frame, arg_start_i + arg_i, ""); LLVMBuildStore(g->builder, gen_param_values.at(arg_i), arg_ptr); } } - LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type; if (instruction->is_async) { LLVMValueRef args[] = {frame_result_loc, LLVMGetUndef(usize_type_ref)}; ZigLLVMBuildCall(g->builder, fn_val, args, 2, llvm_cc, fn_inline, ""); + if (instruction->new_stack != nullptr) { + return frame_result_loc; + } return nullptr; } else if (callee_is_async) { ZigType *ptr_result_type = get_pointer_to_type(g, src_return_type, true); @@ -5223,13 +5281,8 @@ static LLVMValueRef ir_render_coro_resume(CodeGen *g, IrExecutable *executable, static LLVMValueRef ir_render_frame_size(CodeGen *g, IrExecutable *executable, IrInstructionFrameSizeGen *instruction) { - LLVMTypeRef usize_llvm_type = g->builtin_types.entry_usize->llvm_type; - LLVMTypeRef ptr_usize_llvm_type = LLVMPointerType(usize_llvm_type, 0); LLVMValueRef fn_val = ir_llvm_value(g, instruction->fn); - LLVMValueRef casted_fn_val = LLVMBuildBitCast(g->builder, fn_val, ptr_usize_llvm_type, ""); - LLVMValueRef negative_one = LLVMConstInt(LLVMInt32Type(), -1, true); - LLVMValueRef prefix_ptr = LLVMBuildInBoundsGEP(g->builder, casted_fn_val, &negative_one, 1, ""); - return LLVMBuildLoad(g->builder, prefix_ptr, ""); + return gen_frame_size(g, fn_val); } static void set_debug_location(CodeGen *g, IrInstruction *instruction) { @@ -7097,13 +7150,13 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdFloor, "floor", 2); create_builtin_fn(g, BuiltinFnIdCeil, "ceil", 2); create_builtin_fn(g, BuiltinFnIdTrunc, "trunc", 2); - //Needs library support on Windows - //create_builtin_fn(g, BuiltinFnIdNearbyInt, "nearbyInt", 2); + create_builtin_fn(g, BuiltinFnIdNearbyInt, "nearbyInt", 2); create_builtin_fn(g, BuiltinFnIdRound, "round", 2); create_builtin_fn(g, BuiltinFnIdMulAdd, "mulAdd", 4); create_builtin_fn(g, BuiltinFnIdInlineCall, "inlineCall", SIZE_MAX); create_builtin_fn(g, BuiltinFnIdNoInlineCall, "noInlineCall", SIZE_MAX); create_builtin_fn(g, BuiltinFnIdNewStackCall, "newStackCall", SIZE_MAX); + create_builtin_fn(g, BuiltinFnIdAsyncCall, "asyncCall", SIZE_MAX); create_builtin_fn(g, BuiltinFnIdTypeId, "typeId", 1); create_builtin_fn(g, BuiltinFnIdShlExact, "shlExact", 2); create_builtin_fn(g, BuiltinFnIdShrExact, "shrExact", 2); diff --git a/src/ir.cpp b/src/ir.cpp index b01f43b3e1..fbf9da9656 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -1402,6 +1402,10 @@ static IrInstruction *ir_build_call_src(IrBuilder *irb, Scope *scope, AstNode *s if (fn_ref != nullptr) ir_ref_instruction(fn_ref, irb->current_basic_block); for (size_t i = 0; i < arg_count; i += 1) ir_ref_instruction(args[i], irb->current_basic_block); + if (is_async && new_stack != nullptr) { + // in this case the arg at the end is the return pointer + ir_ref_instruction(args[arg_count], irb->current_basic_block); + } if (new_stack != nullptr) ir_ref_instruction(new_stack, irb->current_basic_block); return &call_instruction->base; @@ -5203,8 +5207,10 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo } case BuiltinFnIdNewStackCall: { - if (node->data.fn_call_expr.params.length == 0) { - add_node_error(irb->codegen, node, buf_sprintf("expected at least 1 argument, found 0")); + if (node->data.fn_call_expr.params.length < 2) { + add_node_error(irb->codegen, node, + buf_sprintf("expected at least 2 arguments, found %" ZIG_PRI_usize, + node->data.fn_call_expr.params.length)); return irb->codegen->invalid_instruction; } @@ -5232,6 +5238,50 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo FnInlineAuto, 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); + } case BuiltinFnIdTypeId: { AstNode *arg0_node = node->data.fn_call_expr.params.at(0); @@ -14817,11 +14867,31 @@ static IrInstruction *ir_analyze_instruction_reset_result(IrAnalyze *ira, IrInst } 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) + ZigType *fn_type, IrInstruction *fn_ref, IrInstruction **casted_args, size_t arg_count, + IrInstruction *casted_new_stack) { if (fn_entry == nullptr) { - ir_add_error(ira, fn_ref, buf_sprintf("function is not comptime-known; @asyncCall required")); - return ira->codegen->invalid_instruction; + if (call_instruction->new_stack == nullptr) { + ir_add_error(ira, fn_ref, buf_sprintf("function is not comptime-known; @asyncCall required")); + return ira->codegen->invalid_instruction; + } + // this is an @asyncCall + + 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)) + return ira->codegen->invalid_instruction; + + ZigType *anyframe_type = get_any_frame_type(ira->codegen, fn_type->data.fn.fn_type_id.return_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); + return &call_gen->base; } ZigType *frame_type = get_coro_frame_type(ira->codegen, fn_entry); @@ -15559,13 +15629,13 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c size_t impl_param_count = impl_fn_type_id->param_count; if (call_instruction->is_async) { - zig_panic("TODO async call"); + IrInstruction *result = ir_analyze_async_call(ira, call_instruction, impl_fn, impl_fn->type_entry, + nullptr, casted_args, call_param_count, casted_new_stack); + return ir_finish_anal(ira, result); } - if (!call_instruction->is_async) { - if (impl_fn_type_id->cc == CallingConventionAsync && parent_fn_entry->inferred_async_node == nullptr) { - parent_fn_entry->inferred_async_node = fn_ref->source_node; - } + if (impl_fn_type_id->cc == CallingConventionAsync && parent_fn_entry->inferred_async_node == nullptr) { + parent_fn_entry->inferred_async_node = fn_ref->source_node; } IrInstructionCallGen *new_call_instruction = ir_build_call_gen(ira, &call_instruction->base, @@ -15645,18 +15715,16 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c return ira->codegen->invalid_instruction; } - if (!call_instruction->is_async) { - if (fn_type_id->cc == CallingConventionAsync && parent_fn_entry->inferred_async_node == nullptr) { - parent_fn_entry->inferred_async_node = fn_ref->source_node; - } - } - if (call_instruction->is_async) { IrInstruction *result = ir_analyze_async_call(ira, call_instruction, fn_entry, fn_type, fn_ref, - casted_args, call_param_count); + casted_args, call_param_count, casted_new_stack); return ir_finish_anal(ira, result); } + if (fn_type_id->cc == CallingConventionAsync && parent_fn_entry->inferred_async_node == nullptr) { + parent_fn_entry->inferred_async_node = fn_ref->source_node; + } + IrInstruction *result_loc; if (handle_is_ptr(return_type)) { result_loc = ir_resolve_result(ira, &call_instruction->base, call_instruction->result_loc, diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 3245632e37..2941cadcf5 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2,6 +2,18 @@ const tests = @import("tests.zig"); const builtin = @import("builtin"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.add( + "non async function pointer passed to @asyncCall", + \\export fn entry() void { + \\ var ptr = afunc; + \\ var bytes: [100]u8 = undefined; + \\ _ = @asyncCall(&bytes, {}, ptr); + \\} + \\fn afunc() void { } + , + "tmp.zig:4:32: error: expected async function, found 'fn() void'", + ); + cases.add( "runtime-known async function called", \\export fn entry() void { diff --git a/test/runtime_safety.zig b/test/runtime_safety.zig index 43cf0856c3..ac9037caae 100644 --- a/test/runtime_safety.zig +++ b/test/runtime_safety.zig @@ -1,6 +1,20 @@ const tests = @import("tests.zig"); pub fn addCases(cases: *tests.CompareOutputContext) void { + cases.addRuntimeSafety("@asyncCall with too small a frame", + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ @import("std").os.exit(126); + \\} + \\pub fn main() void { + \\ var bytes: [1]u8 = undefined; + \\ var ptr = other; + \\ var frame = @asyncCall(&bytes, {}, ptr); + \\} + \\async fn other() void { + \\ suspend; + \\} + ); + cases.addRuntimeSafety("resuming a function which is awaiting a frame", \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); @@ -17,6 +31,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ suspend; \\} ); + cases.addRuntimeSafety("resuming a function which is awaiting a call", \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126); diff --git a/test/stage1/behavior/coroutines.zig b/test/stage1/behavior/coroutines.zig index 2b82dce707..511568a898 100644 --- a/test/stage1/behavior/coroutines.zig +++ b/test/stage1/behavior/coroutines.zig @@ -260,22 +260,42 @@ test "async function with dot syntax" { expect(S.y == 2); } -//test "async fn pointer in a struct field" { -// var data: i32 = 1; -// const Foo = struct { -// bar: async fn (*i32) void, -// }; -// var foo = Foo{ .bar = simpleAsyncFn2 }; -// const p = async foo.bar(&data); -// expect(data == 2); -// resume p; -// expect(data == 4); -//} -//async fn simpleAsyncFn2(y: *i32) void { -// defer y.* += 2; -// y.* += 1; -// suspend; -//} +test "async fn pointer in a struct field" { + var data: i32 = 1; + const Foo = struct { + bar: async fn (*i32) void, + }; + var foo = Foo{ .bar = simpleAsyncFn2 }; + var bytes: [64]u8 = undefined; + const p = @asyncCall(&bytes, {}, foo.bar, &data); + comptime expect(@typeOf(p) == anyframe->void); + expect(data == 2); + resume p; + expect(data == 4); +} +async fn simpleAsyncFn2(y: *i32) void { + defer y.* += 2; + y.* += 1; + suspend; +} + +test "@asyncCall with return type" { + const Foo = struct { + bar: async fn () i32, + + async fn afunc() i32 { + suspend; + return 1234; + } + }; + var foo = Foo{ .bar = Foo.afunc }; + var bytes: [64]u8 = undefined; + var aresult: i32 = 0; + const frame = @asyncCall(&bytes, &aresult, foo.bar); + expect(aresult == 0); + resume frame; + expect(aresult == 1234); +} //test "async fn with inferred error set" { // const p = async failing();