mirror of
https://github.com/ziglang/zig.git
synced 2026-02-21 16:54:52 +00:00
Merge pull request #3033 from ziglang/rewrite-coroutines
rework async function semantics
This commit is contained in:
commit
8b97a1aee2
@ -426,7 +426,6 @@ set(ZIG_MAIN_SRC "${CMAKE_SOURCE_DIR}/src/main.cpp")
|
||||
set(ZIG0_SHIM_SRC "${CMAKE_SOURCE_DIR}/src/userland.cpp")
|
||||
|
||||
set(ZIG_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/src/glibc.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/analyze.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/ast_render.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/bigfloat.cpp"
|
||||
@ -438,6 +437,7 @@ set(ZIG_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/src/compiler.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/errmsg.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/error.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/glibc.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/ir.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/ir_print.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/libc_installation.cpp"
|
||||
|
||||
@ -375,7 +375,9 @@ fn addLibUserlandStep(b: *Builder) void {
|
||||
artifact.bundle_compiler_rt = true;
|
||||
artifact.setTarget(builtin.arch, builtin.os, builtin.abi);
|
||||
artifact.linkSystemLibrary("c");
|
||||
artifact.linkSystemLibrary("ntdll");
|
||||
if (builtin.os == .windows) {
|
||||
artifact.linkSystemLibrary("ntdll");
|
||||
}
|
||||
const libuserland_step = b.step("libuserland", "Build the userland compiler library for use in stage1");
|
||||
libuserland_step.dependOn(&artifact.step);
|
||||
|
||||
|
||||
@ -750,7 +750,6 @@ fn tokenizeAndPrintRaw(docgen_tokenizer: *Tokenizer, out: var, source_token: Tok
|
||||
.Keyword_async,
|
||||
.Keyword_await,
|
||||
.Keyword_break,
|
||||
.Keyword_cancel,
|
||||
.Keyword_catch,
|
||||
.Keyword_comptime,
|
||||
.Keyword_const,
|
||||
@ -770,7 +769,7 @@ fn tokenizeAndPrintRaw(docgen_tokenizer: *Tokenizer, out: var, source_token: Tok
|
||||
.Keyword_or,
|
||||
.Keyword_orelse,
|
||||
.Keyword_packed,
|
||||
.Keyword_promise,
|
||||
.Keyword_anyframe,
|
||||
.Keyword_pub,
|
||||
.Keyword_resume,
|
||||
.Keyword_return,
|
||||
|
||||
@ -5968,55 +5968,27 @@ test "global assembly" {
|
||||
<p>TODO: @atomic rmw</p>
|
||||
<p>TODO: builtin atomic memory ordering enum</p>
|
||||
{#header_close#}
|
||||
{#header_open|Coroutines#}
|
||||
{#header_open|Async Functions#}
|
||||
<p>
|
||||
A coroutine is a generalization of a function.
|
||||
When a function is called, a frame is pushed to the stack,
|
||||
the function runs until it reaches a return statement, and then the frame is popped from the stack.
|
||||
At the callsite, the following code does not run until the function returns.
|
||||
</p>
|
||||
<p>
|
||||
When you call a function, it creates a stack frame,
|
||||
and then the function runs until it reaches a return
|
||||
statement, and then the stack frame is destroyed.
|
||||
At the callsite, the next line of code does not run
|
||||
until the function returns.
|
||||
An async function is a function whose callsite 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>
|
||||
A coroutine is like a function, but it can be suspended
|
||||
and resumed any number of times, and then it must be
|
||||
explicitly destroyed. When a coroutine suspends, it
|
||||
returns to the resumer.
|
||||
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|Minimal Coroutine Example#}
|
||||
{#header_open|Suspend and Resume#}
|
||||
<p>
|
||||
Declare a coroutine with the {#syntax#}async{#endsyntax#} keyword.
|
||||
The expression in angle brackets must evaluate to a struct
|
||||
which has these fields:
|
||||
</p>
|
||||
<ul>
|
||||
<li>{#syntax#}allocFn: fn (self: *Allocator, byte_count: usize, alignment: u29) Error![]u8{#endsyntax#} - where {#syntax#}Error{#endsyntax#} can be any error set.</li>
|
||||
<li>{#syntax#}freeFn: fn (self: *Allocator, old_mem: []u8) void{#endsyntax#}</li>
|
||||
</ul>
|
||||
<p>
|
||||
You may notice that this corresponds to the {#syntax#}std.mem.Allocator{#endsyntax#} interface.
|
||||
This makes it convenient to integrate with existing allocators. Note, however,
|
||||
that the language feature does not depend on the standard library, and any struct which
|
||||
has these fields is allowed.
|
||||
</p>
|
||||
<p>
|
||||
Omitting the angle bracket expression when defining an async function makes
|
||||
the function generic. Zig will infer the allocator type when the async function is called.
|
||||
</p>
|
||||
<p>
|
||||
Call a coroutine with the {#syntax#}async{#endsyntax#} keyword. Here, the expression in angle brackets
|
||||
is a pointer to the allocator struct that the coroutine expects.
|
||||
</p>
|
||||
<p>
|
||||
The result of an async function call is a {#syntax#}promise->T{#endsyntax#} type, where {#syntax#}T{#endsyntax#}
|
||||
is the return type of the async function. Once a promise has been created, it must be
|
||||
consumed, either with {#syntax#}cancel{#endsyntax#} or {#syntax#}await{#endsyntax#}:
|
||||
</p>
|
||||
<p>
|
||||
Async functions start executing when created, so in the following example, the entire
|
||||
async function completes before it is canceled:
|
||||
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#}
|
||||
const std = @import("std");
|
||||
@ -6024,99 +5996,62 @@ const assert = std.debug.assert;
|
||||
|
||||
var x: i32 = 1;
|
||||
|
||||
test "create a coroutine and cancel it" {
|
||||
const p = try async<std.debug.global_allocator> simpleAsyncFn();
|
||||
comptime assert(@typeOf(p) == promise->void);
|
||||
cancel p;
|
||||
test "suspend with no resume" {
|
||||
var frame = async func();
|
||||
assert(x == 2);
|
||||
}
|
||||
async<*std.mem.Allocator> fn simpleAsyncFn() void {
|
||||
|
||||
fn func() void {
|
||||
x += 1;
|
||||
suspend;
|
||||
// This line is never reached because the suspend has no matching resume.
|
||||
x += 1;
|
||||
}
|
||||
{#code_end#}
|
||||
{#header_close#}
|
||||
{#header_open|Suspend and Resume#}
|
||||
<p>
|
||||
At any point, an async function may suspend itself. This causes control flow to
|
||||
return to the caller or resumer. The following code demonstrates where control flow
|
||||
goes:
|
||||
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#}
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
test "coroutine suspend, resume, cancel" {
|
||||
seq('a');
|
||||
const p = try async<std.debug.global_allocator> testAsyncSeq();
|
||||
seq('c');
|
||||
resume p;
|
||||
seq('f');
|
||||
cancel p;
|
||||
seq('g');
|
||||
|
||||
assert(std.mem.eql(u8, points, "abcdefg"));
|
||||
}
|
||||
async fn testAsyncSeq() void {
|
||||
defer seq('e');
|
||||
|
||||
seq('b');
|
||||
suspend;
|
||||
seq('d');
|
||||
}
|
||||
var points = [_]u8{0} ** "abcdefg".len;
|
||||
var index: usize = 0;
|
||||
|
||||
fn seq(c: u8) void {
|
||||
points[index] = c;
|
||||
index += 1;
|
||||
}
|
||||
{#code_end#}
|
||||
<p>
|
||||
When an async function suspends itself, it must be sure that it will be
|
||||
resumed or canceled somehow, for example by registering its promise handle
|
||||
in an event loop. Use a suspend capture block to gain access to the
|
||||
promise:
|
||||
</p>
|
||||
{#code_begin|test#}
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
test "coroutine suspend with block" {
|
||||
const p = try async<std.debug.global_allocator> testSuspendBlock();
|
||||
std.debug.assert(!result);
|
||||
resume a_promise;
|
||||
std.debug.assert(result);
|
||||
cancel p;
|
||||
}
|
||||
|
||||
var a_promise: promise = undefined;
|
||||
var the_frame: anyframe = undefined;
|
||||
var result = false;
|
||||
async fn testSuspendBlock() void {
|
||||
|
||||
test "async function suspend with block" {
|
||||
_ = async testSuspendBlock();
|
||||
assert(!result);
|
||||
resume the_frame;
|
||||
assert(result);
|
||||
}
|
||||
|
||||
fn testSuspendBlock() void {
|
||||
suspend {
|
||||
comptime assert(@typeOf(@handle()) == promise->void);
|
||||
a_promise = @handle();
|
||||
comptime assert(@typeOf(@frame()) == *@Frame(testSuspendBlock));
|
||||
the_frame = @frame();
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
{#code_end#}
|
||||
<p>
|
||||
Every suspend point in an async function represents a point at which the coroutine
|
||||
could be destroyed. If that happens, {#syntax#}defer{#endsyntax#} expressions that are in
|
||||
scope are run, as well as {#syntax#}errdefer{#endsyntax#} expressions.
|
||||
</p>
|
||||
<p>
|
||||
{#link|Await#} counts as a suspend point.
|
||||
{#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 coroutine is already considered
|
||||
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 promise handle provided by the
|
||||
{#syntax#}suspend{#endsyntax#} block, the new thread would begin executing after the suspend
|
||||
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 coroutine can be directly resumed from the suspend block, in which case it
|
||||
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#}
|
||||
@ -6124,16 +6059,13 @@ const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
test "resume from suspend" {
|
||||
var buf: [500]u8 = undefined;
|
||||
var a = &std.heap.FixedBufferAllocator.init(buf[0..]).allocator;
|
||||
var my_result: i32 = 1;
|
||||
const p = try async<a> testResumeFromSuspend(&my_result);
|
||||
cancel p;
|
||||
_ = async testResumeFromSuspend(&my_result);
|
||||
std.debug.assert(my_result == 2);
|
||||
}
|
||||
async fn testResumeFromSuspend(my_result: *i32) void {
|
||||
fn testResumeFromSuspend(my_result: *i32) void {
|
||||
suspend {
|
||||
resume @handle();
|
||||
resume @frame();
|
||||
}
|
||||
my_result.* += 1;
|
||||
suspend;
|
||||
@ -6141,61 +6073,88 @@ async fn testResumeFromSuspend(my_result: *i32) void {
|
||||
}
|
||||
{#code_end#}
|
||||
<p>
|
||||
This is guaranteed to be a tail call, and therefore will not cause a new stack frame.
|
||||
This is guaranteed to tail call, and therefore will not cause a new stack frame.
|
||||
</p>
|
||||
{#header_close#}
|
||||
{#header_close#}
|
||||
{#header_open|Await#}
|
||||
|
||||
{#header_open|Async and Await#}
|
||||
<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 valid only in an {#syntax#}async{#endsyntax#} function, and it takes
|
||||
as an operand a promise handle.
|
||||
If the async function associated with the promise handle has already returned,
|
||||
then {#syntax#}await{#endsyntax#} destroys the target async function, and gives the return value.
|
||||
Otherwise, {#syntax#}await{#endsyntax#} suspends the current async function, registering its
|
||||
promise handle with the target coroutine. It becomes the target coroutine's responsibility
|
||||
to have ensured that it will be resumed or destroyed. When the target coroutine reaches
|
||||
its return statement, it gives the return value to the awaiter, destroys itself, and then
|
||||
resumes the awaiter.
|
||||
</p>
|
||||
<p>
|
||||
A promise handle must be consumed exactly once after it is created, either by {#syntax#}cancel{#endsyntax#} or {#syntax#}await{#endsyntax#}.
|
||||
</p>
|
||||
<p>
|
||||
{#syntax#}await{#endsyntax#} counts as a suspend point, and therefore at every {#syntax#}await{#endsyntax#},
|
||||
a coroutine can be potentially destroyed, which would run {#syntax#}defer{#endsyntax#} and {#syntax#}errdefer{#endsyntax#} expressions.
|
||||
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#}.
|
||||
</p>
|
||||
{#code_begin|test#}
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
var a_promise: promise = undefined;
|
||||
test "async and await" {
|
||||
// Here we have an exception where we do not match an async
|
||||
// with an await. The test block is not async and so cannot
|
||||
// have a suspend point in it.
|
||||
// This is well-defined behavior, and everything is OK here.
|
||||
// Note however that there would be no way to collect the
|
||||
// return value of amain, if it were something other than void.
|
||||
_ = async amain();
|
||||
}
|
||||
|
||||
fn amain() void {
|
||||
var frame = async func();
|
||||
comptime assert(@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
|
||||
implicitly casts to {#syntax#}anyframe->T{#endsyntax#}.
|
||||
</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#}
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
var the_frame: anyframe = undefined;
|
||||
var final_result: i32 = 0;
|
||||
|
||||
test "coroutine await" {
|
||||
test "async function await" {
|
||||
seq('a');
|
||||
const p = async<std.debug.global_allocator> amain() catch unreachable;
|
||||
_ = async amain();
|
||||
seq('f');
|
||||
resume a_promise;
|
||||
resume the_frame;
|
||||
seq('i');
|
||||
assert(final_result == 1234);
|
||||
assert(std.mem.eql(u8, seq_points, "abcdefghi"));
|
||||
}
|
||||
async fn amain() void {
|
||||
fn amain() void {
|
||||
seq('b');
|
||||
const p = async another() catch unreachable;
|
||||
var f = async another();
|
||||
seq('e');
|
||||
final_result = await p;
|
||||
final_result = await f;
|
||||
seq('h');
|
||||
}
|
||||
async fn another() i32 {
|
||||
fn another() i32 {
|
||||
seq('c');
|
||||
suspend {
|
||||
seq('d');
|
||||
a_promise = @handle();
|
||||
the_frame = @frame();
|
||||
}
|
||||
seq('g');
|
||||
return 1234;
|
||||
@ -6211,31 +6170,156 @@ fn seq(c: u8) void {
|
||||
{#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.
|
||||
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|Open Issues#}
|
||||
|
||||
{#header_open|Async Function Example#}
|
||||
<p>
|
||||
There are a few issues with coroutines that are considered unresolved. Best be aware of them,
|
||||
as the situation is likely to change before 1.0.0:
|
||||
Putting all of this together, here is an example of typical
|
||||
{#syntax#}async{#endsyntax#}/{#syntax#}await{#endsyntax#} usage:
|
||||
</p>
|
||||
{#code_begin|exe|async#}
|
||||
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.warn("{}\n", e);
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
}
|
||||
std.process.exit(1);
|
||||
};
|
||||
}
|
||||
|
||||
fn amain() !void {
|
||||
const allocator = std.heap.direct_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.warn("download_text: {}\n", download_text);
|
||||
std.debug.warn("file_text: {}\n", file_text);
|
||||
}
|
||||
|
||||
var global_download_frame: anyframe = undefined;
|
||||
fn fetchUrl(allocator: *Allocator, url: []const u8) ![]u8 {
|
||||
const result = try std.mem.dupe(allocator, u8, "this is the downloaded url contents");
|
||||
errdefer allocator.free(result);
|
||||
suspend {
|
||||
global_download_frame = @frame();
|
||||
}
|
||||
std.debug.warn("fetchUrl returning\n");
|
||||
return result;
|
||||
}
|
||||
|
||||
var global_file_frame: anyframe = undefined;
|
||||
fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 {
|
||||
const result = try std.mem.dupe(allocator, u8, "this is the file contents");
|
||||
errdefer allocator.free(result);
|
||||
suspend {
|
||||
global_file_frame = @frame();
|
||||
}
|
||||
std.debug.warn("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#}
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
pub fn main() void {
|
||||
_ = async amainWrap();
|
||||
}
|
||||
|
||||
fn amainWrap() void {
|
||||
amain() catch |e| {
|
||||
std.debug.warn("{}\n", e);
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
}
|
||||
std.process.exit(1);
|
||||
};
|
||||
}
|
||||
|
||||
fn amain() !void {
|
||||
const allocator = std.heap.direct_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.warn("download_text: {}\n", download_text);
|
||||
std.debug.warn("file_text: {}\n", file_text);
|
||||
}
|
||||
|
||||
fn fetchUrl(allocator: *Allocator, url: []const u8) ![]u8 {
|
||||
const result = try std.mem.dupe(allocator, u8, "this is the downloaded url contents");
|
||||
errdefer allocator.free(result);
|
||||
std.debug.warn("fetchUrl returning\n");
|
||||
return result;
|
||||
}
|
||||
|
||||
fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 {
|
||||
const result = try std.mem.dupe(allocator, u8, "this is the file contents");
|
||||
errdefer allocator.free(result);
|
||||
std.debug.warn("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>
|
||||
<ul>
|
||||
<li>Async functions have optimizations disabled - even in release modes - due to an
|
||||
<a href="https://github.com/ziglang/zig/issues/802">LLVM bug</a>.
|
||||
</li>
|
||||
<li>
|
||||
There are some situations where we can know statically that there will not be
|
||||
memory allocation failure, but Zig still forces us to handle it.
|
||||
TODO file an issue for this and link it here.
|
||||
</li>
|
||||
<li>
|
||||
Zig does not take advantage of LLVM's allocation elision optimization for
|
||||
coroutines. It crashed LLVM when I tried to do it the first time. This is
|
||||
related to the other 2 bullet points here. See
|
||||
<a href="https://github.com/ziglang/zig/issues/802">#802</a>.
|
||||
</li>
|
||||
</ul>
|
||||
{#header_close#}
|
||||
|
||||
{#header_close#}
|
||||
@ -6293,6 +6377,49 @@ comptime {
|
||||
Note: This function is deprecated. Use {#link|@typeInfo#} instead.
|
||||
</p>
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|@asyncCall#}
|
||||
<pre>{#syntax#}@asyncCall(frame_buffer: []u8, result_ptr, function_ptr, args: ...) 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#}
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
test "async fn pointer in a struct field" {
|
||||
var data: i32 = 1;
|
||||
const Foo = struct {
|
||||
bar: async fn (*i32) void,
|
||||
};
|
||||
var foo = Foo{ .bar = func };
|
||||
var bytes: [64]u8 = undefined;
|
||||
const f = @asyncCall(&bytes, {}, foo.bar, &data);
|
||||
assert(data == 2);
|
||||
resume f;
|
||||
assert(data == 4);
|
||||
}
|
||||
|
||||
async fn func(y: *i32) void {
|
||||
defer y.* += 2;
|
||||
y.* += 1;
|
||||
suspend;
|
||||
}
|
||||
{#code_end#}
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|@atomicLoad#}
|
||||
<pre>{#syntax#}@atomicLoad(comptime T: type, ptr: *const T, comptime ordering: builtin.AtomicOrder) T{#endsyntax#}</pre>
|
||||
<p>
|
||||
@ -6883,6 +7010,44 @@ export fn @"A function name that is a complete sentence."() void {}
|
||||
{#see_also|@intToFloat#}
|
||||
{#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|implicitly cast|Implicit Casts#} 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: var) 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#}
|
||||
const std = @import("std");
|
||||
|
||||
test "heap allocated frame" {
|
||||
const frame = try std.heap.direct_allocator.create(@Frame(func));
|
||||
frame.* = async func();
|
||||
}
|
||||
|
||||
fn func() void {
|
||||
suspend;
|
||||
}
|
||||
{#code_end#}
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|@frameAddress#}
|
||||
<pre>{#syntax#}@frameAddress() usize{#endsyntax#}</pre>
|
||||
<p>
|
||||
@ -6898,14 +7063,14 @@ export fn @"A function name that is a complete sentence."() void {}
|
||||
</p>
|
||||
{#header_close#}
|
||||
|
||||
{#header_open|@handle#}
|
||||
<pre>{#syntax#}@handle(){#endsyntax#}</pre>
|
||||
{#header_open|@frameSize#}
|
||||
<pre>{#syntax#}@frameSize() usize{#endsyntax#}</pre>
|
||||
<p>
|
||||
This function returns a {#syntax#}promise->T{#endsyntax#} type, where {#syntax#}T{#endsyntax#}
|
||||
is the return type of the async function in scope.
|
||||
This is the same as {#syntax#}@sizeOf(@Frame(func)){#endsyntax#}, where {#syntax#}func{#endsyntax#}
|
||||
may be runtime-known.
|
||||
</p>
|
||||
<p>
|
||||
This function is only valid within an async function scope.
|
||||
This function is typically used in conjunction with {#link|@asyncCall#}.
|
||||
</p>
|
||||
{#header_close#}
|
||||
|
||||
@ -8045,8 +8210,7 @@ pub fn build(b: *Builder) void {
|
||||
<p>Zig has a compile option <code>--single-threaded</code> which has the following effects:
|
||||
<ul>
|
||||
<li>All {#link|Thread Local Variables#} are treated as {#link|Global Variables#}.</li>
|
||||
<li>The overhead of {#link|Coroutines#} becomes equivalent to function call overhead.
|
||||
TODO: please note this will not be implemented until the upcoming Coroutine Rewrite</li>
|
||||
<li>The overhead of {#link|Async Functions#} becomes equivalent to function call overhead.</li>
|
||||
<li>The {#syntax#}@import("builtin").single_threaded{#endsyntax#} becomes {#syntax#}true{#endsyntax#}
|
||||
and therefore various userland APIs which read this variable become more efficient.
|
||||
For example {#syntax#}std.Mutex{#endsyntax#} becomes
|
||||
@ -9793,7 +9957,6 @@ PrimaryExpr
|
||||
<- AsmExpr
|
||||
/ IfExpr
|
||||
/ KEYWORD_break BreakLabel? Expr?
|
||||
/ KEYWORD_cancel Expr
|
||||
/ KEYWORD_comptime Expr
|
||||
/ KEYWORD_continue BreakLabel?
|
||||
/ KEYWORD_resume Expr
|
||||
@ -10149,7 +10312,6 @@ KEYWORD_asm <- 'asm' end_of_word
|
||||
KEYWORD_async <- 'async' end_of_word
|
||||
KEYWORD_await <- 'await' end_of_word
|
||||
KEYWORD_break <- 'break' end_of_word
|
||||
KEYWORD_cancel <- 'cancel' end_of_word
|
||||
KEYWORD_catch <- 'catch' end_of_word
|
||||
KEYWORD_comptime <- 'comptime' end_of_word
|
||||
KEYWORD_const <- 'const' end_of_word
|
||||
@ -10194,7 +10356,7 @@ KEYWORD_volatile <- 'volatile' end_of_word
|
||||
KEYWORD_while <- 'while' end_of_word
|
||||
|
||||
keyword <- KEYWORD_align / KEYWORD_and / KEYWORD_allowzero / KEYWORD_asm
|
||||
/ KEYWORD_async / KEYWORD_await / KEYWORD_break / KEYWORD_cancel
|
||||
/ KEYWORD_async / KEYWORD_await / KEYWORD_break
|
||||
/ KEYWORD_catch / KEYWORD_comptime / KEYWORD_const / KEYWORD_continue
|
||||
/ KEYWORD_defer / KEYWORD_else / KEYWORD_enum / KEYWORD_errdefer
|
||||
/ KEYWORD_error / KEYWORD_export / KEYWORD_extern / KEYWORD_false
|
||||
|
||||
@ -1904,20 +1904,6 @@ pub const Builder = struct {
|
||||
}
|
||||
return error.Unimplemented;
|
||||
|
||||
//ir_build_store_ptr(irb, scope, node, irb->exec->coro_result_field_ptr, return_value);
|
||||
//IrInstruction *promise_type_val = ir_build_const_type(irb, scope, node,
|
||||
// get_optional_type(irb->codegen, irb->codegen->builtin_types.entry_promise));
|
||||
//// TODO replace replacement_value with @intToPtr(?promise, 0x1) when it doesn't crash zig
|
||||
//IrInstruction *replacement_value = irb->exec->coro_handle;
|
||||
//IrInstruction *maybe_await_handle = ir_build_atomic_rmw(irb, scope, node,
|
||||
// promise_type_val, irb->exec->coro_awaiter_field_ptr, nullptr, replacement_value, nullptr,
|
||||
// AtomicRmwOp_xchg, AtomicOrderSeqCst);
|
||||
//ir_build_store_ptr(irb, scope, node, irb->exec->await_handle_var_ptr, maybe_await_handle);
|
||||
//IrInstruction *is_non_null = ir_build_test_nonnull(irb, scope, node, maybe_await_handle);
|
||||
//IrInstruction *is_comptime = ir_build_const_bool(irb, scope, node, false);
|
||||
//return ir_build_cond_br(irb, scope, node, is_non_null, irb->exec->coro_normal_final, irb->exec->coro_early_final,
|
||||
// is_comptime);
|
||||
//// the above blocks are rendered by ir_gen after the rest of codegen
|
||||
}
|
||||
|
||||
const Ident = union(enum) {
|
||||
|
||||
@ -627,7 +627,7 @@ fn constructLinkerArgsWasm(ctx: *Context) void {
|
||||
|
||||
fn addFnObjects(ctx: *Context) !void {
|
||||
// at this point it's guaranteed nobody else has this lock, so we circumvent it
|
||||
// and avoid having to be a coroutine
|
||||
// and avoid having to be an async function
|
||||
const fn_link_set = &ctx.comp.fn_link_set.private_data;
|
||||
|
||||
var it = fn_link_set.first;
|
||||
|
||||
@ -52,7 +52,7 @@ const Command = struct {
|
||||
|
||||
pub fn main() !void {
|
||||
// This allocator needs to be thread-safe because we use it for the event.Loop
|
||||
// which multiplexes coroutines onto kernel threads.
|
||||
// which multiplexes async functions onto kernel threads.
|
||||
// libc allocator is guaranteed to have this property.
|
||||
const allocator = std.heap.c_allocator;
|
||||
|
||||
@ -466,8 +466,7 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co
|
||||
comp.link_objects = link_objects;
|
||||
|
||||
comp.start();
|
||||
const process_build_events_handle = try async<loop.allocator> processBuildEvents(comp, color);
|
||||
defer cancel process_build_events_handle;
|
||||
// TODO const process_build_events_handle = try async<loop.allocator> processBuildEvents(comp, color);
|
||||
loop.run();
|
||||
}
|
||||
|
||||
@ -578,8 +577,7 @@ fn cmdLibC(allocator: *Allocator, args: []const []const u8) !void {
|
||||
var zig_compiler = try ZigCompiler.init(&loop);
|
||||
defer zig_compiler.deinit();
|
||||
|
||||
const handle = try async<loop.allocator> findLibCAsync(&zig_compiler);
|
||||
defer cancel handle;
|
||||
// TODO const handle = try async<loop.allocator> findLibCAsync(&zig_compiler);
|
||||
|
||||
loop.run();
|
||||
}
|
||||
@ -663,13 +661,12 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void {
|
||||
defer loop.deinit();
|
||||
|
||||
var result: FmtError!void = undefined;
|
||||
const main_handle = try async<allocator> asyncFmtMainChecked(
|
||||
&result,
|
||||
&loop,
|
||||
&flags,
|
||||
color,
|
||||
);
|
||||
defer cancel main_handle;
|
||||
// TODO const main_handle = try async<allocator> asyncFmtMainChecked(
|
||||
// TODO &result,
|
||||
// TODO &loop,
|
||||
// TODO &flags,
|
||||
// TODO color,
|
||||
// TODO );
|
||||
loop.run();
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -142,7 +142,8 @@ export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error {
|
||||
return Error.None;
|
||||
}
|
||||
|
||||
// TODO: just use the actual self-hosted zig fmt. Until the coroutine rewrite, we use a blocking implementation.
|
||||
// TODO: just use the actual self-hosted zig fmt. Until https://github.com/ziglang/zig/issues/2377,
|
||||
// we use a blocking implementation.
|
||||
export fn stage2_fmt(argc: c_int, argv: [*]const [*]const u8) c_int {
|
||||
if (std.debug.runtime_safety) {
|
||||
fmtMain(argc, argv) catch unreachable;
|
||||
|
||||
@ -35,6 +35,7 @@ struct ConstExprValue;
|
||||
struct IrInstruction;
|
||||
struct IrInstructionCast;
|
||||
struct IrInstructionAllocaGen;
|
||||
struct IrInstructionCallGen;
|
||||
struct IrBasicBlock;
|
||||
struct ScopeDecls;
|
||||
struct ZigWindowsSDK;
|
||||
@ -70,20 +71,10 @@ struct IrExecutable {
|
||||
Scope *begin_scope;
|
||||
ZigList<Tld *> tld_list;
|
||||
|
||||
IrInstruction *coro_handle;
|
||||
IrInstruction *atomic_state_field_ptr; // this one is shared and in the promise
|
||||
IrInstruction *coro_result_ptr_field_ptr;
|
||||
IrInstruction *coro_result_field_ptr;
|
||||
IrInstruction *await_handle_var_ptr; // this one is where we put the one we extracted from the promise
|
||||
IrBasicBlock *coro_early_final;
|
||||
IrBasicBlock *coro_normal_final;
|
||||
IrBasicBlock *coro_suspend_block;
|
||||
IrBasicBlock *coro_final_cleanup_block;
|
||||
ZigVar *coro_allocator_var;
|
||||
|
||||
bool invalid;
|
||||
bool is_inline;
|
||||
bool is_generic_instantiation;
|
||||
bool need_err_code_spill;
|
||||
};
|
||||
|
||||
enum OutType {
|
||||
@ -485,11 +476,10 @@ enum NodeType {
|
||||
NodeTypeIfErrorExpr,
|
||||
NodeTypeIfOptional,
|
||||
NodeTypeErrorSetDecl,
|
||||
NodeTypeCancel,
|
||||
NodeTypeResume,
|
||||
NodeTypeAwaitExpr,
|
||||
NodeTypeSuspend,
|
||||
NodeTypePromiseType,
|
||||
NodeTypeAnyFrameType,
|
||||
NodeTypeEnumLiteral,
|
||||
};
|
||||
|
||||
@ -522,7 +512,6 @@ struct AstNodeFnProto {
|
||||
AstNode *section_expr;
|
||||
|
||||
bool auto_err_set;
|
||||
AstNode *async_allocator_type;
|
||||
};
|
||||
|
||||
struct AstNodeFnDef {
|
||||
@ -657,7 +646,6 @@ struct AstNodeFnCallExpr {
|
||||
bool is_builtin;
|
||||
bool is_async;
|
||||
bool seen; // used by @compileLog
|
||||
AstNode *async_allocator;
|
||||
};
|
||||
|
||||
struct AstNodeArrayAccessExpr {
|
||||
@ -922,10 +910,6 @@ struct AstNodeBreakExpr {
|
||||
AstNode *expr; // may be null
|
||||
};
|
||||
|
||||
struct AstNodeCancelExpr {
|
||||
AstNode *expr;
|
||||
};
|
||||
|
||||
struct AstNodeResumeExpr {
|
||||
AstNode *expr;
|
||||
};
|
||||
@ -949,7 +933,7 @@ struct AstNodeSuspend {
|
||||
AstNode *block;
|
||||
};
|
||||
|
||||
struct AstNodePromiseType {
|
||||
struct AstNodeAnyFrameType {
|
||||
AstNode *payload_type; // can be NULL
|
||||
};
|
||||
|
||||
@ -1014,13 +998,16 @@ struct AstNode {
|
||||
AstNodeInferredArrayType inferred_array_type;
|
||||
AstNodeErrorType error_type;
|
||||
AstNodeErrorSetDecl err_set_decl;
|
||||
AstNodeCancelExpr cancel_expr;
|
||||
AstNodeResumeExpr resume_expr;
|
||||
AstNodeAwaitExpr await_expr;
|
||||
AstNodeSuspend suspend;
|
||||
AstNodePromiseType promise_type;
|
||||
AstNodeAnyFrameType anyframe_type;
|
||||
AstNodeEnumLiteral enum_literal;
|
||||
} data;
|
||||
|
||||
// This is a function for use in the debugger to print
|
||||
// the source location.
|
||||
void src();
|
||||
};
|
||||
|
||||
// this struct is allocated with allocate_nonzero
|
||||
@ -1047,7 +1034,6 @@ struct FnTypeId {
|
||||
bool is_var_args;
|
||||
CallingConvention cc;
|
||||
uint32_t alignment;
|
||||
ZigType *async_allocator_type;
|
||||
};
|
||||
|
||||
uint32_t fn_type_id_hash(FnTypeId*);
|
||||
@ -1095,6 +1081,7 @@ struct TypeStructField {
|
||||
ConstExprValue *init_val; // null and then memoized
|
||||
uint32_t bit_offset_in_host; // offset from the memory at gen_index
|
||||
uint32_t host_int_bytes; // size of host integer
|
||||
uint32_t align;
|
||||
};
|
||||
|
||||
enum ResolveStatus {
|
||||
@ -1156,6 +1143,8 @@ struct ZigTypeOptional {
|
||||
struct ZigTypeErrorUnion {
|
||||
ZigType *err_set_type;
|
||||
ZigType *payload_type;
|
||||
size_t pad_bytes;
|
||||
LLVMTypeRef pad_llvm_type;
|
||||
};
|
||||
|
||||
struct ZigTypeErrorSet {
|
||||
@ -1241,11 +1230,6 @@ struct ZigTypeBoundFn {
|
||||
ZigType *fn_type;
|
||||
};
|
||||
|
||||
struct ZigTypePromise {
|
||||
// null if `promise` instead of `promise->T`
|
||||
ZigType *result_type;
|
||||
};
|
||||
|
||||
struct ZigTypeVector {
|
||||
// The type must be a pointer, integer, or float
|
||||
ZigType *elem_type;
|
||||
@ -1276,7 +1260,8 @@ enum ZigTypeId {
|
||||
ZigTypeIdBoundFn,
|
||||
ZigTypeIdArgTuple,
|
||||
ZigTypeIdOpaque,
|
||||
ZigTypeIdPromise,
|
||||
ZigTypeIdFnFrame,
|
||||
ZigTypeIdAnyFrame,
|
||||
ZigTypeIdVector,
|
||||
ZigTypeIdEnumLiteral,
|
||||
};
|
||||
@ -1291,6 +1276,15 @@ struct ZigTypeOpaque {
|
||||
Buf *bare_name;
|
||||
};
|
||||
|
||||
struct ZigTypeFnFrame {
|
||||
ZigFn *fn;
|
||||
ZigType *locals_struct;
|
||||
};
|
||||
|
||||
struct ZigTypeAnyFrame {
|
||||
ZigType *result_type; // null if `anyframe` instead of `anyframe->T`
|
||||
};
|
||||
|
||||
struct ZigType {
|
||||
ZigTypeId id;
|
||||
Buf name;
|
||||
@ -1314,16 +1308,16 @@ struct ZigType {
|
||||
ZigTypeUnion unionation;
|
||||
ZigTypeFn fn;
|
||||
ZigTypeBoundFn bound_fn;
|
||||
ZigTypePromise promise;
|
||||
ZigTypeVector vector;
|
||||
ZigTypeOpaque opaque;
|
||||
ZigTypeFnFrame frame;
|
||||
ZigTypeAnyFrame any_frame;
|
||||
} data;
|
||||
|
||||
// use these fields to make sure we don't duplicate type table entries for the same type
|
||||
ZigType *pointer_parent[2]; // [0 - mut, 1 - const]
|
||||
ZigType *optional_parent;
|
||||
ZigType *promise_parent;
|
||||
ZigType *promise_frame_parent;
|
||||
ZigType *any_frame_parent;
|
||||
// If we generate a constant name value for this type, we memoize it here.
|
||||
// The type of this is array
|
||||
ConstExprValue *cached_const_name_val;
|
||||
@ -1359,7 +1353,6 @@ struct GlobalExport {
|
||||
};
|
||||
|
||||
struct ZigFn {
|
||||
CodeGen *codegen;
|
||||
LLVMValueRef llvm_value;
|
||||
const char *llvm_name;
|
||||
AstNode *proto_node;
|
||||
@ -1368,7 +1361,17 @@ struct ZigFn {
|
||||
Scope *child_scope; // parent is scope for last parameter
|
||||
ScopeBlock *def_scope; // parent is child_scope
|
||||
Buf symbol_name;
|
||||
ZigType *type_entry; // function type
|
||||
// This is the function type assuming the function does not suspend.
|
||||
// Note that for an async function, this can be shared with non-async functions. So the value here
|
||||
// should only be read for things in common between non-async and async function types.
|
||||
ZigType *type_entry;
|
||||
// For normal functions one could use the type_entry->raw_type_ref and type_entry->raw_di_type.
|
||||
// However for functions that suspend, those values could possibly be their non-suspending equivalents.
|
||||
// So these values should be preferred.
|
||||
LLVMTypeRef raw_type_ref;
|
||||
ZigLLVMDIType *raw_di_type;
|
||||
|
||||
ZigType *frame_type;
|
||||
// in the case of normal functions this is the implicit return type
|
||||
// in the case of async functions this is the implicit return type according to the
|
||||
// zig source code, not according to zig ir
|
||||
@ -1379,6 +1382,7 @@ struct ZigFn {
|
||||
size_t prealloc_backward_branch_quota;
|
||||
AstNode **param_source_nodes;
|
||||
Buf **param_names;
|
||||
IrInstruction *err_code_spill;
|
||||
|
||||
AstNode *fn_no_inline_set_node;
|
||||
AstNode *fn_static_eval_set_node;
|
||||
@ -1390,8 +1394,11 @@ struct ZigFn {
|
||||
AstNode *set_alignstack_node;
|
||||
|
||||
AstNode *set_cold_node;
|
||||
const AstNode *inferred_async_node;
|
||||
ZigFn *inferred_async_fn;
|
||||
|
||||
ZigList<GlobalExport> export_list;
|
||||
ZigList<IrInstructionCallGen *> call_list;
|
||||
|
||||
LLVMValueRef valgrind_client_request_array;
|
||||
|
||||
@ -1442,8 +1449,6 @@ enum BuiltinFnId {
|
||||
BuiltinFnIdErrName,
|
||||
BuiltinFnIdBreakpoint,
|
||||
BuiltinFnIdReturnAddress,
|
||||
BuiltinFnIdFrameAddress,
|
||||
BuiltinFnIdHandle,
|
||||
BuiltinFnIdEmbedFile,
|
||||
BuiltinFnIdCmpxchgWeak,
|
||||
BuiltinFnIdCmpxchgStrong,
|
||||
@ -1499,6 +1504,7 @@ enum BuiltinFnId {
|
||||
BuiltinFnIdInlineCall,
|
||||
BuiltinFnIdNoInlineCall,
|
||||
BuiltinFnIdNewStackCall,
|
||||
BuiltinFnIdAsyncCall,
|
||||
BuiltinFnIdTypeId,
|
||||
BuiltinFnIdShlExact,
|
||||
BuiltinFnIdShrExact,
|
||||
@ -1514,6 +1520,10 @@ enum BuiltinFnId {
|
||||
BuiltinFnIdAtomicLoad,
|
||||
BuiltinFnIdHasDecl,
|
||||
BuiltinFnIdUnionInit,
|
||||
BuiltinFnIdFrameAddress,
|
||||
BuiltinFnIdFrameType,
|
||||
BuiltinFnIdFrameHandle,
|
||||
BuiltinFnIdFrameSize,
|
||||
};
|
||||
|
||||
struct BuiltinFnEntry {
|
||||
@ -1541,6 +1551,12 @@ enum PanicMsgId {
|
||||
PanicMsgIdBadEnumValue,
|
||||
PanicMsgIdFloatToInt,
|
||||
PanicMsgIdPtrCastNull,
|
||||
PanicMsgIdBadResume,
|
||||
PanicMsgIdBadAwait,
|
||||
PanicMsgIdBadReturn,
|
||||
PanicMsgIdResumedAnAwaitingFn,
|
||||
PanicMsgIdFrameTooSmall,
|
||||
PanicMsgIdResumedFnPendingAwait,
|
||||
|
||||
PanicMsgIdCount,
|
||||
};
|
||||
@ -1701,7 +1717,13 @@ struct CodeGen {
|
||||
LLVMTargetMachineRef target_machine;
|
||||
ZigLLVMDIFile *dummy_di_file;
|
||||
LLVMValueRef cur_ret_ptr;
|
||||
LLVMValueRef cur_frame_ptr;
|
||||
LLVMValueRef cur_fn_val;
|
||||
LLVMValueRef cur_async_switch_instr;
|
||||
LLVMValueRef cur_async_resume_index_ptr;
|
||||
LLVMValueRef cur_async_awaiter_ptr;
|
||||
LLVMBasicBlockRef cur_preamble_llvm_block;
|
||||
size_t cur_resume_block_count;
|
||||
LLVMValueRef cur_err_ret_trace_val_arg;
|
||||
LLVMValueRef cur_err_ret_trace_val_stack;
|
||||
LLVMValueRef memcpy_fn_val;
|
||||
@ -1709,28 +1731,16 @@ struct CodeGen {
|
||||
LLVMValueRef trap_fn_val;
|
||||
LLVMValueRef return_address_fn_val;
|
||||
LLVMValueRef frame_address_fn_val;
|
||||
LLVMValueRef coro_destroy_fn_val;
|
||||
LLVMValueRef coro_id_fn_val;
|
||||
LLVMValueRef coro_alloc_fn_val;
|
||||
LLVMValueRef coro_size_fn_val;
|
||||
LLVMValueRef coro_begin_fn_val;
|
||||
LLVMValueRef coro_suspend_fn_val;
|
||||
LLVMValueRef coro_end_fn_val;
|
||||
LLVMValueRef coro_free_fn_val;
|
||||
LLVMValueRef coro_resume_fn_val;
|
||||
LLVMValueRef coro_save_fn_val;
|
||||
LLVMValueRef coro_promise_fn_val;
|
||||
LLVMValueRef coro_alloc_helper_fn_val;
|
||||
LLVMValueRef coro_frame_fn_val;
|
||||
LLVMValueRef merge_err_ret_traces_fn_val;
|
||||
LLVMValueRef add_error_return_trace_addr_fn_val;
|
||||
LLVMValueRef stacksave_fn_val;
|
||||
LLVMValueRef stackrestore_fn_val;
|
||||
LLVMValueRef write_register_fn_val;
|
||||
LLVMValueRef merge_err_ret_traces_fn_val;
|
||||
LLVMValueRef sp_md_node;
|
||||
LLVMValueRef err_name_table;
|
||||
LLVMValueRef safety_crash_err_fn;
|
||||
LLVMValueRef return_err_fn;
|
||||
LLVMTypeRef anyframe_fn_type;
|
||||
|
||||
// reminder: hash tables must be initialized before use
|
||||
HashMap<Buf *, ZigType *, buf_hash, buf_eql_buf> import_table;
|
||||
@ -1797,12 +1807,12 @@ struct CodeGen {
|
||||
ZigType *entry_var;
|
||||
ZigType *entry_global_error_set;
|
||||
ZigType *entry_arg_tuple;
|
||||
ZigType *entry_promise;
|
||||
ZigType *entry_enum_literal;
|
||||
ZigType *entry_any_frame;
|
||||
} builtin_types;
|
||||
|
||||
ZigType *align_amt_type;
|
||||
ZigType *stack_trace_type;
|
||||
ZigType *ptr_to_stack_trace_type;
|
||||
ZigType *err_tag_type;
|
||||
ZigType *test_fn_type;
|
||||
|
||||
@ -1938,6 +1948,7 @@ struct ZigVar {
|
||||
ZigType *var_type;
|
||||
LLVMValueRef value_ref;
|
||||
IrInstruction *is_comptime;
|
||||
IrInstruction *ptr_instruction;
|
||||
// which node is the declaration of the variable
|
||||
AstNode *decl_node;
|
||||
ZigLLVMDILocalVariable *di_loc_var;
|
||||
@ -1985,7 +1996,6 @@ enum ScopeId {
|
||||
ScopeIdSuspend,
|
||||
ScopeIdFnDef,
|
||||
ScopeIdCompTime,
|
||||
ScopeIdCoroPrelude,
|
||||
ScopeIdRuntime,
|
||||
};
|
||||
|
||||
@ -2109,7 +2119,6 @@ struct ScopeRuntime {
|
||||
struct ScopeSuspend {
|
||||
Scope base;
|
||||
|
||||
IrBasicBlock *resume_block;
|
||||
bool reported_err;
|
||||
};
|
||||
|
||||
@ -2128,12 +2137,6 @@ struct ScopeFnDef {
|
||||
ZigFn *fn_entry;
|
||||
};
|
||||
|
||||
// This scope is created to indicate that the code in the scope
|
||||
// is auto-generated coroutine prelude stuff.
|
||||
struct ScopeCoroPrelude {
|
||||
Scope base;
|
||||
};
|
||||
|
||||
// synchronized with code in define_builtin_compile_vars
|
||||
enum AtomicOrder {
|
||||
AtomicOrderUnordered,
|
||||
@ -2231,7 +2234,7 @@ enum IrInstructionId {
|
||||
IrInstructionIdSetRuntimeSafety,
|
||||
IrInstructionIdSetFloatMode,
|
||||
IrInstructionIdArrayType,
|
||||
IrInstructionIdPromiseType,
|
||||
IrInstructionIdAnyFrameType,
|
||||
IrInstructionIdSliceType,
|
||||
IrInstructionIdGlobalAsm,
|
||||
IrInstructionIdAsm,
|
||||
@ -2278,7 +2281,10 @@ enum IrInstructionId {
|
||||
IrInstructionIdBreakpoint,
|
||||
IrInstructionIdReturnAddress,
|
||||
IrInstructionIdFrameAddress,
|
||||
IrInstructionIdHandle,
|
||||
IrInstructionIdFrameHandle,
|
||||
IrInstructionIdFrameType,
|
||||
IrInstructionIdFrameSizeSrc,
|
||||
IrInstructionIdFrameSizeGen,
|
||||
IrInstructionIdAlignOf,
|
||||
IrInstructionIdOverflowOp,
|
||||
IrInstructionIdTestErrSrc,
|
||||
@ -2321,35 +2327,16 @@ enum IrInstructionId {
|
||||
IrInstructionIdImplicitCast,
|
||||
IrInstructionIdResolveResult,
|
||||
IrInstructionIdResetResult,
|
||||
IrInstructionIdResultPtr,
|
||||
IrInstructionIdOpaqueType,
|
||||
IrInstructionIdSetAlignStack,
|
||||
IrInstructionIdArgType,
|
||||
IrInstructionIdExport,
|
||||
IrInstructionIdErrorReturnTrace,
|
||||
IrInstructionIdErrorUnion,
|
||||
IrInstructionIdCancel,
|
||||
IrInstructionIdGetImplicitAllocator,
|
||||
IrInstructionIdCoroId,
|
||||
IrInstructionIdCoroAlloc,
|
||||
IrInstructionIdCoroSize,
|
||||
IrInstructionIdCoroBegin,
|
||||
IrInstructionIdCoroAllocFail,
|
||||
IrInstructionIdCoroSuspend,
|
||||
IrInstructionIdCoroEnd,
|
||||
IrInstructionIdCoroFree,
|
||||
IrInstructionIdCoroResume,
|
||||
IrInstructionIdCoroSave,
|
||||
IrInstructionIdCoroPromise,
|
||||
IrInstructionIdCoroAllocHelper,
|
||||
IrInstructionIdAtomicRmw,
|
||||
IrInstructionIdAtomicLoad,
|
||||
IrInstructionIdPromiseResultType,
|
||||
IrInstructionIdAwaitBookkeeping,
|
||||
IrInstructionIdSaveErrRetAddr,
|
||||
IrInstructionIdAddImplicitReturnType,
|
||||
IrInstructionIdMergeErrRetTraces,
|
||||
IrInstructionIdMarkErrRetTracePtr,
|
||||
IrInstructionIdErrSetCast,
|
||||
IrInstructionIdToBytes,
|
||||
IrInstructionIdFromBytes,
|
||||
@ -2365,6 +2352,13 @@ enum IrInstructionId {
|
||||
IrInstructionIdEndExpr,
|
||||
IrInstructionIdPtrOfArrayToSlice,
|
||||
IrInstructionIdUnionInitNamedField,
|
||||
IrInstructionIdSuspendBegin,
|
||||
IrInstructionIdSuspendFinish,
|
||||
IrInstructionIdAwaitSrc,
|
||||
IrInstructionIdAwaitGen,
|
||||
IrInstructionIdResume,
|
||||
IrInstructionIdSpillBegin,
|
||||
IrInstructionIdSpillEnd,
|
||||
};
|
||||
|
||||
struct IrInstruction {
|
||||
@ -2607,7 +2601,6 @@ struct IrInstructionCallSrc {
|
||||
IrInstruction **args;
|
||||
ResultLoc *result_loc;
|
||||
|
||||
IrInstruction *async_allocator;
|
||||
IrInstruction *new_stack;
|
||||
FnInline fn_inline;
|
||||
bool is_async;
|
||||
@ -2622,8 +2615,8 @@ struct IrInstructionCallGen {
|
||||
size_t arg_count;
|
||||
IrInstruction **args;
|
||||
IrInstruction *result_loc;
|
||||
IrInstruction *frame_result_loc;
|
||||
|
||||
IrInstruction *async_allocator;
|
||||
IrInstruction *new_stack;
|
||||
FnInline fn_inline;
|
||||
bool is_async;
|
||||
@ -2639,7 +2632,7 @@ struct IrInstructionConst {
|
||||
struct IrInstructionReturn {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *value;
|
||||
IrInstruction *operand;
|
||||
};
|
||||
|
||||
enum CastOp {
|
||||
@ -2744,7 +2737,7 @@ struct IrInstructionPtrType {
|
||||
bool is_allow_zero;
|
||||
};
|
||||
|
||||
struct IrInstructionPromiseType {
|
||||
struct IrInstructionAnyFrameType {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *payload_type;
|
||||
@ -3084,10 +3077,28 @@ struct IrInstructionFrameAddress {
|
||||
IrInstruction base;
|
||||
};
|
||||
|
||||
struct IrInstructionHandle {
|
||||
struct IrInstructionFrameHandle {
|
||||
IrInstruction base;
|
||||
};
|
||||
|
||||
struct IrInstructionFrameType {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *fn;
|
||||
};
|
||||
|
||||
struct IrInstructionFrameSizeSrc {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *fn;
|
||||
};
|
||||
|
||||
struct IrInstructionFrameSizeGen {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *fn;
|
||||
};
|
||||
|
||||
enum IrOverflowOp {
|
||||
IrOverflowOpAdd,
|
||||
IrOverflowOpSub,
|
||||
@ -3127,6 +3138,7 @@ struct IrInstructionTestErrSrc {
|
||||
IrInstruction base;
|
||||
|
||||
bool resolve_err_set;
|
||||
bool base_ptr_is_payload;
|
||||
IrInstruction *base_ptr;
|
||||
};
|
||||
|
||||
@ -3179,7 +3191,6 @@ struct IrInstructionFnProto {
|
||||
IrInstruction **param_types;
|
||||
IrInstruction *align_value;
|
||||
IrInstruction *return_type;
|
||||
IrInstruction *async_allocator_type_value;
|
||||
bool is_var_args;
|
||||
};
|
||||
|
||||
@ -3409,95 +3420,6 @@ struct IrInstructionErrorUnion {
|
||||
IrInstruction *payload;
|
||||
};
|
||||
|
||||
struct IrInstructionCancel {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *target;
|
||||
};
|
||||
|
||||
enum ImplicitAllocatorId {
|
||||
ImplicitAllocatorIdArg,
|
||||
ImplicitAllocatorIdLocalVar,
|
||||
};
|
||||
|
||||
struct IrInstructionGetImplicitAllocator {
|
||||
IrInstruction base;
|
||||
|
||||
ImplicitAllocatorId id;
|
||||
};
|
||||
|
||||
struct IrInstructionCoroId {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *promise_ptr;
|
||||
};
|
||||
|
||||
struct IrInstructionCoroAlloc {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *coro_id;
|
||||
};
|
||||
|
||||
struct IrInstructionCoroSize {
|
||||
IrInstruction base;
|
||||
};
|
||||
|
||||
struct IrInstructionCoroBegin {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *coro_id;
|
||||
IrInstruction *coro_mem_ptr;
|
||||
};
|
||||
|
||||
struct IrInstructionCoroAllocFail {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *err_val;
|
||||
};
|
||||
|
||||
struct IrInstructionCoroSuspend {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *save_point;
|
||||
IrInstruction *is_final;
|
||||
};
|
||||
|
||||
struct IrInstructionCoroEnd {
|
||||
IrInstruction base;
|
||||
};
|
||||
|
||||
struct IrInstructionCoroFree {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *coro_id;
|
||||
IrInstruction *coro_handle;
|
||||
};
|
||||
|
||||
struct IrInstructionCoroResume {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *awaiter_handle;
|
||||
};
|
||||
|
||||
struct IrInstructionCoroSave {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *coro_handle;
|
||||
};
|
||||
|
||||
struct IrInstructionCoroPromise {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *coro_handle;
|
||||
};
|
||||
|
||||
struct IrInstructionCoroAllocHelper {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *realloc_fn;
|
||||
IrInstruction *coro_size;
|
||||
};
|
||||
|
||||
struct IrInstructionAtomicRmw {
|
||||
IrInstruction base;
|
||||
|
||||
@ -3519,18 +3441,6 @@ struct IrInstructionAtomicLoad {
|
||||
AtomicOrder resolved_ordering;
|
||||
};
|
||||
|
||||
struct IrInstructionPromiseResultType {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *promise_type;
|
||||
};
|
||||
|
||||
struct IrInstructionAwaitBookkeeping {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *promise_result_type;
|
||||
};
|
||||
|
||||
struct IrInstructionSaveErrRetAddr {
|
||||
IrInstruction base;
|
||||
};
|
||||
@ -3541,20 +3451,6 @@ struct IrInstructionAddImplicitReturnType {
|
||||
IrInstruction *value;
|
||||
};
|
||||
|
||||
struct IrInstructionMergeErrRetTraces {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *coro_promise_ptr;
|
||||
IrInstruction *src_err_ret_trace_ptr;
|
||||
IrInstruction *dest_err_ret_trace_ptr;
|
||||
};
|
||||
|
||||
struct IrInstructionMarkErrRetTracePtr {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *err_ret_trace_ptr;
|
||||
};
|
||||
|
||||
// For float ops which take a single argument
|
||||
struct IrInstructionFloatOp {
|
||||
IrInstruction base;
|
||||
@ -3645,6 +3541,7 @@ struct IrInstructionAllocaGen {
|
||||
|
||||
uint32_t align;
|
||||
const char *name_hint;
|
||||
size_t field_index;
|
||||
};
|
||||
|
||||
struct IrInstructionEndExpr {
|
||||
@ -3692,6 +3589,56 @@ struct IrInstructionPtrOfArrayToSlice {
|
||||
IrInstruction *result_loc;
|
||||
};
|
||||
|
||||
struct IrInstructionSuspendBegin {
|
||||
IrInstruction base;
|
||||
|
||||
LLVMBasicBlockRef resume_bb;
|
||||
};
|
||||
|
||||
struct IrInstructionSuspendFinish {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstructionSuspendBegin *begin;
|
||||
};
|
||||
|
||||
struct IrInstructionAwaitSrc {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *frame;
|
||||
ResultLoc *result_loc;
|
||||
};
|
||||
|
||||
struct IrInstructionAwaitGen {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *frame;
|
||||
IrInstruction *result_loc;
|
||||
};
|
||||
|
||||
struct IrInstructionResume {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstruction *frame;
|
||||
};
|
||||
|
||||
enum SpillId {
|
||||
SpillIdInvalid,
|
||||
SpillIdRetErrCode,
|
||||
};
|
||||
|
||||
struct IrInstructionSpillBegin {
|
||||
IrInstruction base;
|
||||
|
||||
SpillId spill_id;
|
||||
IrInstruction *operand;
|
||||
};
|
||||
|
||||
struct IrInstructionSpillEnd {
|
||||
IrInstruction base;
|
||||
|
||||
IrInstructionSpillBegin *begin;
|
||||
};
|
||||
|
||||
enum ResultLocId {
|
||||
ResultLocIdInvalid,
|
||||
ResultLocIdNone,
|
||||
@ -3775,20 +3722,16 @@ static const size_t maybe_null_index = 1;
|
||||
static const size_t err_union_payload_index = 0;
|
||||
static const size_t err_union_err_index = 1;
|
||||
|
||||
// TODO call graph analysis to find out what this number needs to be for every function
|
||||
// MUST BE A POWER OF TWO.
|
||||
static const size_t stack_trace_ptr_count = 32;
|
||||
// label (grep this): [fn_frame_struct_layout]
|
||||
static const size_t frame_fn_ptr_index = 0;
|
||||
static const size_t frame_resume_index = 1;
|
||||
static const size_t frame_awaiter_index = 2;
|
||||
static const size_t frame_ret_start = 3;
|
||||
|
||||
// these belong to the async function
|
||||
#define RETURN_ADDRESSES_FIELD_NAME "return_addresses"
|
||||
#define ERR_RET_TRACE_FIELD_NAME "err_ret_trace"
|
||||
#define RESULT_FIELD_NAME "result"
|
||||
#define ASYNC_REALLOC_FIELD_NAME "reallocFn"
|
||||
#define ASYNC_SHRINK_FIELD_NAME "shrinkFn"
|
||||
#define ATOMIC_STATE_FIELD_NAME "atomic_state"
|
||||
// these point to data belonging to the awaiter
|
||||
#define ERR_RET_TRACE_PTR_FIELD_NAME "err_ret_trace_ptr"
|
||||
#define RESULT_PTR_FIELD_NAME "result_ptr"
|
||||
// TODO https://github.com/ziglang/zig/issues/3056
|
||||
// We require this to be a power of 2 so that we can use shifting rather than
|
||||
// remainder division.
|
||||
static const size_t stack_trace_ptr_count = 32; // Must be a power of 2.
|
||||
|
||||
#define NAMESPACE_SEP_CHAR '.'
|
||||
#define NAMESPACE_SEP_STR "."
|
||||
@ -3811,11 +3754,13 @@ enum FnWalkId {
|
||||
|
||||
struct FnWalkAttrs {
|
||||
ZigFn *fn;
|
||||
LLVMValueRef llvm_fn;
|
||||
unsigned gen_i;
|
||||
};
|
||||
|
||||
struct FnWalkCall {
|
||||
ZigList<LLVMValueRef> *gen_param_values;
|
||||
ZigList<ZigType *> *gen_param_types;
|
||||
IrInstructionCallGen *inst;
|
||||
bool is_var_args;
|
||||
};
|
||||
|
||||
1119
src/analyze.cpp
1119
src/analyze.cpp
File diff suppressed because it is too large
Load Diff
@ -11,11 +11,12 @@
|
||||
#include "all_types.hpp"
|
||||
|
||||
void semantic_analyze(CodeGen *g);
|
||||
ErrorMsg *add_node_error(CodeGen *g, AstNode *node, Buf *msg);
|
||||
ErrorMsg *add_node_error(CodeGen *g, const AstNode *node, Buf *msg);
|
||||
ErrorMsg *add_token_error(CodeGen *g, ZigType *owner, Token *token, Buf *msg);
|
||||
ErrorMsg *add_error_note(CodeGen *g, ErrorMsg *parent_msg, AstNode *node, Buf *msg);
|
||||
ErrorMsg *add_error_note(CodeGen *g, ErrorMsg *parent_msg, const AstNode *node, Buf *msg);
|
||||
void emit_error_notes_for_ref_stack(CodeGen *g, ErrorMsg *msg);
|
||||
ZigType *new_type_table_entry(ZigTypeId id);
|
||||
ZigType *get_fn_frame_type(CodeGen *g, ZigFn *fn);
|
||||
ZigType *get_pointer_to_type(CodeGen *g, ZigType *child_type, bool is_const);
|
||||
ZigType *get_pointer_to_type_extra(CodeGen *g, ZigType *child_type, bool is_const,
|
||||
bool is_volatile, PtrLen ptr_len,
|
||||
@ -37,11 +38,8 @@ ZigType *get_smallest_unsigned_int_type(CodeGen *g, uint64_t x);
|
||||
ZigType *get_error_union_type(CodeGen *g, ZigType *err_set_type, ZigType *payload_type);
|
||||
ZigType *get_bound_fn_type(CodeGen *g, ZigFn *fn_entry);
|
||||
ZigType *get_opaque_type(CodeGen *g, Scope *scope, AstNode *source_node, const char *full_name, Buf *bare_name);
|
||||
ZigType *get_struct_type(CodeGen *g, const char *type_name, const char *field_names[],
|
||||
ZigType *field_types[], size_t field_count);
|
||||
ZigType *get_promise_type(CodeGen *g, ZigType *result_type);
|
||||
ZigType *get_promise_frame_type(CodeGen *g, ZigType *return_type);
|
||||
ZigType *get_test_fn_type(CodeGen *g);
|
||||
ZigType *get_any_frame_type(CodeGen *g, ZigType *result_type);
|
||||
bool handle_is_ptr(ZigType *type_entry);
|
||||
|
||||
bool type_has_bits(ZigType *type_entry);
|
||||
@ -106,7 +104,6 @@ void eval_min_max_value(CodeGen *g, ZigType *type_entry, ConstExprValue *const_v
|
||||
void eval_min_max_value_int(CodeGen *g, ZigType *int_type, BigInt *bigint, bool is_max);
|
||||
|
||||
void render_const_value(CodeGen *g, Buf *buf, ConstExprValue *const_val);
|
||||
void analyze_fn_ir(CodeGen *g, ZigFn *fn_table_entry, AstNode *return_type_node);
|
||||
|
||||
ScopeBlock *create_block_scope(CodeGen *g, AstNode *node, Scope *parent);
|
||||
ScopeDefer *create_defer_scope(CodeGen *g, AstNode *node, Scope *parent);
|
||||
@ -117,7 +114,6 @@ ScopeLoop *create_loop_scope(CodeGen *g, AstNode *node, Scope *parent);
|
||||
ScopeSuspend *create_suspend_scope(CodeGen *g, AstNode *node, Scope *parent);
|
||||
ScopeFnDef *create_fndef_scope(CodeGen *g, AstNode *node, Scope *parent, ZigFn *fn_entry);
|
||||
Scope *create_comptime_scope(CodeGen *g, AstNode *node, Scope *parent);
|
||||
Scope *create_coro_prelude_scope(CodeGen *g, AstNode *node, Scope *parent);
|
||||
Scope *create_runtime_scope(CodeGen *g, AstNode *node, Scope *parent, IrInstruction *is_comptime);
|
||||
|
||||
void init_const_str_lit(CodeGen *g, ConstExprValue *const_val, Buf *str);
|
||||
@ -199,12 +195,11 @@ void add_var_export(CodeGen *g, ZigVar *fn_table_entry, Buf *symbol_name, Global
|
||||
|
||||
|
||||
ConstExprValue *get_builtin_value(CodeGen *codegen, const char *name);
|
||||
ZigType *get_ptr_to_stack_trace_type(CodeGen *g);
|
||||
ZigType *get_stack_trace_type(CodeGen *g);
|
||||
bool resolve_inferred_error_set(CodeGen *g, ZigType *err_set_type, AstNode *source_node);
|
||||
|
||||
ZigType *get_auto_err_set_type(CodeGen *g, ZigFn *fn_entry);
|
||||
|
||||
uint32_t get_coro_frame_align_bytes(CodeGen *g);
|
||||
bool fn_type_can_fail(FnTypeId *fn_type_id);
|
||||
bool type_can_fail(ZigType *type_entry);
|
||||
bool fn_eval_cacheable(Scope *scope, ZigType *return_type);
|
||||
@ -251,4 +246,7 @@ void src_assert(bool ok, AstNode *source_node);
|
||||
bool is_container(ZigType *type_entry);
|
||||
ConstExprValue *analyze_const_value(CodeGen *g, Scope *scope, AstNode *node, ZigType *type_entry, Buf *type_name);
|
||||
|
||||
void resolve_llvm_types_fn(CodeGen *g, ZigFn *fn);
|
||||
bool fn_is_async(ZigFn *fn);
|
||||
|
||||
#endif
|
||||
|
||||
@ -249,18 +249,16 @@ static const char *node_type_str(NodeType node_type) {
|
||||
return "IfOptional";
|
||||
case NodeTypeErrorSetDecl:
|
||||
return "ErrorSetDecl";
|
||||
case NodeTypeCancel:
|
||||
return "Cancel";
|
||||
case NodeTypeResume:
|
||||
return "Resume";
|
||||
case NodeTypeAwaitExpr:
|
||||
return "AwaitExpr";
|
||||
case NodeTypeSuspend:
|
||||
return "Suspend";
|
||||
case NodeTypePromiseType:
|
||||
return "PromiseType";
|
||||
case NodeTypePointerType:
|
||||
return "PointerType";
|
||||
case NodeTypeAnyFrameType:
|
||||
return "AnyFrameType";
|
||||
case NodeTypeEnumLiteral:
|
||||
return "EnumLiteral";
|
||||
}
|
||||
@ -699,13 +697,7 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
|
||||
fprintf(ar->f, "@");
|
||||
}
|
||||
if (node->data.fn_call_expr.is_async) {
|
||||
fprintf(ar->f, "async");
|
||||
if (node->data.fn_call_expr.async_allocator != nullptr) {
|
||||
fprintf(ar->f, "<");
|
||||
render_node_extra(ar, node->data.fn_call_expr.async_allocator, true);
|
||||
fprintf(ar->f, ">");
|
||||
}
|
||||
fprintf(ar->f, " ");
|
||||
fprintf(ar->f, "async ");
|
||||
}
|
||||
AstNode *fn_ref_node = node->data.fn_call_expr.fn_ref_expr;
|
||||
bool grouped = (fn_ref_node->type != NodeTypePrefixOpExpr && fn_ref_node->type != NodeTypePointerType);
|
||||
@ -862,15 +854,14 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
|
||||
render_node_ungrouped(ar, node->data.inferred_array_type.child_type);
|
||||
break;
|
||||
}
|
||||
case NodeTypePromiseType:
|
||||
{
|
||||
fprintf(ar->f, "promise");
|
||||
if (node->data.promise_type.payload_type != nullptr) {
|
||||
fprintf(ar->f, "->");
|
||||
render_node_grouped(ar, node->data.promise_type.payload_type);
|
||||
}
|
||||
break;
|
||||
case NodeTypeAnyFrameType: {
|
||||
fprintf(ar->f, "anyframe");
|
||||
if (node->data.anyframe_type.payload_type != nullptr) {
|
||||
fprintf(ar->f, "->");
|
||||
render_node_grouped(ar, node->data.anyframe_type.payload_type);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NodeTypeErrorType:
|
||||
fprintf(ar->f, "anyerror");
|
||||
break;
|
||||
@ -1143,12 +1134,6 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
|
||||
fprintf(ar->f, "}");
|
||||
break;
|
||||
}
|
||||
case NodeTypeCancel:
|
||||
{
|
||||
fprintf(ar->f, "cancel ");
|
||||
render_node_grouped(ar, node->data.cancel_expr.expr);
|
||||
break;
|
||||
}
|
||||
case NodeTypeResume:
|
||||
{
|
||||
fprintf(ar->f, "resume ");
|
||||
@ -1163,9 +1148,11 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
|
||||
}
|
||||
case NodeTypeSuspend:
|
||||
{
|
||||
fprintf(ar->f, "suspend");
|
||||
if (node->data.suspend.block != nullptr) {
|
||||
fprintf(ar->f, "suspend ");
|
||||
render_node_grouped(ar, node->data.suspend.block);
|
||||
} else {
|
||||
fprintf(ar->f, "suspend\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -1191,3 +1178,9 @@ void ast_render(FILE *f, AstNode *node, int indent_size) {
|
||||
|
||||
render_node_grouped(&ar, node);
|
||||
}
|
||||
|
||||
void AstNode::src() {
|
||||
fprintf(stderr, "%s:%" ZIG_PRI_usize ":%" ZIG_PRI_usize "\n",
|
||||
buf_ptr(this->owner->data.structure.root_struct->path),
|
||||
this->line + 1, this->column + 1);
|
||||
}
|
||||
|
||||
2148
src/codegen.cpp
2148
src/codegen.cpp
File diff suppressed because it is too large
Load Diff
@ -61,5 +61,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g);
|
||||
TargetSubsystem detect_subsystem(CodeGen *g);
|
||||
|
||||
void codegen_release_caches(CodeGen *codegen);
|
||||
bool codegen_fn_has_err_ret_tracing_arg(CodeGen *g, ZigType *return_type);
|
||||
bool codegen_fn_has_err_ret_tracing_stack(CodeGen *g, ZigFn *fn, bool is_async);
|
||||
|
||||
#endif
|
||||
|
||||
2552
src/ir.cpp
2552
src/ir.cpp
File diff suppressed because it is too large
Load Diff
@ -28,4 +28,6 @@ ConstExprValue *const_ptr_pointee(IrAnalyze *ira, CodeGen *codegen, ConstExprVal
|
||||
AstNode *source_node);
|
||||
const char *float_op_to_name(BuiltinFnId op, bool llvm_name);
|
||||
|
||||
void ir_add_analysis_trace(IrAnalyze *ira, ErrorMsg *err_msg, Buf *text);
|
||||
|
||||
#endif
|
||||
|
||||
333
src/ir_print.cpp
333
src/ir_print.cpp
@ -64,11 +64,9 @@ static void ir_print_other_block(IrPrint *irp, IrBasicBlock *bb) {
|
||||
}
|
||||
}
|
||||
|
||||
static void ir_print_return(IrPrint *irp, IrInstructionReturn *return_instruction) {
|
||||
static void ir_print_return(IrPrint *irp, IrInstructionReturn *instruction) {
|
||||
fprintf(irp->f, "return ");
|
||||
if (return_instruction->value != nullptr) {
|
||||
ir_print_other_instruction(irp, return_instruction->value);
|
||||
}
|
||||
ir_print_other_instruction(irp, instruction->operand);
|
||||
}
|
||||
|
||||
static void ir_print_const(IrPrint *irp, IrInstructionConst *const_instruction) {
|
||||
@ -257,13 +255,7 @@ static void ir_print_result_loc(IrPrint *irp, ResultLoc *result_loc) {
|
||||
|
||||
static void ir_print_call_src(IrPrint *irp, IrInstructionCallSrc *call_instruction) {
|
||||
if (call_instruction->is_async) {
|
||||
fprintf(irp->f, "async");
|
||||
if (call_instruction->async_allocator != nullptr) {
|
||||
fprintf(irp->f, "<");
|
||||
ir_print_other_instruction(irp, call_instruction->async_allocator);
|
||||
fprintf(irp->f, ">");
|
||||
}
|
||||
fprintf(irp->f, " ");
|
||||
fprintf(irp->f, "async ");
|
||||
}
|
||||
if (call_instruction->fn_entry) {
|
||||
fprintf(irp->f, "%s", buf_ptr(&call_instruction->fn_entry->symbol_name));
|
||||
@ -284,13 +276,7 @@ static void ir_print_call_src(IrPrint *irp, IrInstructionCallSrc *call_instructi
|
||||
|
||||
static void ir_print_call_gen(IrPrint *irp, IrInstructionCallGen *call_instruction) {
|
||||
if (call_instruction->is_async) {
|
||||
fprintf(irp->f, "async");
|
||||
if (call_instruction->async_allocator != nullptr) {
|
||||
fprintf(irp->f, "<");
|
||||
ir_print_other_instruction(irp, call_instruction->async_allocator);
|
||||
fprintf(irp->f, ">");
|
||||
}
|
||||
fprintf(irp->f, " ");
|
||||
fprintf(irp->f, "async ");
|
||||
}
|
||||
if (call_instruction->fn_entry) {
|
||||
fprintf(irp->f, "%s", buf_ptr(&call_instruction->fn_entry->symbol_name));
|
||||
@ -477,20 +463,21 @@ static void ir_print_array_type(IrPrint *irp, IrInstructionArrayType *instructio
|
||||
ir_print_other_instruction(irp, instruction->child_type);
|
||||
}
|
||||
|
||||
static void ir_print_promise_type(IrPrint *irp, IrInstructionPromiseType *instruction) {
|
||||
fprintf(irp->f, "promise");
|
||||
if (instruction->payload_type != nullptr) {
|
||||
fprintf(irp->f, "->");
|
||||
ir_print_other_instruction(irp, instruction->payload_type);
|
||||
}
|
||||
}
|
||||
|
||||
static void ir_print_slice_type(IrPrint *irp, IrInstructionSliceType *instruction) {
|
||||
const char *const_kw = instruction->is_const ? "const " : "";
|
||||
fprintf(irp->f, "[]%s", const_kw);
|
||||
ir_print_other_instruction(irp, instruction->child_type);
|
||||
}
|
||||
|
||||
static void ir_print_any_frame_type(IrPrint *irp, IrInstructionAnyFrameType *instruction) {
|
||||
if (instruction->payload_type == nullptr) {
|
||||
fprintf(irp->f, "anyframe");
|
||||
} else {
|
||||
fprintf(irp->f, "anyframe->");
|
||||
ir_print_other_instruction(irp, instruction->payload_type);
|
||||
}
|
||||
}
|
||||
|
||||
static void ir_print_global_asm(IrPrint *irp, IrInstructionGlobalAsm *instruction) {
|
||||
fprintf(irp->f, "asm(\"%s\")", buf_ptr(instruction->asm_code));
|
||||
}
|
||||
@ -926,8 +913,26 @@ static void ir_print_frame_address(IrPrint *irp, IrInstructionFrameAddress *inst
|
||||
fprintf(irp->f, "@frameAddress()");
|
||||
}
|
||||
|
||||
static void ir_print_handle(IrPrint *irp, IrInstructionHandle *instruction) {
|
||||
fprintf(irp->f, "@handle()");
|
||||
static void ir_print_handle(IrPrint *irp, IrInstructionFrameHandle *instruction) {
|
||||
fprintf(irp->f, "@frame()");
|
||||
}
|
||||
|
||||
static void ir_print_frame_type(IrPrint *irp, IrInstructionFrameType *instruction) {
|
||||
fprintf(irp->f, "@Frame(");
|
||||
ir_print_other_instruction(irp, instruction->fn);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_frame_size_src(IrPrint *irp, IrInstructionFrameSizeSrc *instruction) {
|
||||
fprintf(irp->f, "@frameSize(");
|
||||
ir_print_other_instruction(irp, instruction->fn);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_frame_size_gen(IrPrint *irp, IrInstructionFrameSizeGen *instruction) {
|
||||
fprintf(irp->f, "@frameSize(");
|
||||
ir_print_other_instruction(irp, instruction->fn);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_return_address(IrPrint *irp, IrInstructionReturnAddress *instruction) {
|
||||
@ -1322,14 +1327,6 @@ static void ir_print_reset_result(IrPrint *irp, IrInstructionResetResult *instru
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_result_ptr(IrPrint *irp, IrInstructionResultPtr *instruction) {
|
||||
fprintf(irp->f, "ResultPtr(");
|
||||
ir_print_result_loc(irp, instruction->result_loc);
|
||||
fprintf(irp->f, ",");
|
||||
ir_print_other_instruction(irp, instruction->result);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_opaque_type(IrPrint *irp, IrInstructionOpaqueType *instruction) {
|
||||
fprintf(irp->f, "@OpaqueType()");
|
||||
}
|
||||
@ -1391,110 +1388,6 @@ static void ir_print_error_union(IrPrint *irp, IrInstructionErrorUnion *instruct
|
||||
ir_print_other_instruction(irp, instruction->payload);
|
||||
}
|
||||
|
||||
static void ir_print_cancel(IrPrint *irp, IrInstructionCancel *instruction) {
|
||||
fprintf(irp->f, "cancel ");
|
||||
ir_print_other_instruction(irp, instruction->target);
|
||||
}
|
||||
|
||||
static void ir_print_get_implicit_allocator(IrPrint *irp, IrInstructionGetImplicitAllocator *instruction) {
|
||||
fprintf(irp->f, "@getImplicitAllocator(");
|
||||
switch (instruction->id) {
|
||||
case ImplicitAllocatorIdArg:
|
||||
fprintf(irp->f, "Arg");
|
||||
break;
|
||||
case ImplicitAllocatorIdLocalVar:
|
||||
fprintf(irp->f, "LocalVar");
|
||||
break;
|
||||
}
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_coro_id(IrPrint *irp, IrInstructionCoroId *instruction) {
|
||||
fprintf(irp->f, "@coroId(");
|
||||
ir_print_other_instruction(irp, instruction->promise_ptr);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_coro_alloc(IrPrint *irp, IrInstructionCoroAlloc *instruction) {
|
||||
fprintf(irp->f, "@coroAlloc(");
|
||||
ir_print_other_instruction(irp, instruction->coro_id);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_coro_size(IrPrint *irp, IrInstructionCoroSize *instruction) {
|
||||
fprintf(irp->f, "@coroSize()");
|
||||
}
|
||||
|
||||
static void ir_print_coro_begin(IrPrint *irp, IrInstructionCoroBegin *instruction) {
|
||||
fprintf(irp->f, "@coroBegin(");
|
||||
ir_print_other_instruction(irp, instruction->coro_id);
|
||||
fprintf(irp->f, ",");
|
||||
ir_print_other_instruction(irp, instruction->coro_mem_ptr);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_coro_alloc_fail(IrPrint *irp, IrInstructionCoroAllocFail *instruction) {
|
||||
fprintf(irp->f, "@coroAllocFail(");
|
||||
ir_print_other_instruction(irp, instruction->err_val);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_coro_suspend(IrPrint *irp, IrInstructionCoroSuspend *instruction) {
|
||||
fprintf(irp->f, "@coroSuspend(");
|
||||
if (instruction->save_point != nullptr) {
|
||||
ir_print_other_instruction(irp, instruction->save_point);
|
||||
} else {
|
||||
fprintf(irp->f, "null");
|
||||
}
|
||||
fprintf(irp->f, ",");
|
||||
ir_print_other_instruction(irp, instruction->is_final);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_coro_end(IrPrint *irp, IrInstructionCoroEnd *instruction) {
|
||||
fprintf(irp->f, "@coroEnd()");
|
||||
}
|
||||
|
||||
static void ir_print_coro_free(IrPrint *irp, IrInstructionCoroFree *instruction) {
|
||||
fprintf(irp->f, "@coroFree(");
|
||||
ir_print_other_instruction(irp, instruction->coro_id);
|
||||
fprintf(irp->f, ",");
|
||||
ir_print_other_instruction(irp, instruction->coro_handle);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_coro_resume(IrPrint *irp, IrInstructionCoroResume *instruction) {
|
||||
fprintf(irp->f, "@coroResume(");
|
||||
ir_print_other_instruction(irp, instruction->awaiter_handle);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_coro_save(IrPrint *irp, IrInstructionCoroSave *instruction) {
|
||||
fprintf(irp->f, "@coroSave(");
|
||||
ir_print_other_instruction(irp, instruction->coro_handle);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_coro_promise(IrPrint *irp, IrInstructionCoroPromise *instruction) {
|
||||
fprintf(irp->f, "@coroPromise(");
|
||||
ir_print_other_instruction(irp, instruction->coro_handle);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_promise_result_type(IrPrint *irp, IrInstructionPromiseResultType *instruction) {
|
||||
fprintf(irp->f, "@PromiseResultType(");
|
||||
ir_print_other_instruction(irp, instruction->promise_type);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_coro_alloc_helper(IrPrint *irp, IrInstructionCoroAllocHelper *instruction) {
|
||||
fprintf(irp->f, "@coroAllocHelper(");
|
||||
ir_print_other_instruction(irp, instruction->realloc_fn);
|
||||
fprintf(irp->f, ",");
|
||||
ir_print_other_instruction(irp, instruction->coro_size);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_atomic_rmw(IrPrint *irp, IrInstructionAtomicRmw *instruction) {
|
||||
fprintf(irp->f, "@atomicRmw(");
|
||||
if (instruction->operand_type != nullptr) {
|
||||
@ -1539,12 +1432,6 @@ static void ir_print_atomic_load(IrPrint *irp, IrInstructionAtomicLoad *instruct
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_await_bookkeeping(IrPrint *irp, IrInstructionAwaitBookkeeping *instruction) {
|
||||
fprintf(irp->f, "@awaitBookkeeping(");
|
||||
ir_print_other_instruction(irp, instruction->promise_result_type);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_save_err_ret_addr(IrPrint *irp, IrInstructionSaveErrRetAddr *instruction) {
|
||||
fprintf(irp->f, "@saveErrRetAddr()");
|
||||
}
|
||||
@ -1555,22 +1442,6 @@ static void ir_print_add_implicit_return_type(IrPrint *irp, IrInstructionAddImpl
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_merge_err_ret_traces(IrPrint *irp, IrInstructionMergeErrRetTraces *instruction) {
|
||||
fprintf(irp->f, "@mergeErrRetTraces(");
|
||||
ir_print_other_instruction(irp, instruction->coro_promise_ptr);
|
||||
fprintf(irp->f, ",");
|
||||
ir_print_other_instruction(irp, instruction->src_err_ret_trace_ptr);
|
||||
fprintf(irp->f, ",");
|
||||
ir_print_other_instruction(irp, instruction->dest_err_ret_trace_ptr);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_mark_err_ret_trace_ptr(IrPrint *irp, IrInstructionMarkErrRetTracePtr *instruction) {
|
||||
fprintf(irp->f, "@markErrRetTracePtr(");
|
||||
ir_print_other_instruction(irp, instruction->err_ret_trace_ptr);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_float_op(IrPrint *irp, IrInstructionFloatOp *instruction) {
|
||||
|
||||
fprintf(irp->f, "@%s(", float_op_to_name(instruction->op, false));
|
||||
@ -1638,6 +1509,47 @@ static void ir_print_union_init_named_field(IrPrint *irp, IrInstructionUnionInit
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_suspend_begin(IrPrint *irp, IrInstructionSuspendBegin *instruction) {
|
||||
fprintf(irp->f, "@suspendBegin()");
|
||||
}
|
||||
|
||||
static void ir_print_suspend_finish(IrPrint *irp, IrInstructionSuspendFinish *instruction) {
|
||||
fprintf(irp->f, "@suspendFinish()");
|
||||
}
|
||||
|
||||
static void ir_print_resume(IrPrint *irp, IrInstructionResume *instruction) {
|
||||
fprintf(irp->f, "resume ");
|
||||
ir_print_other_instruction(irp, instruction->frame);
|
||||
}
|
||||
|
||||
static void ir_print_await_src(IrPrint *irp, IrInstructionAwaitSrc *instruction) {
|
||||
fprintf(irp->f, "@await(");
|
||||
ir_print_other_instruction(irp, instruction->frame);
|
||||
fprintf(irp->f, ",");
|
||||
ir_print_result_loc(irp, instruction->result_loc);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_await_gen(IrPrint *irp, IrInstructionAwaitGen *instruction) {
|
||||
fprintf(irp->f, "@await(");
|
||||
ir_print_other_instruction(irp, instruction->frame);
|
||||
fprintf(irp->f, ",");
|
||||
ir_print_other_instruction(irp, instruction->result_loc);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_spill_begin(IrPrint *irp, IrInstructionSpillBegin *instruction) {
|
||||
fprintf(irp->f, "@spillBegin(");
|
||||
ir_print_other_instruction(irp, instruction->operand);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_spill_end(IrPrint *irp, IrInstructionSpillEnd *instruction) {
|
||||
fprintf(irp->f, "@spillEnd(");
|
||||
ir_print_other_instruction(irp, &instruction->begin->base);
|
||||
fprintf(irp->f, ")");
|
||||
}
|
||||
|
||||
static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
|
||||
ir_print_prefix(irp, instruction);
|
||||
switch (instruction->id) {
|
||||
@ -1727,12 +1639,12 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
|
||||
case IrInstructionIdArrayType:
|
||||
ir_print_array_type(irp, (IrInstructionArrayType *)instruction);
|
||||
break;
|
||||
case IrInstructionIdPromiseType:
|
||||
ir_print_promise_type(irp, (IrInstructionPromiseType *)instruction);
|
||||
break;
|
||||
case IrInstructionIdSliceType:
|
||||
ir_print_slice_type(irp, (IrInstructionSliceType *)instruction);
|
||||
break;
|
||||
case IrInstructionIdAnyFrameType:
|
||||
ir_print_any_frame_type(irp, (IrInstructionAnyFrameType *)instruction);
|
||||
break;
|
||||
case IrInstructionIdGlobalAsm:
|
||||
ir_print_global_asm(irp, (IrInstructionGlobalAsm *)instruction);
|
||||
break;
|
||||
@ -1886,8 +1798,17 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
|
||||
case IrInstructionIdFrameAddress:
|
||||
ir_print_frame_address(irp, (IrInstructionFrameAddress *)instruction);
|
||||
break;
|
||||
case IrInstructionIdHandle:
|
||||
ir_print_handle(irp, (IrInstructionHandle *)instruction);
|
||||
case IrInstructionIdFrameHandle:
|
||||
ir_print_handle(irp, (IrInstructionFrameHandle *)instruction);
|
||||
break;
|
||||
case IrInstructionIdFrameType:
|
||||
ir_print_frame_type(irp, (IrInstructionFrameType *)instruction);
|
||||
break;
|
||||
case IrInstructionIdFrameSizeSrc:
|
||||
ir_print_frame_size_src(irp, (IrInstructionFrameSizeSrc *)instruction);
|
||||
break;
|
||||
case IrInstructionIdFrameSizeGen:
|
||||
ir_print_frame_size_gen(irp, (IrInstructionFrameSizeGen *)instruction);
|
||||
break;
|
||||
case IrInstructionIdAlignOf:
|
||||
ir_print_align_of(irp, (IrInstructionAlignOf *)instruction);
|
||||
@ -2006,9 +1927,6 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
|
||||
case IrInstructionIdResetResult:
|
||||
ir_print_reset_result(irp, (IrInstructionResetResult *)instruction);
|
||||
break;
|
||||
case IrInstructionIdResultPtr:
|
||||
ir_print_result_ptr(irp, (IrInstructionResultPtr *)instruction);
|
||||
break;
|
||||
case IrInstructionIdOpaqueType:
|
||||
ir_print_opaque_type(irp, (IrInstructionOpaqueType *)instruction);
|
||||
break;
|
||||
@ -2030,69 +1948,15 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
|
||||
case IrInstructionIdErrorUnion:
|
||||
ir_print_error_union(irp, (IrInstructionErrorUnion *)instruction);
|
||||
break;
|
||||
case IrInstructionIdCancel:
|
||||
ir_print_cancel(irp, (IrInstructionCancel *)instruction);
|
||||
break;
|
||||
case IrInstructionIdGetImplicitAllocator:
|
||||
ir_print_get_implicit_allocator(irp, (IrInstructionGetImplicitAllocator *)instruction);
|
||||
break;
|
||||
case IrInstructionIdCoroId:
|
||||
ir_print_coro_id(irp, (IrInstructionCoroId *)instruction);
|
||||
break;
|
||||
case IrInstructionIdCoroAlloc:
|
||||
ir_print_coro_alloc(irp, (IrInstructionCoroAlloc *)instruction);
|
||||
break;
|
||||
case IrInstructionIdCoroSize:
|
||||
ir_print_coro_size(irp, (IrInstructionCoroSize *)instruction);
|
||||
break;
|
||||
case IrInstructionIdCoroBegin:
|
||||
ir_print_coro_begin(irp, (IrInstructionCoroBegin *)instruction);
|
||||
break;
|
||||
case IrInstructionIdCoroAllocFail:
|
||||
ir_print_coro_alloc_fail(irp, (IrInstructionCoroAllocFail *)instruction);
|
||||
break;
|
||||
case IrInstructionIdCoroSuspend:
|
||||
ir_print_coro_suspend(irp, (IrInstructionCoroSuspend *)instruction);
|
||||
break;
|
||||
case IrInstructionIdCoroEnd:
|
||||
ir_print_coro_end(irp, (IrInstructionCoroEnd *)instruction);
|
||||
break;
|
||||
case IrInstructionIdCoroFree:
|
||||
ir_print_coro_free(irp, (IrInstructionCoroFree *)instruction);
|
||||
break;
|
||||
case IrInstructionIdCoroResume:
|
||||
ir_print_coro_resume(irp, (IrInstructionCoroResume *)instruction);
|
||||
break;
|
||||
case IrInstructionIdCoroSave:
|
||||
ir_print_coro_save(irp, (IrInstructionCoroSave *)instruction);
|
||||
break;
|
||||
case IrInstructionIdCoroAllocHelper:
|
||||
ir_print_coro_alloc_helper(irp, (IrInstructionCoroAllocHelper *)instruction);
|
||||
break;
|
||||
case IrInstructionIdAtomicRmw:
|
||||
ir_print_atomic_rmw(irp, (IrInstructionAtomicRmw *)instruction);
|
||||
break;
|
||||
case IrInstructionIdCoroPromise:
|
||||
ir_print_coro_promise(irp, (IrInstructionCoroPromise *)instruction);
|
||||
break;
|
||||
case IrInstructionIdPromiseResultType:
|
||||
ir_print_promise_result_type(irp, (IrInstructionPromiseResultType *)instruction);
|
||||
break;
|
||||
case IrInstructionIdAwaitBookkeeping:
|
||||
ir_print_await_bookkeeping(irp, (IrInstructionAwaitBookkeeping *)instruction);
|
||||
break;
|
||||
case IrInstructionIdSaveErrRetAddr:
|
||||
ir_print_save_err_ret_addr(irp, (IrInstructionSaveErrRetAddr *)instruction);
|
||||
break;
|
||||
case IrInstructionIdAddImplicitReturnType:
|
||||
ir_print_add_implicit_return_type(irp, (IrInstructionAddImplicitReturnType *)instruction);
|
||||
break;
|
||||
case IrInstructionIdMergeErrRetTraces:
|
||||
ir_print_merge_err_ret_traces(irp, (IrInstructionMergeErrRetTraces *)instruction);
|
||||
break;
|
||||
case IrInstructionIdMarkErrRetTracePtr:
|
||||
ir_print_mark_err_ret_trace_ptr(irp, (IrInstructionMarkErrRetTracePtr *)instruction);
|
||||
break;
|
||||
case IrInstructionIdFloatOp:
|
||||
ir_print_float_op(irp, (IrInstructionFloatOp *)instruction);
|
||||
break;
|
||||
@ -2147,6 +2011,27 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
|
||||
case IrInstructionIdUnionInitNamedField:
|
||||
ir_print_union_init_named_field(irp, (IrInstructionUnionInitNamedField *)instruction);
|
||||
break;
|
||||
case IrInstructionIdSuspendBegin:
|
||||
ir_print_suspend_begin(irp, (IrInstructionSuspendBegin *)instruction);
|
||||
break;
|
||||
case IrInstructionIdSuspendFinish:
|
||||
ir_print_suspend_finish(irp, (IrInstructionSuspendFinish *)instruction);
|
||||
break;
|
||||
case IrInstructionIdResume:
|
||||
ir_print_resume(irp, (IrInstructionResume *)instruction);
|
||||
break;
|
||||
case IrInstructionIdAwaitSrc:
|
||||
ir_print_await_src(irp, (IrInstructionAwaitSrc *)instruction);
|
||||
break;
|
||||
case IrInstructionIdAwaitGen:
|
||||
ir_print_await_gen(irp, (IrInstructionAwaitGen *)instruction);
|
||||
break;
|
||||
case IrInstructionIdSpillBegin:
|
||||
ir_print_spill_begin(irp, (IrInstructionSpillBegin *)instruction);
|
||||
break;
|
||||
case IrInstructionIdSpillEnd:
|
||||
ir_print_spill_end(irp, (IrInstructionSpillEnd *)instruction);
|
||||
break;
|
||||
}
|
||||
fprintf(irp->f, "\n");
|
||||
}
|
||||
|
||||
@ -282,8 +282,8 @@ static AstNode *ast_parse_prefix_op_expr(
|
||||
case NodeTypeAwaitExpr:
|
||||
right = &prefix->data.await_expr.expr;
|
||||
break;
|
||||
case NodeTypePromiseType:
|
||||
right = &prefix->data.promise_type.payload_type;
|
||||
case NodeTypeAnyFrameType:
|
||||
right = &prefix->data.anyframe_type.payload_type;
|
||||
break;
|
||||
case NodeTypeArrayType:
|
||||
right = &prefix->data.array_type.child_type;
|
||||
@ -1167,7 +1167,6 @@ static AstNode *ast_parse_prefix_expr(ParseContext *pc) {
|
||||
// <- AsmExpr
|
||||
// / IfExpr
|
||||
// / KEYWORD_break BreakLabel? Expr?
|
||||
// / KEYWORD_cancel Expr
|
||||
// / KEYWORD_comptime Expr
|
||||
// / KEYWORD_continue BreakLabel?
|
||||
// / KEYWORD_resume Expr
|
||||
@ -1195,14 +1194,6 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc) {
|
||||
return res;
|
||||
}
|
||||
|
||||
Token *cancel = eat_token_if(pc, TokenIdKeywordCancel);
|
||||
if (cancel != nullptr) {
|
||||
AstNode *expr = ast_expect(pc, ast_parse_expr);
|
||||
AstNode *res = ast_create_node(pc, NodeTypeCancel, cancel);
|
||||
res->data.cancel_expr.expr = expr;
|
||||
return res;
|
||||
}
|
||||
|
||||
Token *comptime = eat_token_if(pc, TokenIdKeywordCompTime);
|
||||
if (comptime != nullptr) {
|
||||
AstNode *expr = ast_expect(pc, ast_parse_expr);
|
||||
@ -1643,9 +1634,9 @@ static AstNode *ast_parse_primary_type_expr(ParseContext *pc) {
|
||||
if (null != nullptr)
|
||||
return ast_create_node(pc, NodeTypeNullLiteral, null);
|
||||
|
||||
Token *promise = eat_token_if(pc, TokenIdKeywordPromise);
|
||||
if (promise != nullptr)
|
||||
return ast_create_node(pc, NodeTypePromiseType, promise);
|
||||
Token *anyframe = eat_token_if(pc, TokenIdKeywordAnyFrame);
|
||||
if (anyframe != nullptr)
|
||||
return ast_create_node(pc, NodeTypeAnyFrameType, anyframe);
|
||||
|
||||
Token *true_token = eat_token_if(pc, TokenIdKeywordTrue);
|
||||
if (true_token != nullptr) {
|
||||
@ -2042,11 +2033,6 @@ static Optional<AstNodeFnProto> ast_parse_fn_cc(ParseContext *pc) {
|
||||
}
|
||||
if (eat_token_if(pc, TokenIdKeywordAsync) != nullptr) {
|
||||
res.cc = CallingConventionAsync;
|
||||
if (eat_token_if(pc, TokenIdCmpLessThan) == nullptr)
|
||||
return Optional<AstNodeFnProto>::some(res);
|
||||
|
||||
res.async_allocator_type = ast_expect(pc, ast_parse_type_expr);
|
||||
expect_token(pc, TokenIdCmpGreaterThan);
|
||||
return Optional<AstNodeFnProto>::some(res);
|
||||
}
|
||||
|
||||
@ -2522,7 +2508,7 @@ static AstNode *ast_parse_prefix_op(ParseContext *pc) {
|
||||
|
||||
// PrefixTypeOp
|
||||
// <- QUESTIONMARK
|
||||
// / KEYWORD_promise MINUSRARROW
|
||||
// / KEYWORD_anyframe MINUSRARROW
|
||||
// / ArrayTypeStart (ByteAlign / KEYWORD_const / KEYWORD_volatile)*
|
||||
// / PtrTypeStart (KEYWORD_align LPAREN Expr (COLON INTEGER COLON INTEGER)? RPAREN / KEYWORD_const / KEYWORD_volatile)*
|
||||
static AstNode *ast_parse_prefix_type_op(ParseContext *pc) {
|
||||
@ -2533,10 +2519,10 @@ static AstNode *ast_parse_prefix_type_op(ParseContext *pc) {
|
||||
return res;
|
||||
}
|
||||
|
||||
Token *promise = eat_token_if(pc, TokenIdKeywordPromise);
|
||||
if (promise != nullptr) {
|
||||
Token *anyframe = eat_token_if(pc, TokenIdKeywordAnyFrame);
|
||||
if (anyframe != nullptr) {
|
||||
if (eat_token_if(pc, TokenIdArrow) != nullptr) {
|
||||
AstNode *res = ast_create_node(pc, NodeTypePromiseType, promise);
|
||||
AstNode *res = ast_create_node(pc, NodeTypeAnyFrameType, anyframe);
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -2680,11 +2666,6 @@ static AstNode *ast_parse_async_prefix(ParseContext *pc) {
|
||||
AstNode *res = ast_create_node(pc, NodeTypeFnCallExpr, async);
|
||||
res->data.fn_call_expr.is_async = true;
|
||||
res->data.fn_call_expr.seen = false;
|
||||
if (eat_token_if(pc, TokenIdCmpLessThan) != nullptr) {
|
||||
AstNode *prefix_expr = ast_expect(pc, ast_parse_prefix_expr);
|
||||
expect_token(pc, TokenIdCmpGreaterThan);
|
||||
res->data.fn_call_expr.async_allocator = prefix_expr;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@ -2858,7 +2839,6 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont
|
||||
visit_node_list(&node->data.fn_proto.params, visit, context);
|
||||
visit_field(&node->data.fn_proto.align_expr, visit, context);
|
||||
visit_field(&node->data.fn_proto.section_expr, visit, context);
|
||||
visit_field(&node->data.fn_proto.async_allocator_type, visit, context);
|
||||
break;
|
||||
case NodeTypeFnDef:
|
||||
visit_field(&node->data.fn_def.fn_proto, visit, context);
|
||||
@ -2918,7 +2898,6 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont
|
||||
case NodeTypeFnCallExpr:
|
||||
visit_field(&node->data.fn_call_expr.fn_ref_expr, visit, context);
|
||||
visit_node_list(&node->data.fn_call_expr.params, visit, context);
|
||||
visit_field(&node->data.fn_call_expr.async_allocator, visit, context);
|
||||
break;
|
||||
case NodeTypeArrayAccessExpr:
|
||||
visit_field(&node->data.array_access_expr.array_ref_expr, visit, context);
|
||||
@ -3034,8 +3013,8 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont
|
||||
case NodeTypeInferredArrayType:
|
||||
visit_field(&node->data.array_type.child_type, visit, context);
|
||||
break;
|
||||
case NodeTypePromiseType:
|
||||
visit_field(&node->data.promise_type.payload_type, visit, context);
|
||||
case NodeTypeAnyFrameType:
|
||||
visit_field(&node->data.anyframe_type.payload_type, visit, context);
|
||||
break;
|
||||
case NodeTypeErrorType:
|
||||
// none
|
||||
@ -3047,9 +3026,6 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont
|
||||
case NodeTypeErrorSetDecl:
|
||||
visit_node_list(&node->data.err_set_decl.decls, visit, context);
|
||||
break;
|
||||
case NodeTypeCancel:
|
||||
visit_field(&node->data.cancel_expr.expr, visit, context);
|
||||
break;
|
||||
case NodeTypeResume:
|
||||
visit_field(&node->data.resume_expr.expr, visit, context);
|
||||
break;
|
||||
|
||||
@ -1759,3 +1759,7 @@ bool target_supports_libunwind(const ZigTarget *target) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
unsigned target_fn_align(const ZigTarget *target) {
|
||||
return 16;
|
||||
}
|
||||
|
||||
@ -197,4 +197,6 @@ uint32_t target_arch_largest_atomic_bits(ZigLLVM_ArchType arch);
|
||||
size_t target_libc_count(void);
|
||||
void target_libc_enum(size_t index, ZigTarget *out_target);
|
||||
|
||||
unsigned target_fn_align(const ZigTarget *target);
|
||||
|
||||
#endif
|
||||
|
||||
@ -109,11 +109,11 @@ static const struct ZigKeyword zig_keywords[] = {
|
||||
{"align", TokenIdKeywordAlign},
|
||||
{"allowzero", TokenIdKeywordAllowZero},
|
||||
{"and", TokenIdKeywordAnd},
|
||||
{"anyframe", TokenIdKeywordAnyFrame},
|
||||
{"asm", TokenIdKeywordAsm},
|
||||
{"async", TokenIdKeywordAsync},
|
||||
{"await", TokenIdKeywordAwait},
|
||||
{"break", TokenIdKeywordBreak},
|
||||
{"cancel", TokenIdKeywordCancel},
|
||||
{"catch", TokenIdKeywordCatch},
|
||||
{"comptime", TokenIdKeywordCompTime},
|
||||
{"const", TokenIdKeywordConst},
|
||||
@ -136,7 +136,6 @@ static const struct ZigKeyword zig_keywords[] = {
|
||||
{"or", TokenIdKeywordOr},
|
||||
{"orelse", TokenIdKeywordOrElse},
|
||||
{"packed", TokenIdKeywordPacked},
|
||||
{"promise", TokenIdKeywordPromise},
|
||||
{"pub", TokenIdKeywordPub},
|
||||
{"resume", TokenIdKeywordResume},
|
||||
{"return", TokenIdKeywordReturn},
|
||||
@ -1531,9 +1530,9 @@ const char * token_name(TokenId id) {
|
||||
case TokenIdKeywordAwait: return "await";
|
||||
case TokenIdKeywordResume: return "resume";
|
||||
case TokenIdKeywordSuspend: return "suspend";
|
||||
case TokenIdKeywordCancel: return "cancel";
|
||||
case TokenIdKeywordAlign: return "align";
|
||||
case TokenIdKeywordAnd: return "and";
|
||||
case TokenIdKeywordAnyFrame: return "anyframe";
|
||||
case TokenIdKeywordAsm: return "asm";
|
||||
case TokenIdKeywordBreak: return "break";
|
||||
case TokenIdKeywordCatch: return "catch";
|
||||
@ -1558,7 +1557,6 @@ const char * token_name(TokenId id) {
|
||||
case TokenIdKeywordOr: return "or";
|
||||
case TokenIdKeywordOrElse: return "orelse";
|
||||
case TokenIdKeywordPacked: return "packed";
|
||||
case TokenIdKeywordPromise: return "promise";
|
||||
case TokenIdKeywordPub: return "pub";
|
||||
case TokenIdKeywordReturn: return "return";
|
||||
case TokenIdKeywordLinkSection: return "linksection";
|
||||
|
||||
@ -53,11 +53,11 @@ enum TokenId {
|
||||
TokenIdKeywordAlign,
|
||||
TokenIdKeywordAllowZero,
|
||||
TokenIdKeywordAnd,
|
||||
TokenIdKeywordAnyFrame,
|
||||
TokenIdKeywordAsm,
|
||||
TokenIdKeywordAsync,
|
||||
TokenIdKeywordAwait,
|
||||
TokenIdKeywordBreak,
|
||||
TokenIdKeywordCancel,
|
||||
TokenIdKeywordCatch,
|
||||
TokenIdKeywordCompTime,
|
||||
TokenIdKeywordConst,
|
||||
@ -81,7 +81,6 @@ enum TokenId {
|
||||
TokenIdKeywordOr,
|
||||
TokenIdKeywordOrElse,
|
||||
TokenIdKeywordPacked,
|
||||
TokenIdKeywordPromise,
|
||||
TokenIdKeywordPub,
|
||||
TokenIdKeywordResume,
|
||||
TokenIdKeywordReturn,
|
||||
|
||||
@ -42,7 +42,6 @@
|
||||
#include <llvm/Support/TargetRegistry.h>
|
||||
#include <llvm/Target/TargetMachine.h>
|
||||
#include <llvm/Target/CodeGenCWrappers.h>
|
||||
#include <llvm/Transforms/Coroutines.h>
|
||||
#include <llvm/Transforms/IPO.h>
|
||||
#include <llvm/Transforms/IPO/AlwaysInliner.h>
|
||||
#include <llvm/Transforms/IPO/PassManagerBuilder.h>
|
||||
@ -203,8 +202,6 @@ bool ZigLLVMTargetMachineEmitToFile(LLVMTargetMachineRef targ_machine_ref, LLVMM
|
||||
PMBuilder->Inliner = createFunctionInliningPass(PMBuilder->OptLevel, PMBuilder->SizeLevel, false);
|
||||
}
|
||||
|
||||
addCoroutinePassesToExtensionPoints(*PMBuilder);
|
||||
|
||||
// Set up the per-function pass manager.
|
||||
legacy::FunctionPassManager FPM = legacy::FunctionPassManager(module);
|
||||
auto tliwp = new(std::nothrow) TargetLibraryInfoWrapperPass(tlii);
|
||||
@ -898,6 +895,14 @@ LLVMValueRef ZigLLVMBuildAShrExact(LLVMBuilderRef builder, LLVMValueRef LHS, LLV
|
||||
return wrap(unwrap(builder)->CreateAShr(unwrap(LHS), unwrap(RHS), name, true));
|
||||
}
|
||||
|
||||
void ZigLLVMSetTailCall(LLVMValueRef Call) {
|
||||
unwrap<CallInst>(Call)->setTailCallKind(CallInst::TCK_MustTail);
|
||||
}
|
||||
|
||||
void ZigLLVMFunctionSetPrefixData(LLVMValueRef function, LLVMValueRef data) {
|
||||
unwrap<Function>(function)->setPrefixData(unwrap<Constant>(data));
|
||||
}
|
||||
|
||||
|
||||
class MyOStream: public raw_ostream {
|
||||
public:
|
||||
|
||||
@ -211,6 +211,8 @@ ZIG_EXTERN_C LLVMValueRef ZigLLVMInsertDeclare(struct ZigLLVMDIBuilder *dibuilde
|
||||
ZIG_EXTERN_C struct ZigLLVMDILocation *ZigLLVMGetDebugLoc(unsigned line, unsigned col, struct ZigLLVMDIScope *scope);
|
||||
|
||||
ZIG_EXTERN_C void ZigLLVMSetFastMath(LLVMBuilderRef builder_wrapped, bool on_state);
|
||||
ZIG_EXTERN_C void ZigLLVMSetTailCall(LLVMValueRef Call);
|
||||
ZIG_EXTERN_C void ZigLLVMFunctionSetPrefixData(LLVMValueRef fn, LLVMValueRef data);
|
||||
|
||||
ZIG_EXTERN_C void ZigLLVMAddFunctionAttr(LLVMValueRef fn, const char *attr_name, const char *attr_value);
|
||||
ZIG_EXTERN_C void ZigLLVMAddFunctionAttrCold(LLVMValueRef fn);
|
||||
|
||||
@ -2,8 +2,6 @@ const std = @import("../std.zig");
|
||||
const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const testing = std.testing;
|
||||
const AtomicRmwOp = builtin.AtomicRmwOp;
|
||||
const AtomicOrder = builtin.AtomicOrder;
|
||||
const Loop = std.event.Loop;
|
||||
|
||||
/// many producer, many consumer, thread-safe, runtime configurable buffer size
|
||||
@ -77,24 +75,20 @@ pub fn Channel(comptime T: type) type {
|
||||
/// must be called when all calls to put and get have suspended and no more calls occur
|
||||
pub fn destroy(self: *SelfChannel) void {
|
||||
while (self.getters.get()) |get_node| {
|
||||
cancel get_node.data.tick_node.data;
|
||||
resume get_node.data.tick_node.data;
|
||||
}
|
||||
while (self.putters.get()) |put_node| {
|
||||
cancel put_node.data.tick_node.data;
|
||||
resume put_node.data.tick_node.data;
|
||||
}
|
||||
self.loop.allocator.free(self.buffer_nodes);
|
||||
self.loop.allocator.destroy(self);
|
||||
}
|
||||
|
||||
/// puts a data item in the channel. The promise completes when the value has been added to the
|
||||
/// puts a data item in the channel. The function returns when the value has been added to the
|
||||
/// buffer, or in the case of a zero size buffer, when the item has been retrieved by a getter.
|
||||
pub async fn put(self: *SelfChannel, data: T) void {
|
||||
// TODO fix this workaround
|
||||
suspend {
|
||||
resume @handle();
|
||||
}
|
||||
|
||||
var my_tick_node = Loop.NextTickNode.init(@handle());
|
||||
/// Or when the channel is destroyed.
|
||||
pub fn put(self: *SelfChannel, data: T) void {
|
||||
var my_tick_node = Loop.NextTickNode.init(@frame());
|
||||
var queue_node = std.atomic.Queue(PutNode).Node.init(PutNode{
|
||||
.tick_node = &my_tick_node,
|
||||
.data = data,
|
||||
@ -102,35 +96,29 @@ pub fn Channel(comptime T: type) type {
|
||||
|
||||
// TODO test canceling a put()
|
||||
errdefer {
|
||||
_ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(usize, &self.put_count, .Sub, 1, .SeqCst);
|
||||
const need_dispatch = !self.putters.remove(&queue_node);
|
||||
self.loop.cancelOnNextTick(&my_tick_node);
|
||||
if (need_dispatch) {
|
||||
// oops we made the put_count incorrect for a period of time. fix by dispatching.
|
||||
_ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(usize, &self.put_count, .Add, 1, .SeqCst);
|
||||
self.dispatch();
|
||||
}
|
||||
}
|
||||
suspend {
|
||||
self.putters.put(&queue_node);
|
||||
_ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(usize, &self.put_count, .Add, 1, .SeqCst);
|
||||
|
||||
self.dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
/// await this function to get an item from the channel. If the buffer is empty, the promise will
|
||||
/// await this function to get an item from the channel. If the buffer is empty, the frame will
|
||||
/// complete when the next item is put in the channel.
|
||||
pub async fn get(self: *SelfChannel) T {
|
||||
// TODO fix this workaround
|
||||
suspend {
|
||||
resume @handle();
|
||||
}
|
||||
|
||||
// TODO integrate this function with named return values
|
||||
// so we can get rid of this extra result copy
|
||||
// TODO https://github.com/ziglang/zig/issues/2765
|
||||
var result: T = undefined;
|
||||
var my_tick_node = Loop.NextTickNode.init(@handle());
|
||||
var my_tick_node = Loop.NextTickNode.init(@frame());
|
||||
var queue_node = std.atomic.Queue(GetNode).Node.init(GetNode{
|
||||
.tick_node = &my_tick_node,
|
||||
.data = GetNode.Data{
|
||||
@ -140,19 +128,19 @@ pub fn Channel(comptime T: type) type {
|
||||
|
||||
// TODO test canceling a get()
|
||||
errdefer {
|
||||
_ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(usize, &self.get_count, .Sub, 1, .SeqCst);
|
||||
const need_dispatch = !self.getters.remove(&queue_node);
|
||||
self.loop.cancelOnNextTick(&my_tick_node);
|
||||
if (need_dispatch) {
|
||||
// oops we made the get_count incorrect for a period of time. fix by dispatching.
|
||||
_ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(usize, &self.get_count, .Add, 1, .SeqCst);
|
||||
self.dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
suspend {
|
||||
self.getters.put(&queue_node);
|
||||
_ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(usize, &self.get_count, .Add, 1, .SeqCst);
|
||||
|
||||
self.dispatch();
|
||||
}
|
||||
@ -173,15 +161,10 @@ pub fn Channel(comptime T: type) type {
|
||||
/// Await is necessary for locking purposes. The function will be resumed after checking the channel
|
||||
/// for data and will not wait for data to be available.
|
||||
pub async fn getOrNull(self: *SelfChannel) ?T {
|
||||
// TODO fix this workaround
|
||||
suspend {
|
||||
resume @handle();
|
||||
}
|
||||
|
||||
// TODO integrate this function with named return values
|
||||
// so we can get rid of this extra result copy
|
||||
var result: ?T = null;
|
||||
var my_tick_node = Loop.NextTickNode.init(@handle());
|
||||
var my_tick_node = Loop.NextTickNode.init(@frame());
|
||||
var or_null_node = std.atomic.Queue(*std.atomic.Queue(GetNode).Node).Node.init(undefined);
|
||||
var queue_node = std.atomic.Queue(GetNode).Node.init(GetNode{
|
||||
.tick_node = &my_tick_node,
|
||||
@ -197,19 +180,19 @@ pub fn Channel(comptime T: type) type {
|
||||
// TODO test canceling getOrNull
|
||||
errdefer {
|
||||
_ = self.or_null_queue.remove(&or_null_node);
|
||||
_ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(usize, &self.get_count, .Sub, 1, .SeqCst);
|
||||
const need_dispatch = !self.getters.remove(&queue_node);
|
||||
self.loop.cancelOnNextTick(&my_tick_node);
|
||||
if (need_dispatch) {
|
||||
// oops we made the get_count incorrect for a period of time. fix by dispatching.
|
||||
_ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(usize, &self.get_count, .Add, 1, .SeqCst);
|
||||
self.dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
suspend {
|
||||
self.getters.put(&queue_node);
|
||||
_ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(usize, &self.get_count, .Add, 1, .SeqCst);
|
||||
self.or_null_queue.put(&or_null_node);
|
||||
|
||||
self.dispatch();
|
||||
@ -219,21 +202,21 @@ pub fn Channel(comptime T: type) type {
|
||||
|
||||
fn dispatch(self: *SelfChannel) void {
|
||||
// set the "need dispatch" flag
|
||||
_ = @atomicRmw(u8, &self.need_dispatch, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(u8, &self.need_dispatch, .Xchg, 1, .SeqCst);
|
||||
|
||||
lock: while (true) {
|
||||
// set the lock flag
|
||||
const prev_lock = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
|
||||
const prev_lock = @atomicRmw(u8, &self.dispatch_lock, .Xchg, 1, .SeqCst);
|
||||
if (prev_lock != 0) return;
|
||||
|
||||
// clear the need_dispatch flag since we're about to do it
|
||||
_ = @atomicRmw(u8, &self.need_dispatch, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(u8, &self.need_dispatch, .Xchg, 0, .SeqCst);
|
||||
|
||||
while (true) {
|
||||
one_dispatch: {
|
||||
// later we correct these extra subtractions
|
||||
var get_count = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
|
||||
var put_count = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
|
||||
var get_count = @atomicRmw(usize, &self.get_count, .Sub, 1, .SeqCst);
|
||||
var put_count = @atomicRmw(usize, &self.put_count, .Sub, 1, .SeqCst);
|
||||
|
||||
// transfer self.buffer to self.getters
|
||||
while (self.buffer_len != 0) {
|
||||
@ -252,7 +235,7 @@ pub fn Channel(comptime T: type) type {
|
||||
self.loop.onNextTick(get_node.tick_node);
|
||||
self.buffer_len -= 1;
|
||||
|
||||
get_count = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
|
||||
get_count = @atomicRmw(usize, &self.get_count, .Sub, 1, .SeqCst);
|
||||
}
|
||||
|
||||
// direct transfer self.putters to self.getters
|
||||
@ -272,8 +255,8 @@ pub fn Channel(comptime T: type) type {
|
||||
self.loop.onNextTick(get_node.tick_node);
|
||||
self.loop.onNextTick(put_node.tick_node);
|
||||
|
||||
get_count = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
|
||||
put_count = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
|
||||
get_count = @atomicRmw(usize, &self.get_count, .Sub, 1, .SeqCst);
|
||||
put_count = @atomicRmw(usize, &self.put_count, .Sub, 1, .SeqCst);
|
||||
}
|
||||
|
||||
// transfer self.putters to self.buffer
|
||||
@ -285,13 +268,13 @@ pub fn Channel(comptime T: type) type {
|
||||
self.buffer_index +%= 1;
|
||||
self.buffer_len += 1;
|
||||
|
||||
put_count = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
|
||||
put_count = @atomicRmw(usize, &self.put_count, .Sub, 1, .SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
// undo the extra subtractions
|
||||
_ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(usize, &self.get_count, .Add, 1, .SeqCst);
|
||||
_ = @atomicRmw(usize, &self.put_count, .Add, 1, .SeqCst);
|
||||
|
||||
// All the "get or null" functions should resume now.
|
||||
var remove_count: usize = 0;
|
||||
@ -300,18 +283,18 @@ pub fn Channel(comptime T: type) type {
|
||||
self.loop.onNextTick(or_null_node.data.data.tick_node);
|
||||
}
|
||||
if (remove_count != 0) {
|
||||
_ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Sub, remove_count, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(usize, &self.get_count, .Sub, remove_count, .SeqCst);
|
||||
}
|
||||
|
||||
// clear need-dispatch flag
|
||||
const need_dispatch = @atomicRmw(u8, &self.need_dispatch, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
|
||||
const need_dispatch = @atomicRmw(u8, &self.need_dispatch, .Xchg, 0, .SeqCst);
|
||||
if (need_dispatch != 0) continue;
|
||||
|
||||
const my_lock = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
|
||||
const my_lock = @atomicRmw(u8, &self.dispatch_lock, .Xchg, 0, .SeqCst);
|
||||
assert(my_lock != 0);
|
||||
|
||||
// we have to check again now that we unlocked
|
||||
if (@atomicLoad(u8, &self.need_dispatch, AtomicOrder.SeqCst) != 0) continue :lock;
|
||||
if (@atomicLoad(u8, &self.need_dispatch, .SeqCst) != 0) continue :lock;
|
||||
|
||||
return;
|
||||
}
|
||||
@ -324,51 +307,41 @@ test "std.event.Channel" {
|
||||
// https://github.com/ziglang/zig/issues/1908
|
||||
if (builtin.single_threaded) return error.SkipZigTest;
|
||||
|
||||
const allocator = std.heap.direct_allocator;
|
||||
|
||||
var loop: Loop = undefined;
|
||||
// TODO make a multi threaded test
|
||||
try loop.initSingleThreaded(allocator);
|
||||
try loop.initSingleThreaded(std.heap.direct_allocator);
|
||||
defer loop.deinit();
|
||||
|
||||
const channel = try Channel(i32).create(&loop, 0);
|
||||
defer channel.destroy();
|
||||
|
||||
const handle = try async<allocator> testChannelGetter(&loop, channel);
|
||||
defer cancel handle;
|
||||
|
||||
const putter = try async<allocator> testChannelPutter(channel);
|
||||
defer cancel putter;
|
||||
const handle = async testChannelGetter(&loop, channel);
|
||||
const putter = async testChannelPutter(channel);
|
||||
|
||||
loop.run();
|
||||
}
|
||||
|
||||
async fn testChannelGetter(loop: *Loop, channel: *Channel(i32)) void {
|
||||
errdefer @panic("test failed");
|
||||
|
||||
const value1_promise = try async channel.get();
|
||||
const value1 = await value1_promise;
|
||||
const value1 = channel.get();
|
||||
testing.expect(value1 == 1234);
|
||||
|
||||
const value2_promise = try async channel.get();
|
||||
const value2 = await value2_promise;
|
||||
const value2 = channel.get();
|
||||
testing.expect(value2 == 4567);
|
||||
|
||||
const value3_promise = try async channel.getOrNull();
|
||||
const value3 = await value3_promise;
|
||||
const value3 = channel.getOrNull();
|
||||
testing.expect(value3 == null);
|
||||
|
||||
const last_put = try async testPut(channel, 4444);
|
||||
const value4 = await try async channel.getOrNull();
|
||||
const last_put = async testPut(channel, 4444);
|
||||
const value4 = channel.getOrNull();
|
||||
testing.expect(value4.? == 4444);
|
||||
await last_put;
|
||||
}
|
||||
|
||||
async fn testChannelPutter(channel: *Channel(i32)) void {
|
||||
await (async channel.put(1234) catch @panic("out of memory"));
|
||||
await (async channel.put(4567) catch @panic("out of memory"));
|
||||
channel.put(1234);
|
||||
channel.put(4567);
|
||||
}
|
||||
|
||||
async fn testPut(channel: *Channel(i32), value: i32) void {
|
||||
await (async channel.put(value) catch @panic("out of memory"));
|
||||
channel.put(value);
|
||||
}
|
||||
|
||||
1334
std/event/fs.zig
1334
std/event/fs.zig
File diff suppressed because it is too large
Load Diff
@ -2,13 +2,11 @@ const std = @import("../std.zig");
|
||||
const assert = std.debug.assert;
|
||||
const testing = std.testing;
|
||||
const builtin = @import("builtin");
|
||||
const AtomicRmwOp = builtin.AtomicRmwOp;
|
||||
const AtomicOrder = builtin.AtomicOrder;
|
||||
const Lock = std.event.Lock;
|
||||
const Loop = std.event.Loop;
|
||||
|
||||
/// This is a value that starts out unavailable, until resolve() is called
|
||||
/// While it is unavailable, coroutines suspend when they try to get() it,
|
||||
/// While it is unavailable, functions suspend when they try to get() it,
|
||||
/// and then are resumed when resolve() is called.
|
||||
/// At this point the value remains forever available, and another resolve() is not allowed.
|
||||
pub fn Future(comptime T: type) type {
|
||||
@ -23,7 +21,7 @@ pub fn Future(comptime T: type) type {
|
||||
available: u8,
|
||||
|
||||
const Self = @This();
|
||||
const Queue = std.atomic.Queue(promise);
|
||||
const Queue = std.atomic.Queue(anyframe);
|
||||
|
||||
pub fn init(loop: *Loop) Self {
|
||||
return Self{
|
||||
@ -37,10 +35,10 @@ pub fn Future(comptime T: type) type {
|
||||
/// available.
|
||||
/// Thread-safe.
|
||||
pub async fn get(self: *Self) *T {
|
||||
if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 2) {
|
||||
if (@atomicLoad(u8, &self.available, .SeqCst) == 2) {
|
||||
return &self.data;
|
||||
}
|
||||
const held = await (async self.lock.acquire() catch unreachable);
|
||||
const held = self.lock.acquire();
|
||||
held.release();
|
||||
|
||||
return &self.data;
|
||||
@ -49,7 +47,7 @@ pub fn Future(comptime T: type) type {
|
||||
/// Gets the data without waiting for it. If it's available, a pointer is
|
||||
/// returned. Otherwise, null is returned.
|
||||
pub fn getOrNull(self: *Self) ?*T {
|
||||
if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 2) {
|
||||
if (@atomicLoad(u8, &self.available, .SeqCst) == 2) {
|
||||
return &self.data;
|
||||
} else {
|
||||
return null;
|
||||
@ -62,10 +60,10 @@ pub fn Future(comptime T: type) type {
|
||||
/// It's not required to call start() before resolve() but it can be useful since
|
||||
/// this method is thread-safe.
|
||||
pub async fn start(self: *Self) ?*T {
|
||||
const state = @cmpxchgStrong(u8, &self.available, 0, 1, AtomicOrder.SeqCst, AtomicOrder.SeqCst) orelse return null;
|
||||
const state = @cmpxchgStrong(u8, &self.available, 0, 1, .SeqCst, .SeqCst) orelse return null;
|
||||
switch (state) {
|
||||
1 => {
|
||||
const held = await (async self.lock.acquire() catch unreachable);
|
||||
const held = self.lock.acquire();
|
||||
held.release();
|
||||
return &self.data;
|
||||
},
|
||||
@ -77,7 +75,7 @@ pub fn Future(comptime T: type) type {
|
||||
/// Make the data become available. May be called only once.
|
||||
/// Before calling this, modify the `data` property.
|
||||
pub fn resolve(self: *Self) void {
|
||||
const prev = @atomicRmw(u8, &self.available, AtomicRmwOp.Xchg, 2, AtomicOrder.SeqCst);
|
||||
const prev = @atomicRmw(u8, &self.available, .Xchg, 2, .SeqCst);
|
||||
assert(prev == 0 or prev == 1); // resolve() called twice
|
||||
Lock.Held.release(Lock.Held{ .lock = &self.lock });
|
||||
}
|
||||
@ -86,7 +84,7 @@ pub fn Future(comptime T: type) type {
|
||||
|
||||
test "std.event.Future" {
|
||||
// https://github.com/ziglang/zig/issues/1908
|
||||
if (builtin.single_threaded or builtin.os != builtin.Os.linux) return error.SkipZigTest;
|
||||
if (builtin.single_threaded) return error.SkipZigTest;
|
||||
|
||||
const allocator = std.heap.direct_allocator;
|
||||
|
||||
@ -94,38 +92,33 @@ test "std.event.Future" {
|
||||
try loop.initMultiThreaded(allocator);
|
||||
defer loop.deinit();
|
||||
|
||||
const handle = try async<allocator> testFuture(&loop);
|
||||
defer cancel handle;
|
||||
const handle = async testFuture(&loop);
|
||||
|
||||
loop.run();
|
||||
}
|
||||
|
||||
async fn testFuture(loop: *Loop) void {
|
||||
suspend {
|
||||
resume @handle();
|
||||
}
|
||||
var future = Future(i32).init(loop);
|
||||
|
||||
const a = async waitOnFuture(&future) catch @panic("memory");
|
||||
const b = async waitOnFuture(&future) catch @panic("memory");
|
||||
const c = async resolveFuture(&future) catch @panic("memory");
|
||||
const a = async waitOnFuture(&future);
|
||||
const b = async waitOnFuture(&future);
|
||||
const c = async resolveFuture(&future);
|
||||
|
||||
const result = (await a) + (await b);
|
||||
cancel c;
|
||||
// TODO make this work:
|
||||
//const result = (await a) + (await b);
|
||||
const a_result = await a;
|
||||
const b_result = await b;
|
||||
const result = a_result + b_result;
|
||||
|
||||
await c;
|
||||
testing.expect(result == 12);
|
||||
}
|
||||
|
||||
async fn waitOnFuture(future: *Future(i32)) i32 {
|
||||
suspend {
|
||||
resume @handle();
|
||||
}
|
||||
return (await (async future.get() catch @panic("memory"))).*;
|
||||
return future.get().*;
|
||||
}
|
||||
|
||||
async fn resolveFuture(future: *Future(i32)) void {
|
||||
suspend {
|
||||
resume @handle();
|
||||
}
|
||||
future.data = 6;
|
||||
future.resolve();
|
||||
}
|
||||
|
||||
@ -2,46 +2,33 @@ const std = @import("../std.zig");
|
||||
const builtin = @import("builtin");
|
||||
const Lock = std.event.Lock;
|
||||
const Loop = std.event.Loop;
|
||||
const AtomicRmwOp = builtin.AtomicRmwOp;
|
||||
const AtomicOrder = builtin.AtomicOrder;
|
||||
const testing = std.testing;
|
||||
|
||||
/// ReturnType must be `void` or `E!void`
|
||||
pub fn Group(comptime ReturnType: type) type {
|
||||
return struct {
|
||||
coro_stack: Stack,
|
||||
frame_stack: Stack,
|
||||
alloc_stack: Stack,
|
||||
lock: Lock,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
const Error = switch (@typeInfo(ReturnType)) {
|
||||
builtin.TypeId.ErrorUnion => |payload| payload.error_set,
|
||||
.ErrorUnion => |payload| payload.error_set,
|
||||
else => void,
|
||||
};
|
||||
const Stack = std.atomic.Stack(promise->ReturnType);
|
||||
const Stack = std.atomic.Stack(anyframe->ReturnType);
|
||||
|
||||
pub fn init(loop: *Loop) Self {
|
||||
return Self{
|
||||
.coro_stack = Stack.init(),
|
||||
.frame_stack = Stack.init(),
|
||||
.alloc_stack = Stack.init(),
|
||||
.lock = Lock.init(loop),
|
||||
};
|
||||
}
|
||||
|
||||
/// Cancel all the outstanding promises. Can be called even if wait was already called.
|
||||
pub fn deinit(self: *Self) void {
|
||||
while (self.coro_stack.pop()) |node| {
|
||||
cancel node.data;
|
||||
}
|
||||
while (self.alloc_stack.pop()) |node| {
|
||||
cancel node.data;
|
||||
self.lock.loop.allocator.destroy(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a promise to the group. Thread-safe.
|
||||
pub fn add(self: *Self, handle: promise->ReturnType) (error{OutOfMemory}!void) {
|
||||
/// Add a frame to the group. Thread-safe.
|
||||
pub fn add(self: *Self, handle: anyframe->ReturnType) (error{OutOfMemory}!void) {
|
||||
const node = try self.lock.loop.allocator.create(Stack.Node);
|
||||
node.* = Stack.Node{
|
||||
.next = undefined,
|
||||
@ -51,57 +38,29 @@ pub fn Group(comptime ReturnType: type) type {
|
||||
}
|
||||
|
||||
/// Add a node to the group. Thread-safe. Cannot fail.
|
||||
/// `node.data` should be the promise handle to add to the group.
|
||||
/// The node's memory should be in the coroutine frame of
|
||||
/// `node.data` should be the frame handle to add to the group.
|
||||
/// The node's memory should be in the function frame of
|
||||
/// the handle that is in the node, or somewhere guaranteed to live
|
||||
/// at least as long.
|
||||
pub fn addNode(self: *Self, node: *Stack.Node) void {
|
||||
self.coro_stack.push(node);
|
||||
}
|
||||
|
||||
/// This is equivalent to an async call, but the async function is added to the group, instead
|
||||
/// of returning a promise. func must be async and have return type ReturnType.
|
||||
/// Thread-safe.
|
||||
pub fn call(self: *Self, comptime func: var, args: ...) (error{OutOfMemory}!void) {
|
||||
const S = struct {
|
||||
async fn asyncFunc(node: **Stack.Node, args2: ...) ReturnType {
|
||||
// TODO this is a hack to make the memory following be inside the coro frame
|
||||
suspend {
|
||||
var my_node: Stack.Node = undefined;
|
||||
node.* = &my_node;
|
||||
resume @handle();
|
||||
}
|
||||
|
||||
// TODO this allocation elision should be guaranteed because we await it in
|
||||
// this coro frame
|
||||
return await (async func(args2) catch unreachable);
|
||||
}
|
||||
};
|
||||
var node: *Stack.Node = undefined;
|
||||
const handle = try async<self.lock.loop.allocator> S.asyncFunc(&node, args);
|
||||
node.* = Stack.Node{
|
||||
.next = undefined,
|
||||
.data = handle,
|
||||
};
|
||||
self.coro_stack.push(node);
|
||||
self.frame_stack.push(node);
|
||||
}
|
||||
|
||||
/// Wait for all the calls and promises of the group to complete.
|
||||
/// Thread-safe.
|
||||
/// Safe to call any number of times.
|
||||
pub async fn wait(self: *Self) ReturnType {
|
||||
// TODO catch unreachable because the allocation can be grouped with
|
||||
// the coro frame allocation
|
||||
const held = await (async self.lock.acquire() catch unreachable);
|
||||
const held = self.lock.acquire();
|
||||
defer held.release();
|
||||
|
||||
while (self.coro_stack.pop()) |node| {
|
||||
var result: ReturnType = {};
|
||||
|
||||
while (self.frame_stack.pop()) |node| {
|
||||
if (Error == void) {
|
||||
await node.data;
|
||||
} else {
|
||||
(await node.data) catch |err| {
|
||||
self.deinit();
|
||||
return err;
|
||||
result = err;
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -112,11 +71,11 @@ pub fn Group(comptime ReturnType: type) type {
|
||||
await handle;
|
||||
} else {
|
||||
(await handle) catch |err| {
|
||||
self.deinit();
|
||||
return err;
|
||||
result = err;
|
||||
};
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -131,8 +90,7 @@ test "std.event.Group" {
|
||||
try loop.initMultiThreaded(allocator);
|
||||
defer loop.deinit();
|
||||
|
||||
const handle = try async<allocator> testGroup(&loop);
|
||||
defer cancel handle;
|
||||
const handle = async testGroup(&loop);
|
||||
|
||||
loop.run();
|
||||
}
|
||||
@ -140,26 +98,30 @@ test "std.event.Group" {
|
||||
async fn testGroup(loop: *Loop) void {
|
||||
var count: usize = 0;
|
||||
var group = Group(void).init(loop);
|
||||
group.add(async sleepALittle(&count) catch @panic("memory")) catch @panic("memory");
|
||||
group.call(increaseByTen, &count) catch @panic("memory");
|
||||
await (async group.wait() catch @panic("memory"));
|
||||
var sleep_a_little_frame = async sleepALittle(&count);
|
||||
group.add(&sleep_a_little_frame) catch @panic("memory");
|
||||
var increase_by_ten_frame = async increaseByTen(&count);
|
||||
group.add(&increase_by_ten_frame) catch @panic("memory");
|
||||
group.wait();
|
||||
testing.expect(count == 11);
|
||||
|
||||
var another = Group(anyerror!void).init(loop);
|
||||
another.add(async somethingElse() catch @panic("memory")) catch @panic("memory");
|
||||
another.call(doSomethingThatFails) catch @panic("memory");
|
||||
testing.expectError(error.ItBroke, await (async another.wait() catch @panic("memory")));
|
||||
var something_else_frame = async somethingElse();
|
||||
another.add(&something_else_frame) catch @panic("memory");
|
||||
var something_that_fails_frame = async doSomethingThatFails();
|
||||
another.add(&something_that_fails_frame) catch @panic("memory");
|
||||
testing.expectError(error.ItBroke, another.wait());
|
||||
}
|
||||
|
||||
async fn sleepALittle(count: *usize) void {
|
||||
std.time.sleep(1 * std.time.millisecond);
|
||||
_ = @atomicRmw(usize, count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(usize, count, .Add, 1, .SeqCst);
|
||||
}
|
||||
|
||||
async fn increaseByTen(count: *usize) void {
|
||||
var i: usize = 0;
|
||||
while (i < 10) : (i += 1) {
|
||||
_ = @atomicRmw(usize, count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(usize, count, .Add, 1, .SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
const std = @import("../std.zig");
|
||||
const builtin = @import("builtin");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
const mem = std.mem;
|
||||
|
||||
@ -12,13 +11,13 @@ pub fn InStream(comptime ReadError: type) type {
|
||||
/// Return the number of bytes read. It may be less than buffer.len.
|
||||
/// If the number of bytes read is 0, it means end of stream.
|
||||
/// End of stream is not an error condition.
|
||||
readFn: async<*Allocator> fn (self: *Self, buffer: []u8) Error!usize,
|
||||
readFn: async fn (self: *Self, buffer: []u8) Error!usize,
|
||||
|
||||
/// Return the number of bytes read. It may be less than buffer.len.
|
||||
/// If the number of bytes read is 0, it means end of stream.
|
||||
/// End of stream is not an error condition.
|
||||
pub async fn read(self: *Self, buffer: []u8) !usize {
|
||||
return await (async self.readFn(self, buffer) catch unreachable);
|
||||
return self.readFn(self, buffer);
|
||||
}
|
||||
|
||||
/// Return the number of bytes read. If it is less than buffer.len
|
||||
@ -26,7 +25,7 @@ pub fn InStream(comptime ReadError: type) type {
|
||||
pub async fn readFull(self: *Self, buffer: []u8) !usize {
|
||||
var index: usize = 0;
|
||||
while (index != buf.len) {
|
||||
const amt_read = try await (async self.read(buf[index..]) catch unreachable);
|
||||
const amt_read = try self.read(buf[index..]);
|
||||
if (amt_read == 0) return index;
|
||||
index += amt_read;
|
||||
}
|
||||
@ -35,25 +34,25 @@ pub fn InStream(comptime ReadError: type) type {
|
||||
|
||||
/// Same as `readFull` but end of stream returns `error.EndOfStream`.
|
||||
pub async fn readNoEof(self: *Self, buf: []u8) !void {
|
||||
const amt_read = try await (async self.readFull(buf[index..]) catch unreachable);
|
||||
const amt_read = try self.readFull(buf[index..]);
|
||||
if (amt_read < buf.len) return error.EndOfStream;
|
||||
}
|
||||
|
||||
pub async fn readIntLittle(self: *Self, comptime T: type) !T {
|
||||
var bytes: [@sizeOf(T)]u8 = undefined;
|
||||
try await (async self.readNoEof(bytes[0..]) catch unreachable);
|
||||
try self.readNoEof(bytes[0..]);
|
||||
return mem.readIntLittle(T, &bytes);
|
||||
}
|
||||
|
||||
pub async fn readIntBe(self: *Self, comptime T: type) !T {
|
||||
var bytes: [@sizeOf(T)]u8 = undefined;
|
||||
try await (async self.readNoEof(bytes[0..]) catch unreachable);
|
||||
try self.readNoEof(bytes[0..]);
|
||||
return mem.readIntBig(T, &bytes);
|
||||
}
|
||||
|
||||
pub async fn readInt(self: *Self, comptime T: type, endian: builtin.Endian) !T {
|
||||
var bytes: [@sizeOf(T)]u8 = undefined;
|
||||
try await (async self.readNoEof(bytes[0..]) catch unreachable);
|
||||
try self.readNoEof(bytes[0..]);
|
||||
return mem.readInt(T, &bytes, endian);
|
||||
}
|
||||
|
||||
@ -61,7 +60,7 @@ pub fn InStream(comptime ReadError: type) type {
|
||||
// Only extern and packed structs have defined in-memory layout.
|
||||
comptime assert(@typeInfo(T).Struct.layout != builtin.TypeInfo.ContainerLayout.Auto);
|
||||
var res: [1]T = undefined;
|
||||
try await (async self.readNoEof(@sliceToBytes(res[0..])) catch unreachable);
|
||||
try self.readNoEof(@sliceToBytes(res[0..]));
|
||||
return res[0];
|
||||
}
|
||||
};
|
||||
@ -72,6 +71,6 @@ pub fn OutStream(comptime WriteError: type) type {
|
||||
const Self = @This();
|
||||
pub const Error = WriteError;
|
||||
|
||||
writeFn: async<*Allocator> fn (self: *Self, buffer: []u8) Error!void,
|
||||
writeFn: async fn (self: *Self, buffer: []u8) Error!void,
|
||||
};
|
||||
}
|
||||
|
||||
@ -3,12 +3,10 @@ const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const testing = std.testing;
|
||||
const mem = std.mem;
|
||||
const AtomicRmwOp = builtin.AtomicRmwOp;
|
||||
const AtomicOrder = builtin.AtomicOrder;
|
||||
const Loop = std.event.Loop;
|
||||
|
||||
/// Thread-safe async/await lock.
|
||||
/// coroutines which are waiting for the lock are suspended, and
|
||||
/// Functions which are waiting for the lock are suspended, and
|
||||
/// are resumed when the lock is released, in order.
|
||||
/// Allows only one actor to hold the lock.
|
||||
pub const Lock = struct {
|
||||
@ -17,7 +15,7 @@ pub const Lock = struct {
|
||||
queue: Queue,
|
||||
queue_empty_bit: u8, // TODO make this a bool
|
||||
|
||||
const Queue = std.atomic.Queue(promise);
|
||||
const Queue = std.atomic.Queue(anyframe);
|
||||
|
||||
pub const Held = struct {
|
||||
lock: *Lock,
|
||||
@ -30,19 +28,19 @@ pub const Lock = struct {
|
||||
}
|
||||
|
||||
// We need to release the lock.
|
||||
_ = @atomicRmw(u8, &self.lock.queue_empty_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(u8, &self.lock.shared_bit, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(u8, &self.lock.queue_empty_bit, .Xchg, 1, .SeqCst);
|
||||
_ = @atomicRmw(u8, &self.lock.shared_bit, .Xchg, 0, .SeqCst);
|
||||
|
||||
// There might be a queue item. If we know the queue is empty, we can be done,
|
||||
// because the other actor will try to obtain the lock.
|
||||
// But if there's a queue item, we are the actor which must loop and attempt
|
||||
// to grab the lock again.
|
||||
if (@atomicLoad(u8, &self.lock.queue_empty_bit, AtomicOrder.SeqCst) == 1) {
|
||||
if (@atomicLoad(u8, &self.lock.queue_empty_bit, .SeqCst) == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const old_bit = @atomicRmw(u8, &self.lock.shared_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
|
||||
const old_bit = @atomicRmw(u8, &self.lock.shared_bit, .Xchg, 1, .SeqCst);
|
||||
if (old_bit != 0) {
|
||||
// We did not obtain the lock. Great, the queue is someone else's problem.
|
||||
return;
|
||||
@ -55,11 +53,11 @@ pub const Lock = struct {
|
||||
}
|
||||
|
||||
// Release the lock again.
|
||||
_ = @atomicRmw(u8, &self.lock.queue_empty_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(u8, &self.lock.shared_bit, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(u8, &self.lock.queue_empty_bit, .Xchg, 1, .SeqCst);
|
||||
_ = @atomicRmw(u8, &self.lock.shared_bit, .Xchg, 0, .SeqCst);
|
||||
|
||||
// Find out if we can be done.
|
||||
if (@atomicLoad(u8, &self.lock.queue_empty_bit, AtomicOrder.SeqCst) == 1) {
|
||||
if (@atomicLoad(u8, &self.lock.queue_empty_bit, .SeqCst) == 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -88,28 +86,23 @@ pub const Lock = struct {
|
||||
/// All calls to acquire() and release() must complete before calling deinit().
|
||||
pub fn deinit(self: *Lock) void {
|
||||
assert(self.shared_bit == 0);
|
||||
while (self.queue.get()) |node| cancel node.data;
|
||||
while (self.queue.get()) |node| resume node.data;
|
||||
}
|
||||
|
||||
pub async fn acquire(self: *Lock) Held {
|
||||
// TODO explicitly put this memory in the coroutine frame #1194
|
||||
suspend {
|
||||
resume @handle();
|
||||
}
|
||||
var my_tick_node = Loop.NextTickNode.init(@handle());
|
||||
var my_tick_node = Loop.NextTickNode.init(@frame());
|
||||
|
||||
errdefer _ = self.queue.remove(&my_tick_node); // TODO test canceling an acquire
|
||||
suspend {
|
||||
self.queue.put(&my_tick_node);
|
||||
|
||||
// At this point, we are in the queue, so we might have already been resumed and this coroutine
|
||||
// frame might be destroyed. For the rest of the suspend block we cannot access the coroutine frame.
|
||||
// At this point, we are in the queue, so we might have already been resumed.
|
||||
|
||||
// We set this bit so that later we can rely on the fact, that if queue_empty_bit is 1, some actor
|
||||
// will attempt to grab the lock.
|
||||
_ = @atomicRmw(u8, &self.queue_empty_bit, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(u8, &self.queue_empty_bit, .Xchg, 0, .SeqCst);
|
||||
|
||||
const old_bit = @atomicRmw(u8, &self.shared_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
|
||||
const old_bit = @atomicRmw(u8, &self.shared_bit, .Xchg, 1, .SeqCst);
|
||||
if (old_bit == 0) {
|
||||
if (self.queue.get()) |node| {
|
||||
// Whether this node is us or someone else, we tail resume it.
|
||||
@ -123,8 +116,7 @@ pub const Lock = struct {
|
||||
};
|
||||
|
||||
test "std.event.Lock" {
|
||||
// TODO https://github.com/ziglang/zig/issues/2377
|
||||
if (true) return error.SkipZigTest;
|
||||
// TODO https://github.com/ziglang/zig/issues/1908
|
||||
if (builtin.single_threaded) return error.SkipZigTest;
|
||||
|
||||
const allocator = std.heap.direct_allocator;
|
||||
@ -136,39 +128,34 @@ test "std.event.Lock" {
|
||||
var lock = Lock.init(&loop);
|
||||
defer lock.deinit();
|
||||
|
||||
const handle = try async<allocator> testLock(&loop, &lock);
|
||||
defer cancel handle;
|
||||
_ = async testLock(&loop, &lock);
|
||||
loop.run();
|
||||
|
||||
testing.expectEqualSlices(i32, [1]i32{3 * @intCast(i32, shared_test_data.len)} ** shared_test_data.len, shared_test_data);
|
||||
}
|
||||
|
||||
async fn testLock(loop: *Loop, lock: *Lock) void {
|
||||
// TODO explicitly put next tick node memory in the coroutine frame #1194
|
||||
suspend {
|
||||
resume @handle();
|
||||
}
|
||||
const handle1 = async lockRunner(lock) catch @panic("out of memory");
|
||||
const handle1 = async lockRunner(lock);
|
||||
var tick_node1 = Loop.NextTickNode{
|
||||
.prev = undefined,
|
||||
.next = undefined,
|
||||
.data = handle1,
|
||||
.data = &handle1,
|
||||
};
|
||||
loop.onNextTick(&tick_node1);
|
||||
|
||||
const handle2 = async lockRunner(lock) catch @panic("out of memory");
|
||||
const handle2 = async lockRunner(lock);
|
||||
var tick_node2 = Loop.NextTickNode{
|
||||
.prev = undefined,
|
||||
.next = undefined,
|
||||
.data = handle2,
|
||||
.data = &handle2,
|
||||
};
|
||||
loop.onNextTick(&tick_node2);
|
||||
|
||||
const handle3 = async lockRunner(lock) catch @panic("out of memory");
|
||||
const handle3 = async lockRunner(lock);
|
||||
var tick_node3 = Loop.NextTickNode{
|
||||
.prev = undefined,
|
||||
.next = undefined,
|
||||
.data = handle3,
|
||||
.data = &handle3,
|
||||
};
|
||||
loop.onNextTick(&tick_node3);
|
||||
|
||||
@ -185,7 +172,7 @@ async fn lockRunner(lock: *Lock) void {
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < shared_test_data.len) : (i += 1) {
|
||||
const lock_promise = async lock.acquire() catch @panic("out of memory");
|
||||
const lock_promise = async lock.acquire();
|
||||
const handle = await lock_promise;
|
||||
defer handle.release();
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ const Lock = std.event.Lock;
|
||||
const Loop = std.event.Loop;
|
||||
|
||||
/// Thread-safe async/await lock that protects one piece of data.
|
||||
/// coroutines which are waiting for the lock are suspended, and
|
||||
/// Functions which are waiting for the lock are suspended, and
|
||||
/// are resumed when the lock is released, in order.
|
||||
pub fn Locked(comptime T: type) type {
|
||||
return struct {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
const std = @import("../std.zig");
|
||||
const builtin = @import("builtin");
|
||||
const root = @import("root");
|
||||
const assert = std.debug.assert;
|
||||
const testing = std.testing;
|
||||
const mem = std.mem;
|
||||
@ -13,7 +14,7 @@ const Thread = std.Thread;
|
||||
|
||||
pub const Loop = struct {
|
||||
allocator: *mem.Allocator,
|
||||
next_tick_queue: std.atomic.Queue(promise),
|
||||
next_tick_queue: std.atomic.Queue(anyframe),
|
||||
os_data: OsData,
|
||||
final_resume_node: ResumeNode,
|
||||
pending_event_count: usize,
|
||||
@ -24,11 +25,11 @@ pub const Loop = struct {
|
||||
available_eventfd_resume_nodes: std.atomic.Stack(ResumeNode.EventFd),
|
||||
eventfd_resume_nodes: []std.atomic.Stack(ResumeNode.EventFd).Node,
|
||||
|
||||
pub const NextTickNode = std.atomic.Queue(promise).Node;
|
||||
pub const NextTickNode = std.atomic.Queue(anyframe).Node;
|
||||
|
||||
pub const ResumeNode = struct {
|
||||
id: Id,
|
||||
handle: promise,
|
||||
handle: anyframe,
|
||||
overlapped: Overlapped,
|
||||
|
||||
pub const overlapped_init = switch (builtin.os) {
|
||||
@ -85,18 +86,43 @@ pub const Loop = struct {
|
||||
};
|
||||
};
|
||||
|
||||
pub const IoMode = enum {
|
||||
blocking,
|
||||
evented,
|
||||
};
|
||||
pub const io_mode: IoMode = if (@hasDecl(root, "io_mode")) root.io_mode else IoMode.blocking;
|
||||
var global_instance_state: Loop = undefined;
|
||||
const default_instance: ?*Loop = switch (io_mode) {
|
||||
.blocking => null,
|
||||
.evented => &global_instance_state,
|
||||
};
|
||||
pub const instance: ?*Loop = if (@hasDecl(root, "event_loop")) root.event_loop else default_instance;
|
||||
|
||||
/// TODO copy elision / named return values so that the threads referencing *Loop
|
||||
/// have the correct pointer value.
|
||||
/// https://github.com/ziglang/zig/issues/2761 and https://github.com/ziglang/zig/issues/2765
|
||||
pub fn init(self: *Loop, allocator: *mem.Allocator) !void {
|
||||
if (builtin.single_threaded) {
|
||||
return self.initSingleThreaded(allocator);
|
||||
} else {
|
||||
return self.initMultiThreaded(allocator);
|
||||
}
|
||||
}
|
||||
|
||||
/// After initialization, call run().
|
||||
/// TODO copy elision / named return values so that the threads referencing *Loop
|
||||
/// have the correct pointer value.
|
||||
/// https://github.com/ziglang/zig/issues/2761 and https://github.com/ziglang/zig/issues/2765
|
||||
pub fn initSingleThreaded(self: *Loop, allocator: *mem.Allocator) !void {
|
||||
return self.initInternal(allocator, 1);
|
||||
}
|
||||
|
||||
/// The allocator must be thread-safe because we use it for multiplexing
|
||||
/// coroutines onto kernel threads.
|
||||
/// async functions onto kernel threads.
|
||||
/// After initialization, call run().
|
||||
/// TODO copy elision / named return values so that the threads referencing *Loop
|
||||
/// have the correct pointer value.
|
||||
/// https://github.com/ziglang/zig/issues/2761 and https://github.com/ziglang/zig/issues/2765
|
||||
pub fn initMultiThreaded(self: *Loop, allocator: *mem.Allocator) !void {
|
||||
if (builtin.single_threaded) @compileError("initMultiThreaded unavailable when building in single-threaded mode");
|
||||
const core_count = try Thread.cpuCount();
|
||||
@ -110,7 +136,7 @@ pub const Loop = struct {
|
||||
.pending_event_count = 1,
|
||||
.allocator = allocator,
|
||||
.os_data = undefined,
|
||||
.next_tick_queue = std.atomic.Queue(promise).init(),
|
||||
.next_tick_queue = std.atomic.Queue(anyframe).init(),
|
||||
.extra_threads = undefined,
|
||||
.available_eventfd_resume_nodes = std.atomic.Stack(ResumeNode.EventFd).init(),
|
||||
.eventfd_resume_nodes = undefined,
|
||||
@ -397,7 +423,7 @@ pub const Loop = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// resume_node must live longer than the promise that it holds a reference to.
|
||||
/// resume_node must live longer than the anyframe that it holds a reference to.
|
||||
/// flags must contain EPOLLET
|
||||
pub fn linuxAddFd(self: *Loop, fd: i32, resume_node: *ResumeNode, flags: u32) !void {
|
||||
assert(flags & os.EPOLLET == os.EPOLLET);
|
||||
@ -428,11 +454,10 @@ pub const Loop = struct {
|
||||
pub async fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) !void {
|
||||
defer self.linuxRemoveFd(fd);
|
||||
suspend {
|
||||
// TODO explicitly put this memory in the coroutine frame #1194
|
||||
var resume_node = ResumeNode.Basic{
|
||||
.base = ResumeNode{
|
||||
.id = ResumeNode.Id.Basic,
|
||||
.handle = @handle(),
|
||||
.handle = @frame(),
|
||||
.overlapped = ResumeNode.overlapped_init,
|
||||
},
|
||||
};
|
||||
@ -441,14 +466,10 @@ pub const Loop = struct {
|
||||
}
|
||||
|
||||
pub async fn bsdWaitKev(self: *Loop, ident: usize, filter: i16, fflags: u32) !os.Kevent {
|
||||
// TODO #1194
|
||||
suspend {
|
||||
resume @handle();
|
||||
}
|
||||
var resume_node = ResumeNode.Basic{
|
||||
.base = ResumeNode{
|
||||
.id = ResumeNode.Id.Basic,
|
||||
.handle = @handle(),
|
||||
.handle = @frame(),
|
||||
.overlapped = ResumeNode.overlapped_init,
|
||||
},
|
||||
.kev = undefined,
|
||||
@ -460,7 +481,7 @@ pub const Loop = struct {
|
||||
return resume_node.kev;
|
||||
}
|
||||
|
||||
/// resume_node must live longer than the promise that it holds a reference to.
|
||||
/// resume_node must live longer than the anyframe that it holds a reference to.
|
||||
pub fn bsdAddKev(self: *Loop, resume_node: *ResumeNode.Basic, ident: usize, filter: i16, fflags: u32) !void {
|
||||
self.beginOneEvent();
|
||||
errdefer self.finishOneEvent();
|
||||
@ -561,10 +582,10 @@ pub const Loop = struct {
|
||||
self.workerRun();
|
||||
|
||||
switch (builtin.os) {
|
||||
builtin.Os.linux,
|
||||
builtin.Os.macosx,
|
||||
builtin.Os.freebsd,
|
||||
builtin.Os.netbsd,
|
||||
.linux,
|
||||
.macosx,
|
||||
.freebsd,
|
||||
.netbsd,
|
||||
=> self.os_data.fs_thread.wait(),
|
||||
else => {},
|
||||
}
|
||||
@ -574,45 +595,39 @@ pub const Loop = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// This is equivalent to an async call, except instead of beginning execution of the async function,
|
||||
/// it immediately returns to the caller, and the async function is queued in the event loop. It still
|
||||
/// returns a promise to be awaited.
|
||||
pub fn call(self: *Loop, comptime func: var, args: ...) !(promise->@typeOf(func).ReturnType) {
|
||||
const S = struct {
|
||||
async fn asyncFunc(loop: *Loop, handle: *promise->@typeOf(func).ReturnType, args2: ...) @typeOf(func).ReturnType {
|
||||
suspend {
|
||||
handle.* = @handle();
|
||||
var my_tick_node = Loop.NextTickNode{
|
||||
.prev = undefined,
|
||||
.next = undefined,
|
||||
.data = @handle(),
|
||||
};
|
||||
loop.onNextTick(&my_tick_node);
|
||||
}
|
||||
// TODO guaranteed allocation elision for await in same func as async
|
||||
return await (async func(args2) catch unreachable);
|
||||
}
|
||||
};
|
||||
var handle: promise->@typeOf(func).ReturnType = undefined;
|
||||
return async<self.allocator> S.asyncFunc(self, &handle, args);
|
||||
/// This is equivalent to function call, except it calls `startCpuBoundOperation` first.
|
||||
pub fn call(comptime func: var, args: ...) @typeOf(func).ReturnType {
|
||||
startCpuBoundOperation();
|
||||
return func(args);
|
||||
}
|
||||
|
||||
/// Awaiting a yield lets the event loop run, starting any unstarted async operations.
|
||||
/// Yielding lets the event loop run, starting any unstarted async operations.
|
||||
/// Note that async operations automatically start when a function yields for any other reason,
|
||||
/// for example, when async I/O is performed. This function is intended to be used only when
|
||||
/// CPU bound tasks would be waiting in the event loop but never get started because no async I/O
|
||||
/// is performed.
|
||||
pub async fn yield(self: *Loop) void {
|
||||
pub fn yield(self: *Loop) void {
|
||||
suspend {
|
||||
var my_tick_node = Loop.NextTickNode{
|
||||
var my_tick_node = NextTickNode{
|
||||
.prev = undefined,
|
||||
.next = undefined,
|
||||
.data = @handle(),
|
||||
.data = @frame(),
|
||||
};
|
||||
self.onNextTick(&my_tick_node);
|
||||
}
|
||||
}
|
||||
|
||||
/// If the build is multi-threaded and there is an event loop, then it calls `yield`. Otherwise,
|
||||
/// does nothing.
|
||||
pub fn startCpuBoundOperation() void {
|
||||
if (builtin.single_threaded) {
|
||||
return;
|
||||
} else if (instance) |event_loop| {
|
||||
event_loop.yield();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// call finishOneEvent when done
|
||||
pub fn beginOneEvent(self: *Loop) void {
|
||||
_ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
|
||||
@ -672,9 +687,9 @@ pub const Loop = struct {
|
||||
const handle = resume_node.handle;
|
||||
const resume_node_id = resume_node.id;
|
||||
switch (resume_node_id) {
|
||||
ResumeNode.Id.Basic => {},
|
||||
ResumeNode.Id.Stop => return,
|
||||
ResumeNode.Id.EventFd => {
|
||||
.Basic => {},
|
||||
.Stop => return,
|
||||
.EventFd => {
|
||||
const event_fd_node = @fieldParentPtr(ResumeNode.EventFd, "base", resume_node);
|
||||
event_fd_node.epoll_op = os.EPOLL_CTL_MOD;
|
||||
const stack_node = @fieldParentPtr(std.atomic.Stack(ResumeNode.EventFd).Node, "data", event_fd_node);
|
||||
@ -696,12 +711,12 @@ pub const Loop = struct {
|
||||
const handle = resume_node.handle;
|
||||
const resume_node_id = resume_node.id;
|
||||
switch (resume_node_id) {
|
||||
ResumeNode.Id.Basic => {
|
||||
.Basic => {
|
||||
const basic_node = @fieldParentPtr(ResumeNode.Basic, "base", resume_node);
|
||||
basic_node.kev = ev;
|
||||
},
|
||||
ResumeNode.Id.Stop => return,
|
||||
ResumeNode.Id.EventFd => {
|
||||
.Stop => return,
|
||||
.EventFd => {
|
||||
const event_fd_node = @fieldParentPtr(ResumeNode.EventFd, "base", resume_node);
|
||||
const stack_node = @fieldParentPtr(std.atomic.Stack(ResumeNode.EventFd).Node, "data", event_fd_node);
|
||||
self.available_eventfd_resume_nodes.push(stack_node);
|
||||
@ -730,9 +745,9 @@ pub const Loop = struct {
|
||||
const handle = resume_node.handle;
|
||||
const resume_node_id = resume_node.id;
|
||||
switch (resume_node_id) {
|
||||
ResumeNode.Id.Basic => {},
|
||||
ResumeNode.Id.Stop => return,
|
||||
ResumeNode.Id.EventFd => {
|
||||
.Basic => {},
|
||||
.Stop => return,
|
||||
.EventFd => {
|
||||
const event_fd_node = @fieldParentPtr(ResumeNode.EventFd, "base", resume_node);
|
||||
const stack_node = @fieldParentPtr(std.atomic.Stack(ResumeNode.EventFd).Node, "data", event_fd_node);
|
||||
self.available_eventfd_resume_nodes.push(stack_node);
|
||||
@ -750,12 +765,12 @@ pub const Loop = struct {
|
||||
self.beginOneEvent(); // finished in posixFsRun after processing the msg
|
||||
self.os_data.fs_queue.put(request_node);
|
||||
switch (builtin.os) {
|
||||
builtin.Os.macosx, builtin.Os.freebsd, builtin.Os.netbsd => {
|
||||
.macosx, .freebsd, .netbsd => {
|
||||
const fs_kevs = (*const [1]os.Kevent)(&self.os_data.fs_kevent_wake);
|
||||
const empty_kevs = ([*]os.Kevent)(undefined)[0..0];
|
||||
_ = os.kevent(self.os_data.fs_kqfd, fs_kevs, empty_kevs, null) catch unreachable;
|
||||
},
|
||||
builtin.Os.linux => {
|
||||
.linux => {
|
||||
_ = @atomicRmw(i32, &self.os_data.fs_queue_item, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
|
||||
const rc = os.linux.futex_wake(&self.os_data.fs_queue_item, os.linux.FUTEX_WAKE, 1);
|
||||
switch (os.linux.getErrno(rc)) {
|
||||
@ -781,18 +796,18 @@ pub const Loop = struct {
|
||||
}
|
||||
while (self.os_data.fs_queue.get()) |node| {
|
||||
switch (node.data.msg) {
|
||||
@TagType(fs.Request.Msg).End => return,
|
||||
@TagType(fs.Request.Msg).PWriteV => |*msg| {
|
||||
.End => return,
|
||||
.PWriteV => |*msg| {
|
||||
msg.result = os.pwritev(msg.fd, msg.iov, msg.offset);
|
||||
},
|
||||
@TagType(fs.Request.Msg).PReadV => |*msg| {
|
||||
.PReadV => |*msg| {
|
||||
msg.result = os.preadv(msg.fd, msg.iov, msg.offset);
|
||||
},
|
||||
@TagType(fs.Request.Msg).Open => |*msg| {
|
||||
.Open => |*msg| {
|
||||
msg.result = os.openC(msg.path.ptr, msg.flags, msg.mode);
|
||||
},
|
||||
@TagType(fs.Request.Msg).Close => |*msg| os.close(msg.fd),
|
||||
@TagType(fs.Request.Msg).WriteFile => |*msg| blk: {
|
||||
.Close => |*msg| os.close(msg.fd),
|
||||
.WriteFile => |*msg| blk: {
|
||||
const flags = os.O_LARGEFILE | os.O_WRONLY | os.O_CREAT |
|
||||
os.O_CLOEXEC | os.O_TRUNC;
|
||||
const fd = os.openC(msg.path.ptr, flags, msg.mode) catch |err| {
|
||||
@ -804,11 +819,11 @@ pub const Loop = struct {
|
||||
},
|
||||
}
|
||||
switch (node.data.finish) {
|
||||
@TagType(fs.Request.Finish).TickNode => |*tick_node| self.onNextTick(tick_node),
|
||||
@TagType(fs.Request.Finish).DeallocCloseOperation => |close_op| {
|
||||
.TickNode => |*tick_node| self.onNextTick(tick_node),
|
||||
.DeallocCloseOperation => |close_op| {
|
||||
self.allocator.destroy(close_op);
|
||||
},
|
||||
@TagType(fs.Request.Finish).NoAction => {},
|
||||
.NoAction => {},
|
||||
}
|
||||
self.finishOneEvent();
|
||||
}
|
||||
@ -864,7 +879,7 @@ pub const Loop = struct {
|
||||
|
||||
test "std.event.Loop - basic" {
|
||||
// https://github.com/ziglang/zig/issues/1908
|
||||
if (builtin.single_threaded or builtin.os != builtin.Os.linux) return error.SkipZigTest;
|
||||
if (builtin.single_threaded) return error.SkipZigTest;
|
||||
|
||||
const allocator = std.heap.direct_allocator;
|
||||
|
||||
@ -877,7 +892,7 @@ test "std.event.Loop - basic" {
|
||||
|
||||
test "std.event.Loop - call" {
|
||||
// https://github.com/ziglang/zig/issues/1908
|
||||
if (builtin.single_threaded or builtin.os != builtin.Os.linux) return error.SkipZigTest;
|
||||
if (builtin.single_threaded) return error.SkipZigTest;
|
||||
|
||||
const allocator = std.heap.direct_allocator;
|
||||
|
||||
@ -886,9 +901,8 @@ test "std.event.Loop - call" {
|
||||
defer loop.deinit();
|
||||
|
||||
var did_it = false;
|
||||
const handle = try loop.call(testEventLoop);
|
||||
const handle2 = try loop.call(testEventLoop2, handle, &did_it);
|
||||
defer cancel handle2;
|
||||
const handle = async Loop.call(testEventLoop);
|
||||
const handle2 = async Loop.call(testEventLoop2, &handle, &did_it);
|
||||
|
||||
loop.run();
|
||||
|
||||
@ -899,7 +913,7 @@ async fn testEventLoop() i32 {
|
||||
return 1234;
|
||||
}
|
||||
|
||||
async fn testEventLoop2(h: promise->i32, did_it: *bool) void {
|
||||
async fn testEventLoop2(h: anyframe->i32, did_it: *bool) void {
|
||||
const value = await h;
|
||||
testing.expect(value == 1234);
|
||||
did_it.* = true;
|
||||
|
||||
@ -9,24 +9,24 @@ const File = std.fs.File;
|
||||
const fd_t = os.fd_t;
|
||||
|
||||
pub const Server = struct {
|
||||
handleRequestFn: async<*mem.Allocator> fn (*Server, *const std.net.Address, File) void,
|
||||
handleRequestFn: async fn (*Server, *const std.net.Address, File) void,
|
||||
|
||||
loop: *Loop,
|
||||
sockfd: ?i32,
|
||||
accept_coro: ?promise,
|
||||
accept_frame: ?anyframe,
|
||||
listen_address: std.net.Address,
|
||||
|
||||
waiting_for_emfile_node: PromiseNode,
|
||||
listen_resume_node: event.Loop.ResumeNode,
|
||||
|
||||
const PromiseNode = std.TailQueue(promise).Node;
|
||||
const PromiseNode = std.TailQueue(anyframe).Node;
|
||||
|
||||
pub fn init(loop: *Loop) Server {
|
||||
// TODO can't initialize handler coroutine here because we need well defined copy elision
|
||||
// TODO can't initialize handler here because we need well defined copy elision
|
||||
return Server{
|
||||
.loop = loop,
|
||||
.sockfd = null,
|
||||
.accept_coro = null,
|
||||
.accept_frame = null,
|
||||
.handleRequestFn = undefined,
|
||||
.waiting_for_emfile_node = undefined,
|
||||
.listen_address = undefined,
|
||||
@ -41,7 +41,7 @@ pub const Server = struct {
|
||||
pub fn listen(
|
||||
self: *Server,
|
||||
address: *const std.net.Address,
|
||||
handleRequestFn: async<*mem.Allocator> fn (*Server, *const std.net.Address, File) void,
|
||||
handleRequestFn: async fn (*Server, *const std.net.Address, File) void,
|
||||
) !void {
|
||||
self.handleRequestFn = handleRequestFn;
|
||||
|
||||
@ -53,10 +53,10 @@ pub const Server = struct {
|
||||
try os.listen(sockfd, os.SOMAXCONN);
|
||||
self.listen_address = std.net.Address.initPosix(try os.getsockname(sockfd));
|
||||
|
||||
self.accept_coro = try async<self.loop.allocator> Server.handler(self);
|
||||
errdefer cancel self.accept_coro.?;
|
||||
self.accept_frame = async Server.handler(self);
|
||||
errdefer await self.accept_frame.?;
|
||||
|
||||
self.listen_resume_node.handle = self.accept_coro.?;
|
||||
self.listen_resume_node.handle = self.accept_frame.?;
|
||||
try self.loop.linuxAddFd(sockfd, &self.listen_resume_node, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET);
|
||||
errdefer self.loop.removeFd(sockfd);
|
||||
}
|
||||
@ -71,7 +71,7 @@ pub const Server = struct {
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Server) void {
|
||||
if (self.accept_coro) |accept_coro| cancel accept_coro;
|
||||
if (self.accept_frame) |accept_frame| await accept_frame;
|
||||
if (self.sockfd) |sockfd| os.close(sockfd);
|
||||
}
|
||||
|
||||
@ -86,12 +86,7 @@ pub const Server = struct {
|
||||
continue;
|
||||
}
|
||||
var socket = File.openHandle(accepted_fd);
|
||||
_ = async<self.loop.allocator> self.handleRequestFn(self, &accepted_addr, socket) catch |err| switch (err) {
|
||||
error.OutOfMemory => {
|
||||
socket.close();
|
||||
continue;
|
||||
},
|
||||
};
|
||||
self.handleRequestFn(self, &accepted_addr, socket);
|
||||
} else |err| switch (err) {
|
||||
error.ProcessFdQuotaExceeded => @panic("TODO handle this error"),
|
||||
error.ConnectionAborted => continue,
|
||||
@ -124,7 +119,7 @@ pub async fn connectUnixSocket(loop: *Loop, path: []const u8) !i32 {
|
||||
mem.copy(u8, sock_addr.path[0..], path);
|
||||
const size = @intCast(u32, @sizeOf(os.sa_family_t) + path.len);
|
||||
try os.connect_async(sockfd, &sock_addr, size);
|
||||
try await try async loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET);
|
||||
try loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET);
|
||||
try os.getsockoptError(sockfd);
|
||||
|
||||
return sockfd;
|
||||
@ -149,7 +144,7 @@ pub async fn read(loop: *std.event.Loop, fd: fd_t, buffer: []u8) ReadError!usize
|
||||
.iov_len = buffer.len,
|
||||
};
|
||||
const iovs: *const [1]os.iovec = &iov;
|
||||
return await (async readvPosix(loop, fd, iovs, 1) catch unreachable);
|
||||
return readvPosix(loop, fd, iovs, 1);
|
||||
}
|
||||
|
||||
pub const WriteError = error{};
|
||||
@ -160,7 +155,7 @@ pub async fn write(loop: *std.event.Loop, fd: fd_t, buffer: []const u8) WriteErr
|
||||
.iov_len = buffer.len,
|
||||
};
|
||||
const iovs: *const [1]os.iovec_const = &iov;
|
||||
return await (async writevPosix(loop, fd, iovs, 1) catch unreachable);
|
||||
return writevPosix(loop, fd, iovs, 1);
|
||||
}
|
||||
|
||||
pub async fn writevPosix(loop: *Loop, fd: i32, iov: [*]const os.iovec_const, count: usize) !void {
|
||||
@ -174,7 +169,7 @@ pub async fn writevPosix(loop: *Loop, fd: i32, iov: [*]const os.iovec_const, cou
|
||||
os.EINVAL => unreachable,
|
||||
os.EFAULT => unreachable,
|
||||
os.EAGAIN => {
|
||||
try await (async loop.linuxWaitFd(fd, os.EPOLLET | os.EPOLLOUT) catch unreachable);
|
||||
try loop.linuxWaitFd(fd, os.EPOLLET | os.EPOLLOUT);
|
||||
continue;
|
||||
},
|
||||
os.EBADF => unreachable, // always a race condition
|
||||
@ -205,7 +200,7 @@ pub async fn readvPosix(loop: *std.event.Loop, fd: i32, iov: [*]os.iovec, count:
|
||||
os.EINVAL => unreachable,
|
||||
os.EFAULT => unreachable,
|
||||
os.EAGAIN => {
|
||||
try await (async loop.linuxWaitFd(fd, os.EPOLLET | os.EPOLLIN) catch unreachable);
|
||||
try loop.linuxWaitFd(fd, os.EPOLLET | os.EPOLLIN);
|
||||
continue;
|
||||
},
|
||||
os.EBADF => unreachable, // always a race condition
|
||||
@ -232,7 +227,7 @@ pub async fn writev(loop: *Loop, fd: fd_t, data: []const []const u8) !void {
|
||||
};
|
||||
}
|
||||
|
||||
return await (async writevPosix(loop, fd, iovecs.ptr, data.len) catch unreachable);
|
||||
return writevPosix(loop, fd, iovecs.ptr, data.len);
|
||||
}
|
||||
|
||||
pub async fn readv(loop: *Loop, fd: fd_t, data: []const []u8) !usize {
|
||||
@ -246,7 +241,7 @@ pub async fn readv(loop: *Loop, fd: fd_t, data: []const []u8) !usize {
|
||||
};
|
||||
}
|
||||
|
||||
return await (async readvPosix(loop, fd, iovecs.ptr, data.len) catch unreachable);
|
||||
return readvPosix(loop, fd, iovecs.ptr, data.len);
|
||||
}
|
||||
|
||||
pub async fn connect(loop: *Loop, _address: *const std.net.Address) !File {
|
||||
@ -256,7 +251,7 @@ pub async fn connect(loop: *Loop, _address: *const std.net.Address) !File {
|
||||
errdefer os.close(sockfd);
|
||||
|
||||
try os.connect_async(sockfd, &address.os_addr, @sizeOf(os.sockaddr_in));
|
||||
try await try async loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET);
|
||||
try loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET);
|
||||
try os.getsockoptError(sockfd);
|
||||
|
||||
return File.openHandle(sockfd);
|
||||
@ -275,18 +270,13 @@ test "listen on a port, send bytes, receive bytes" {
|
||||
tcp_server: Server,
|
||||
|
||||
const Self = @This();
|
||||
async<*mem.Allocator> fn handler(tcp_server: *Server, _addr: *const std.net.Address, _socket: File) void {
|
||||
async fn handler(tcp_server: *Server, _addr: *const std.net.Address, _socket: File) void {
|
||||
const self = @fieldParentPtr(Self, "tcp_server", tcp_server);
|
||||
var socket = _socket; // TODO https://github.com/ziglang/zig/issues/1592
|
||||
defer socket.close();
|
||||
// TODO guarantee elision of this allocation
|
||||
const next_handler = async errorableHandler(self, _addr, socket) catch unreachable;
|
||||
(await next_handler) catch |err| {
|
||||
const next_handler = errorableHandler(self, _addr, socket) catch |err| {
|
||||
std.debug.panic("unable to handle connection: {}\n", err);
|
||||
};
|
||||
suspend {
|
||||
cancel @handle();
|
||||
}
|
||||
}
|
||||
async fn errorableHandler(self: *Self, _addr: *const std.net.Address, _socket: File) !void {
|
||||
const addr = _addr.*; // TODO https://github.com/ziglang/zig/issues/1592
|
||||
@ -306,15 +296,14 @@ test "listen on a port, send bytes, receive bytes" {
|
||||
defer server.tcp_server.deinit();
|
||||
try server.tcp_server.listen(&addr, MyServer.handler);
|
||||
|
||||
const p = try async<std.debug.global_allocator> doAsyncTest(&loop, &server.tcp_server.listen_address, &server.tcp_server);
|
||||
defer cancel p;
|
||||
_ = async doAsyncTest(&loop, &server.tcp_server.listen_address, &server.tcp_server);
|
||||
loop.run();
|
||||
}
|
||||
|
||||
async fn doAsyncTest(loop: *Loop, address: *const std.net.Address, server: *Server) void {
|
||||
errdefer @panic("test failure");
|
||||
|
||||
var socket_file = try await try async connect(loop, address);
|
||||
var socket_file = try connect(loop, address);
|
||||
defer socket_file.close();
|
||||
|
||||
var buf: [512]u8 = undefined;
|
||||
@ -340,9 +329,9 @@ pub const OutStream = struct {
|
||||
};
|
||||
}
|
||||
|
||||
async<*mem.Allocator> fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
|
||||
async fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
|
||||
const self = @fieldParentPtr(OutStream, "stream", out_stream);
|
||||
return await (async write(self.loop, self.fd, bytes) catch unreachable);
|
||||
return write(self.loop, self.fd, bytes);
|
||||
}
|
||||
};
|
||||
|
||||
@ -362,8 +351,8 @@ pub const InStream = struct {
|
||||
};
|
||||
}
|
||||
|
||||
async<*mem.Allocator> fn readFn(in_stream: *Stream, bytes: []u8) Error!usize {
|
||||
async fn readFn(in_stream: *Stream, bytes: []u8) Error!usize {
|
||||
const self = @fieldParentPtr(InStream, "stream", in_stream);
|
||||
return await (async read(self.loop, self.fd, bytes) catch unreachable);
|
||||
return read(self.loop, self.fd, bytes);
|
||||
}
|
||||
};
|
||||
|
||||
@ -3,12 +3,10 @@ const builtin = @import("builtin");
|
||||
const assert = std.debug.assert;
|
||||
const testing = std.testing;
|
||||
const mem = std.mem;
|
||||
const AtomicRmwOp = builtin.AtomicRmwOp;
|
||||
const AtomicOrder = builtin.AtomicOrder;
|
||||
const Loop = std.event.Loop;
|
||||
|
||||
/// Thread-safe async/await lock.
|
||||
/// coroutines which are waiting for the lock are suspended, and
|
||||
/// Functions which are waiting for the lock are suspended, and
|
||||
/// are resumed when the lock is released, in order.
|
||||
/// Many readers can hold the lock at the same time; however locking for writing is exclusive.
|
||||
/// When a read lock is held, it will not be released until the reader queue is empty.
|
||||
@ -28,19 +26,19 @@ pub const RwLock = struct {
|
||||
const ReadLock = 2;
|
||||
};
|
||||
|
||||
const Queue = std.atomic.Queue(promise);
|
||||
const Queue = std.atomic.Queue(anyframe);
|
||||
|
||||
pub const HeldRead = struct {
|
||||
lock: *RwLock,
|
||||
|
||||
pub fn release(self: HeldRead) void {
|
||||
// If other readers still hold the lock, we're done.
|
||||
if (@atomicRmw(usize, &self.lock.reader_lock_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst) != 1) {
|
||||
if (@atomicRmw(usize, &self.lock.reader_lock_count, .Sub, 1, .SeqCst) != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
_ = @atomicRmw(u8, &self.lock.reader_queue_empty_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
|
||||
if (@cmpxchgStrong(u8, &self.lock.shared_state, State.ReadLock, State.Unlocked, AtomicOrder.SeqCst, AtomicOrder.SeqCst) != null) {
|
||||
_ = @atomicRmw(u8, &self.lock.reader_queue_empty_bit, .Xchg, 1, .SeqCst);
|
||||
if (@cmpxchgStrong(u8, &self.lock.shared_state, State.ReadLock, State.Unlocked, .SeqCst, .SeqCst) != null) {
|
||||
// Didn't unlock. Someone else's problem.
|
||||
return;
|
||||
}
|
||||
@ -61,17 +59,17 @@ pub const RwLock = struct {
|
||||
}
|
||||
|
||||
// We need to release the write lock. Check if any readers are waiting to grab the lock.
|
||||
if (@atomicLoad(u8, &self.lock.reader_queue_empty_bit, AtomicOrder.SeqCst) == 0) {
|
||||
if (@atomicLoad(u8, &self.lock.reader_queue_empty_bit, .SeqCst) == 0) {
|
||||
// Switch to a read lock.
|
||||
_ = @atomicRmw(u8, &self.lock.shared_state, AtomicRmwOp.Xchg, State.ReadLock, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(u8, &self.lock.shared_state, .Xchg, State.ReadLock, .SeqCst);
|
||||
while (self.lock.reader_queue.get()) |node| {
|
||||
self.lock.loop.onNextTick(node);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_ = @atomicRmw(u8, &self.lock.writer_queue_empty_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(u8, &self.lock.shared_state, AtomicRmwOp.Xchg, State.Unlocked, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(u8, &self.lock.writer_queue_empty_bit, .Xchg, 1, .SeqCst);
|
||||
_ = @atomicRmw(u8, &self.lock.shared_state, .Xchg, State.Unlocked, .SeqCst);
|
||||
|
||||
self.lock.commonPostUnlock();
|
||||
}
|
||||
@ -93,32 +91,30 @@ pub const RwLock = struct {
|
||||
/// All calls to acquire() and release() must complete before calling deinit().
|
||||
pub fn deinit(self: *RwLock) void {
|
||||
assert(self.shared_state == State.Unlocked);
|
||||
while (self.writer_queue.get()) |node| cancel node.data;
|
||||
while (self.reader_queue.get()) |node| cancel node.data;
|
||||
while (self.writer_queue.get()) |node| resume node.data;
|
||||
while (self.reader_queue.get()) |node| resume node.data;
|
||||
}
|
||||
|
||||
pub async fn acquireRead(self: *RwLock) HeldRead {
|
||||
_ = @atomicRmw(usize, &self.reader_lock_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(usize, &self.reader_lock_count, .Add, 1, .SeqCst);
|
||||
|
||||
suspend {
|
||||
// TODO explicitly put this memory in the coroutine frame #1194
|
||||
var my_tick_node = Loop.NextTickNode{
|
||||
.data = @handle(),
|
||||
.data = @frame(),
|
||||
.prev = undefined,
|
||||
.next = undefined,
|
||||
};
|
||||
|
||||
self.reader_queue.put(&my_tick_node);
|
||||
|
||||
// At this point, we are in the reader_queue, so we might have already been resumed and this coroutine
|
||||
// frame might be destroyed. For the rest of the suspend block we cannot access the coroutine frame.
|
||||
// At this point, we are in the reader_queue, so we might have already been resumed.
|
||||
|
||||
// We set this bit so that later we can rely on the fact, that if reader_queue_empty_bit is 1,
|
||||
// some actor will attempt to grab the lock.
|
||||
_ = @atomicRmw(u8, &self.reader_queue_empty_bit, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(u8, &self.reader_queue_empty_bit, .Xchg, 0, .SeqCst);
|
||||
|
||||
// Here we don't care if we are the one to do the locking or if it was already locked for reading.
|
||||
const have_read_lock = if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.ReadLock, AtomicOrder.SeqCst, AtomicOrder.SeqCst)) |old_state| old_state == State.ReadLock else true;
|
||||
const have_read_lock = if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.ReadLock, .SeqCst, .SeqCst)) |old_state| old_state == State.ReadLock else true;
|
||||
if (have_read_lock) {
|
||||
// Give out all the read locks.
|
||||
if (self.reader_queue.get()) |first_node| {
|
||||
@ -134,24 +130,22 @@ pub const RwLock = struct {
|
||||
|
||||
pub async fn acquireWrite(self: *RwLock) HeldWrite {
|
||||
suspend {
|
||||
// TODO explicitly put this memory in the coroutine frame #1194
|
||||
var my_tick_node = Loop.NextTickNode{
|
||||
.data = @handle(),
|
||||
.data = @frame(),
|
||||
.prev = undefined,
|
||||
.next = undefined,
|
||||
};
|
||||
|
||||
self.writer_queue.put(&my_tick_node);
|
||||
|
||||
// At this point, we are in the writer_queue, so we might have already been resumed and this coroutine
|
||||
// frame might be destroyed. For the rest of the suspend block we cannot access the coroutine frame.
|
||||
// At this point, we are in the writer_queue, so we might have already been resumed.
|
||||
|
||||
// We set this bit so that later we can rely on the fact, that if writer_queue_empty_bit is 1,
|
||||
// some actor will attempt to grab the lock.
|
||||
_ = @atomicRmw(u8, &self.writer_queue_empty_bit, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(u8, &self.writer_queue_empty_bit, .Xchg, 0, .SeqCst);
|
||||
|
||||
// Here we must be the one to acquire the write lock. It cannot already be locked.
|
||||
if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.WriteLock, AtomicOrder.SeqCst, AtomicOrder.SeqCst) == null) {
|
||||
if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.WriteLock, .SeqCst, .SeqCst) == null) {
|
||||
// We now have a write lock.
|
||||
if (self.writer_queue.get()) |node| {
|
||||
// Whether this node is us or someone else, we tail resume it.
|
||||
@ -169,8 +163,8 @@ pub const RwLock = struct {
|
||||
// obtain the lock.
|
||||
// But if there's a writer_queue item or a reader_queue item,
|
||||
// we are the actor which must loop and attempt to grab the lock again.
|
||||
if (@atomicLoad(u8, &self.writer_queue_empty_bit, AtomicOrder.SeqCst) == 0) {
|
||||
if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.WriteLock, AtomicOrder.SeqCst, AtomicOrder.SeqCst) != null) {
|
||||
if (@atomicLoad(u8, &self.writer_queue_empty_bit, .SeqCst) == 0) {
|
||||
if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.WriteLock, .SeqCst, .SeqCst) != null) {
|
||||
// We did not obtain the lock. Great, the queues are someone else's problem.
|
||||
return;
|
||||
}
|
||||
@ -180,13 +174,13 @@ pub const RwLock = struct {
|
||||
return;
|
||||
}
|
||||
// Release the lock again.
|
||||
_ = @atomicRmw(u8, &self.writer_queue_empty_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(u8, &self.shared_state, AtomicRmwOp.Xchg, State.Unlocked, AtomicOrder.SeqCst);
|
||||
_ = @atomicRmw(u8, &self.writer_queue_empty_bit, .Xchg, 1, .SeqCst);
|
||||
_ = @atomicRmw(u8, &self.shared_state, .Xchg, State.Unlocked, .SeqCst);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (@atomicLoad(u8, &self.reader_queue_empty_bit, AtomicOrder.SeqCst) == 0) {
|
||||
if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.ReadLock, AtomicOrder.SeqCst, AtomicOrder.SeqCst) != null) {
|
||||
if (@atomicLoad(u8, &self.reader_queue_empty_bit, .SeqCst) == 0) {
|
||||
if (@cmpxchgStrong(u8, &self.shared_state, State.Unlocked, State.ReadLock, .SeqCst, .SeqCst) != null) {
|
||||
// We did not obtain the lock. Great, the queues are someone else's problem.
|
||||
return;
|
||||
}
|
||||
@ -199,8 +193,8 @@ pub const RwLock = struct {
|
||||
return;
|
||||
}
|
||||
// Release the lock again.
|
||||
_ = @atomicRmw(u8, &self.reader_queue_empty_bit, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
|
||||
if (@cmpxchgStrong(u8, &self.shared_state, State.ReadLock, State.Unlocked, AtomicOrder.SeqCst, AtomicOrder.SeqCst) != null) {
|
||||
_ = @atomicRmw(u8, &self.reader_queue_empty_bit, .Xchg, 1, .SeqCst);
|
||||
if (@cmpxchgStrong(u8, &self.shared_state, State.ReadLock, State.Unlocked, .SeqCst, .SeqCst) != null) {
|
||||
// Didn't unlock. Someone else's problem.
|
||||
return;
|
||||
}
|
||||
@ -215,6 +209,9 @@ test "std.event.RwLock" {
|
||||
// https://github.com/ziglang/zig/issues/2377
|
||||
if (true) return error.SkipZigTest;
|
||||
|
||||
// https://github.com/ziglang/zig/issues/1908
|
||||
if (builtin.single_threaded) return error.SkipZigTest;
|
||||
|
||||
const allocator = std.heap.direct_allocator;
|
||||
|
||||
var loop: Loop = undefined;
|
||||
@ -224,8 +221,7 @@ test "std.event.RwLock" {
|
||||
var lock = RwLock.init(&loop);
|
||||
defer lock.deinit();
|
||||
|
||||
const handle = try async<allocator> testLock(&loop, &lock);
|
||||
defer cancel handle;
|
||||
const handle = testLock(&loop, &lock);
|
||||
loop.run();
|
||||
|
||||
const expected_result = [1]i32{shared_it_count * @intCast(i32, shared_test_data.len)} ** shared_test_data.len;
|
||||
@ -233,28 +229,31 @@ test "std.event.RwLock" {
|
||||
}
|
||||
|
||||
async fn testLock(loop: *Loop, lock: *RwLock) void {
|
||||
// TODO explicitly put next tick node memory in the coroutine frame #1194
|
||||
suspend {
|
||||
resume @handle();
|
||||
}
|
||||
|
||||
var read_nodes: [100]Loop.NextTickNode = undefined;
|
||||
for (read_nodes) |*read_node| {
|
||||
read_node.data = async readRunner(lock) catch @panic("out of memory");
|
||||
const frame = loop.allocator.create(@Frame(readRunner)) catch @panic("memory");
|
||||
read_node.data = frame;
|
||||
frame.* = async readRunner(lock);
|
||||
loop.onNextTick(read_node);
|
||||
}
|
||||
|
||||
var write_nodes: [shared_it_count]Loop.NextTickNode = undefined;
|
||||
for (write_nodes) |*write_node| {
|
||||
write_node.data = async writeRunner(lock) catch @panic("out of memory");
|
||||
const frame = loop.allocator.create(@Frame(writeRunner)) catch @panic("memory");
|
||||
write_node.data = frame;
|
||||
frame.* = async writeRunner(lock);
|
||||
loop.onNextTick(write_node);
|
||||
}
|
||||
|
||||
for (write_nodes) |*write_node| {
|
||||
await @ptrCast(promise->void, write_node.data);
|
||||
const casted = @ptrCast(*const @Frame(writeRunner), write_node.data);
|
||||
await casted;
|
||||
loop.allocator.destroy(casted);
|
||||
}
|
||||
for (read_nodes) |*read_node| {
|
||||
await @ptrCast(promise->void, read_node.data);
|
||||
const casted = @ptrCast(*const @Frame(readRunner), read_node.data);
|
||||
await casted;
|
||||
loop.allocator.destroy(casted);
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,7 +268,7 @@ async fn writeRunner(lock: *RwLock) void {
|
||||
var i: usize = 0;
|
||||
while (i < shared_test_data.len) : (i += 1) {
|
||||
std.time.sleep(100 * std.time.microsecond);
|
||||
const lock_promise = async lock.acquireWrite() catch @panic("out of memory");
|
||||
const lock_promise = async lock.acquireWrite();
|
||||
const handle = await lock_promise;
|
||||
defer handle.release();
|
||||
|
||||
@ -287,7 +286,7 @@ async fn readRunner(lock: *RwLock) void {
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < shared_test_data.len) : (i += 1) {
|
||||
const lock_promise = async lock.acquireRead() catch @panic("out of memory");
|
||||
const lock_promise = async lock.acquireRead();
|
||||
const handle = await lock_promise;
|
||||
defer handle.release();
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ const RwLock = std.event.RwLock;
|
||||
const Loop = std.event.Loop;
|
||||
|
||||
/// Thread-safe async/await RW lock that protects one piece of data.
|
||||
/// coroutines which are waiting for the lock are suspended, and
|
||||
/// Functions which are waiting for the lock are suspended, and
|
||||
/// are resumed when the lock is released, in order.
|
||||
pub fn RwLocked(comptime T: type) type {
|
||||
return struct {
|
||||
|
||||
@ -328,9 +328,6 @@ pub fn formatType(
|
||||
try output(context, "error.");
|
||||
return output(context, @errorName(value));
|
||||
},
|
||||
.Promise => {
|
||||
return format(context, Errors, output, "promise@{x}", @ptrToInt(value));
|
||||
},
|
||||
.Enum => {
|
||||
if (comptime std.meta.trait.hasFn("format")(T)) {
|
||||
return value.format(fmt, options, context, Errors, output);
|
||||
|
||||
@ -8,31 +8,32 @@ const meta = std.meta;
|
||||
pub fn autoHash(hasher: var, key: var) void {
|
||||
const Key = @typeOf(key);
|
||||
switch (@typeInfo(Key)) {
|
||||
builtin.TypeId.NoReturn,
|
||||
builtin.TypeId.Opaque,
|
||||
builtin.TypeId.Undefined,
|
||||
builtin.TypeId.ArgTuple,
|
||||
builtin.TypeId.Void,
|
||||
builtin.TypeId.Null,
|
||||
builtin.TypeId.BoundFn,
|
||||
builtin.TypeId.ComptimeFloat,
|
||||
builtin.TypeId.ComptimeInt,
|
||||
builtin.TypeId.Type,
|
||||
builtin.TypeId.EnumLiteral,
|
||||
.NoReturn,
|
||||
.Opaque,
|
||||
.Undefined,
|
||||
.ArgTuple,
|
||||
.Void,
|
||||
.Null,
|
||||
.BoundFn,
|
||||
.ComptimeFloat,
|
||||
.ComptimeInt,
|
||||
.Type,
|
||||
.EnumLiteral,
|
||||
.Frame,
|
||||
=> @compileError("cannot hash this type"),
|
||||
|
||||
// Help the optimizer see that hashing an int is easy by inlining!
|
||||
// TODO Check if the situation is better after #561 is resolved.
|
||||
builtin.TypeId.Int => @inlineCall(hasher.update, std.mem.asBytes(&key)),
|
||||
.Int => @inlineCall(hasher.update, std.mem.asBytes(&key)),
|
||||
|
||||
builtin.TypeId.Float => |info| autoHash(hasher, @bitCast(@IntType(false, info.bits), key)),
|
||||
.Float => |info| autoHash(hasher, @bitCast(@IntType(false, info.bits), key)),
|
||||
|
||||
builtin.TypeId.Bool => autoHash(hasher, @boolToInt(key)),
|
||||
builtin.TypeId.Enum => autoHash(hasher, @enumToInt(key)),
|
||||
builtin.TypeId.ErrorSet => autoHash(hasher, @errorToInt(key)),
|
||||
builtin.TypeId.Promise, builtin.TypeId.Fn => autoHash(hasher, @ptrToInt(key)),
|
||||
.Bool => autoHash(hasher, @boolToInt(key)),
|
||||
.Enum => autoHash(hasher, @enumToInt(key)),
|
||||
.ErrorSet => autoHash(hasher, @errorToInt(key)),
|
||||
.AnyFrame, .Fn => autoHash(hasher, @ptrToInt(key)),
|
||||
|
||||
builtin.TypeId.Pointer => |info| switch (info.size) {
|
||||
.Pointer => |info| switch (info.size) {
|
||||
builtin.TypeInfo.Pointer.Size.One,
|
||||
builtin.TypeInfo.Pointer.Size.Many,
|
||||
builtin.TypeInfo.Pointer.Size.C,
|
||||
@ -44,9 +45,9 @@ pub fn autoHash(hasher: var, key: var) void {
|
||||
},
|
||||
},
|
||||
|
||||
builtin.TypeId.Optional => if (key) |k| autoHash(hasher, k),
|
||||
.Optional => if (key) |k| autoHash(hasher, k),
|
||||
|
||||
builtin.TypeId.Array => {
|
||||
.Array => {
|
||||
// TODO detect via a trait when Key has no padding bits to
|
||||
// hash it as an array of bytes.
|
||||
// Otherwise, hash every element.
|
||||
@ -55,7 +56,7 @@ pub fn autoHash(hasher: var, key: var) void {
|
||||
}
|
||||
},
|
||||
|
||||
builtin.TypeId.Vector => |info| {
|
||||
.Vector => |info| {
|
||||
if (info.child.bit_count % 8 == 0) {
|
||||
// If there's no unused bits in the child type, we can just hash
|
||||
// this as an array of bytes.
|
||||
@ -71,7 +72,7 @@ pub fn autoHash(hasher: var, key: var) void {
|
||||
}
|
||||
},
|
||||
|
||||
builtin.TypeId.Struct => |info| {
|
||||
.Struct => |info| {
|
||||
// TODO detect via a trait when Key has no padding bits to
|
||||
// hash it as an array of bytes.
|
||||
// Otherwise, hash every field.
|
||||
@ -82,7 +83,7 @@ pub fn autoHash(hasher: var, key: var) void {
|
||||
}
|
||||
},
|
||||
|
||||
builtin.TypeId.Union => |info| blk: {
|
||||
.Union => |info| blk: {
|
||||
if (info.tag_type) |tag_type| {
|
||||
const tag = meta.activeTag(key);
|
||||
const s = autoHash(hasher, tag);
|
||||
@ -99,7 +100,7 @@ pub fn autoHash(hasher: var, key: var) void {
|
||||
} else @compileError("cannot hash untagged union type: " ++ @typeName(Key) ++ ", provide your own hash function");
|
||||
},
|
||||
|
||||
builtin.TypeId.ErrorUnion => blk: {
|
||||
.ErrorUnion => blk: {
|
||||
const payload = key catch |err| {
|
||||
autoHash(hasher, err);
|
||||
break :blk;
|
||||
|
||||
@ -104,8 +104,7 @@ pub fn Child(comptime T: type) type {
|
||||
TypeId.Array => |info| info.child,
|
||||
TypeId.Pointer => |info| info.child,
|
||||
TypeId.Optional => |info| info.child,
|
||||
TypeId.Promise => |info| if (info.child) |child| child else null,
|
||||
else => @compileError("Expected promise, pointer, optional, or array type, " ++ "found '" ++ @typeName(T) ++ "'"),
|
||||
else => @compileError("Expected pointer, optional, or array type, " ++ "found '" ++ @typeName(T) ++ "'"),
|
||||
};
|
||||
}
|
||||
|
||||
@ -114,7 +113,6 @@ test "std.meta.Child" {
|
||||
testing.expect(Child(*u8) == u8);
|
||||
testing.expect(Child([]u8) == u8);
|
||||
testing.expect(Child(?u8) == u8);
|
||||
testing.expect(Child(promise->u8) == u8);
|
||||
}
|
||||
|
||||
pub fn containerLayout(comptime T: type) TypeInfo.ContainerLayout {
|
||||
|
||||
@ -25,36 +25,37 @@ pub fn expectError(expected_error: anyerror, actual_error_union: var) void {
|
||||
/// The types must match exactly.
|
||||
pub fn expectEqual(expected: var, actual: @typeOf(expected)) void {
|
||||
switch (@typeInfo(@typeOf(actual))) {
|
||||
TypeId.NoReturn,
|
||||
TypeId.BoundFn,
|
||||
TypeId.ArgTuple,
|
||||
TypeId.Opaque,
|
||||
.NoReturn,
|
||||
.BoundFn,
|
||||
.ArgTuple,
|
||||
.Opaque,
|
||||
.Frame,
|
||||
.AnyFrame,
|
||||
=> @compileError("value of type " ++ @typeName(@typeOf(actual)) ++ " encountered"),
|
||||
|
||||
TypeId.Undefined,
|
||||
TypeId.Null,
|
||||
TypeId.Void,
|
||||
.Undefined,
|
||||
.Null,
|
||||
.Void,
|
||||
=> return,
|
||||
|
||||
TypeId.Type,
|
||||
TypeId.Bool,
|
||||
TypeId.Int,
|
||||
TypeId.Float,
|
||||
TypeId.ComptimeFloat,
|
||||
TypeId.ComptimeInt,
|
||||
TypeId.EnumLiteral,
|
||||
TypeId.Enum,
|
||||
TypeId.Fn,
|
||||
TypeId.Promise,
|
||||
TypeId.Vector,
|
||||
TypeId.ErrorSet,
|
||||
.Type,
|
||||
.Bool,
|
||||
.Int,
|
||||
.Float,
|
||||
.ComptimeFloat,
|
||||
.ComptimeInt,
|
||||
.EnumLiteral,
|
||||
.Enum,
|
||||
.Fn,
|
||||
.Vector,
|
||||
.ErrorSet,
|
||||
=> {
|
||||
if (actual != expected) {
|
||||
std.debug.panic("expected {}, found {}", expected, actual);
|
||||
}
|
||||
},
|
||||
|
||||
TypeId.Pointer => |pointer| {
|
||||
.Pointer => |pointer| {
|
||||
switch (pointer.size) {
|
||||
builtin.TypeInfo.Pointer.Size.One,
|
||||
builtin.TypeInfo.Pointer.Size.Many,
|
||||
@ -76,22 +77,22 @@ pub fn expectEqual(expected: var, actual: @typeOf(expected)) void {
|
||||
}
|
||||
},
|
||||
|
||||
TypeId.Array => |array| expectEqualSlices(array.child, &expected, &actual),
|
||||
.Array => |array| expectEqualSlices(array.child, &expected, &actual),
|
||||
|
||||
TypeId.Struct => |structType| {
|
||||
.Struct => |structType| {
|
||||
inline for (structType.fields) |field| {
|
||||
expectEqual(@field(expected, field.name), @field(actual, field.name));
|
||||
}
|
||||
},
|
||||
|
||||
TypeId.Union => |union_info| {
|
||||
.Union => |union_info| {
|
||||
if (union_info.tag_type == null) {
|
||||
@compileError("Unable to compare untagged union values");
|
||||
}
|
||||
@compileError("TODO implement testing.expectEqual for tagged unions");
|
||||
},
|
||||
|
||||
TypeId.Optional => {
|
||||
.Optional => {
|
||||
if (expected) |expected_payload| {
|
||||
if (actual) |actual_payload| {
|
||||
expectEqual(expected_payload, actual_payload);
|
||||
@ -105,7 +106,7 @@ pub fn expectEqual(expected: var, actual: @typeOf(expected)) void {
|
||||
}
|
||||
},
|
||||
|
||||
TypeId.ErrorUnion => {
|
||||
.ErrorUnion => {
|
||||
if (expected) |expected_payload| {
|
||||
if (actual) |actual_payload| {
|
||||
expectEqual(expected_payload, actual_payload);
|
||||
|
||||
@ -400,7 +400,7 @@ pub const Node = struct {
|
||||
VarType,
|
||||
ErrorType,
|
||||
FnProto,
|
||||
PromiseType,
|
||||
AnyFrameType,
|
||||
|
||||
// Primary expressions
|
||||
IntegerLiteral,
|
||||
@ -952,9 +952,9 @@ pub const Node = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const PromiseType = struct {
|
||||
pub const AnyFrameType = struct {
|
||||
base: Node,
|
||||
promise_token: TokenIndex,
|
||||
anyframe_token: TokenIndex,
|
||||
result: ?Result,
|
||||
|
||||
pub const Result = struct {
|
||||
@ -962,7 +962,7 @@ pub const Node = struct {
|
||||
return_type: *Node,
|
||||
};
|
||||
|
||||
pub fn iterate(self: *PromiseType, index: usize) ?*Node {
|
||||
pub fn iterate(self: *AnyFrameType, index: usize) ?*Node {
|
||||
var i = index;
|
||||
|
||||
if (self.result) |result| {
|
||||
@ -973,13 +973,13 @@ pub const Node = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn firstToken(self: *const PromiseType) TokenIndex {
|
||||
return self.promise_token;
|
||||
pub fn firstToken(self: *const AnyFrameType) TokenIndex {
|
||||
return self.anyframe_token;
|
||||
}
|
||||
|
||||
pub fn lastToken(self: *const PromiseType) TokenIndex {
|
||||
pub fn lastToken(self: *const AnyFrameType) TokenIndex {
|
||||
if (self.result) |result| return result.return_type.lastToken();
|
||||
return self.promise_token;
|
||||
return self.anyframe_token;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -814,7 +814,6 @@ fn parsePrefixExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
||||
/// <- AsmExpr
|
||||
/// / IfExpr
|
||||
/// / KEYWORD_break BreakLabel? Expr?
|
||||
/// / KEYWORD_cancel Expr
|
||||
/// / KEYWORD_comptime Expr
|
||||
/// / KEYWORD_continue BreakLabel?
|
||||
/// / KEYWORD_resume Expr
|
||||
@ -839,20 +838,6 @@ fn parsePrimaryExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node
|
||||
return &node.base;
|
||||
}
|
||||
|
||||
if (eatToken(it, .Keyword_cancel)) |token| {
|
||||
const expr_node = try expectNode(arena, it, tree, parseExpr, AstError{
|
||||
.ExpectedExpr = AstError.ExpectedExpr{ .token = it.index },
|
||||
});
|
||||
const node = try arena.create(Node.PrefixOp);
|
||||
node.* = Node.PrefixOp{
|
||||
.base = Node{ .id = .PrefixOp },
|
||||
.op_token = token,
|
||||
.op = Node.PrefixOp.Op.Cancel,
|
||||
.rhs = expr_node,
|
||||
};
|
||||
return &node.base;
|
||||
}
|
||||
|
||||
if (eatToken(it, .Keyword_comptime)) |token| {
|
||||
const expr_node = try expectNode(arena, it, tree, parseExpr, AstError{
|
||||
.ExpectedExpr = AstError.ExpectedExpr{ .token = it.index },
|
||||
@ -1201,7 +1186,7 @@ fn parseSuffixExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
||||
/// / KEYWORD_error DOT IDENTIFIER
|
||||
/// / KEYWORD_false
|
||||
/// / KEYWORD_null
|
||||
/// / KEYWORD_promise
|
||||
/// / KEYWORD_anyframe
|
||||
/// / KEYWORD_true
|
||||
/// / KEYWORD_undefined
|
||||
/// / KEYWORD_unreachable
|
||||
@ -1256,11 +1241,11 @@ fn parsePrimaryTypeExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*N
|
||||
}
|
||||
if (eatToken(it, .Keyword_false)) |token| return createLiteral(arena, Node.BoolLiteral, token);
|
||||
if (eatToken(it, .Keyword_null)) |token| return createLiteral(arena, Node.NullLiteral, token);
|
||||
if (eatToken(it, .Keyword_promise)) |token| {
|
||||
const node = try arena.create(Node.PromiseType);
|
||||
node.* = Node.PromiseType{
|
||||
.base = Node{ .id = .PromiseType },
|
||||
.promise_token = token,
|
||||
if (eatToken(it, .Keyword_anyframe)) |token| {
|
||||
const node = try arena.create(Node.AnyFrameType);
|
||||
node.* = Node.AnyFrameType{
|
||||
.base = Node{ .id = .AnyFrameType },
|
||||
.anyframe_token = token,
|
||||
.result = null,
|
||||
};
|
||||
return &node.base;
|
||||
@ -2194,7 +2179,7 @@ fn parsePrefixOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
||||
|
||||
/// PrefixTypeOp
|
||||
/// <- QUESTIONMARK
|
||||
/// / KEYWORD_promise MINUSRARROW
|
||||
/// / KEYWORD_anyframe MINUSRARROW
|
||||
/// / ArrayTypeStart (ByteAlign / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
|
||||
/// / PtrTypeStart (KEYWORD_align LPAREN Expr (COLON INTEGER COLON INTEGER)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
|
||||
fn parsePrefixTypeOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
||||
@ -2209,20 +2194,20 @@ fn parsePrefixTypeOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node
|
||||
return &node.base;
|
||||
}
|
||||
|
||||
// TODO: Returning a PromiseType instead of PrefixOp makes casting and setting .rhs or
|
||||
// TODO: Returning a AnyFrameType instead of PrefixOp makes casting and setting .rhs or
|
||||
// .return_type more difficult for the caller (see parsePrefixOpExpr helper).
|
||||
// Consider making the PromiseType a member of PrefixOp and add a
|
||||
// PrefixOp.PromiseType variant?
|
||||
if (eatToken(it, .Keyword_promise)) |token| {
|
||||
// Consider making the AnyFrameType a member of PrefixOp and add a
|
||||
// PrefixOp.AnyFrameType variant?
|
||||
if (eatToken(it, .Keyword_anyframe)) |token| {
|
||||
const arrow = eatToken(it, .Arrow) orelse {
|
||||
putBackToken(it, token);
|
||||
return null;
|
||||
};
|
||||
const node = try arena.create(Node.PromiseType);
|
||||
node.* = Node.PromiseType{
|
||||
.base = Node{ .id = .PromiseType },
|
||||
.promise_token = token,
|
||||
.result = Node.PromiseType.Result{
|
||||
const node = try arena.create(Node.AnyFrameType);
|
||||
node.* = Node.AnyFrameType{
|
||||
.base = Node{ .id = .AnyFrameType },
|
||||
.anyframe_token = token,
|
||||
.result = Node.AnyFrameType.Result{
|
||||
.arrow_token = arrow,
|
||||
.return_type = undefined, // set by caller
|
||||
},
|
||||
@ -2903,8 +2888,8 @@ fn parsePrefixOpExpr(
|
||||
rightmost_op = rhs;
|
||||
} else break;
|
||||
},
|
||||
.PromiseType => {
|
||||
const prom = rightmost_op.cast(Node.PromiseType).?;
|
||||
.AnyFrameType => {
|
||||
const prom = rightmost_op.cast(Node.AnyFrameType).?;
|
||||
if (try opParseFn(arena, it, tree)) |rhs| {
|
||||
prom.result.?.return_type = rhs;
|
||||
rightmost_op = rhs;
|
||||
@ -2922,8 +2907,8 @@ fn parsePrefixOpExpr(
|
||||
.InvalidToken = AstError.InvalidToken{ .token = it.index },
|
||||
});
|
||||
},
|
||||
.PromiseType => {
|
||||
const prom = rightmost_op.cast(Node.PromiseType).?;
|
||||
.AnyFrameType => {
|
||||
const prom = rightmost_op.cast(Node.AnyFrameType).?;
|
||||
prom.result.?.return_type = try expectNode(arena, it, tree, childParseFn, AstError{
|
||||
.InvalidToken = AstError.InvalidToken{ .token = it.index },
|
||||
});
|
||||
|
||||
@ -1183,7 +1183,7 @@ test "zig fmt: resume from suspend block" {
|
||||
try testCanonical(
|
||||
\\fn foo() void {
|
||||
\\ suspend {
|
||||
\\ resume @handle();
|
||||
\\ resume @frame();
|
||||
\\ }
|
||||
\\}
|
||||
\\
|
||||
@ -2103,7 +2103,7 @@ test "zig fmt: inline asm" {
|
||||
);
|
||||
}
|
||||
|
||||
test "zig fmt: coroutines" {
|
||||
test "zig fmt: async functions" {
|
||||
try testCanonical(
|
||||
\\async fn simpleAsyncFn() void {
|
||||
\\ const a = async a.b();
|
||||
@ -2111,14 +2111,14 @@ test "zig fmt: coroutines" {
|
||||
\\ suspend;
|
||||
\\ x += 1;
|
||||
\\ suspend;
|
||||
\\ const p: promise->void = async simpleAsyncFn() catch unreachable;
|
||||
\\ const p: anyframe->void = async simpleAsyncFn() catch unreachable;
|
||||
\\ await p;
|
||||
\\}
|
||||
\\
|
||||
\\test "coroutine suspend, resume, cancel" {
|
||||
\\ const p: promise = try async<std.debug.global_allocator> testAsyncSeq();
|
||||
\\test "suspend, resume, await" {
|
||||
\\ const p: anyframe = async testAsyncSeq();
|
||||
\\ resume p;
|
||||
\\ cancel p;
|
||||
\\ await p;
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
|
||||
@ -1205,15 +1205,15 @@ fn renderExpression(
|
||||
}
|
||||
},
|
||||
|
||||
ast.Node.Id.PromiseType => {
|
||||
const promise_type = @fieldParentPtr(ast.Node.PromiseType, "base", base);
|
||||
ast.Node.Id.AnyFrameType => {
|
||||
const anyframe_type = @fieldParentPtr(ast.Node.AnyFrameType, "base", base);
|
||||
|
||||
if (promise_type.result) |result| {
|
||||
try renderToken(tree, stream, promise_type.promise_token, indent, start_col, Space.None); // promise
|
||||
if (anyframe_type.result) |result| {
|
||||
try renderToken(tree, stream, anyframe_type.anyframe_token, indent, start_col, Space.None); // anyframe
|
||||
try renderToken(tree, stream, result.arrow_token, indent, start_col, Space.None); // ->
|
||||
return renderExpression(allocator, stream, tree, indent, start_col, result.return_type, space);
|
||||
} else {
|
||||
return renderToken(tree, stream, promise_type.promise_token, indent, start_col, space); // promise
|
||||
return renderToken(tree, stream, anyframe_type.anyframe_token, indent, start_col, space); // anyframe
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -15,12 +15,12 @@ pub const Token = struct {
|
||||
Keyword{ .bytes = "align", .id = Id.Keyword_align },
|
||||
Keyword{ .bytes = "allowzero", .id = Id.Keyword_allowzero },
|
||||
Keyword{ .bytes = "and", .id = Id.Keyword_and },
|
||||
Keyword{ .bytes = "anyframe", .id = Id.Keyword_anyframe },
|
||||
Keyword{ .bytes = "asm", .id = Id.Keyword_asm },
|
||||
Keyword{ .bytes = "async", .id = Id.Keyword_async },
|
||||
Keyword{ .bytes = "await", .id = Id.Keyword_await },
|
||||
Keyword{ .bytes = "break", .id = Id.Keyword_break },
|
||||
Keyword{ .bytes = "catch", .id = Id.Keyword_catch },
|
||||
Keyword{ .bytes = "cancel", .id = Id.Keyword_cancel },
|
||||
Keyword{ .bytes = "comptime", .id = Id.Keyword_comptime },
|
||||
Keyword{ .bytes = "const", .id = Id.Keyword_const },
|
||||
Keyword{ .bytes = "continue", .id = Id.Keyword_continue },
|
||||
@ -42,7 +42,6 @@ pub const Token = struct {
|
||||
Keyword{ .bytes = "or", .id = Id.Keyword_or },
|
||||
Keyword{ .bytes = "orelse", .id = Id.Keyword_orelse },
|
||||
Keyword{ .bytes = "packed", .id = Id.Keyword_packed },
|
||||
Keyword{ .bytes = "promise", .id = Id.Keyword_promise },
|
||||
Keyword{ .bytes = "pub", .id = Id.Keyword_pub },
|
||||
Keyword{ .bytes = "resume", .id = Id.Keyword_resume },
|
||||
Keyword{ .bytes = "return", .id = Id.Keyword_return },
|
||||
@ -151,7 +150,6 @@ pub const Token = struct {
|
||||
Keyword_async,
|
||||
Keyword_await,
|
||||
Keyword_break,
|
||||
Keyword_cancel,
|
||||
Keyword_catch,
|
||||
Keyword_comptime,
|
||||
Keyword_const,
|
||||
@ -174,7 +172,7 @@ pub const Token = struct {
|
||||
Keyword_or,
|
||||
Keyword_orelse,
|
||||
Keyword_packed,
|
||||
Keyword_promise,
|
||||
Keyword_anyframe,
|
||||
Keyword_pub,
|
||||
Keyword_resume,
|
||||
Keyword_return,
|
||||
|
||||
@ -2,6 +2,118 @@ const tests = @import("tests.zig");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pub fn addCases(cases: *tests.CompileErrorContext) void {
|
||||
cases.add(
|
||||
"@frame() causes function to be async",
|
||||
\\export fn entry() void {
|
||||
\\ func();
|
||||
\\}
|
||||
\\fn func() void {
|
||||
\\ _ = @frame();
|
||||
\\}
|
||||
,
|
||||
"tmp.zig:1:1: error: function with calling convention 'ccc' cannot be async",
|
||||
"tmp.zig:5:9: note: @frame() causes function to be async",
|
||||
);
|
||||
cases.add(
|
||||
"invalid suspend in exported function",
|
||||
\\export fn entry() void {
|
||||
\\ var frame = async func();
|
||||
\\ var result = await frame;
|
||||
\\}
|
||||
\\fn func() void {
|
||||
\\ suspend;
|
||||
\\}
|
||||
,
|
||||
"tmp.zig:1:1: error: function with calling convention 'ccc' cannot be async",
|
||||
"tmp.zig:3:18: note: await is a suspend point",
|
||||
);
|
||||
|
||||
cases.add(
|
||||
"async function indirectly depends on its own frame",
|
||||
\\export fn entry() void {
|
||||
\\ _ = async amain();
|
||||
\\}
|
||||
\\async fn amain() void {
|
||||
\\ other();
|
||||
\\}
|
||||
\\fn other() void {
|
||||
\\ var x: [@sizeOf(@Frame(amain))]u8 = undefined;
|
||||
\\}
|
||||
,
|
||||
"tmp.zig:4:1: error: unable to determine async function frame of 'amain'",
|
||||
"tmp.zig:5:10: note: analysis of function 'other' depends on the frame",
|
||||
"tmp.zig:8:13: note: depends on the frame here",
|
||||
);
|
||||
|
||||
cases.add(
|
||||
"async function depends on its own frame",
|
||||
\\export fn entry() void {
|
||||
\\ _ = async amain();
|
||||
\\}
|
||||
\\async fn amain() void {
|
||||
\\ var x: [@sizeOf(@Frame(amain))]u8 = undefined;
|
||||
\\}
|
||||
,
|
||||
"tmp.zig:4:1: error: cannot resolve '@Frame(amain)': function not fully analyzed yet",
|
||||
"tmp.zig:5:13: note: depends on its own frame here",
|
||||
);
|
||||
|
||||
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 {
|
||||
\\ _ = async amain();
|
||||
\\}
|
||||
\\fn amain() void {
|
||||
\\ var ptr = afunc;
|
||||
\\ _ = ptr();
|
||||
\\}
|
||||
\\async fn afunc() void {}
|
||||
,
|
||||
"tmp.zig:6:12: error: function is not comptime-known; @asyncCall required",
|
||||
);
|
||||
|
||||
cases.add(
|
||||
"runtime-known function called with async keyword",
|
||||
\\export fn entry() void {
|
||||
\\ var ptr = afunc;
|
||||
\\ _ = async ptr();
|
||||
\\}
|
||||
\\
|
||||
\\async fn afunc() void { }
|
||||
,
|
||||
"tmp.zig:3:15: error: function is not comptime-known; @asyncCall required",
|
||||
);
|
||||
|
||||
cases.add(
|
||||
"function with ccc indirectly calling async function",
|
||||
\\export fn entry() void {
|
||||
\\ foo();
|
||||
\\}
|
||||
\\fn foo() void {
|
||||
\\ bar();
|
||||
\\}
|
||||
\\fn bar() void {
|
||||
\\ suspend;
|
||||
\\}
|
||||
,
|
||||
"tmp.zig:1:1: error: function with calling convention 'ccc' cannot be async",
|
||||
"tmp.zig:2:8: note: async function call here",
|
||||
"tmp.zig:5:8: note: async function call here",
|
||||
"tmp.zig:8:5: note: suspends here",
|
||||
);
|
||||
|
||||
cases.add(
|
||||
"capture group on switch prong with incompatible payload types",
|
||||
\\const Union = union(enum) {
|
||||
@ -1319,24 +1431,14 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
|
||||
);
|
||||
|
||||
cases.add(
|
||||
"@handle() called outside of function definition",
|
||||
\\var handle_undef: promise = undefined;
|
||||
\\var handle_dummy: promise = @handle();
|
||||
"@frame() called outside of function definition",
|
||||
\\var handle_undef: anyframe = undefined;
|
||||
\\var handle_dummy: anyframe = @frame();
|
||||
\\export fn entry() bool {
|
||||
\\ return handle_undef == handle_dummy;
|
||||
\\}
|
||||
,
|
||||
"tmp.zig:2:29: error: @handle() called outside of function definition",
|
||||
);
|
||||
|
||||
cases.add(
|
||||
"@handle() in non-async function",
|
||||
\\export fn entry() bool {
|
||||
\\ var handle_undef: promise = undefined;
|
||||
\\ return handle_undef == @handle();
|
||||
\\}
|
||||
,
|
||||
"tmp.zig:3:28: error: @handle() in non-async function",
|
||||
"tmp.zig:2:30: error: @frame() called outside of function definition",
|
||||
);
|
||||
|
||||
cases.add(
|
||||
@ -1712,15 +1814,9 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
|
||||
|
||||
cases.add(
|
||||
"suspend inside suspend block",
|
||||
\\const std = @import("std",);
|
||||
\\
|
||||
\\export fn entry() void {
|
||||
\\ var buf: [500]u8 = undefined;
|
||||
\\ var a = &std.heap.FixedBufferAllocator.init(buf[0..]).allocator;
|
||||
\\ const p = (async<a> foo()) catch unreachable;
|
||||
\\ cancel p;
|
||||
\\ _ = async foo();
|
||||
\\}
|
||||
\\
|
||||
\\async fn foo() void {
|
||||
\\ suspend {
|
||||
\\ suspend {
|
||||
@ -1728,8 +1824,8 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
|
||||
\\ }
|
||||
\\}
|
||||
,
|
||||
"tmp.zig:12:9: error: cannot suspend inside suspend block",
|
||||
"tmp.zig:11:5: note: other suspend block here",
|
||||
"tmp.zig:6:9: error: cannot suspend inside suspend block",
|
||||
"tmp.zig:5:5: note: other suspend block here",
|
||||
);
|
||||
|
||||
cases.add(
|
||||
@ -1770,15 +1866,14 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
|
||||
|
||||
cases.add(
|
||||
"returning error from void async function",
|
||||
\\const std = @import("std",);
|
||||
\\export fn entry() void {
|
||||
\\ const p = async<std.debug.global_allocator> amain() catch unreachable;
|
||||
\\ _ = async amain();
|
||||
\\}
|
||||
\\async fn amain() void {
|
||||
\\ return error.ShouldBeCompileError;
|
||||
\\}
|
||||
,
|
||||
"tmp.zig:6:17: error: expected type 'void', found 'error{ShouldBeCompileError}'",
|
||||
"tmp.zig:5:17: error: expected type 'void', found 'error{ShouldBeCompileError}'",
|
||||
);
|
||||
|
||||
cases.add(
|
||||
@ -3307,7 +3402,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
|
||||
\\
|
||||
\\export fn entry() usize { return @sizeOf(@typeOf(Foo)); }
|
||||
,
|
||||
"tmp.zig:5:18: error: unable to evaluate constant expression",
|
||||
"tmp.zig:5:25: error: unable to evaluate constant expression",
|
||||
"tmp.zig:2:12: note: called from here",
|
||||
"tmp.zig:2:8: note: called from here",
|
||||
);
|
||||
|
||||
@ -1,6 +1,91 @@
|
||||
const tests = @import("tests.zig");
|
||||
|
||||
pub fn addCases(cases: *tests.CompareOutputContext) void {
|
||||
cases.addRuntimeSafety("awaiting twice",
|
||||
\\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn {
|
||||
\\ @import("std").os.exit(126);
|
||||
\\}
|
||||
\\var frame: anyframe = undefined;
|
||||
\\
|
||||
\\pub fn main() void {
|
||||
\\ _ = async amain();
|
||||
\\ resume frame;
|
||||
\\}
|
||||
\\
|
||||
\\fn amain() void {
|
||||
\\ var f = async func();
|
||||
\\ await f;
|
||||
\\ await f;
|
||||
\\}
|
||||
\\
|
||||
\\fn func() void {
|
||||
\\ suspend {
|
||||
\\ frame = @frame();
|
||||
\\ }
|
||||
\\}
|
||||
);
|
||||
|
||||
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);
|
||||
\\}
|
||||
\\pub fn main() void {
|
||||
\\ var frame = async first();
|
||||
\\ resume frame;
|
||||
\\}
|
||||
\\fn first() void {
|
||||
\\ var frame = async other();
|
||||
\\ await frame;
|
||||
\\}
|
||||
\\fn other() 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);
|
||||
\\}
|
||||
\\pub fn main() void {
|
||||
\\ var frame = async first();
|
||||
\\ resume frame;
|
||||
\\}
|
||||
\\fn first() void {
|
||||
\\ other();
|
||||
\\}
|
||||
\\fn other() void {
|
||||
\\ suspend;
|
||||
\\}
|
||||
);
|
||||
|
||||
cases.addRuntimeSafety("invalid resume of async function",
|
||||
\\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn {
|
||||
\\ @import("std").os.exit(126);
|
||||
\\}
|
||||
\\pub fn main() void {
|
||||
\\ var p = async suspendOnce();
|
||||
\\ resume p; //ok
|
||||
\\ resume p; //bad
|
||||
\\}
|
||||
\\fn suspendOnce() void {
|
||||
\\ suspend;
|
||||
\\}
|
||||
);
|
||||
|
||||
cases.addRuntimeSafety(".? operator on null pointer",
|
||||
\\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn {
|
||||
\\ @import("std").os.exit(126);
|
||||
@ -483,23 +568,29 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {
|
||||
\\ std.os.exit(126);
|
||||
\\}
|
||||
\\
|
||||
\\var failing_frame: @Frame(failing) = undefined;
|
||||
\\
|
||||
\\pub fn main() void {
|
||||
\\ const p = nonFailing();
|
||||
\\ resume p;
|
||||
\\ const p2 = async<std.debug.global_allocator> printTrace(p) catch unreachable;
|
||||
\\ cancel p2;
|
||||
\\ const p2 = async printTrace(p);
|
||||
\\}
|
||||
\\
|
||||
\\fn nonFailing() promise->anyerror!void {
|
||||
\\ return async<std.debug.global_allocator> failing() catch unreachable;
|
||||
\\fn nonFailing() anyframe->anyerror!void {
|
||||
\\ failing_frame = async failing();
|
||||
\\ return &failing_frame;
|
||||
\\}
|
||||
\\
|
||||
\\async fn failing() anyerror!void {
|
||||
\\ suspend;
|
||||
\\ return second();
|
||||
\\}
|
||||
\\
|
||||
\\async fn second() anyerror!void {
|
||||
\\ return error.Fail;
|
||||
\\}
|
||||
\\
|
||||
\\async fn printTrace(p: promise->anyerror!void) void {
|
||||
\\async fn printTrace(p: anyframe->anyerror!void) void {
|
||||
\\ (await p) catch unreachable;
|
||||
\\}
|
||||
);
|
||||
|
||||
@ -3,12 +3,13 @@ comptime {
|
||||
_ = @import("behavior/alignof.zig");
|
||||
_ = @import("behavior/array.zig");
|
||||
_ = @import("behavior/asm.zig");
|
||||
_ = @import("behavior/async_fn.zig");
|
||||
_ = @import("behavior/atomics.zig");
|
||||
_ = @import("behavior/await_struct.zig");
|
||||
_ = @import("behavior/bit_shifting.zig");
|
||||
_ = @import("behavior/bitcast.zig");
|
||||
_ = @import("behavior/bitreverse.zig");
|
||||
_ = @import("behavior/bool.zig");
|
||||
_ = @import("behavior/byteswap.zig");
|
||||
_ = @import("behavior/bugs/1025.zig");
|
||||
_ = @import("behavior/bugs/1076.zig");
|
||||
_ = @import("behavior/bugs/1111.zig");
|
||||
@ -38,23 +39,23 @@ comptime {
|
||||
_ = @import("behavior/bugs/726.zig");
|
||||
_ = @import("behavior/bugs/828.zig");
|
||||
_ = @import("behavior/bugs/920.zig");
|
||||
_ = @import("behavior/byteswap.zig");
|
||||
_ = @import("behavior/byval_arg_var.zig");
|
||||
_ = @import("behavior/cancel.zig");
|
||||
_ = @import("behavior/cast.zig");
|
||||
_ = @import("behavior/const_slice_child.zig");
|
||||
_ = @import("behavior/coroutine_await_struct.zig");
|
||||
_ = @import("behavior/coroutines.zig");
|
||||
_ = @import("behavior/defer.zig");
|
||||
_ = @import("behavior/enum.zig");
|
||||
_ = @import("behavior/enum_with_members.zig");
|
||||
_ = @import("behavior/error.zig");
|
||||
_ = @import("behavior/eval.zig");
|
||||
_ = @import("behavior/field_parent_ptr.zig");
|
||||
_ = @import("behavior/floatop.zig");
|
||||
_ = @import("behavior/fn.zig");
|
||||
_ = @import("behavior/fn_in_struct_in_comptime.zig");
|
||||
_ = @import("behavior/for.zig");
|
||||
_ = @import("behavior/generics.zig");
|
||||
_ = @import("behavior/hasdecl.zig");
|
||||
_ = @import("behavior/hasfield.zig");
|
||||
_ = @import("behavior/if.zig");
|
||||
_ = @import("behavior/import.zig");
|
||||
_ = @import("behavior/incomplete_struct_param_tld.zig");
|
||||
@ -63,14 +64,13 @@ comptime {
|
||||
_ = @import("behavior/math.zig");
|
||||
_ = @import("behavior/merge_error_sets.zig");
|
||||
_ = @import("behavior/misc.zig");
|
||||
_ = @import("behavior/muladd.zig");
|
||||
_ = @import("behavior/namespace_depends_on_compile_var.zig");
|
||||
_ = @import("behavior/new_stack_call.zig");
|
||||
_ = @import("behavior/null.zig");
|
||||
_ = @import("behavior/optional.zig");
|
||||
_ = @import("behavior/pointers.zig");
|
||||
_ = @import("behavior/popcount.zig");
|
||||
_ = @import("behavior/muladd.zig");
|
||||
_ = @import("behavior/floatop.zig");
|
||||
_ = @import("behavior/ptrcast.zig");
|
||||
_ = @import("behavior/pub_enum.zig");
|
||||
_ = @import("behavior/ref_var_in_if_after_if_2nd_switch_prong.zig");
|
||||
@ -99,5 +99,4 @@ comptime {
|
||||
_ = @import("behavior/void.zig");
|
||||
_ = @import("behavior/while.zig");
|
||||
_ = @import("behavior/widening.zig");
|
||||
_ = @import("behavior/hasfield.zig");
|
||||
}
|
||||
|
||||
@ -228,3 +228,65 @@ test "alignment of extern() void" {
|
||||
}
|
||||
|
||||
extern fn nothing() void {}
|
||||
|
||||
test "return error union with 128-bit integer" {
|
||||
expect(3 == try give());
|
||||
}
|
||||
fn give() anyerror!u128 {
|
||||
return 3;
|
||||
}
|
||||
|
||||
test "alignment of >= 128-bit integer type" {
|
||||
expect(@alignOf(u128) == 16);
|
||||
expect(@alignOf(u129) == 16);
|
||||
}
|
||||
|
||||
test "alignment of struct with 128-bit field" {
|
||||
expect(@alignOf(struct {
|
||||
x: u128,
|
||||
}) == 16);
|
||||
|
||||
comptime {
|
||||
expect(@alignOf(struct {
|
||||
x: u128,
|
||||
}) == 16);
|
||||
}
|
||||
}
|
||||
|
||||
test "size of extern struct with 128-bit field" {
|
||||
expect(@sizeOf(extern struct {
|
||||
x: u128,
|
||||
y: u8,
|
||||
}) == 32);
|
||||
|
||||
comptime {
|
||||
expect(@sizeOf(extern struct {
|
||||
x: u128,
|
||||
y: u8,
|
||||
}) == 32);
|
||||
}
|
||||
}
|
||||
|
||||
const DefaultAligned = struct {
|
||||
nevermind: u32,
|
||||
badguy: i128,
|
||||
};
|
||||
|
||||
test "read 128-bit field from default aligned struct in stack memory" {
|
||||
var default_aligned = DefaultAligned{
|
||||
.nevermind = 1,
|
||||
.badguy = 12,
|
||||
};
|
||||
expect((@ptrToInt(&default_aligned.badguy) % 16) == 0);
|
||||
expect(12 == default_aligned.badguy);
|
||||
}
|
||||
|
||||
var default_aligned_global = DefaultAligned{
|
||||
.nevermind = 1,
|
||||
.badguy = 12,
|
||||
};
|
||||
|
||||
test "read 128-bit field from default aligned struct in global memory" {
|
||||
expect((@ptrToInt(&default_aligned_global.badguy) % 16) == 0);
|
||||
expect(12 == default_aligned_global.badguy);
|
||||
}
|
||||
|
||||
736
test/stage1/behavior/async_fn.zig
Normal file
736
test/stage1/behavior/async_fn.zig
Normal file
@ -0,0 +1,736 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const expect = std.testing.expect;
|
||||
const expectEqual = std.testing.expectEqual;
|
||||
|
||||
var global_x: i32 = 1;
|
||||
|
||||
test "simple coroutine suspend and resume" {
|
||||
const 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" {
|
||||
const 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);
|
||||
const 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(async fn(i32) void, other);
|
||||
const size = @frameSize(ptr);
|
||||
expect(size == @sizeOf(@Frame(other)));
|
||||
}
|
||||
{
|
||||
var ptr = @ptrCast(async fn() 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;
|
||||
async fn testSuspendBlock() 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 == anyframe(@frame()));
|
||||
|
||||
global_result = true;
|
||||
}
|
||||
|
||||
var await_a_promise: anyframe = undefined;
|
||||
var await_final_result: i32 = 0;
|
||||
|
||||
test "coroutine await" {
|
||||
await_seq('a');
|
||||
const 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"));
|
||||
}
|
||||
async fn await_amain() void {
|
||||
await_seq('b');
|
||||
const p = async await_another();
|
||||
await_seq('e');
|
||||
await_final_result = await p;
|
||||
await_seq('h');
|
||||
}
|
||||
async fn await_another() 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');
|
||||
const p = async early_amain();
|
||||
early_seq('f');
|
||||
expect(early_final_result == 1234);
|
||||
expect(std.mem.eql(u8, early_points, "abcdef"));
|
||||
}
|
||||
async fn early_amain() void {
|
||||
early_seq('b');
|
||||
const p = async early_another();
|
||||
early_seq('d');
|
||||
early_final_result = await p;
|
||||
early_seq('e');
|
||||
}
|
||||
async fn early_another() 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;
|
||||
async fn foo() 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: async fn (*i32) void,
|
||||
};
|
||||
var foo = Foo{ .bar = simpleAsyncFn2 };
|
||||
var bytes: [64]u8 = 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;
|
||||
}
|
||||
|
||||
async fn simpleAsyncFn2(y: *i32) void {
|
||||
defer y.* += 2;
|
||||
y.* += 1;
|
||||
suspend;
|
||||
}
|
||||
|
||||
test "@asyncCall with return type" {
|
||||
const Foo = struct {
|
||||
bar: async fn () i32,
|
||||
|
||||
var global_frame: anyframe = undefined;
|
||||
|
||||
async fn middle() i32 {
|
||||
return afunc();
|
||||
}
|
||||
|
||||
fn afunc() i32 {
|
||||
global_frame = @frame();
|
||||
suspend;
|
||||
return 1234;
|
||||
}
|
||||
};
|
||||
var foo = Foo{ .bar = Foo.middle };
|
||||
var bytes: [150]u8 = 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 result: anyerror!void = undefined;
|
||||
_ = @asyncCall(@sliceToBytes(frame[0..]), &result, middle);
|
||||
resume global_frame;
|
||||
std.testing.expectError(error.Fail, result);
|
||||
}
|
||||
|
||||
async fn middle() !void {
|
||||
var f = async middle2();
|
||||
return await f;
|
||||
}
|
||||
|
||||
fn middle2() !void {
|
||||
return failing();
|
||||
}
|
||||
|
||||
fn failing() !void {
|
||||
global_frame = @frame();
|
||||
suspend;
|
||||
return error.Fail;
|
||||
}
|
||||
};
|
||||
S.doTheTest();
|
||||
}
|
||||
|
||||
test "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;
|
||||
}
|
||||
async fn suspendThenFail() anyerror!void {
|
||||
suspend;
|
||||
return error.Fail;
|
||||
}
|
||||
async fn printTrace(p: anyframe->(anyerror!void)) 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);
|
||||
}
|
||||
async fn testBreakFromSuspend(my_result: *i32) 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.heap.direct_allocator.create(@Frame(someFunc));
|
||||
defer std.heap.direct_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;
|
||||
expectEqual(([]const u8)("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.heap.direct_allocator; // TODO once we have the debug allocator, use that, so that this can detect leaks
|
||||
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();
|
||||
}
|
||||
@ -6,12 +6,12 @@ const Foo = struct {
|
||||
x: i32,
|
||||
};
|
||||
|
||||
var await_a_promise: promise = undefined;
|
||||
var await_a_promise: anyframe = undefined;
|
||||
var await_final_result = Foo{ .x = 0 };
|
||||
|
||||
test "coroutine await struct" {
|
||||
await_seq('a');
|
||||
const p = async<std.heap.direct_allocator> await_amain() catch unreachable;
|
||||
const p = async await_amain();
|
||||
await_seq('f');
|
||||
resume await_a_promise;
|
||||
await_seq('i');
|
||||
@ -20,7 +20,7 @@ test "coroutine await struct" {
|
||||
}
|
||||
async fn await_amain() void {
|
||||
await_seq('b');
|
||||
const p = async await_another() catch unreachable;
|
||||
const p = async await_another();
|
||||
await_seq('e');
|
||||
await_final_result = await p;
|
||||
await_seq('h');
|
||||
@ -29,7 +29,7 @@ async fn await_another() Foo {
|
||||
await_seq('c');
|
||||
suspend {
|
||||
await_seq('d');
|
||||
await_a_promise = @handle();
|
||||
await_a_promise = @frame();
|
||||
}
|
||||
await_seq('g');
|
||||
return Foo{ .x = 1234 };
|
||||
@ -1,86 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
var defer_f1: bool = false;
|
||||
var defer_f2: bool = false;
|
||||
var defer_f3: bool = false;
|
||||
|
||||
test "cancel forwards" {
|
||||
const p = async<std.heap.direct_allocator> f1() catch unreachable;
|
||||
cancel p;
|
||||
std.testing.expect(defer_f1);
|
||||
std.testing.expect(defer_f2);
|
||||
std.testing.expect(defer_f3);
|
||||
}
|
||||
|
||||
async fn f1() void {
|
||||
defer {
|
||||
defer_f1 = true;
|
||||
}
|
||||
await (async f2() catch unreachable);
|
||||
}
|
||||
|
||||
async fn f2() void {
|
||||
defer {
|
||||
defer_f2 = true;
|
||||
}
|
||||
await (async f3() catch unreachable);
|
||||
}
|
||||
|
||||
async fn f3() void {
|
||||
defer {
|
||||
defer_f3 = true;
|
||||
}
|
||||
suspend;
|
||||
}
|
||||
|
||||
var defer_b1: bool = false;
|
||||
var defer_b2: bool = false;
|
||||
var defer_b3: bool = false;
|
||||
var defer_b4: bool = false;
|
||||
|
||||
test "cancel backwards" {
|
||||
const p = async<std.heap.direct_allocator> b1() catch unreachable;
|
||||
cancel p;
|
||||
std.testing.expect(defer_b1);
|
||||
std.testing.expect(defer_b2);
|
||||
std.testing.expect(defer_b3);
|
||||
std.testing.expect(defer_b4);
|
||||
}
|
||||
|
||||
async fn b1() void {
|
||||
defer {
|
||||
defer_b1 = true;
|
||||
}
|
||||
await (async b2() catch unreachable);
|
||||
}
|
||||
|
||||
var b4_handle: promise = undefined;
|
||||
|
||||
async fn b2() void {
|
||||
const b3_handle = async b3() catch unreachable;
|
||||
resume b4_handle;
|
||||
cancel b4_handle;
|
||||
defer {
|
||||
defer_b2 = true;
|
||||
}
|
||||
const value = await b3_handle;
|
||||
@panic("unreachable");
|
||||
}
|
||||
|
||||
async fn b3() i32 {
|
||||
defer {
|
||||
defer_b3 = true;
|
||||
}
|
||||
await (async b4() catch unreachable);
|
||||
return 1234;
|
||||
}
|
||||
|
||||
async fn b4() void {
|
||||
defer {
|
||||
defer_b4 = true;
|
||||
}
|
||||
suspend {
|
||||
b4_handle = @handle();
|
||||
}
|
||||
suspend;
|
||||
}
|
||||
@ -1,236 +0,0 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const expect = std.testing.expect;
|
||||
const allocator = std.heap.direct_allocator;
|
||||
|
||||
var x: i32 = 1;
|
||||
|
||||
test "create a coroutine and cancel it" {
|
||||
const p = try async<allocator> simpleAsyncFn();
|
||||
comptime expect(@typeOf(p) == promise->void);
|
||||
cancel p;
|
||||
expect(x == 2);
|
||||
}
|
||||
async fn simpleAsyncFn() void {
|
||||
x += 1;
|
||||
suspend;
|
||||
x += 1;
|
||||
}
|
||||
|
||||
test "coroutine suspend, resume, cancel" {
|
||||
seq('a');
|
||||
const p = try async<allocator> testAsyncSeq();
|
||||
seq('c');
|
||||
resume p;
|
||||
seq('f');
|
||||
cancel p;
|
||||
seq('g');
|
||||
|
||||
expect(std.mem.eql(u8, points, "abcdefg"));
|
||||
}
|
||||
async fn testAsyncSeq() void {
|
||||
defer seq('e');
|
||||
|
||||
seq('b');
|
||||
suspend;
|
||||
seq('d');
|
||||
}
|
||||
var points = [_]u8{0} ** "abcdefg".len;
|
||||
var index: usize = 0;
|
||||
|
||||
fn seq(c: u8) void {
|
||||
points[index] = c;
|
||||
index += 1;
|
||||
}
|
||||
|
||||
test "coroutine suspend with block" {
|
||||
const p = try async<allocator> testSuspendBlock();
|
||||
std.testing.expect(!result);
|
||||
resume a_promise;
|
||||
std.testing.expect(result);
|
||||
cancel p;
|
||||
}
|
||||
|
||||
var a_promise: promise = undefined;
|
||||
var result = false;
|
||||
async fn testSuspendBlock() void {
|
||||
suspend {
|
||||
comptime expect(@typeOf(@handle()) == promise->void);
|
||||
a_promise = @handle();
|
||||
}
|
||||
|
||||
//Test to make sure that @handle() works as advertised (issue #1296)
|
||||
//var our_handle: promise = @handle();
|
||||
expect(a_promise == @handle());
|
||||
|
||||
result = true;
|
||||
}
|
||||
|
||||
var await_a_promise: promise = undefined;
|
||||
var await_final_result: i32 = 0;
|
||||
|
||||
test "coroutine await" {
|
||||
await_seq('a');
|
||||
const p = async<allocator> await_amain() catch unreachable;
|
||||
await_seq('f');
|
||||
resume await_a_promise;
|
||||
await_seq('i');
|
||||
expect(await_final_result == 1234);
|
||||
expect(std.mem.eql(u8, await_points, "abcdefghi"));
|
||||
}
|
||||
async fn await_amain() void {
|
||||
await_seq('b');
|
||||
const p = async await_another() catch unreachable;
|
||||
await_seq('e');
|
||||
await_final_result = await p;
|
||||
await_seq('h');
|
||||
}
|
||||
async fn await_another() i32 {
|
||||
await_seq('c');
|
||||
suspend {
|
||||
await_seq('d');
|
||||
await_a_promise = @handle();
|
||||
}
|
||||
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');
|
||||
const p = async<allocator> early_amain() catch @panic("out of memory");
|
||||
early_seq('f');
|
||||
expect(early_final_result == 1234);
|
||||
expect(std.mem.eql(u8, early_points, "abcdef"));
|
||||
}
|
||||
async fn early_amain() void {
|
||||
early_seq('b');
|
||||
const p = async early_another() catch @panic("out of memory");
|
||||
early_seq('d');
|
||||
early_final_result = await p;
|
||||
early_seq('e');
|
||||
}
|
||||
async fn early_another() 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 "coro allocation failure" {
|
||||
var failing_allocator = std.debug.FailingAllocator.init(std.debug.global_allocator, 0);
|
||||
if (async<&failing_allocator.allocator> asyncFuncThatNeverGetsRun()) {
|
||||
@panic("expected allocation failure");
|
||||
} else |err| switch (err) {
|
||||
error.OutOfMemory => {},
|
||||
}
|
||||
}
|
||||
async fn asyncFuncThatNeverGetsRun() void {
|
||||
@panic("coro frame allocation should fail");
|
||||
}
|
||||
|
||||
test "async function with dot syntax" {
|
||||
const S = struct {
|
||||
var y: i32 = 1;
|
||||
async fn foo() void {
|
||||
y += 1;
|
||||
suspend;
|
||||
}
|
||||
};
|
||||
const p = try async<allocator> S.foo();
|
||||
cancel p;
|
||||
expect(S.y == 2);
|
||||
}
|
||||
|
||||
test "async fn pointer in a struct field" {
|
||||
var data: i32 = 1;
|
||||
const Foo = struct {
|
||||
bar: async<*std.mem.Allocator> fn (*i32) void,
|
||||
};
|
||||
var foo = Foo{ .bar = simpleAsyncFn2 };
|
||||
const p = (async<allocator> foo.bar(&data)) catch unreachable;
|
||||
expect(data == 2);
|
||||
cancel p;
|
||||
expect(data == 4);
|
||||
}
|
||||
async<*std.mem.Allocator> fn simpleAsyncFn2(y: *i32) void {
|
||||
defer y.* += 2;
|
||||
y.* += 1;
|
||||
suspend;
|
||||
}
|
||||
|
||||
test "async fn with inferred error set" {
|
||||
const p = (async<allocator> failing()) catch unreachable;
|
||||
resume p;
|
||||
cancel p;
|
||||
}
|
||||
|
||||
async fn failing() !void {
|
||||
suspend;
|
||||
return error.Fail;
|
||||
}
|
||||
|
||||
test "error return trace across suspend points - early return" {
|
||||
const p = nonFailing();
|
||||
resume p;
|
||||
const p2 = try async<allocator> printTrace(p);
|
||||
cancel p2;
|
||||
}
|
||||
|
||||
test "error return trace across suspend points - async return" {
|
||||
const p = nonFailing();
|
||||
const p2 = try async<std.debug.global_allocator> printTrace(p);
|
||||
resume p;
|
||||
cancel p2;
|
||||
}
|
||||
|
||||
fn nonFailing() (promise->anyerror!void) {
|
||||
return async<std.debug.global_allocator> suspendThenFail() catch unreachable;
|
||||
}
|
||||
async fn suspendThenFail() anyerror!void {
|
||||
suspend;
|
||||
return error.Fail;
|
||||
}
|
||||
async fn printTrace(p: promise->(anyerror!void)) void {
|
||||
(await p) catch |e| {
|
||||
std.testing.expect(e == error.Fail);
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
expect(trace.index == 1);
|
||||
} else switch (builtin.mode) {
|
||||
builtin.Mode.Debug, builtin.Mode.ReleaseSafe => @panic("expected return trace"),
|
||||
builtin.Mode.ReleaseFast, builtin.Mode.ReleaseSmall => {},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test "break from suspend" {
|
||||
var buf: [500]u8 = undefined;
|
||||
var a = &std.heap.FixedBufferAllocator.init(buf[0..]).allocator;
|
||||
var my_result: i32 = 1;
|
||||
const p = try async<a> testBreakFromSuspend(&my_result);
|
||||
cancel p;
|
||||
std.testing.expect(my_result == 2);
|
||||
}
|
||||
async fn testBreakFromSuspend(my_result: *i32) void {
|
||||
suspend {
|
||||
resume @handle();
|
||||
}
|
||||
my_result.* += 1;
|
||||
suspend;
|
||||
my_result.* += 1;
|
||||
}
|
||||
@ -116,21 +116,6 @@ fn testOptional() void {
|
||||
expect(null_info.Optional.child == void);
|
||||
}
|
||||
|
||||
test "type info: promise info" {
|
||||
testPromise();
|
||||
comptime testPromise();
|
||||
}
|
||||
|
||||
fn testPromise() void {
|
||||
const null_promise_info = @typeInfo(promise);
|
||||
expect(TypeId(null_promise_info) == TypeId.Promise);
|
||||
expect(null_promise_info.Promise.child == null);
|
||||
|
||||
const promise_info = @typeInfo(promise->usize);
|
||||
expect(TypeId(promise_info) == TypeId.Promise);
|
||||
expect(promise_info.Promise.child.? == usize);
|
||||
}
|
||||
|
||||
test "type info: error set, error union info" {
|
||||
testErrorSet();
|
||||
comptime testErrorSet();
|
||||
@ -192,7 +177,7 @@ fn testUnion() void {
|
||||
expect(TypeId(typeinfo_info) == TypeId.Union);
|
||||
expect(typeinfo_info.Union.layout == TypeInfo.ContainerLayout.Auto);
|
||||
expect(typeinfo_info.Union.tag_type.? == TypeId);
|
||||
expect(typeinfo_info.Union.fields.len == 25);
|
||||
expect(typeinfo_info.Union.fields.len == 26);
|
||||
expect(typeinfo_info.Union.fields[4].enum_field != null);
|
||||
expect(typeinfo_info.Union.fields[4].enum_field.?.value == 4);
|
||||
expect(typeinfo_info.Union.fields[4].field_type == @typeOf(@typeInfo(u8).Int));
|
||||
@ -265,7 +250,6 @@ fn testFunction() void {
|
||||
expect(fn_info.Fn.args.len == 2);
|
||||
expect(fn_info.Fn.is_var_args);
|
||||
expect(fn_info.Fn.return_type == null);
|
||||
expect(fn_info.Fn.async_allocator_type == null);
|
||||
|
||||
const test_instance: TestStruct = undefined;
|
||||
const bound_fn_info = @typeInfo(@typeOf(test_instance.foo));
|
||||
@ -296,6 +280,25 @@ fn testVector() void {
|
||||
expect(vec_info.Vector.child == i32);
|
||||
}
|
||||
|
||||
test "type info: anyframe and anyframe->T" {
|
||||
testAnyFrame();
|
||||
comptime testAnyFrame();
|
||||
}
|
||||
|
||||
fn testAnyFrame() void {
|
||||
{
|
||||
const anyframe_info = @typeInfo(anyframe->i32);
|
||||
expect(TypeId(anyframe_info) == .AnyFrame);
|
||||
expect(anyframe_info.AnyFrame.child.? == i32);
|
||||
}
|
||||
|
||||
{
|
||||
const anyframe_info = @typeInfo(anyframe);
|
||||
expect(TypeId(anyframe_info) == .AnyFrame);
|
||||
expect(anyframe_info.AnyFrame.child == null);
|
||||
}
|
||||
}
|
||||
|
||||
test "type info: optional field unwrapping" {
|
||||
const Struct = struct {
|
||||
cdOffset: u32,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user