aboutsummaryrefslogtreecommitdiff
path: root/test/behavior/async_fn.zig
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2021-04-29 15:54:04 -0700
committerAndrew Kelley <andrew@ziglang.org>2021-04-29 15:54:04 -0700
commit4307436b9945f814ff5731981df1d19febf3ba0a (patch)
treeefaaec94a41d632d45f255d464d9787eb02c4c9b /test/behavior/async_fn.zig
parent5a02c938dafdf2bb11b2350b6ad3161ef93744f0 (diff)
downloadzig-4307436b9945f814ff5731981df1d19febf3ba0a.tar.gz
zig-4307436b9945f814ff5731981df1d19febf3ba0a.zip
move behavior tests from test/stage1/ to test/
And fix test cases to make them pass. This is in preparation for starting to pass behavior tests with self-hosted.
Diffstat (limited to 'test/behavior/async_fn.zig')
-rw-r--r--test/behavior/async_fn.zig1673
1 files changed, 1673 insertions, 0 deletions
diff --git a/test/behavior/async_fn.zig b/test/behavior/async_fn.zig
new file mode 100644
index 0000000000..0765eac7e8
--- /dev/null
+++ b/test/behavior/async_fn.zig
@@ -0,0 +1,1673 @@
+const std = @import("std");
+const builtin = std.builtin;
+const expect = std.testing.expect;
+const expectEqual = std.testing.expectEqual;
+const expectEqualStrings = std.testing.expectEqualStrings;
+const expectError = std.testing.expectError;
+
+var global_x: i32 = 1;
+
+test "simple coroutine suspend and resume" {
+ var frame = async simpleAsyncFn();
+ expect(global_x == 2);
+ resume frame;
+ expect(global_x == 3);
+ const af: anyframe->void = &frame;
+ resume frame;
+ expect(global_x == 4);
+}
+fn simpleAsyncFn() void {
+ global_x += 1;
+ suspend {}
+ global_x += 1;
+ suspend {}
+ global_x += 1;
+}
+
+var global_y: i32 = 1;
+
+test "pass parameter to coroutine" {
+ var p = async simpleAsyncFnWithArg(2);
+ expect(global_y == 3);
+ resume p;
+ expect(global_y == 5);
+}
+fn simpleAsyncFnWithArg(delta: i32) void {
+ global_y += delta;
+ suspend {}
+ global_y += delta;
+}
+
+test "suspend at end of function" {
+ const S = struct {
+ var x: i32 = 1;
+
+ fn doTheTest() void {
+ expect(x == 1);
+ const p = async suspendAtEnd();
+ expect(x == 2);
+ }
+
+ fn suspendAtEnd() void {
+ x += 1;
+ suspend {}
+ }
+ };
+ S.doTheTest();
+}
+
+test "local variable in async function" {
+ const S = struct {
+ var x: i32 = 0;
+
+ fn doTheTest() void {
+ expect(x == 0);
+ var p = async add(1, 2);
+ expect(x == 0);
+ resume p;
+ expect(x == 0);
+ resume p;
+ expect(x == 0);
+ resume p;
+ expect(x == 3);
+ }
+
+ fn add(a: i32, b: i32) void {
+ var accum: i32 = 0;
+ suspend {}
+ accum += a;
+ suspend {}
+ accum += b;
+ suspend {}
+ x = accum;
+ }
+ };
+ S.doTheTest();
+}
+
+test "calling an inferred async function" {
+ const S = struct {
+ var x: i32 = 1;
+ var other_frame: *@Frame(other) = undefined;
+
+ fn doTheTest() void {
+ _ = async first();
+ expect(x == 1);
+ resume other_frame.*;
+ expect(x == 2);
+ }
+
+ fn first() void {
+ other();
+ }
+ fn other() void {
+ other_frame = @frame();
+ suspend {}
+ x += 1;
+ }
+ };
+ S.doTheTest();
+}
+
+test "@frameSize" {
+ const S = struct {
+ fn doTheTest() void {
+ {
+ var ptr = @ptrCast(fn (i32) callconv(.Async) void, other);
+ const size = @frameSize(ptr);
+ expect(size == @sizeOf(@Frame(other)));
+ }
+ {
+ var ptr = @ptrCast(fn () callconv(.Async) void, first);
+ const size = @frameSize(ptr);
+ expect(size == @sizeOf(@Frame(first)));
+ }
+ }
+
+ fn first() void {
+ other(1);
+ }
+ fn other(param: i32) void {
+ var local: i32 = undefined;
+ suspend {}
+ }
+ };
+ S.doTheTest();
+}
+
+test "coroutine suspend, resume" {
+ const S = struct {
+ var frame: anyframe = undefined;
+
+ fn doTheTest() void {
+ _ = async amain();
+ seq('d');
+ resume frame;
+ seq('h');
+
+ expect(std.mem.eql(u8, &points, "abcdefgh"));
+ }
+
+ fn amain() void {
+ seq('a');
+ var f = async testAsyncSeq();
+ seq('c');
+ await f;
+ seq('g');
+ }
+
+ fn testAsyncSeq() void {
+ defer seq('f');
+
+ seq('b');
+ suspend {
+ frame = @frame();
+ }
+ seq('e');
+ }
+ var points = [_]u8{'x'} ** "abcdefgh".len;
+ var index: usize = 0;
+
+ fn seq(c: u8) void {
+ points[index] = c;
+ index += 1;
+ }
+ };
+ S.doTheTest();
+}
+
+test "coroutine suspend with block" {
+ const p = async testSuspendBlock();
+ expect(!global_result);
+ resume a_promise;
+ expect(global_result);
+}
+
+var a_promise: anyframe = undefined;
+var global_result = false;
+fn testSuspendBlock() callconv(.Async) void {
+ suspend {
+ comptime expect(@TypeOf(@frame()) == *@Frame(testSuspendBlock));
+ a_promise = @frame();
+ }
+
+ // Test to make sure that @frame() works as advertised (issue #1296)
+ // var our_handle: anyframe = @frame();
+ expect(a_promise == @as(anyframe, @frame()));
+
+ global_result = true;
+}
+
+var await_a_promise: anyframe = undefined;
+var await_final_result: i32 = 0;
+
+test "coroutine await" {
+ await_seq('a');
+ var p = async await_amain();
+ await_seq('f');
+ resume await_a_promise;
+ await_seq('i');
+ expect(await_final_result == 1234);
+ expect(std.mem.eql(u8, &await_points, "abcdefghi"));
+}
+fn await_amain() callconv(.Async) void {
+ await_seq('b');
+ var p = async await_another();
+ await_seq('e');
+ await_final_result = await p;
+ await_seq('h');
+}
+fn await_another() callconv(.Async) i32 {
+ await_seq('c');
+ suspend {
+ await_seq('d');
+ await_a_promise = @frame();
+ }
+ await_seq('g');
+ return 1234;
+}
+
+var await_points = [_]u8{0} ** "abcdefghi".len;
+var await_seq_index: usize = 0;
+
+fn await_seq(c: u8) void {
+ await_points[await_seq_index] = c;
+ await_seq_index += 1;
+}
+
+var early_final_result: i32 = 0;
+
+test "coroutine await early return" {
+ early_seq('a');
+ var p = async early_amain();
+ early_seq('f');
+ expect(early_final_result == 1234);
+ expect(std.mem.eql(u8, &early_points, "abcdef"));
+}
+fn early_amain() callconv(.Async) void {
+ early_seq('b');
+ var p = async early_another();
+ early_seq('d');
+ early_final_result = await p;
+ early_seq('e');
+}
+fn early_another() callconv(.Async) i32 {
+ early_seq('c');
+ return 1234;
+}
+
+var early_points = [_]u8{0} ** "abcdef".len;
+var early_seq_index: usize = 0;
+
+fn early_seq(c: u8) void {
+ early_points[early_seq_index] = c;
+ early_seq_index += 1;
+}
+
+test "async function with dot syntax" {
+ const S = struct {
+ var y: i32 = 1;
+ fn foo() callconv(.Async) void {
+ y += 1;
+ suspend {}
+ }
+ };
+ const p = async S.foo();
+ expect(S.y == 2);
+}
+
+test "async fn pointer in a struct field" {
+ var data: i32 = 1;
+ const Foo = struct {
+ bar: fn (*i32) callconv(.Async) void,
+ };
+ var foo = Foo{ .bar = simpleAsyncFn2 };
+ var bytes: [64]u8 align(16) = undefined;
+ const f = @asyncCall(&bytes, {}, foo.bar, .{&data});
+ comptime expect(@TypeOf(f) == anyframe->void);
+ expect(data == 2);
+ resume f;
+ expect(data == 4);
+ _ = async doTheAwait(f);
+ expect(data == 4);
+}
+
+fn doTheAwait(f: anyframe->void) void {
+ await f;
+}
+fn simpleAsyncFn2(y: *i32) callconv(.Async) void {
+ defer y.* += 2;
+ y.* += 1;
+ suspend {}
+}
+
+test "@asyncCall with return type" {
+ const Foo = struct {
+ bar: fn () callconv(.Async) i32,
+
+ var global_frame: anyframe = undefined;
+ fn middle() callconv(.Async) i32 {
+ return afunc();
+ }
+
+ fn afunc() i32 {
+ global_frame = @frame();
+ suspend {}
+ return 1234;
+ }
+ };
+ var foo = Foo{ .bar = Foo.middle };
+ var bytes: [150]u8 align(16) = undefined;
+ var aresult: i32 = 0;
+ _ = @asyncCall(&bytes, &aresult, foo.bar, .{});
+ expect(aresult == 0);
+ resume Foo.global_frame;
+ expect(aresult == 1234);
+}
+
+test "async fn with inferred error set" {
+ const S = struct {
+ var global_frame: anyframe = undefined;
+
+ fn doTheTest() void {
+ var frame: [1]@Frame(middle) = undefined;
+ var fn_ptr = middle;
+ var result: @typeInfo(@typeInfo(@TypeOf(fn_ptr)).Fn.return_type.?).ErrorUnion.error_set!void = undefined;
+ _ = @asyncCall(std.mem.sliceAsBytes(frame[0..]), &result, fn_ptr, .{});
+ resume global_frame;
+ std.testing.expectError(error.Fail, result);
+ }
+ fn middle() callconv(.Async) !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 "error return trace across suspend points - early return" {
+ const p = nonFailing();
+ resume p;
+ const p2 = async printTrace(p);
+}
+
+test "error return trace across suspend points - async return" {
+ const p = nonFailing();
+ const p2 = async printTrace(p);
+ resume p;
+}
+
+fn nonFailing() (anyframe->anyerror!void) {
+ const Static = struct {
+ var frame: @Frame(suspendThenFail) = undefined;
+ };
+ Static.frame = async suspendThenFail();
+ return &Static.frame;
+}
+fn suspendThenFail() callconv(.Async) anyerror!void {
+ suspend {}
+ return error.Fail;
+}
+fn printTrace(p: anyframe->(anyerror!void)) callconv(.Async) void {
+ (await p) catch |e| {
+ std.testing.expect(e == error.Fail);
+ if (@errorReturnTrace()) |trace| {
+ expect(trace.index == 1);
+ } else switch (builtin.mode) {
+ .Debug, .ReleaseSafe => @panic("expected return trace"),
+ .ReleaseFast, .ReleaseSmall => {},
+ }
+ };
+}
+
+test "break from suspend" {
+ var my_result: i32 = 1;
+ const p = async testBreakFromSuspend(&my_result);
+ std.testing.expect(my_result == 2);
+}
+fn testBreakFromSuspend(my_result: *i32) callconv(.Async) void {
+ suspend {
+ resume @frame();
+ }
+ my_result.* += 1;
+ suspend {}
+ my_result.* += 1;
+}
+
+test "heap allocated async function frame" {
+ const S = struct {
+ var x: i32 = 42;
+
+ fn doTheTest() !void {
+ const frame = try std.testing.allocator.create(@Frame(someFunc));
+ defer std.testing.allocator.destroy(frame);
+
+ expect(x == 42);
+ frame.* = async someFunc();
+ expect(x == 43);
+ resume frame;
+ expect(x == 44);
+ }
+
+ fn someFunc() void {
+ x += 1;
+ suspend {}
+ x += 1;
+ }
+ };
+ try S.doTheTest();
+}
+
+test "async function call return value" {
+ const S = struct {
+ var frame: anyframe = undefined;
+ var pt = Point{ .x = 10, .y = 11 };
+
+ fn doTheTest() void {
+ expectEqual(pt.x, 10);
+ expectEqual(pt.y, 11);
+ _ = async first();
+ expectEqual(pt.x, 10);
+ expectEqual(pt.y, 11);
+ resume frame;
+ expectEqual(pt.x, 1);
+ expectEqual(pt.y, 2);
+ }
+
+ fn first() void {
+ pt = second(1, 2);
+ }
+
+ fn second(x: i32, y: i32) Point {
+ return other(x, y);
+ }
+
+ fn other(x: i32, y: i32) Point {
+ frame = @frame();
+ suspend {}
+ return Point{
+ .x = x,
+ .y = y,
+ };
+ }
+
+ const Point = struct {
+ x: i32,
+ y: i32,
+ };
+ };
+ S.doTheTest();
+}
+
+test "suspension points inside branching control flow" {
+ const S = struct {
+ var result: i32 = 10;
+
+ fn doTheTest() void {
+ expect(10 == result);
+ var frame = async func(true);
+ expect(10 == result);
+ resume frame;
+ expect(11 == result);
+ resume frame;
+ expect(12 == result);
+ resume frame;
+ expect(13 == result);
+ }
+
+ fn func(b: bool) void {
+ while (b) {
+ suspend {}
+ result += 1;
+ }
+ }
+ };
+ S.doTheTest();
+}
+
+test "call async function which has struct return type" {
+ const S = struct {
+ var frame: anyframe = undefined;
+
+ fn doTheTest() void {
+ _ = async atest();
+ resume frame;
+ }
+
+ fn atest() void {
+ const result = func();
+ expect(result.x == 5);
+ expect(result.y == 6);
+ }
+
+ const Point = struct {
+ x: usize,
+ y: usize,
+ };
+
+ fn func() Point {
+ suspend {
+ frame = @frame();
+ }
+ return Point{
+ .x = 5,
+ .y = 6,
+ };
+ }
+ };
+ S.doTheTest();
+}
+
+test "pass string literal to async function" {
+ const S = struct {
+ var frame: anyframe = undefined;
+ var ok: bool = false;
+
+ fn doTheTest() void {
+ _ = async hello("hello");
+ resume frame;
+ expect(ok);
+ }
+
+ fn hello(msg: []const u8) void {
+ frame = @frame();
+ suspend {}
+ expectEqualStrings("hello", msg);
+ ok = true;
+ }
+ };
+ S.doTheTest();
+}
+
+test "await inside an errdefer" {
+ const S = struct {
+ var frame: anyframe = undefined;
+
+ fn doTheTest() void {
+ _ = async amainWrap();
+ resume frame;
+ }
+
+ fn amainWrap() !void {
+ var foo = async func();
+ errdefer await foo;
+ return error.Bad;
+ }
+
+ fn func() void {
+ frame = @frame();
+ suspend {}
+ }
+ };
+ S.doTheTest();
+}
+
+test "try in an async function with error union and non-zero-bit payload" {
+ const S = struct {
+ var frame: anyframe = undefined;
+ var ok = false;
+
+ fn doTheTest() void {
+ _ = async amain();
+ resume frame;
+ expect(ok);
+ }
+
+ fn amain() void {
+ std.testing.expectError(error.Bad, theProblem());
+ ok = true;
+ }
+
+ fn theProblem() ![]u8 {
+ frame = @frame();
+ suspend {}
+ const result = try other();
+ return result;
+ }
+
+ fn other() ![]u8 {
+ return error.Bad;
+ }
+ };
+ S.doTheTest();
+}
+
+test "returning a const error from async function" {
+ const S = struct {
+ var frame: anyframe = undefined;
+ var ok = false;
+
+ fn doTheTest() void {
+ _ = async amain();
+ resume frame;
+ expect(ok);
+ }
+
+ fn amain() !void {
+ var download_frame = async fetchUrl(10, "a string");
+ const download_text = try await download_frame;
+
+ @panic("should not get here");
+ }
+
+ fn fetchUrl(unused: i32, url: []const u8) ![]u8 {
+ frame = @frame();
+ suspend {}
+ ok = true;
+ return error.OutOfMemory;
+ }
+ };
+ S.doTheTest();
+}
+
+test "async/await typical usage" {
+ inline for ([_]bool{ false, true }) |b1| {
+ inline for ([_]bool{ false, true }) |b2| {
+ inline for ([_]bool{ false, true }) |b3| {
+ inline for ([_]bool{ false, true }) |b4| {
+ testAsyncAwaitTypicalUsage(b1, b2, b3, b4).doTheTest();
+ }
+ }
+ }
+ }
+}
+
+fn testAsyncAwaitTypicalUsage(
+ comptime simulate_fail_download: bool,
+ comptime simulate_fail_file: bool,
+ comptime suspend_download: bool,
+ comptime suspend_file: bool,
+) type {
+ return struct {
+ fn doTheTest() void {
+ _ = async amainWrap();
+ if (suspend_file) {
+ resume global_file_frame;
+ }
+ if (suspend_download) {
+ resume global_download_frame;
+ }
+ }
+ fn amainWrap() void {
+ if (amain()) |_| {
+ expect(!simulate_fail_download);
+ expect(!simulate_fail_file);
+ } else |e| switch (e) {
+ error.NoResponse => expect(simulate_fail_download),
+ error.FileNotFound => expect(simulate_fail_file),
+ else => @panic("test failure"),
+ }
+ }
+
+ fn amain() !void {
+ const allocator = std.testing.allocator;
+ var download_frame = async fetchUrl(allocator, "https://example.com/");
+ var download_awaited = false;
+ errdefer if (!download_awaited) {
+ if (await download_frame) |x| allocator.free(x) else |_| {}
+ };
+
+ var file_frame = async readFile(allocator, "something.txt");
+ var file_awaited = false;
+ errdefer if (!file_awaited) {
+ if (await file_frame) |x| allocator.free(x) else |_| {}
+ };
+
+ download_awaited = true;
+ const download_text = try await download_frame;
+ defer allocator.free(download_text);
+
+ file_awaited = true;
+ const file_text = try await file_frame;
+ defer allocator.free(file_text);
+
+ expect(std.mem.eql(u8, "expected download text", download_text));
+ expect(std.mem.eql(u8, "expected file text", file_text));
+ }
+
+ var global_download_frame: anyframe = undefined;
+ fn fetchUrl(allocator: *std.mem.Allocator, url: []const u8) anyerror![]u8 {
+ const result = try std.mem.dupe(allocator, u8, "expected download text");
+ errdefer allocator.free(result);
+ if (suspend_download) {
+ suspend {
+ global_download_frame = @frame();
+ }
+ }
+ if (simulate_fail_download) return error.NoResponse;
+ return result;
+ }
+
+ var global_file_frame: anyframe = undefined;
+ fn readFile(allocator: *std.mem.Allocator, filename: []const u8) anyerror![]u8 {
+ const result = try std.mem.dupe(allocator, u8, "expected file text");
+ errdefer allocator.free(result);
+ if (suspend_file) {
+ suspend {
+ global_file_frame = @frame();
+ }
+ }
+ if (simulate_fail_file) return error.FileNotFound;
+ return result;
+ }
+ };
+}
+
+test "alignment of local variables in async functions" {
+ const S = struct {
+ fn doTheTest() void {
+ var y: u8 = 123;
+ var x: u8 align(128) = 1;
+ expect(@ptrToInt(&x) % 128 == 0);
+ }
+ };
+ S.doTheTest();
+}
+
+test "no reason to resolve frame still works" {
+ _ = async simpleNothing();
+}
+fn simpleNothing() void {
+ var x: i32 = 1234;
+}
+
+test "async call a generic function" {
+ const S = struct {
+ fn doTheTest() void {
+ var f = async func(i32, 2);
+ const result = await f;
+ expect(result == 3);
+ }
+
+ fn func(comptime T: type, inc: T) T {
+ var x: T = 1;
+ suspend {
+ resume @frame();
+ }
+ x += inc;
+ return x;
+ }
+ };
+ _ = async S.doTheTest();
+}
+
+test "return from suspend block" {
+ const S = struct {
+ fn doTheTest() void {
+ expect(func() == 1234);
+ }
+ fn func() i32 {
+ suspend {
+ return 1234;
+ }
+ }
+ };
+ _ = async S.doTheTest();
+}
+
+test "struct parameter to async function is copied to the frame" {
+ const S = struct {
+ const Point = struct {
+ x: i32,
+ y: i32,
+ };
+
+ var frame: anyframe = undefined;
+
+ fn doTheTest() void {
+ _ = async atest();
+ resume frame;
+ }
+
+ fn atest() void {
+ var f: @Frame(foo) = undefined;
+ bar(&f);
+ clobberStack(10);
+ }
+
+ fn clobberStack(x: i32) void {
+ if (x == 0) return;
+ clobberStack(x - 1);
+ var y: i32 = x;
+ }
+
+ fn bar(f: *@Frame(foo)) void {
+ var pt = Point{ .x = 1, .y = 2 };
+ f.* = async foo(pt);
+ var result = await f;
+ expect(result == 1);
+ }
+
+ fn foo(point: Point) i32 {
+ suspend {
+ frame = @frame();
+ }
+ return point.x;
+ }
+ };
+ S.doTheTest();
+}
+
+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: fn () callconv(.Async) 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;
+
+ fn doTheTest() void {
+ var ptr: fn () callconv(.Async) i32 = undefined;
+ ptr = func;
+ var buf: [100]u8 align(16) = undefined;
+ var result: i32 = undefined;
+ _ = await @asyncCall(&buf, &result, ptr, .{});
+ expect(result == 1234);
+ ok = true;
+ }
+
+ fn func() i32 {
+ suspend {
+ frame = @frame();
+ }
+ return 1234;
+ }
+ };
+ _ = async S.doTheTest();
+ resume S.frame;
+ expect(S.ok);
+}
+
+test "await does not force async if callee is blocking" {
+ const S = struct {
+ fn simple() i32 {
+ return 1234;
+ }
+ };
+ var x = async S.simple();
+ expect(await x == 1234);
+}
+
+test "recursive async function" {
+ expect(recursiveAsyncFunctionTest(false).doTheTest() == 55);
+ expect(recursiveAsyncFunctionTest(true).doTheTest() == 55);
+}
+
+fn recursiveAsyncFunctionTest(comptime suspending_implementation: bool) type {
+ return struct {
+ fn fib(allocator: *std.mem.Allocator, x: u32) error{OutOfMemory}!u32 {
+ if (x <= 1) return x;
+
+ if (suspending_implementation) {
+ suspend {
+ resume @frame();
+ }
+ }
+
+ const f1 = try allocator.create(@Frame(fib));
+ defer allocator.destroy(f1);
+
+ const f2 = try allocator.create(@Frame(fib));
+ defer allocator.destroy(f2);
+
+ f1.* = async fib(allocator, x - 1);
+ var f1_awaited = false;
+ errdefer if (!f1_awaited) {
+ _ = await f1;
+ };
+
+ f2.* = async fib(allocator, x - 2);
+ var f2_awaited = false;
+ errdefer if (!f2_awaited) {
+ _ = await f2;
+ };
+
+ var sum: u32 = 0;
+
+ f1_awaited = true;
+ sum += try await f1;
+
+ f2_awaited = true;
+ sum += try await f2;
+
+ return sum;
+ }
+
+ fn doTheTest() u32 {
+ if (suspending_implementation) {
+ var result: u32 = undefined;
+ _ = async amain(&result);
+ return result;
+ } else {
+ return fib(std.testing.allocator, 10) catch unreachable;
+ }
+ }
+
+ fn amain(result: *u32) void {
+ var x = async fib(std.testing.allocator, 10);
+ result.* = (await x) catch unreachable;
+ }
+ };
+}
+
+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: @typeInfo(@typeInfo(@TypeOf(middle)).Fn.return_type.?).ErrorUnion.error_set!void = undefined;
+ _ = @asyncCall(std.mem.sliceAsBytes(frame[0..]), &result, middle, .{});
+ resume global_frame;
+ std.testing.expectError(error.Fail, result);
+ }
+ fn middle() callconv(.Async) !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 {
+ fn simple2(y: *i32) callconv(.Async) i32 {
+ defer y.* += 2;
+ y.* += 1;
+ suspend {}
+ return 1234;
+ }
+ fn getAnswer(f: anyframe->i32, out: *i32) void {
+ out.* = await f;
+ }
+ };
+ var data: i32 = 1;
+ const Foo = struct {
+ bar: fn (*i32) callconv(.Async) 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);
+}
+
+test "@TypeOf an async function call of generic fn with error union type" {
+ const S = struct {
+ fn func(comptime x: anytype) anyerror!i32 {
+ const T = @TypeOf(async func(x));
+ comptime expect(T == @typeInfo(@TypeOf(@frame())).Pointer.child);
+ return undefined;
+ }
+ };
+ _ = async S.func(i32);
+}
+
+test "using @TypeOf on a generic function call" {
+ const S = struct {
+ var global_frame: anyframe = undefined;
+ var global_ok = false;
+
+ var buf: [100]u8 align(16) = undefined;
+
+ fn amain(x: anytype) void {
+ if (x == 0) {
+ global_ok = true;
+ return;
+ }
+ suspend {
+ global_frame = @frame();
+ }
+ const F = @TypeOf(async amain(x - 1));
+ const frame = @intToPtr(*F, @ptrToInt(&buf));
+ return await @asyncCall(frame, {}, amain, .{x - 1});
+ }
+ };
+ _ = async S.amain(@as(u32, 1));
+ resume S.global_frame;
+ expect(S.global_ok);
+}
+
+test "recursive call of await @asyncCall with struct return type" {
+ const S = struct {
+ var global_frame: anyframe = undefined;
+ var global_ok = false;
+
+ var buf: [100]u8 align(16) = undefined;
+
+ fn amain(x: anytype) Foo {
+ if (x == 0) {
+ global_ok = true;
+ return Foo{ .x = 1, .y = 2, .z = 3 };
+ }
+ suspend {
+ global_frame = @frame();
+ }
+ const F = @TypeOf(async amain(x - 1));
+ const frame = @intToPtr(*F, @ptrToInt(&buf));
+ return await @asyncCall(frame, {}, amain, .{x - 1});
+ }
+
+ const Foo = struct {
+ x: u64,
+ y: u64,
+ z: u64,
+ };
+ };
+ var res: S.Foo = undefined;
+ var frame: @TypeOf(async S.amain(@as(u32, 1))) = undefined;
+ _ = @asyncCall(&frame, &res, S.amain, .{@as(u32, 1)});
+ resume S.global_frame;
+ expect(S.global_ok);
+ expect(res.x == 1);
+ expect(res.y == 2);
+ expect(res.z == 3);
+}
+
+test "nosuspend function call" {
+ const S = struct {
+ fn doTheTest() void {
+ const result = nosuspend add(50, 100);
+ expect(result == 150);
+ }
+ fn add(a: i32, b: i32) i32 {
+ if (a > 100) {
+ suspend {}
+ }
+ return a + b;
+ }
+ };
+ S.doTheTest();
+}
+
+test "await used in expression and awaiting fn with no suspend but async calling convention" {
+ const S = struct {
+ fn atest() void {
+ var f1 = async add(1, 2);
+ var f2 = async add(3, 4);
+
+ const sum = (await f1) + (await f2);
+ expect(sum == 10);
+ }
+ fn add(a: i32, b: i32) callconv(.Async) i32 {
+ return a + b;
+ }
+ };
+ _ = async S.atest();
+}
+
+test "await used in expression after a fn call" {
+ const S = struct {
+ fn atest() void {
+ var f1 = async add(3, 4);
+ var sum: i32 = 0;
+ sum = foo() + await f1;
+ expect(sum == 8);
+ }
+ fn add(a: i32, b: i32) callconv(.Async) i32 {
+ return a + b;
+ }
+ fn foo() i32 {
+ return 1;
+ }
+ };
+ _ = async S.atest();
+}
+
+test "async fn call used in expression after a fn call" {
+ const S = struct {
+ fn atest() void {
+ var sum: i32 = 0;
+ sum = foo() + add(3, 4);
+ expect(sum == 8);
+ }
+ fn add(a: i32, b: i32) callconv(.Async) i32 {
+ return a + b;
+ }
+ fn foo() i32 {
+ return 1;
+ }
+ };
+ _ = async S.atest();
+}
+
+test "suspend in for loop" {
+ const S = struct {
+ var global_frame: ?anyframe = null;
+
+ fn doTheTest() void {
+ _ = async atest();
+ while (global_frame) |f| resume f;
+ }
+
+ fn atest() void {
+ expect(func(&[_]u8{ 1, 2, 3 }) == 6);
+ }
+ fn func(stuff: []const u8) u32 {
+ global_frame = @frame();
+ var sum: u32 = 0;
+ for (stuff) |x| {
+ suspend {}
+ sum += x;
+ }
+ global_frame = null;
+ return sum;
+ }
+ };
+ S.doTheTest();
+}
+
+test "suspend in while loop" {
+ const S = struct {
+ var global_frame: ?anyframe = null;
+
+ fn doTheTest() void {
+ _ = async atest();
+ while (global_frame) |f| resume f;
+ }
+
+ fn atest() void {
+ expect(optional(6) == 6);
+ expect(errunion(6) == 6);
+ }
+ fn optional(stuff: ?u32) u32 {
+ global_frame = @frame();
+ defer global_frame = null;
+ while (stuff) |val| {
+ suspend {}
+ return val;
+ }
+ return 0;
+ }
+ fn errunion(stuff: anyerror!u32) u32 {
+ global_frame = @frame();
+ defer global_frame = null;
+ while (stuff) |val| {
+ suspend {}
+ return val;
+ } else |err| {
+ return 0;
+ }
+ }
+ };
+ S.doTheTest();
+}
+
+test "correctly spill when returning the error union result of another async fn" {
+ const S = struct {
+ var global_frame: anyframe = undefined;
+
+ fn doTheTest() void {
+ expect((atest() catch unreachable) == 1234);
+ }
+
+ fn atest() !i32 {
+ return fallible1();
+ }
+
+ fn fallible1() anyerror!i32 {
+ suspend {
+ global_frame = @frame();
+ }
+ return 1234;
+ }
+ };
+ _ = async S.doTheTest();
+ resume S.global_frame;
+}
+
+test "spill target expr in a for loop" {
+ const S = struct {
+ var global_frame: anyframe = undefined;
+
+ fn doTheTest() void {
+ var foo = Foo{
+ .slice = &[_]i32{ 1, 2 },
+ };
+ expect(atest(&foo) == 3);
+ }
+
+ const Foo = struct {
+ slice: []const i32,
+ };
+
+ fn atest(foo: *Foo) i32 {
+ var sum: i32 = 0;
+ for (foo.slice) |x| {
+ suspend {
+ global_frame = @frame();
+ }
+ sum += x;
+ }
+ return sum;
+ }
+ };
+ _ = async S.doTheTest();
+ resume S.global_frame;
+ resume S.global_frame;
+}
+
+test "spill target expr in a for loop, with a var decl in the loop body" {
+ const S = struct {
+ var global_frame: anyframe = undefined;
+
+ fn doTheTest() void {
+ var foo = Foo{
+ .slice = &[_]i32{ 1, 2 },
+ };
+ expect(atest(&foo) == 3);
+ }
+
+ const Foo = struct {
+ slice: []const i32,
+ };
+
+ fn atest(foo: *Foo) i32 {
+ var sum: i32 = 0;
+ for (foo.slice) |x| {
+ // Previously this var decl would prevent spills. This test makes sure
+ // the for loop spills still happen even though there is a VarDecl in scope
+ // before the suspend.
+ var anything = true;
+ _ = anything;
+ suspend {
+ global_frame = @frame();
+ }
+ sum += x;
+ }
+ return sum;
+ }
+ };
+ _ = async S.doTheTest();
+ resume S.global_frame;
+ resume S.global_frame;
+}
+
+test "async call with @call" {
+ const S = struct {
+ var global_frame: anyframe = undefined;
+ fn doTheTest() void {
+ _ = @call(.{ .modifier = .async_kw }, atest, .{});
+ resume global_frame;
+ }
+ fn atest() void {
+ var frame = @call(.{ .modifier = .async_kw }, afoo, .{});
+ const res = await frame;
+ expect(res == 42);
+ }
+ fn afoo() i32 {
+ suspend {
+ global_frame = @frame();
+ }
+ return 42;
+ }
+ };
+ S.doTheTest();
+}
+
+test "async function passed 0-bit arg after non-0-bit arg" {
+ const S = struct {
+ var global_frame: anyframe = undefined;
+ var global_int: i32 = 0;
+
+ fn foo() void {
+ bar(1, .{}) catch unreachable;
+ }
+
+ fn bar(x: i32, args: anytype) anyerror!void {
+ global_frame = @frame();
+ suspend {}
+ global_int = x;
+ }
+ };
+ _ = async S.foo();
+ resume S.global_frame;
+ expect(S.global_int == 1);
+}
+
+test "async function passed align(16) arg after align(8) arg" {
+ const S = struct {
+ var global_frame: anyframe = undefined;
+ var global_int: u128 = 0;
+
+ fn foo() void {
+ var a: u128 = 99;
+ bar(10, .{a}) catch unreachable;
+ }
+
+ fn bar(x: u64, args: anytype) anyerror!void {
+ expect(x == 10);
+ global_frame = @frame();
+ suspend {}
+ global_int = args[0];
+ }
+ };
+ _ = async S.foo();
+ resume S.global_frame;
+ expect(S.global_int == 99);
+}
+
+test "async function call resolves target fn frame, comptime func" {
+ const S = struct {
+ var global_frame: anyframe = undefined;
+ var global_int: i32 = 9;
+
+ fn foo() anyerror!void {
+ const stack_size = 1000;
+ var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
+ return await @asyncCall(&stack_frame, {}, bar, .{});
+ }
+
+ fn bar() anyerror!void {
+ global_frame = @frame();
+ suspend {}
+ global_int += 1;
+ }
+ };
+ _ = async S.foo();
+ resume S.global_frame;
+ expect(S.global_int == 10);
+}
+
+test "async function call resolves target fn frame, runtime func" {
+ const S = struct {
+ var global_frame: anyframe = undefined;
+ var global_int: i32 = 9;
+
+ fn foo() anyerror!void {
+ const stack_size = 1000;
+ var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
+ var func: fn () callconv(.Async) anyerror!void = bar;
+ return await @asyncCall(&stack_frame, {}, func, .{});
+ }
+
+ fn bar() anyerror!void {
+ global_frame = @frame();
+ suspend {}
+ global_int += 1;
+ }
+ };
+ _ = async S.foo();
+ resume S.global_frame;
+ expect(S.global_int == 10);
+}
+
+test "properly spill optional payload capture value" {
+ const S = struct {
+ var global_frame: anyframe = undefined;
+ var global_int: usize = 2;
+
+ fn foo() void {
+ var opt: ?usize = 1234;
+ if (opt) |x| {
+ bar();
+ global_int += x;
+ }
+ }
+
+ fn bar() void {
+ global_frame = @frame();
+ suspend {}
+ global_int += 1;
+ }
+ };
+ _ = async S.foo();
+ resume S.global_frame;
+ expect(S.global_int == 1237);
+}
+
+test "handle defer interfering with return value spill" {
+ const S = struct {
+ var global_frame1: anyframe = undefined;
+ var global_frame2: anyframe = undefined;
+ var finished = false;
+ var baz_happened = false;
+
+ fn doTheTest() void {
+ _ = async testFoo();
+ resume global_frame1;
+ resume global_frame2;
+ expect(baz_happened);
+ expect(finished);
+ }
+
+ fn testFoo() void {
+ expectError(error.Bad, foo());
+ finished = true;
+ }
+
+ fn foo() anyerror!void {
+ defer baz();
+ return bar() catch |err| return err;
+ }
+
+ fn bar() anyerror!void {
+ global_frame1 = @frame();
+ suspend {}
+ return error.Bad;
+ }
+
+ fn baz() void {
+ global_frame2 = @frame();
+ suspend {}
+ baz_happened = true;
+ }
+ };
+ S.doTheTest();
+}
+
+test "take address of temporary async frame" {
+ const S = struct {
+ var global_frame: anyframe = undefined;
+ var finished = false;
+
+ fn doTheTest() void {
+ _ = async asyncDoTheTest();
+ resume global_frame;
+ expect(finished);
+ }
+
+ fn asyncDoTheTest() void {
+ expect(finishIt(&async foo(10)) == 1245);
+ finished = true;
+ }
+
+ fn foo(arg: i32) i32 {
+ global_frame = @frame();
+ suspend {}
+ return arg + 1234;
+ }
+
+ fn finishIt(frame: anyframe->i32) i32 {
+ return (await frame) + 1;
+ }
+ };
+ S.doTheTest();
+}
+
+test "nosuspend await" {
+ const S = struct {
+ var finished = false;
+
+ fn doTheTest() void {
+ var frame = async foo(false);
+ expect(nosuspend await frame == 42);
+ finished = true;
+ }
+
+ fn foo(want_suspend: bool) i32 {
+ if (want_suspend) {
+ suspend {}
+ }
+ return 42;
+ }
+ };
+ S.doTheTest();
+ expect(S.finished);
+}
+
+test "nosuspend on function calls" {
+ const S0 = struct {
+ b: i32 = 42,
+ };
+ const S1 = struct {
+ fn c() S0 {
+ return S0{};
+ }
+ fn d() !S0 {
+ return S0{};
+ }
+ };
+ expectEqual(@as(i32, 42), nosuspend S1.c().b);
+ expectEqual(@as(i32, 42), (try nosuspend S1.d()).b);
+}
+
+test "nosuspend on async function calls" {
+ const S0 = struct {
+ b: i32 = 42,
+ };
+ const S1 = struct {
+ fn c() S0 {
+ return S0{};
+ }
+ fn d() !S0 {
+ return S0{};
+ }
+ };
+ var frame_c = nosuspend async S1.c();
+ expectEqual(@as(i32, 42), (await frame_c).b);
+ var frame_d = nosuspend async S1.d();
+ expectEqual(@as(i32, 42), (try await frame_d).b);
+}
+
+// test "resume nosuspend async function calls" {
+// const S0 = struct {
+// b: i32 = 42,
+// };
+// const S1 = struct {
+// fn c() S0 {
+// suspend {}
+// return S0{};
+// }
+// fn d() !S0 {
+// suspend {}
+// return S0{};
+// }
+// };
+// var frame_c = nosuspend async S1.c();
+// resume frame_c;
+// expectEqual(@as(i32, 42), (await frame_c).b);
+// var frame_d = nosuspend async S1.d();
+// resume frame_d;
+// expectEqual(@as(i32, 42), (try await frame_d).b);
+// }
+
+test "nosuspend resume async function calls" {
+ const S0 = struct {
+ b: i32 = 42,
+ };
+ const S1 = struct {
+ fn c() S0 {
+ suspend {}
+ return S0{};
+ }
+ fn d() !S0 {
+ suspend {}
+ return S0{};
+ }
+ };
+ var frame_c = async S1.c();
+ nosuspend resume frame_c;
+ expectEqual(@as(i32, 42), (await frame_c).b);
+ var frame_d = async S1.d();
+ nosuspend resume frame_d;
+ expectEqual(@as(i32, 42), (try await frame_d).b);
+}
+
+test "avoid forcing frame alignment resolution implicit cast to *c_void" {
+ const S = struct {
+ var x: ?*c_void = null;
+
+ fn foo() bool {
+ suspend {
+ x = @frame();
+ }
+ return true;
+ }
+ };
+ var frame = async S.foo();
+ resume @ptrCast(anyframe->bool, @alignCast(@alignOf(@Frame(S.foo)), S.x));
+ expect(nosuspend await frame);
+}
+
+test "@asyncCall with pass-by-value arguments" {
+ const F0: u64 = 0xbeefbeefbeefbeef;
+ const F1: u64 = 0xf00df00df00df00d;
+ const F2: u64 = 0xcafecafecafecafe;
+
+ const S = struct {
+ pub const ST = struct { f0: usize, f1: usize };
+ pub const AT = [5]u8;
+
+ pub fn f(_fill0: u64, s: ST, _fill1: u64, a: AT, _fill2: u64) callconv(.Async) void {
+ // Check that the array and struct arguments passed by value don't
+ // end up overflowing the adjacent fields in the frame structure.
+ expectEqual(F0, _fill0);
+ expectEqual(F1, _fill1);
+ expectEqual(F2, _fill2);
+ }
+ };
+
+ var buffer: [1024]u8 align(@alignOf(@Frame(S.f))) = undefined;
+ // The function pointer must not be comptime-known.
+ var t = S.f;
+ var frame_ptr = @asyncCall(&buffer, {}, t, .{
+ F0,
+ .{ .f0 = 1, .f1 = 2 },
+ F1,
+ [_]u8{ 1, 2, 3, 4, 5 },
+ F2,
+ });
+}
+
+test "@asyncCall with arguments having non-standard alignment" {
+ const F0: u64 = 0xbeefbeef;
+ const F1: u64 = 0xf00df00df00df00d;
+
+ const S = struct {
+ pub fn f(_fill0: u32, s: struct { x: u64 align(16) }, _fill1: u64) callconv(.Async) void {
+ // The compiler inserts extra alignment for s, check that the
+ // generated code picks the right slot for fill1.
+ expectEqual(F0, _fill0);
+ expectEqual(F1, _fill1);
+ }
+ };
+
+ var buffer: [1024]u8 align(@alignOf(@Frame(S.f))) = undefined;
+ // The function pointer must not be comptime-known.
+ var t = S.f;
+ var frame_ptr = @asyncCall(&buffer, {}, t, .{ F0, undefined, F1 });
+}