Commit 87710a1cc2

Andrew Kelley <andrew@ziglang.org>
2019-08-03 22:14:24
implement `@asyncCall` which supports async function pointers
1 parent c879209
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;
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<ZigType *> field_types = {};
     ZigList<const char *> 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
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<LLVMTypeRef>(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);
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<IrInstruction*>(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,
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();
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 {
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);
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