langref: eliminate dependencies on stage1

This commit removes async/await/suspend/resume from the language
reference, as that feature does not yet work in the self-hosted
compiler.

We will be regressing this feature temporarily. Users of these language
features should stick with 0.10.x with the `-fstage1` flag until they
are restored.

See tracking issue #6025.
This commit is contained in:
Andrew Kelley 2022-12-01 15:28:44 -07:00
parent 2823fcabd1
commit 6e52f36d46

View File

@ -1179,33 +1179,8 @@ test "this will be skipped" {
return error.SkipZigTest; return error.SkipZigTest;
} }
{#code_end#} {#code_end#}
<p>
The default test runner skips tests containing a {#link|suspend point|Async Functions#} while the
test is running using the default, blocking IO mode.
(The evented IO mode is enabled using the <kbd>--test-evented-io</kbd> command line parameter.)
</p>
{#code_begin|test|async_skip#}
{#backend_stage1#}
const std = @import("std");
test "async skip test" {
var frame = async func();
const result = await frame;
try std.testing.expect(result == 1);
}
fn func() i32 {
suspend {
resume @frame();
}
return 1;
}
{#code_end#}
<p>
In the code sample above, the test would not be skipped in blocking IO mode if the {#syntax#}nosuspend{#endsyntax#}
keyword was used (see {#link|Async and Await#}).
</p>
{#header_close#} {#header_close#}
{#header_open|Report Memory Leaks#} {#header_open|Report Memory Leaks#}
<p> <p>
When code allocates {#link|Memory#} using the {#link|Zig Standard Library#}'s testing allocator, When code allocates {#link|Memory#} using the {#link|Zig Standard Library#}'s testing allocator,
@ -6288,7 +6263,6 @@ test "float widening" {
<li>Cast {#syntax#}5{#endsyntax#} to {#syntax#}comptime_float{#endsyntax#} resulting in {#syntax#}@as(comptime_float, 10.8){#endsyntax#}, which is casted to {#syntax#}@as(f32, 10.8){#endsyntax#}</li> <li>Cast {#syntax#}5{#endsyntax#} to {#syntax#}comptime_float{#endsyntax#} resulting in {#syntax#}@as(comptime_float, 10.8){#endsyntax#}, which is casted to {#syntax#}@as(f32, 10.8){#endsyntax#}</li>
</ul> </ul>
{#code_begin|test_err#} {#code_begin|test_err#}
{#backend_stage1#}
// Compile time coercion of float to int // Compile time coercion of float to int
test "implicit cast to comptime_int" { test "implicit cast to comptime_int" {
var f: f32 = 54.0 / 5; var f: f32 = 54.0 / 5;
@ -7400,7 +7374,6 @@ pub fn main() void {
</p> </p>
{#code_begin|exe#} {#code_begin|exe#}
{#target_linux_x86_64#} {#target_linux_x86_64#}
{#backend_stage1#}
pub fn main() noreturn { pub fn main() noreturn {
const msg = "hello world\n"; const msg = "hello world\n";
_ = syscall3(SYS_write, STDOUT_FILENO, @ptrToInt(msg), msg.len); _ = syscall3(SYS_write, STDOUT_FILENO, @ptrToInt(msg), msg.len);
@ -7588,388 +7561,15 @@ test "global assembly" {
<p>TODO: @atomic rmw</p> <p>TODO: @atomic rmw</p>
<p>TODO: builtin atomic memory ordering enum</p> <p>TODO: builtin atomic memory ordering enum</p>
{#header_close#} {#header_close#}
{#header_open|Async Functions#} {#header_open|Async Functions#}
<p> <p>Async functions are being temporarily regressed and will be
When a function is called, a frame is pushed to the stack, <a href="https://github.com/ziglang/zig/issues/6025">restored before Zig
the function runs until it reaches a return statement, and then the frame is popped from the stack. 0.11.0 is tagged</a>. I apologize for the instability. Please use Zig 0.10.0 with
The code following the callsite does not run until the function returns. the <code>-fstage1</code> flag for now if you need this feature.</p>
</p>
<p>
An async function is a function whose execution is split into an {#syntax#}async{#endsyntax#} initiation,
followed by an {#syntax#}await{#endsyntax#} completion. Its frame is
provided explicitly by the caller, and it can be suspended and resumed any number of times.
</p>
<p>
The code following the {#syntax#}async{#endsyntax#} callsite runs immediately after the async
function first suspends. When the return value of the async function is needed,
the calling code can {#syntax#}await{#endsyntax#} on the async function frame.
This will suspend the calling code until the async function completes, at which point
execution resumes just after the {#syntax#}await{#endsyntax#} callsite.
</p>
<p>
Zig infers that a function is {#syntax#}async{#endsyntax#} when it observes that the function contains
a <strong>suspension point</strong>. Async functions can be called the same as normal functions. A
function call of an async function is a suspend point.
</p>
{#header_open|Suspend and Resume#}
<p>
At any point, a function may suspend itself. This causes control flow to
return to the callsite (in the case of the first suspension),
or resumer (in the case of subsequent suspensions).
</p>
{#code_begin|test|suspend_no_resume#}
{#backend_stage1#}
const std = @import("std");
const expect = std.testing.expect;
var x: i32 = 1;
test "suspend with no resume" {
var frame = async func();
try expect(x == 2);
_ = frame;
}
fn func() void {
x += 1;
suspend {}
// This line is never reached because the suspend has no matching resume.
x += 1;
}
{#code_end#}
<p>
In the same way that each allocation should have a corresponding free,
Each {#syntax#}suspend{#endsyntax#} should have a corresponding {#syntax#}resume{#endsyntax#}.
A <strong>suspend block</strong> allows a function to put a pointer to its own
frame somewhere, for example into an event loop, even if that action will perform a
{#syntax#}resume{#endsyntax#} operation on a different thread.
{#link|@frame#} provides access to the async function frame pointer.
</p>
{#code_begin|test|async_suspend_block#}
{#backend_stage1#}
const std = @import("std");
const expect = std.testing.expect;
var the_frame: anyframe = undefined;
var result = false;
test "async function suspend with block" {
_ = async testSuspendBlock();
try expect(!result);
resume the_frame;
try expect(result);
}
fn testSuspendBlock() void {
suspend {
comptime try expect(@TypeOf(@frame()) == *@Frame(testSuspendBlock));
the_frame = @frame();
}
result = true;
}
{#code_end#}
<p>
{#syntax#}suspend{#endsyntax#} causes a function to be {#syntax#}async{#endsyntax#}.
</p>
{#header_open|Resuming from Suspend Blocks#}
<p>
Upon entering a {#syntax#}suspend{#endsyntax#} block, the async function is already considered
suspended, and can be resumed. For example, if you started another kernel thread,
and had that thread call {#syntax#}resume{#endsyntax#} on the frame pointer provided by the
{#link|@frame#}, the new thread would begin executing after the suspend
block, while the old thread continued executing the suspend block.
</p>
<p>
However, the async function can be directly resumed from the suspend block, in which case it
never returns to its resumer and continues executing.
</p>
{#code_begin|test|resume_from_suspend#}
{#backend_stage1#}
const std = @import("std");
const expect = std.testing.expect;
test "resume from suspend" {
var my_result: i32 = 1;
_ = async testResumeFromSuspend(&my_result);
try std.testing.expect(my_result == 2);
}
fn testResumeFromSuspend(my_result: *i32) void {
suspend {
resume @frame();
}
my_result.* += 1;
suspend {}
my_result.* += 1;
}
{#code_end#}
<p>
This is guaranteed to tail call, and therefore will not cause a new stack frame.
</p>
{#header_close#}
{#header_close#} {#header_close#}
{#header_open|Async and Await#} {#header_open|Builtin Functions|2col#}
<p>
In the same way that every {#syntax#}suspend{#endsyntax#} has a matching
{#syntax#}resume{#endsyntax#}, every {#syntax#}async{#endsyntax#} has a matching {#syntax#}await{#endsyntax#}
in standard code.
</p>
<p>
However, it is possible to have an {#syntax#}async{#endsyntax#} call
without a matching {#syntax#}await{#endsyntax#}. Upon completion of the async function,
execution would continue at the most recent {#syntax#}async{#endsyntax#} callsite or {#syntax#}resume{#endsyntax#} callsite,
and the return value of the async function would be lost.
</p>
{#code_begin|test|async_await#}
{#backend_stage1#}
const std = @import("std");
const expect = std.testing.expect;
test "async and await" {
// The test block is not async and so cannot have a suspend
// point in it. By using the nosuspend keyword, we promise that
// the code in amain will finish executing without suspending
// back to the test block.
nosuspend amain();
}
fn amain() void {
var frame = async func();
comptime try expect(@TypeOf(frame) == @Frame(func));
const ptr: anyframe->void = &frame;
const any_ptr: anyframe = ptr;
resume any_ptr;
await ptr;
}
fn func() void {
suspend {}
}
{#code_end#}
<p>
The {#syntax#}await{#endsyntax#} keyword is used to coordinate with an async function's
{#syntax#}return{#endsyntax#} statement.
</p>
<p>
{#syntax#}await{#endsyntax#} is a suspend point, and takes as an operand anything that
coerces to {#syntax#}anyframe->T{#endsyntax#}. Calling {#syntax#}await{#endsyntax#} on
the frame of an async function will cause execution to continue at the
{#syntax#}await{#endsyntax#} callsite once the target function completes.
</p>
<p>
There is a common misconception that {#syntax#}await{#endsyntax#} resumes the target function.
It is the other way around: it suspends until the target function completes.
In the event that the target function has already completed, {#syntax#}await{#endsyntax#}
does not suspend; instead it copies the
return value directly from the target function's frame.
</p>
{#code_begin|test|async_await_sequence#}
{#backend_stage1#}
const std = @import("std");
const expect = std.testing.expect;
var the_frame: anyframe = undefined;
var final_result: i32 = 0;
test "async function await" {
seq('a');
_ = async amain();
seq('f');
resume the_frame;
seq('i');
try expect(final_result == 1234);
try expect(std.mem.eql(u8, &seq_points, "abcdefghi"));
}
fn amain() void {
seq('b');
var f = async another();
seq('e');
final_result = await f;
seq('h');
}
fn another() i32 {
seq('c');
suspend {
seq('d');
the_frame = @frame();
}
seq('g');
return 1234;
}
var seq_points = [_]u8{0} ** "abcdefghi".len;
var seq_index: usize = 0;
fn seq(c: u8) void {
seq_points[seq_index] = c;
seq_index += 1;
}
{#code_end#}
<p>
In general, {#syntax#}suspend{#endsyntax#} is lower level than {#syntax#}await{#endsyntax#}. Most application
code will use only {#syntax#}async{#endsyntax#} and {#syntax#}await{#endsyntax#}, but event loop
implementations will make use of {#syntax#}suspend{#endsyntax#} internally.
</p>
{#header_close#}
{#header_open|Async Function Example#}
<p>
Putting all of this together, here is an example of typical
{#syntax#}async{#endsyntax#}/{#syntax#}await{#endsyntax#} usage:
</p>
{#code_begin|exe|async#}
{#backend_stage1#}
const std = @import("std");
const Allocator = std.mem.Allocator;
pub fn main() void {
_ = async amainWrap();
// Typically we would use an event loop to manage resuming async functions,
// but in this example we hard code what the event loop would do,
// to make things deterministic.
resume global_file_frame;
resume global_download_frame;
}
fn amainWrap() void {
amain() catch |e| {
std.debug.print("{}\n", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
std.process.exit(1);
};
}
fn amain() !void {
const allocator = std.heap.page_allocator;
var download_frame = async fetchUrl(allocator, "https://example.com/");
var awaited_download_frame = false;
errdefer if (!awaited_download_frame) {
if (await download_frame) |r| allocator.free(r) else |_| {}
};
var file_frame = async readFile(allocator, "something.txt");
var awaited_file_frame = false;
errdefer if (!awaited_file_frame) {
if (await file_frame) |r| allocator.free(r) else |_| {}
};
awaited_file_frame = true;
const file_text = try await file_frame;
defer allocator.free(file_text);
awaited_download_frame = true;
const download_text = try await download_frame;
defer allocator.free(download_text);
std.debug.print("download_text: {s}\n", .{download_text});
std.debug.print("file_text: {s}\n", .{file_text});
}
var global_download_frame: anyframe = undefined;
fn fetchUrl(allocator: Allocator, url: []const u8) ![]u8 {
_ = url; // this is just an example, we don't actually do it!
const result = try allocator.dupe(u8, "this is the downloaded url contents");
errdefer allocator.free(result);
suspend {
global_download_frame = @frame();
}
std.debug.print("fetchUrl returning\n", .{});
return result;
}
var global_file_frame: anyframe = undefined;
fn readFile(allocator: Allocator, filename: []const u8) ![]u8 {
_ = filename; // this is just an example, we don't actually do it!
const result = try allocator.dupe(u8, "this is the file contents");
errdefer allocator.free(result);
suspend {
global_file_frame = @frame();
}
std.debug.print("readFile returning\n", .{});
return result;
}
{#code_end#}
<p>
Now we remove the {#syntax#}suspend{#endsyntax#} and {#syntax#}resume{#endsyntax#} code, and
observe the same behavior, with one tiny difference:
</p>
{#code_begin|exe|blocking#}
{#backend_stage1#}
const std = @import("std");
const Allocator = std.mem.Allocator;
pub fn main() void {
_ = async amainWrap();
}
fn amainWrap() void {
amain() catch |e| {
std.debug.print("{}\n", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
std.process.exit(1);
};
}
fn amain() !void {
const allocator = std.heap.page_allocator;
var download_frame = async fetchUrl(allocator, "https://example.com/");
var awaited_download_frame = false;
errdefer if (!awaited_download_frame) {
if (await download_frame) |r| allocator.free(r) else |_| {}
};
var file_frame = async readFile(allocator, "something.txt");
var awaited_file_frame = false;
errdefer if (!awaited_file_frame) {
if (await file_frame) |r| allocator.free(r) else |_| {}
};
awaited_file_frame = true;
const file_text = try await file_frame;
defer allocator.free(file_text);
awaited_download_frame = true;
const download_text = try await download_frame;
defer allocator.free(download_text);
std.debug.print("download_text: {s}\n", .{download_text});
std.debug.print("file_text: {s}\n", .{file_text});
}
fn fetchUrl(allocator: Allocator, url: []const u8) ![]u8 {
_ = url; // this is just an example, we don't actually do it!
const result = try allocator.dupe(u8, "this is the downloaded url contents");
errdefer allocator.free(result);
std.debug.print("fetchUrl returning\n", .{});
return result;
}
fn readFile(allocator: Allocator, filename: []const u8) ![]u8 {
_ = filename; // this is just an example, we don't actually do it!
const result = try allocator.dupe(u8, "this is the file contents");
errdefer allocator.free(result);
std.debug.print("readFile returning\n", .{});
return result;
}
{#code_end#}
<p>
Previously, the {#syntax#}fetchUrl{#endsyntax#} and {#syntax#}readFile{#endsyntax#} functions suspended,
and were resumed in an order determined by the {#syntax#}main{#endsyntax#} function. Now,
since there are no suspend points, the order of the printed "... returning" messages
is determined by the order of {#syntax#}async{#endsyntax#} callsites.
</p>
{#header_close#}
{#header_close#}
{#header_open|Builtin Functions|2col#}
<p> <p>
Builtin functions are provided by the compiler and are prefixed with <code>@</code>. Builtin functions are provided by the compiler and are prefixed with <code>@</code>.
The {#syntax#}comptime{#endsyntax#} keyword on a parameter means that the parameter must be known The {#syntax#}comptime{#endsyntax#} keyword on a parameter means that the parameter must be known
@ -8028,49 +7628,6 @@ comptime {
</p> </p>
{#header_close#} {#header_close#}
{#header_open|@asyncCall#}
<pre>{#syntax#}@asyncCall(frame_buffer: []align(@alignOf(@Frame(anyAsyncFunction))) u8, result_ptr, function_ptr, args: anytype) anyframe->T{#endsyntax#}</pre>
<p>
{#syntax#}@asyncCall{#endsyntax#} performs an {#syntax#}async{#endsyntax#} call on a function pointer,
which may or may not be an {#link|async function|Async Functions#}.
</p>
<p>
The provided {#syntax#}frame_buffer{#endsyntax#} must be large enough to fit the entire function frame.
This size can be determined with {#link|@frameSize#}. To provide a too-small buffer
invokes safety-checked {#link|Undefined Behavior#}.
</p>
<p>
{#syntax#}result_ptr{#endsyntax#} is optional ({#link|null#} may be provided). If provided,
the function call will write its result directly to the result pointer, which will be available to
read after {#link|await|Async and Await#} completes. Any result location provided to
{#syntax#}await{#endsyntax#} will copy the result from {#syntax#}result_ptr{#endsyntax#}.
</p>
{#code_begin|test|async_struct_field_fn_pointer#}
{#backend_stage1#}
const std = @import("std");
const expect = std.testing.expect;
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 = func };
var bytes: [64]u8 align(@alignOf(@Frame(func))) = undefined;
const f = @asyncCall(&bytes, {}, foo.bar, .{&data});
try expect(data == 2);
resume f;
try expect(data == 4);
}
fn func(y: *i32) void {
defer y.* += 2;
y.* += 1;
suspend {}
}
{#code_end#}
{#header_close#}
{#header_open|@atomicLoad#} {#header_open|@atomicLoad#}
<pre>{#syntax#}@atomicLoad(comptime T: type, ptr: *const T, comptime ordering: builtin.AtomicOrder) T{#endsyntax#}</pre> <pre>{#syntax#}@atomicLoad(comptime T: type, ptr: *const T, comptime ordering: builtin.AtomicOrder) T{#endsyntax#}</pre>
<p> <p>
@ -8786,45 +8343,6 @@ test "decl access by string" {
{#see_also|@intToFloat#} {#see_also|@intToFloat#}
{#header_close#} {#header_close#}
{#header_open|@frame#}
<pre>{#syntax#}@frame() *@Frame(func){#endsyntax#}</pre>
<p>
This function returns a pointer to the frame for a given function. This type
can be {#link|coerced|Type Coercion#} to {#syntax#}anyframe->T{#endsyntax#} and
to {#syntax#}anyframe{#endsyntax#}, where {#syntax#}T{#endsyntax#} is the return type
of the function in scope.
</p>
<p>
This function does not mark a suspension point, but it does cause the function in scope
to become an {#link|async function|Async Functions#}.
</p>
{#header_close#}
{#header_open|@Frame#}
<pre>{#syntax#}@Frame(func: anytype) type{#endsyntax#}</pre>
<p>
This function returns the frame type of a function. This works for {#link|Async Functions#}
as well as any function without a specific calling convention.
</p>
<p>
This type is suitable to be used as the return type of {#link|async|Async and Await#} which
allows one to, for example, heap-allocate an async function frame:
</p>
{#code_begin|test|heap_allocated_frame#}
{#backend_stage1#}
const std = @import("std");
test "heap allocated frame" {
const frame = try std.heap.page_allocator.create(@Frame(func));
frame.* = async func();
}
fn func() void {
suspend {}
}
{#code_end#}
{#header_close#}
{#header_open|@frameAddress#} {#header_open|@frameAddress#}
<pre>{#syntax#}@frameAddress() usize{#endsyntax#}</pre> <pre>{#syntax#}@frameAddress() usize{#endsyntax#}</pre>
<p> <p>
@ -8840,17 +8358,6 @@ fn func() void {
</p> </p>
{#header_close#} {#header_close#}
{#header_open|@frameSize#}
<pre>{#syntax#}@frameSize(func: anytype) usize{#endsyntax#}</pre>
<p>
This is the same as {#syntax#}@sizeOf(@Frame(func)){#endsyntax#}, where {#syntax#}func{#endsyntax#}
may be runtime-known.
</p>
<p>
This function is typically used in conjunction with {#link|@asyncCall#}.
</p>
{#header_close#}
{#header_open|@hasDecl#} {#header_open|@hasDecl#}
<pre>{#syntax#}@hasDecl(comptime Container: type, comptime name: []const u8) bool{#endsyntax#}</pre> <pre>{#syntax#}@hasDecl(comptime Container: type, comptime name: []const u8) bool{#endsyntax#}</pre>
<p> <p>
@ -9851,7 +9358,6 @@ test "integer truncation" {
<li>{#link|Error Union Type#}</li> <li>{#link|Error Union Type#}</li>
<li>{#link|Vectors#}</li> <li>{#link|Vectors#}</li>
<li>{#link|opaque#}</li> <li>{#link|opaque#}</li>
<li>{#link|@Frame#}</li>
<li>{#syntax#}anyframe{#endsyntax#}</li> <li>{#syntax#}anyframe{#endsyntax#}</li>
<li>{#link|struct#}</li> <li>{#link|struct#}</li>
<li>{#link|enum#}</li> <li>{#link|enum#}</li>
@ -10242,7 +9748,6 @@ test "wraparound addition and subtraction" {
{#header_open|Exact Left Shift Overflow#} {#header_open|Exact Left Shift Overflow#}
<p>At compile-time:</p> <p>At compile-time:</p>
{#code_begin|test_err|operation caused overflow#} {#code_begin|test_err|operation caused overflow#}
{#backend_stage1#}
comptime { comptime {
const x = @shlExact(@as(u8, 0b01010101), 2); const x = @shlExact(@as(u8, 0b01010101), 2);
_ = x; _ = x;
@ -10262,7 +9767,6 @@ pub fn main() void {
{#header_open|Exact Right Shift Overflow#} {#header_open|Exact Right Shift Overflow#}
<p>At compile-time:</p> <p>At compile-time:</p>
{#code_begin|test_err|exact shift shifted out 1 bits#} {#code_begin|test_err|exact shift shifted out 1 bits#}
{#backend_stage1#}
comptime { comptime {
const x = @shrExact(@as(u8, 0b10101010), 2); const x = @shrExact(@as(u8, 0b10101010), 2);
_ = x; _ = x;
@ -10325,8 +9829,7 @@ pub fn main() void {
{#header_close#} {#header_close#}
{#header_open|Exact Division Remainder#} {#header_open|Exact Division Remainder#}
<p>At compile-time:</p> <p>At compile-time:</p>
{#code_begin|test_err|exact division had a remainder#} {#code_begin|test_err|exact division produced remainder#}
{#backend_stage1#}
comptime { comptime {
const a: u32 = 10; const a: u32 = 10;
const b: u32 = 3; const b: u32 = 3;
@ -10636,7 +10139,6 @@ fn bar(f: *Foo) void {
</p> </p>
<p>At compile-time:</p> <p>At compile-time:</p>
{#code_begin|test_err|null pointer casted to type#} {#code_begin|test_err|null pointer casted to type#}
{#backend_stage1#}
comptime { comptime {
const opt_ptr: ?*i32 = null; const opt_ptr: ?*i32 = null;
const ptr = @ptrCast(*i32, opt_ptr); const ptr = @ptrCast(*i32, opt_ptr);
@ -12271,9 +11773,6 @@ fn readU32Be() u32 {}
</th> </th>
<td> <td>
{#syntax#}resume{#endsyntax#} will continue execution of a function frame after the point the function was suspended. {#syntax#}resume{#endsyntax#} will continue execution of a function frame after the point the function was suspended.
<ul>
<li>See also {#link|Suspend and Resume#}</li>
</ul>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -12317,9 +11816,6 @@ fn readU32Be() u32 {}
{#syntax#}suspend{#endsyntax#} will cause control flow to return to the call site or resumer of the function. {#syntax#}suspend{#endsyntax#} will cause control flow to return to the call site or resumer of the function.
{#syntax#}suspend{#endsyntax#} can also be used before a block within a function, {#syntax#}suspend{#endsyntax#} can also be used before a block within a function,
to allow the function access to its frame before control flow returns to the call site. to allow the function access to its frame before control flow returns to the call site.
<ul>
<li>See also {#link|Suspend and Resume#}</li>
</ul>
</td> </td>
</tr> </tr>
<tr> <tr>