mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
Merge remote-tracking branch 'origin/master' into llvm9
This commit is contained in:
commit
6529658ad8
@ -202,11 +202,11 @@ else()
|
||||
"${CMAKE_SOURCE_DIR}/deps/lld/wasm/Writer.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/deps/lld/wasm/WriterUtils.cpp"
|
||||
)
|
||||
add_library(embedded_lld_lib ${EMBEDDED_LLD_LIB_SOURCES})
|
||||
add_library(embedded_lld_elf ${EMBEDDED_LLD_ELF_SOURCES})
|
||||
add_library(embedded_lld_coff ${EMBEDDED_LLD_COFF_SOURCES})
|
||||
add_library(embedded_lld_mingw ${EMBEDDED_LLD_MINGW_SOURCES})
|
||||
add_library(embedded_lld_wasm ${EMBEDDED_LLD_WASM_SOURCES})
|
||||
add_library(embedded_lld_lib STATIC ${EMBEDDED_LLD_LIB_SOURCES})
|
||||
add_library(embedded_lld_elf STATIC ${EMBEDDED_LLD_ELF_SOURCES})
|
||||
add_library(embedded_lld_coff STATIC ${EMBEDDED_LLD_COFF_SOURCES})
|
||||
add_library(embedded_lld_mingw STATIC ${EMBEDDED_LLD_MINGW_SOURCES})
|
||||
add_library(embedded_lld_wasm STATIC ${EMBEDDED_LLD_WASM_SOURCES})
|
||||
if(MSVC)
|
||||
set(ZIG_LLD_COMPILE_FLAGS "-std=c++11 -D_CRT_SECURE_NO_WARNINGS /w")
|
||||
else()
|
||||
@ -403,7 +403,7 @@ set(EMBEDDED_SOFTFLOAT_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/ui32_to_f128M.c"
|
||||
"${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e/source/ui64_to_f128M.c"
|
||||
)
|
||||
add_library(embedded_softfloat ${EMBEDDED_SOFTFLOAT_SOURCES})
|
||||
add_library(embedded_softfloat STATIC ${EMBEDDED_SOFTFLOAT_SOURCES})
|
||||
if(MSVC)
|
||||
set_target_properties(embedded_softfloat PROPERTIES
|
||||
COMPILE_FLAGS "-std=c99 /w"
|
||||
@ -429,7 +429,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"
|
||||
@ -441,6 +440,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"
|
||||
|
||||
@ -25,6 +25,7 @@ Here are some examples:
|
||||
|
||||
* [Iterative Replacement of C with Zig](http://tiehuis.github.io/blog/zig1.html)
|
||||
* [The Right Tool for the Right Job: Redis Modules & Zig](https://www.youtube.com/watch?v=eCHM8-_poZY)
|
||||
* [Writing a small ray tracer in Rust and Zig](https://nelari.us/post/raytracer_with_rust_and_zig/)
|
||||
|
||||
Zig is a brand new language, with no advertising budget. Word of mouth is the
|
||||
only way people find out about the project, and the more people hear about it,
|
||||
@ -45,8 +46,8 @@ The most highly regarded argument in such a discussion is a real world use case.
|
||||
|
||||
The issue label
|
||||
[Contributor Friendly](https://github.com/ziglang/zig/issues?q=is%3Aissue+is%3Aopen+label%3A%22contributor+friendly%22)
|
||||
exists to help contributors find issues that are "limited in scope and/or
|
||||
knowledge of Zig internals."
|
||||
exists to help you find issues that are **limited in scope and/or
|
||||
knowledge of Zig internals.**
|
||||
|
||||
### Editing Source Code
|
||||
|
||||
@ -61,8 +62,7 @@ To test changes, do the following from the build directory:
|
||||
|
||||
1. Run `make install` (on POSIX) or
|
||||
`msbuild -p:Configuration=Release INSTALL.vcxproj` (on Windows).
|
||||
2. `bin/zig build --build-file ../build.zig test` (on POSIX) or
|
||||
`bin\zig.exe build --build-file ..\build.zig test` (on Windows).
|
||||
2. `bin/zig build test` (on POSIX) or `bin\zig.exe build test` (on Windows).
|
||||
|
||||
That runs the whole test suite, which does a lot of extra testing that you
|
||||
likely won't always need, and can take upwards of 2 hours. This is what the
|
||||
@ -79,8 +79,8 @@ Another example is choosing a different set of things to test. For example,
|
||||
not the other ones. Combining this suggestion with the previous one, you could
|
||||
do this:
|
||||
|
||||
`bin/zig build --build-file ../build.zig test-std -Dskip-release` (on POSIX) or
|
||||
`bin\zig.exe build --build-file ..\build.zig test-std -Dskip-release` (on Windows).
|
||||
`bin/zig build test-std -Dskip-release` (on POSIX) or
|
||||
`bin\zig.exe build test-std -Dskip-release` (on Windows).
|
||||
|
||||
This will run only the standard library tests, in debug mode only, for all
|
||||
targets (it will cross-compile the tests for non-native targets but not run
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||

|
||||
|
||||
Zig is an open-source programming language designed for **robustness**,
|
||||
A general-purpose programming language designed for **robustness**,
|
||||
**optimality**, and **maintainability**.
|
||||
|
||||
## Resources
|
||||
@ -10,6 +10,7 @@ Zig is an open-source programming language designed for **robustness**,
|
||||
* [Community](https://github.com/ziglang/zig/wiki/Community)
|
||||
* [Contributing](https://github.com/ziglang/zig/blob/master/CONTRIBUTING.md)
|
||||
* [Frequently Asked Questions](https://github.com/ziglang/zig/wiki/FAQ)
|
||||
* [Community Projects](https://github.com/ziglang/zig/wiki/Community-Projects)
|
||||
|
||||
## Building from Source
|
||||
|
||||
|
||||
@ -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
|
||||
@ -9794,7 +9958,6 @@ PrimaryExpr
|
||||
<- AsmExpr
|
||||
/ IfExpr
|
||||
/ KEYWORD_break BreakLabel? Expr?
|
||||
/ KEYWORD_cancel Expr
|
||||
/ KEYWORD_comptime Expr
|
||||
/ KEYWORD_continue BreakLabel?
|
||||
/ KEYWORD_resume Expr
|
||||
@ -9825,7 +9988,7 @@ TypeExpr <- PrefixTypeOp* ErrorUnionExpr
|
||||
ErrorUnionExpr <- SuffixExpr (EXCLAMATIONMARK TypeExpr)?
|
||||
|
||||
SuffixExpr
|
||||
<- AsyncPrefix PrimaryTypeExpr SuffixOp* FnCallArguments
|
||||
<- KEYWORD_async PrimaryTypeExpr SuffixOp* FnCallArguments
|
||||
/ PrimaryTypeExpr (SuffixOp / FnCallArguments)*
|
||||
|
||||
PrimaryTypeExpr
|
||||
@ -9901,7 +10064,7 @@ FnCC
|
||||
<- KEYWORD_nakedcc
|
||||
/ KEYWORD_stdcallcc
|
||||
/ KEYWORD_extern
|
||||
/ KEYWORD_async (LARROW TypeExpr RARROW)?
|
||||
/ KEYWORD_async
|
||||
|
||||
ParamDecl <- (KEYWORD_noalias / KEYWORD_comptime)? (IDENTIFIER COLON)? ParamType
|
||||
|
||||
@ -10006,8 +10169,6 @@ SuffixOp
|
||||
/ DOTASTERISK
|
||||
/ DOTQUESTIONMARK
|
||||
|
||||
AsyncPrefix <- KEYWORD_async (LARROW PrefixExpr RARROW)?
|
||||
|
||||
FnCallArguments <- LPAREN ExprList RPAREN
|
||||
|
||||
# Ptr specific
|
||||
@ -10150,7 +10311,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
|
||||
@ -10195,7 +10355,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
|
||||
|
||||
@ -1181,7 +1181,6 @@ pub const Builder = struct {
|
||||
ast.Node.Id.ErrorTag => return error.Unimplemented,
|
||||
ast.Node.Id.AsmInput => return error.Unimplemented,
|
||||
ast.Node.Id.AsmOutput => return error.Unimplemented,
|
||||
ast.Node.Id.AsyncAttribute => return error.Unimplemented,
|
||||
ast.Node.Id.ParamDecl => return error.Unimplemented,
|
||||
ast.Node.Id.FieldInitializer => return error.Unimplemented,
|
||||
ast.Node.Id.EnumLiteral => return error.Unimplemented,
|
||||
@ -1904,20 +1903,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;
|
||||
|
||||
@ -1037,7 +1037,7 @@ fn transCreateNodeFnCall(c: *Context, fn_expr: *ast.Node) !*ast.Node.SuffixOp {
|
||||
.op = ast.Node.SuffixOp.Op{
|
||||
.Call = ast.Node.SuffixOp.Op.Call{
|
||||
.params = ast.Node.SuffixOp.Op.Call.ParamList.init(c.a()),
|
||||
.async_attr = null,
|
||||
.async_token = null,
|
||||
},
|
||||
},
|
||||
.rtoken = undefined, // set after appending args
|
||||
@ -1355,7 +1355,6 @@ fn finishTransFnProto(
|
||||
.var_args_token = null, // TODO this field is broken in the AST data model
|
||||
.extern_export_inline_token = extern_export_inline_tok,
|
||||
.cc_token = cc_tok,
|
||||
.async_attr = null,
|
||||
.body_node = null,
|
||||
.lib_name = null,
|
||||
.align_expr = null,
|
||||
|
||||
@ -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,
|
||||
@ -3772,23 +3719,19 @@ static const size_t slice_len_index = 1;
|
||||
static const size_t maybe_child_index = 0;
|
||||
static const size_t maybe_null_index = 1;
|
||||
|
||||
static const size_t err_union_err_index = 0;
|
||||
static const size_t err_union_payload_index = 1;
|
||||
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;
|
||||
};
|
||||
|
||||
1134
src/analyze.cpp
1134
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);
|
||||
}
|
||||
|
||||
2185
src/codegen.cpp
2185
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
|
||||
|
||||
2602
src/ir.cpp
2602
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");
|
||||
}
|
||||
|
||||
@ -85,7 +85,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
|
||||
" --verbose-cc enable compiler debug output for C compilation\n"
|
||||
" -dirafter [dir] same as -isystem but do it last\n"
|
||||
" -isystem [dir] add additional search path for other .h files\n"
|
||||
" -mllvm [arg] forward an arg to LLVM's option processing\n"
|
||||
" -mllvm [arg] (unsupported) forward an arg to LLVM's option processing\n"
|
||||
" --override-std-dir [arg] override path to Zig standard library\n"
|
||||
" --override-lib-dir [arg] override path to Zig lib library\n"
|
||||
" -ffunction-sections places each function in a seperate section\n"
|
||||
|
||||
@ -113,7 +113,6 @@ static AstNode *ast_parse_multiply_op(ParseContext *pc);
|
||||
static AstNode *ast_parse_prefix_op(ParseContext *pc);
|
||||
static AstNode *ast_parse_prefix_type_op(ParseContext *pc);
|
||||
static AstNode *ast_parse_suffix_op(ParseContext *pc);
|
||||
static AstNode *ast_parse_async_prefix(ParseContext *pc);
|
||||
static AstNode *ast_parse_fn_call_argumnets(ParseContext *pc);
|
||||
static AstNode *ast_parse_array_type_start(ParseContext *pc);
|
||||
static AstNode *ast_parse_ptr_type_start(ParseContext *pc);
|
||||
@ -282,8 +281,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 +1166,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 +1193,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);
|
||||
@ -1398,22 +1388,18 @@ static AstNode *ast_parse_error_union_expr(ParseContext *pc) {
|
||||
}
|
||||
|
||||
// SuffixExpr
|
||||
// <- AsyncPrefix PrimaryTypeExpr SuffixOp* FnCallArguments
|
||||
// <- KEYWORD_async PrimaryTypeExpr SuffixOp* FnCallArguments
|
||||
// / PrimaryTypeExpr (SuffixOp / FnCallArguments)*
|
||||
static AstNode *ast_parse_suffix_expr(ParseContext *pc) {
|
||||
AstNode *async_call = ast_parse_async_prefix(pc);
|
||||
if (async_call != nullptr) {
|
||||
Token *async_token = eat_token_if(pc, TokenIdKeywordAsync);
|
||||
if (async_token != nullptr) {
|
||||
if (eat_token_if(pc, TokenIdKeywordFn) != nullptr) {
|
||||
// HACK: If we see the keyword `fn`, then we assume that
|
||||
// we are parsing an async fn proto, and not a call.
|
||||
// We therefore put back all tokens consumed by the async
|
||||
// prefix...
|
||||
// HACK: This loop is not actually enough to put back all the
|
||||
// tokens. Let's hope this is fine for most code right now
|
||||
// and wait till we get the async rework for a syntax update.
|
||||
do {
|
||||
put_back_token(pc);
|
||||
} while (peek_token(pc)->id != TokenIdKeywordAsync);
|
||||
put_back_token(pc);
|
||||
put_back_token(pc);
|
||||
|
||||
return ast_parse_primary_type_expr(pc);
|
||||
}
|
||||
@ -1455,10 +1441,14 @@ static AstNode *ast_parse_suffix_expr(ParseContext *pc) {
|
||||
ast_invalid_token_error(pc, peek_token(pc));
|
||||
|
||||
assert(args->type == NodeTypeFnCallExpr);
|
||||
async_call->data.fn_call_expr.fn_ref_expr = child;
|
||||
async_call->data.fn_call_expr.params = args->data.fn_call_expr.params;
|
||||
async_call->data.fn_call_expr.is_builtin = false;
|
||||
return async_call;
|
||||
|
||||
AstNode *res = ast_create_node(pc, NodeTypeFnCallExpr, async_token);
|
||||
res->data.fn_call_expr.is_async = true;
|
||||
res->data.fn_call_expr.seen = false;
|
||||
res->data.fn_call_expr.fn_ref_expr = child;
|
||||
res->data.fn_call_expr.params = args->data.fn_call_expr.params;
|
||||
res->data.fn_call_expr.is_builtin = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
AstNode *res = ast_parse_primary_type_expr(pc);
|
||||
@ -1510,7 +1500,7 @@ static AstNode *ast_parse_suffix_expr(ParseContext *pc) {
|
||||
// <- BUILTINIDENTIFIER FnCallArguments
|
||||
// / CHAR_LITERAL
|
||||
// / ContainerDecl
|
||||
// / DOT IDENTIFIER
|
||||
// / DOT IDENTIFIER
|
||||
// / ErrorSetDecl
|
||||
// / FLOAT
|
||||
// / FnProto
|
||||
@ -1643,9 +1633,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) {
|
||||
@ -2025,7 +2015,7 @@ static AstNode *ast_parse_link_section(ParseContext *pc) {
|
||||
// <- KEYWORD_nakedcc
|
||||
// / KEYWORD_stdcallcc
|
||||
// / KEYWORD_extern
|
||||
// / KEYWORD_async (LARROW TypeExpr RARROW)?
|
||||
// / KEYWORD_async
|
||||
static Optional<AstNodeFnProto> ast_parse_fn_cc(ParseContext *pc) {
|
||||
AstNodeFnProto res = {};
|
||||
if (eat_token_if(pc, TokenIdKeywordNakedCC) != nullptr) {
|
||||
@ -2042,11 +2032,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 +2507,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 +2518,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;
|
||||
}
|
||||
|
||||
@ -2671,24 +2656,6 @@ static AstNode *ast_parse_suffix_op(ParseContext *pc) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// AsyncPrefix <- KEYWORD_async (LARROW PrefixExpr RARROW)?
|
||||
static AstNode *ast_parse_async_prefix(ParseContext *pc) {
|
||||
Token *async = eat_token_if(pc, TokenIdKeywordAsync);
|
||||
if (async == nullptr)
|
||||
return nullptr;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// FnCallArguments <- LPAREN ExprList RPAREN
|
||||
static AstNode *ast_parse_fn_call_argumnets(ParseContext *pc) {
|
||||
Token *paren = eat_token_if(pc, TokenIdLParen);
|
||||
@ -2858,7 +2825,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 +2884,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 +2999,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 +3012,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;
|
||||
|
||||
@ -665,7 +665,63 @@ ZigLLVM_SubArchType target_subarch_enum(SubArchList sub_arch_list, size_t i) {
|
||||
}
|
||||
|
||||
const char *target_subarch_name(ZigLLVM_SubArchType subarch) {
|
||||
return ZigLLVMGetSubArchTypeName(subarch);
|
||||
switch (subarch) {
|
||||
case ZigLLVM_NoSubArch:
|
||||
return "";
|
||||
case ZigLLVM_ARMSubArch_v8_5a:
|
||||
return "v8_5a";
|
||||
case ZigLLVM_ARMSubArch_v8_4a:
|
||||
return "v8_4a";
|
||||
case ZigLLVM_ARMSubArch_v8_3a:
|
||||
return "v8_3a";
|
||||
case ZigLLVM_ARMSubArch_v8_2a:
|
||||
return "v8_2a";
|
||||
case ZigLLVM_ARMSubArch_v8_1a:
|
||||
return "v8_1a";
|
||||
case ZigLLVM_ARMSubArch_v8:
|
||||
return "v8";
|
||||
case ZigLLVM_ARMSubArch_v8r:
|
||||
return "v8r";
|
||||
case ZigLLVM_ARMSubArch_v8m_baseline:
|
||||
return "v8m_baseline";
|
||||
case ZigLLVM_ARMSubArch_v8m_mainline:
|
||||
return "v8m_mainline";
|
||||
case ZigLLVM_ARMSubArch_v7:
|
||||
return "v7";
|
||||
case ZigLLVM_ARMSubArch_v7em:
|
||||
return "v7em";
|
||||
case ZigLLVM_ARMSubArch_v7m:
|
||||
return "v7m";
|
||||
case ZigLLVM_ARMSubArch_v7s:
|
||||
return "v7s";
|
||||
case ZigLLVM_ARMSubArch_v7k:
|
||||
return "v7k";
|
||||
case ZigLLVM_ARMSubArch_v7ve:
|
||||
return "v7ve";
|
||||
case ZigLLVM_ARMSubArch_v6:
|
||||
return "v6";
|
||||
case ZigLLVM_ARMSubArch_v6m:
|
||||
return "v6m";
|
||||
case ZigLLVM_ARMSubArch_v6k:
|
||||
return "v6k";
|
||||
case ZigLLVM_ARMSubArch_v6t2:
|
||||
return "v6t2";
|
||||
case ZigLLVM_ARMSubArch_v5:
|
||||
return "v5";
|
||||
case ZigLLVM_ARMSubArch_v5te:
|
||||
return "v5te";
|
||||
case ZigLLVM_ARMSubArch_v4t:
|
||||
return "v4t";
|
||||
case ZigLLVM_KalimbaSubArch_v3:
|
||||
return "v3";
|
||||
case ZigLLVM_KalimbaSubArch_v4:
|
||||
return "v4";
|
||||
case ZigLLVM_KalimbaSubArch_v5:
|
||||
return "v5";
|
||||
case ZigLLVM_MipsSubArch_r6:
|
||||
return "r6";
|
||||
}
|
||||
zig_unreachable();
|
||||
}
|
||||
|
||||
size_t target_subarch_list_count(void) {
|
||||
@ -1827,3 +1883,7 @@ bool target_libc_needs_crti_crtn(const ZigTarget *target) {
|
||||
bool target_is_riscv(const ZigTarget *target) {
|
||||
return target->arch == ZigLLVM_riscv32 || target->arch == ZigLLVM_riscv64;
|
||||
}
|
||||
|
||||
unsigned target_fn_align(const ZigTarget *target) {
|
||||
return 16;
|
||||
}
|
||||
|
||||
@ -201,4 +201,6 @@ size_t target_libc_count(void);
|
||||
void target_libc_enum(size_t index, ZigTarget *out_target);
|
||||
bool target_libc_needs_crti_crtn(const ZigTarget *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>
|
||||
@ -202,8 +201,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);
|
||||
@ -797,25 +794,25 @@ const char *ZigLLVMGetSubArchTypeName(ZigLLVM_SubArchType sub_arch) {
|
||||
case ZigLLVM_NoSubArch:
|
||||
return "";
|
||||
case ZigLLVM_ARMSubArch_v8_5a:
|
||||
return "v8_5a";
|
||||
return "v8.5a";
|
||||
case ZigLLVM_ARMSubArch_v8_4a:
|
||||
return "v8_4a";
|
||||
return "v8.4a";
|
||||
case ZigLLVM_ARMSubArch_v8_3a:
|
||||
return "v8_3a";
|
||||
return "v8.3a";
|
||||
case ZigLLVM_ARMSubArch_v8_2a:
|
||||
return "v8_2a";
|
||||
return "v8.2a";
|
||||
case ZigLLVM_ARMSubArch_v8_1a:
|
||||
return "v8_1a";
|
||||
return "v8.1a";
|
||||
case ZigLLVM_ARMSubArch_v8:
|
||||
return "v8";
|
||||
case ZigLLVM_ARMSubArch_v8r:
|
||||
return "v8r";
|
||||
case ZigLLVM_ARMSubArch_v8m_baseline:
|
||||
return "v8m_baseline";
|
||||
return "v8m.base";
|
||||
case ZigLLVM_ARMSubArch_v8m_mainline:
|
||||
return "v8m_mainline";
|
||||
return "v8m.main";
|
||||
case ZigLLVM_ARMSubArch_v8_1m_mainline:
|
||||
return "v8_1m_mainline";
|
||||
return "v8.1m.main";
|
||||
case ZigLLVM_ARMSubArch_v7:
|
||||
return "v7";
|
||||
case ZigLLVM_ARMSubArch_v7em:
|
||||
@ -909,6 +906,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 ZigLLVMAddByValAttr(LLVMValueRef fn_ref, unsigned ArgNo, LLVMTypeRef type_val);
|
||||
|
||||
@ -6,3 +6,4 @@ pub const _errno = __error;
|
||||
|
||||
pub extern "c" fn getdents(fd: c_int, buf_ptr: [*]u8, nbytes: usize) usize;
|
||||
pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int;
|
||||
pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) c_int;
|
||||
|
||||
@ -1024,8 +1024,7 @@ pub fn openElfDebugInfo(
|
||||
elf_seekable_stream: *DwarfSeekableStream,
|
||||
elf_in_stream: *DwarfInStream,
|
||||
) !DwarfInfo {
|
||||
var efile: elf.Elf = undefined;
|
||||
try efile.openStream(allocator, elf_seekable_stream, elf_in_stream);
|
||||
var efile = try elf.Elf.openStream(allocator, elf_seekable_stream, elf_in_stream);
|
||||
errdefer efile.close();
|
||||
|
||||
var di = DwarfInfo{
|
||||
|
||||
15
std/elf.zig
15
std/elf.zig
@ -356,7 +356,6 @@ pub const SectionHeader = struct {
|
||||
pub const Elf = struct {
|
||||
seekable_stream: *io.SeekableStream(anyerror, anyerror),
|
||||
in_stream: *io.InStream(anyerror),
|
||||
auto_close_stream: bool,
|
||||
is_64: bool,
|
||||
endian: builtin.Endian,
|
||||
file_type: FileType,
|
||||
@ -368,25 +367,23 @@ pub const Elf = struct {
|
||||
string_section: *SectionHeader,
|
||||
section_headers: []SectionHeader,
|
||||
allocator: *mem.Allocator,
|
||||
prealloc_file: File,
|
||||
|
||||
/// Call close when done.
|
||||
pub fn openPath(elf: *Elf, allocator: *mem.Allocator, path: []const u8) !void {
|
||||
pub fn openPath(allocator: *mem.Allocator, path: []const u8) !Elf {
|
||||
@compileError("TODO implement");
|
||||
}
|
||||
|
||||
/// Call close when done.
|
||||
pub fn openFile(elf: *Elf, allocator: *mem.Allocator, file: File) !void {
|
||||
pub fn openFile(allocator: *mem.Allocator, file: File) !Elf {
|
||||
@compileError("TODO implement");
|
||||
}
|
||||
|
||||
pub fn openStream(
|
||||
elf: *Elf,
|
||||
allocator: *mem.Allocator,
|
||||
seekable_stream: *io.SeekableStream(anyerror, anyerror),
|
||||
in: *io.InStream(anyerror),
|
||||
) !void {
|
||||
elf.auto_close_stream = false;
|
||||
) !Elf {
|
||||
var elf: Elf = undefined;
|
||||
elf.allocator = allocator;
|
||||
elf.seekable_stream = seekable_stream;
|
||||
elf.in_stream = in;
|
||||
@ -523,12 +520,12 @@ pub const Elf = struct {
|
||||
// not a string table
|
||||
return error.InvalidFormat;
|
||||
}
|
||||
|
||||
return elf;
|
||||
}
|
||||
|
||||
pub fn close(elf: *Elf) void {
|
||||
elf.allocator.free(elf.section_headers);
|
||||
|
||||
if (elf.auto_close_stream) elf.prealloc_file.close();
|
||||
}
|
||||
|
||||
pub fn findSection(elf: *Elf, name: []const u8) !?*SectionHeader {
|
||||
|
||||
@ -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();
|
||||
var 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");
|
||||
var a = async waitOnFuture(&future);
|
||||
var b = async waitOnFuture(&future);
|
||||
var 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");
|
||||
var 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");
|
||||
var 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");
|
||||
var handle3 = async lockRunner(lock);
|
||||
var tick_node3 = Loop.NextTickNode{
|
||||
.prev = undefined,
|
||||
.next = undefined,
|
||||
.data = handle3,
|
||||
.data = &handle3,
|
||||
};
|
||||
loop.onNextTick(&tick_node3);
|
||||
|
||||
@ -185,8 +172,8 @@ 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 handle = await lock_promise;
|
||||
var lock_frame = async lock.acquire();
|
||||
const handle = await lock_frame;
|
||||
defer handle.release();
|
||||
|
||||
shared_test_index = 0;
|
||||
|
||||
@ -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,38 @@ 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 +686,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 +710,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 +744,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 +764,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 +795,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 +818,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 +878,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 +891,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 +900,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;
|
||||
var handle = async Loop.call(testEventLoop);
|
||||
var handle2 = async Loop.call(testEventLoop2, &handle, &did_it);
|
||||
|
||||
loop.run();
|
||||
|
||||
@ -899,7 +912,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);
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
const adler = @import("hash/adler.zig");
|
||||
pub const Adler32 = adler.Adler32;
|
||||
|
||||
const auto_hash = @import("hash/auto_hash.zig");
|
||||
pub const autoHash = auto_hash.autoHash;
|
||||
|
||||
// pub for polynomials + generic crc32 construction
|
||||
pub const crc = @import("hash/crc.zig");
|
||||
pub const Crc32 = crc.Crc32;
|
||||
@ -16,6 +19,7 @@ pub const SipHash128 = siphash.SipHash128;
|
||||
|
||||
pub const murmur = @import("hash/murmur.zig");
|
||||
pub const Murmur2_32 = murmur.Murmur2_32;
|
||||
|
||||
pub const Murmur2_64 = murmur.Murmur2_64;
|
||||
pub const Murmur3_32 = murmur.Murmur3_32;
|
||||
|
||||
@ -23,11 +27,16 @@ pub const cityhash = @import("hash/cityhash.zig");
|
||||
pub const CityHash32 = cityhash.CityHash32;
|
||||
pub const CityHash64 = cityhash.CityHash64;
|
||||
|
||||
const wyhash = @import("hash/wyhash.zig");
|
||||
pub const Wyhash = wyhash.Wyhash;
|
||||
|
||||
test "hash" {
|
||||
_ = @import("hash/adler.zig");
|
||||
_ = @import("hash/auto_hash.zig");
|
||||
_ = @import("hash/crc.zig");
|
||||
_ = @import("hash/fnv.zig");
|
||||
_ = @import("hash/siphash.zig");
|
||||
_ = @import("hash/murmur.zig");
|
||||
_ = @import("hash/cityhash.zig");
|
||||
_ = @import("hash/wyhash.zig");
|
||||
}
|
||||
|
||||
211
std/hash/auto_hash.zig
Normal file
211
std/hash/auto_hash.zig
Normal file
@ -0,0 +1,211 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const mem = std.mem;
|
||||
const meta = std.meta;
|
||||
|
||||
/// Provides generic hashing for any eligible type.
|
||||
/// Only hashes `key` itself, pointers are not followed.
|
||||
pub fn autoHash(hasher: var, key: var) void {
|
||||
const Key = @typeOf(key);
|
||||
switch (@typeInfo(Key)) {
|
||||
.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.
|
||||
.Int => @inlineCall(hasher.update, std.mem.asBytes(&key)),
|
||||
|
||||
.Float => |info| autoHash(hasher, @bitCast(@IntType(false, info.bits), key)),
|
||||
|
||||
.Bool => autoHash(hasher, @boolToInt(key)),
|
||||
.Enum => autoHash(hasher, @enumToInt(key)),
|
||||
.ErrorSet => autoHash(hasher, @errorToInt(key)),
|
||||
.AnyFrame, .Fn => autoHash(hasher, @ptrToInt(key)),
|
||||
|
||||
.Pointer => |info| switch (info.size) {
|
||||
builtin.TypeInfo.Pointer.Size.One,
|
||||
builtin.TypeInfo.Pointer.Size.Many,
|
||||
builtin.TypeInfo.Pointer.Size.C,
|
||||
=> autoHash(hasher, @ptrToInt(key)),
|
||||
|
||||
builtin.TypeInfo.Pointer.Size.Slice => {
|
||||
autoHash(hasher, key.ptr);
|
||||
autoHash(hasher, key.len);
|
||||
},
|
||||
},
|
||||
|
||||
.Optional => if (key) |k| autoHash(hasher, k),
|
||||
|
||||
.Array => {
|
||||
// TODO detect via a trait when Key has no padding bits to
|
||||
// hash it as an array of bytes.
|
||||
// Otherwise, hash every element.
|
||||
for (key) |element| {
|
||||
autoHash(hasher, element);
|
||||
}
|
||||
},
|
||||
|
||||
.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.
|
||||
hasher.update(mem.asBytes(&key));
|
||||
} else {
|
||||
// Otherwise, hash every element.
|
||||
// TODO remove the copy to an array once field access is done.
|
||||
const array: [info.len]info.child = key;
|
||||
comptime var i: u32 = 0;
|
||||
inline while (i < info.len) : (i += 1) {
|
||||
autoHash(hasher, array[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
.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.
|
||||
inline for (info.fields) |field| {
|
||||
// We reuse the hash of the previous field as the seed for the
|
||||
// next one so that they're dependant.
|
||||
autoHash(hasher, @field(key, field.name));
|
||||
}
|
||||
},
|
||||
|
||||
.Union => |info| blk: {
|
||||
if (info.tag_type) |tag_type| {
|
||||
const tag = meta.activeTag(key);
|
||||
const s = autoHash(hasher, tag);
|
||||
inline for (info.fields) |field| {
|
||||
const enum_field = field.enum_field.?;
|
||||
if (enum_field.value == @enumToInt(tag)) {
|
||||
autoHash(hasher, @field(key, enum_field.name));
|
||||
// TODO use a labelled break when it does not crash the compiler.
|
||||
// break :blk;
|
||||
return;
|
||||
}
|
||||
}
|
||||
unreachable;
|
||||
} else @compileError("cannot hash untagged union type: " ++ @typeName(Key) ++ ", provide your own hash function");
|
||||
},
|
||||
|
||||
.ErrorUnion => blk: {
|
||||
const payload = key catch |err| {
|
||||
autoHash(hasher, err);
|
||||
break :blk;
|
||||
};
|
||||
autoHash(hasher, payload);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const testing = std.testing;
|
||||
const Wyhash = std.hash.Wyhash;
|
||||
|
||||
fn testAutoHash(key: var) u64 {
|
||||
// Any hash could be used here, for testing autoHash.
|
||||
var hasher = Wyhash.init(0);
|
||||
autoHash(&hasher, key);
|
||||
return hasher.final();
|
||||
}
|
||||
|
||||
test "autoHash slice" {
|
||||
// Allocate one array dynamically so that we're assured it is not merged
|
||||
// with the other by the optimization passes.
|
||||
const array1 = try std.heap.direct_allocator.create([6]u32);
|
||||
defer std.heap.direct_allocator.destroy(array1);
|
||||
array1.* = [_]u32{ 1, 2, 3, 4, 5, 6 };
|
||||
const array2 = [_]u32{ 1, 2, 3, 4, 5, 6 };
|
||||
const a = array1[0..];
|
||||
const b = array2[0..];
|
||||
const c = array1[0..3];
|
||||
testing.expect(testAutoHash(a) == testAutoHash(a));
|
||||
testing.expect(testAutoHash(a) != testAutoHash(array1));
|
||||
testing.expect(testAutoHash(a) != testAutoHash(b));
|
||||
testing.expect(testAutoHash(a) != testAutoHash(c));
|
||||
}
|
||||
|
||||
test "testAutoHash optional" {
|
||||
const a: ?u32 = 123;
|
||||
const b: ?u32 = null;
|
||||
testing.expectEqual(testAutoHash(a), testAutoHash(u32(123)));
|
||||
testing.expect(testAutoHash(a) != testAutoHash(b));
|
||||
testing.expectEqual(testAutoHash(b), 0);
|
||||
}
|
||||
|
||||
test "testAutoHash array" {
|
||||
const a = [_]u32{ 1, 2, 3 };
|
||||
const h = testAutoHash(a);
|
||||
var hasher = Wyhash.init(0);
|
||||
autoHash(&hasher, u32(1));
|
||||
autoHash(&hasher, u32(2));
|
||||
autoHash(&hasher, u32(3));
|
||||
testing.expectEqual(h, hasher.final());
|
||||
}
|
||||
|
||||
test "testAutoHash struct" {
|
||||
const Foo = struct {
|
||||
a: u32 = 1,
|
||||
b: u32 = 2,
|
||||
c: u32 = 3,
|
||||
};
|
||||
const f = Foo{};
|
||||
const h = testAutoHash(f);
|
||||
var hasher = Wyhash.init(0);
|
||||
autoHash(&hasher, u32(1));
|
||||
autoHash(&hasher, u32(2));
|
||||
autoHash(&hasher, u32(3));
|
||||
testing.expectEqual(h, hasher.final());
|
||||
}
|
||||
|
||||
test "testAutoHash union" {
|
||||
const Foo = union(enum) {
|
||||
A: u32,
|
||||
B: f32,
|
||||
C: u32,
|
||||
};
|
||||
|
||||
const a = Foo{ .A = 18 };
|
||||
var b = Foo{ .B = 12.34 };
|
||||
const c = Foo{ .C = 18 };
|
||||
testing.expect(testAutoHash(a) == testAutoHash(a));
|
||||
testing.expect(testAutoHash(a) != testAutoHash(b));
|
||||
testing.expect(testAutoHash(a) != testAutoHash(c));
|
||||
|
||||
b = Foo{ .A = 18 };
|
||||
testing.expect(testAutoHash(a) == testAutoHash(b));
|
||||
}
|
||||
|
||||
test "testAutoHash vector" {
|
||||
const a: @Vector(4, u32) = [_]u32{ 1, 2, 3, 4 };
|
||||
const b: @Vector(4, u32) = [_]u32{ 1, 2, 3, 5 };
|
||||
const c: @Vector(4, u31) = [_]u31{ 1, 2, 3, 4 };
|
||||
testing.expect(testAutoHash(a) == testAutoHash(a));
|
||||
testing.expect(testAutoHash(a) != testAutoHash(b));
|
||||
testing.expect(testAutoHash(a) != testAutoHash(c));
|
||||
}
|
||||
|
||||
test "testAutoHash error union" {
|
||||
const Errors = error{Test};
|
||||
const Foo = struct {
|
||||
a: u32 = 1,
|
||||
b: u32 = 2,
|
||||
c: u32 = 3,
|
||||
};
|
||||
const f = Foo{};
|
||||
const g: Errors!Foo = Errors.Test;
|
||||
testing.expect(testAutoHash(f) != testAutoHash(g));
|
||||
testing.expect(testAutoHash(f) == testAutoHash(Foo{}));
|
||||
testing.expect(testAutoHash(g) == testAutoHash(Errors.Test));
|
||||
}
|
||||
148
std/hash/throughput_test.zig
Normal file
148
std/hash/throughput_test.zig
Normal file
@ -0,0 +1,148 @@
|
||||
const builtin = @import("builtin");
|
||||
const std = @import("std");
|
||||
const time = std.time;
|
||||
const Timer = time.Timer;
|
||||
const hash = std.hash;
|
||||
|
||||
const KiB = 1024;
|
||||
const MiB = 1024 * KiB;
|
||||
const GiB = 1024 * MiB;
|
||||
|
||||
var prng = std.rand.DefaultPrng.init(0);
|
||||
|
||||
const Hash = struct {
|
||||
ty: type,
|
||||
name: []const u8,
|
||||
init_u8s: ?[]const u8 = null,
|
||||
init_u64: ?u64 = null,
|
||||
};
|
||||
|
||||
const siphash_key = "0123456789abcdef";
|
||||
|
||||
const hashes = [_]Hash{
|
||||
Hash{ .ty = hash.Wyhash, .name = "wyhash", .init_u64 = 0 },
|
||||
Hash{ .ty = hash.SipHash64(1, 3), .name = "siphash(1,3)", .init_u8s = siphash_key },
|
||||
Hash{ .ty = hash.SipHash64(2, 4), .name = "siphash(2,4)", .init_u8s = siphash_key },
|
||||
Hash{ .ty = hash.Fnv1a_64, .name = "fnv1a" },
|
||||
Hash{ .ty = hash.Crc32, .name = "crc32" },
|
||||
};
|
||||
|
||||
const Result = struct {
|
||||
hash: u64,
|
||||
throughput: u64,
|
||||
};
|
||||
|
||||
pub fn benchmarkHash(comptime H: var, bytes: usize) !Result {
|
||||
var h = blk: {
|
||||
if (H.init_u8s) |init| {
|
||||
break :blk H.ty.init(init);
|
||||
}
|
||||
if (H.init_u64) |init| {
|
||||
break :blk H.ty.init(init);
|
||||
}
|
||||
break :blk H.ty.init();
|
||||
};
|
||||
|
||||
var block: [8192]u8 = undefined;
|
||||
prng.random.bytes(block[0..]);
|
||||
|
||||
var offset: usize = 0;
|
||||
var timer = try Timer.start();
|
||||
const start = timer.lap();
|
||||
while (offset < bytes) : (offset += block.len) {
|
||||
h.update(block[0..]);
|
||||
}
|
||||
const end = timer.read();
|
||||
|
||||
const elapsed_s = @intToFloat(f64, end - start) / time.ns_per_s;
|
||||
const throughput = @floatToInt(u64, @intToFloat(f64, bytes) / elapsed_s);
|
||||
|
||||
return Result{
|
||||
.hash = h.final(),
|
||||
.throughput = throughput,
|
||||
};
|
||||
}
|
||||
|
||||
fn usage() void {
|
||||
std.debug.warn(
|
||||
\\throughput_test [options]
|
||||
\\
|
||||
\\Options:
|
||||
\\ --filter [test-name]
|
||||
\\ --seed [int]
|
||||
\\ --count [int]
|
||||
\\ --help
|
||||
\\
|
||||
);
|
||||
}
|
||||
|
||||
fn mode(comptime x: comptime_int) comptime_int {
|
||||
return if (builtin.mode == builtin.Mode.Debug) x / 64 else x;
|
||||
}
|
||||
|
||||
// TODO(#1358): Replace with builtin formatted padding when available.
|
||||
fn printPad(stdout: var, s: []const u8) !void {
|
||||
var i: usize = 0;
|
||||
while (i < 12 - s.len) : (i += 1) {
|
||||
try stdout.print(" ");
|
||||
}
|
||||
try stdout.print("{}", s);
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
var stdout_file = try std.io.getStdOut();
|
||||
var stdout_out_stream = stdout_file.outStream();
|
||||
const stdout = &stdout_out_stream.stream;
|
||||
|
||||
var buffer: [1024]u8 = undefined;
|
||||
var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]);
|
||||
const args = try std.process.argsAlloc(&fixed.allocator);
|
||||
|
||||
var filter: ?[]u8 = "";
|
||||
var count: usize = mode(128 * MiB);
|
||||
|
||||
var i: usize = 1;
|
||||
while (i < args.len) : (i += 1) {
|
||||
if (std.mem.eql(u8, args[i], "--seed")) {
|
||||
i += 1;
|
||||
if (i == args.len) {
|
||||
usage();
|
||||
std.os.exit(1);
|
||||
}
|
||||
|
||||
const seed = try std.fmt.parseUnsigned(u32, args[i], 10);
|
||||
prng.seed(seed);
|
||||
} else if (std.mem.eql(u8, args[i], "--filter")) {
|
||||
i += 1;
|
||||
if (i == args.len) {
|
||||
usage();
|
||||
std.os.exit(1);
|
||||
}
|
||||
|
||||
filter = args[i];
|
||||
} else if (std.mem.eql(u8, args[i], "--count")) {
|
||||
i += 1;
|
||||
if (i == args.len) {
|
||||
usage();
|
||||
std.os.exit(1);
|
||||
}
|
||||
|
||||
const c = try std.fmt.parseUnsigned(u32, args[i], 10);
|
||||
count = c * MiB;
|
||||
} else if (std.mem.eql(u8, args[i], "--help")) {
|
||||
usage();
|
||||
return;
|
||||
} else {
|
||||
usage();
|
||||
std.os.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
inline for (hashes) |H| {
|
||||
if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) {
|
||||
const result = try benchmarkHash(H, count);
|
||||
try printPad(stdout, H.name);
|
||||
try stdout.print(": {:4} MiB/s [{:16}]\n", result.throughput / (1 * MiB), result.hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
135
std/hash/wyhash.zig
Normal file
135
std/hash/wyhash.zig
Normal file
@ -0,0 +1,135 @@
|
||||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
|
||||
const primes = [_]u64{
|
||||
0xa0761d6478bd642f,
|
||||
0xe7037ed1a0b428db,
|
||||
0x8ebc6af09c88c6e3,
|
||||
0x589965cc75374cc3,
|
||||
0x1d8e4e27c47d124f,
|
||||
};
|
||||
|
||||
fn read_bytes(comptime bytes: u8, data: []const u8) u64 {
|
||||
return mem.readVarInt(u64, data[0..bytes], .Little);
|
||||
}
|
||||
|
||||
fn read_8bytes_swapped(data: []const u8) u64 {
|
||||
return (read_bytes(4, data) << 32 | read_bytes(4, data[4..]));
|
||||
}
|
||||
|
||||
fn mum(a: u64, b: u64) u64 {
|
||||
var r = std.math.mulWide(u64, a, b);
|
||||
r = (r >> 64) ^ r;
|
||||
return @truncate(u64, r);
|
||||
}
|
||||
|
||||
fn mix0(a: u64, b: u64, seed: u64) u64 {
|
||||
return mum(a ^ seed ^ primes[0], b ^ seed ^ primes[1]);
|
||||
}
|
||||
|
||||
fn mix1(a: u64, b: u64, seed: u64) u64 {
|
||||
return mum(a ^ seed ^ primes[2], b ^ seed ^ primes[3]);
|
||||
}
|
||||
|
||||
pub const Wyhash = struct {
|
||||
seed: u64,
|
||||
msg_len: usize,
|
||||
|
||||
pub fn init(seed: u64) Wyhash {
|
||||
return Wyhash{
|
||||
.seed = seed,
|
||||
.msg_len = 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn round(self: *Wyhash, b: []const u8) void {
|
||||
std.debug.assert(b.len == 32);
|
||||
|
||||
self.seed = mix0(
|
||||
read_bytes(8, b[0..]),
|
||||
read_bytes(8, b[8..]),
|
||||
self.seed,
|
||||
) ^ mix1(
|
||||
read_bytes(8, b[16..]),
|
||||
read_bytes(8, b[24..]),
|
||||
self.seed,
|
||||
);
|
||||
}
|
||||
|
||||
fn partial(self: *Wyhash, b: []const u8) void {
|
||||
const rem_key = b;
|
||||
const rem_len = b.len;
|
||||
|
||||
var seed = self.seed;
|
||||
seed = switch (@intCast(u5, rem_len)) {
|
||||
0 => seed,
|
||||
1 => mix0(read_bytes(1, rem_key), primes[4], seed),
|
||||
2 => mix0(read_bytes(2, rem_key), primes[4], seed),
|
||||
3 => mix0((read_bytes(2, rem_key) << 8) | read_bytes(1, rem_key[2..]), primes[4], seed),
|
||||
4 => mix0(read_bytes(4, rem_key), primes[4], seed),
|
||||
5 => mix0((read_bytes(4, rem_key) << 8) | read_bytes(1, rem_key[4..]), primes[4], seed),
|
||||
6 => mix0((read_bytes(4, rem_key) << 16) | read_bytes(2, rem_key[4..]), primes[4], seed),
|
||||
7 => mix0((read_bytes(4, rem_key) << 24) | (read_bytes(2, rem_key[4..]) << 8) | read_bytes(1, rem_key[6..]), primes[4], seed),
|
||||
8 => mix0(read_8bytes_swapped(rem_key), primes[4], seed),
|
||||
9 => mix0(read_8bytes_swapped(rem_key), read_bytes(1, rem_key[8..]), seed),
|
||||
10 => mix0(read_8bytes_swapped(rem_key), read_bytes(2, rem_key[8..]), seed),
|
||||
11 => mix0(read_8bytes_swapped(rem_key), (read_bytes(2, rem_key[8..]) << 8) | read_bytes(1, rem_key[10..]), seed),
|
||||
12 => mix0(read_8bytes_swapped(rem_key), read_bytes(4, rem_key[8..]), seed),
|
||||
13 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 8) | read_bytes(1, rem_key[12..]), seed),
|
||||
14 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 16) | read_bytes(2, rem_key[12..]), seed),
|
||||
15 => mix0(read_8bytes_swapped(rem_key), (read_bytes(4, rem_key[8..]) << 24) | (read_bytes(2, rem_key[12..]) << 8) | read_bytes(1, rem_key[14..]), seed),
|
||||
16 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed),
|
||||
17 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(1, rem_key[16..]), primes[4], seed),
|
||||
18 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(2, rem_key[16..]), primes[4], seed),
|
||||
19 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(2, rem_key[16..]) << 8) | read_bytes(1, rem_key[18..]), primes[4], seed),
|
||||
20 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_bytes(4, rem_key[16..]), primes[4], seed),
|
||||
21 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 8) | read_bytes(1, rem_key[20..]), primes[4], seed),
|
||||
22 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 16) | read_bytes(2, rem_key[20..]), primes[4], seed),
|
||||
23 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1((read_bytes(4, rem_key[16..]) << 24) | (read_bytes(2, rem_key[20..]) << 8) | read_bytes(1, rem_key[22..]), primes[4], seed),
|
||||
24 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), primes[4], seed),
|
||||
25 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(1, rem_key[24..]), seed),
|
||||
26 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(2, rem_key[24..]), seed),
|
||||
27 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(2, rem_key[24..]) << 8) | read_bytes(1, rem_key[26..]), seed),
|
||||
28 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), read_bytes(4, rem_key[24..]), seed),
|
||||
29 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 8) | read_bytes(1, rem_key[28..]), seed),
|
||||
30 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 16) | read_bytes(2, rem_key[28..]), seed),
|
||||
31 => mix0(read_8bytes_swapped(rem_key), read_8bytes_swapped(rem_key[8..]), seed) ^ mix1(read_8bytes_swapped(rem_key[16..]), (read_bytes(4, rem_key[24..]) << 24) | (read_bytes(2, rem_key[28..]) << 8) | read_bytes(1, rem_key[30..]), seed),
|
||||
};
|
||||
self.seed = seed;
|
||||
}
|
||||
|
||||
pub fn update(self: *Wyhash, b: []const u8) void {
|
||||
var off: usize = 0;
|
||||
|
||||
// Full middle blocks.
|
||||
while (off + 32 <= b.len) : (off += 32) {
|
||||
@inlineCall(self.round, b[off .. off + 32]);
|
||||
}
|
||||
|
||||
self.partial(b[off..]);
|
||||
self.msg_len += b.len;
|
||||
}
|
||||
|
||||
pub fn final(self: *Wyhash) u64 {
|
||||
return mum(self.seed ^ self.msg_len, primes[4]);
|
||||
}
|
||||
|
||||
pub fn hash(seed: u64, input: []const u8) u64 {
|
||||
var c = Wyhash.init(seed);
|
||||
c.update(input);
|
||||
return c.final();
|
||||
}
|
||||
};
|
||||
|
||||
test "test vectors" {
|
||||
const expectEqual = std.testing.expectEqual;
|
||||
const hash = Wyhash.hash;
|
||||
|
||||
expectEqual(hash(0, ""), 0x0);
|
||||
expectEqual(hash(1, "a"), 0xbed235177f41d328);
|
||||
expectEqual(hash(2, "abc"), 0xbe348debe59b27c3);
|
||||
expectEqual(hash(3, "message digest"), 0x37320f657213a290);
|
||||
expectEqual(hash(4, "abcdefghijklmnopqrstuvwxyz"), 0xd0b270e1d8a7019c);
|
||||
expectEqual(hash(5, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 0x602a1894d3bbfe7f);
|
||||
expectEqual(hash(6, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"), 0x829e9c148b75970e);
|
||||
}
|
||||
123
std/hash_map.zig
123
std/hash_map.zig
@ -4,6 +4,9 @@ const assert = debug.assert;
|
||||
const testing = std.testing;
|
||||
const math = std.math;
|
||||
const mem = std.mem;
|
||||
const meta = std.meta;
|
||||
const autoHash = std.hash.autoHash;
|
||||
const Wyhash = std.hash.Wyhash;
|
||||
const Allocator = mem.Allocator;
|
||||
const builtin = @import("builtin");
|
||||
|
||||
@ -448,15 +451,17 @@ test "iterator hash map" {
|
||||
try reset_map.putNoClobber(2, 22);
|
||||
try reset_map.putNoClobber(3, 33);
|
||||
|
||||
// TODO this test depends on the hashing algorithm, because it assumes the
|
||||
// order of the elements in the hashmap. This should not be the case.
|
||||
var keys = [_]i32{
|
||||
1,
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
};
|
||||
var values = [_]i32{
|
||||
11,
|
||||
33,
|
||||
22,
|
||||
11,
|
||||
};
|
||||
|
||||
var it = reset_map.iterator();
|
||||
@ -518,8 +523,9 @@ pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) {
|
||||
pub fn getAutoHashFn(comptime K: type) (fn (K) u32) {
|
||||
return struct {
|
||||
fn hash(key: K) u32 {
|
||||
comptime var rng = comptime std.rand.DefaultPrng.init(0);
|
||||
return autoHash(key, &rng.random, u32);
|
||||
var hasher = Wyhash.init(0);
|
||||
autoHash(&hasher, key);
|
||||
return @truncate(u32, hasher.final());
|
||||
}
|
||||
}.hash;
|
||||
}
|
||||
@ -527,114 +533,7 @@ pub fn getAutoHashFn(comptime K: type) (fn (K) u32) {
|
||||
pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) {
|
||||
return struct {
|
||||
fn eql(a: K, b: K) bool {
|
||||
return autoEql(a, b);
|
||||
return meta.eql(a, b);
|
||||
}
|
||||
}.eql;
|
||||
}
|
||||
|
||||
// TODO improve these hash functions
|
||||
pub fn autoHash(key: var, comptime rng: *std.rand.Random, comptime HashInt: type) HashInt {
|
||||
switch (@typeInfo(@typeOf(key))) {
|
||||
builtin.TypeId.NoReturn,
|
||||
builtin.TypeId.Opaque,
|
||||
builtin.TypeId.Undefined,
|
||||
builtin.TypeId.ArgTuple,
|
||||
=> @compileError("cannot hash this type"),
|
||||
|
||||
builtin.TypeId.Void,
|
||||
builtin.TypeId.Null,
|
||||
=> return 0,
|
||||
|
||||
builtin.TypeId.Int => |info| {
|
||||
const unsigned_x = @bitCast(@IntType(false, info.bits), key);
|
||||
if (info.bits <= HashInt.bit_count) {
|
||||
return HashInt(unsigned_x) ^ comptime rng.scalar(HashInt);
|
||||
} else {
|
||||
return @truncate(HashInt, unsigned_x ^ comptime rng.scalar(@typeOf(unsigned_x)));
|
||||
}
|
||||
},
|
||||
|
||||
builtin.TypeId.Float => |info| {
|
||||
return autoHash(@bitCast(@IntType(false, info.bits), key), rng, HashInt);
|
||||
},
|
||||
builtin.TypeId.Bool => return autoHash(@boolToInt(key), rng, HashInt),
|
||||
builtin.TypeId.Enum => return autoHash(@enumToInt(key), rng, HashInt),
|
||||
builtin.TypeId.ErrorSet => return autoHash(@errorToInt(key), rng, HashInt),
|
||||
builtin.TypeId.Promise, builtin.TypeId.Fn => return autoHash(@ptrToInt(key), rng, HashInt),
|
||||
|
||||
builtin.TypeId.BoundFn,
|
||||
builtin.TypeId.ComptimeFloat,
|
||||
builtin.TypeId.ComptimeInt,
|
||||
builtin.TypeId.Type,
|
||||
builtin.TypeId.EnumLiteral,
|
||||
=> return 0,
|
||||
|
||||
builtin.TypeId.Pointer => |info| switch (info.size) {
|
||||
builtin.TypeInfo.Pointer.Size.One => @compileError("TODO auto hash for single item pointers"),
|
||||
builtin.TypeInfo.Pointer.Size.Many => @compileError("TODO auto hash for many item pointers"),
|
||||
builtin.TypeInfo.Pointer.Size.C => @compileError("TODO auto hash C pointers"),
|
||||
builtin.TypeInfo.Pointer.Size.Slice => {
|
||||
const interval = std.math.max(1, key.len / 256);
|
||||
var i: usize = 0;
|
||||
var h = comptime rng.scalar(HashInt);
|
||||
while (i < key.len) : (i += interval) {
|
||||
h ^= autoHash(key[i], rng, HashInt);
|
||||
}
|
||||
return h;
|
||||
},
|
||||
},
|
||||
|
||||
builtin.TypeId.Optional => @compileError("TODO auto hash for optionals"),
|
||||
builtin.TypeId.Array => @compileError("TODO auto hash for arrays"),
|
||||
builtin.TypeId.Vector => @compileError("TODO auto hash for vectors"),
|
||||
builtin.TypeId.Struct => @compileError("TODO auto hash for structs"),
|
||||
builtin.TypeId.Union => @compileError("TODO auto hash for unions"),
|
||||
builtin.TypeId.ErrorUnion => @compileError("TODO auto hash for unions"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn autoEql(a: var, b: @typeOf(a)) bool {
|
||||
switch (@typeInfo(@typeOf(a))) {
|
||||
builtin.TypeId.NoReturn,
|
||||
builtin.TypeId.Opaque,
|
||||
builtin.TypeId.Undefined,
|
||||
builtin.TypeId.ArgTuple,
|
||||
=> @compileError("cannot test equality of this type"),
|
||||
builtin.TypeId.Void,
|
||||
builtin.TypeId.Null,
|
||||
=> return true,
|
||||
builtin.TypeId.Bool,
|
||||
builtin.TypeId.Int,
|
||||
builtin.TypeId.Float,
|
||||
builtin.TypeId.ComptimeFloat,
|
||||
builtin.TypeId.ComptimeInt,
|
||||
builtin.TypeId.EnumLiteral,
|
||||
builtin.TypeId.Promise,
|
||||
builtin.TypeId.Enum,
|
||||
builtin.TypeId.BoundFn,
|
||||
builtin.TypeId.Fn,
|
||||
builtin.TypeId.ErrorSet,
|
||||
builtin.TypeId.Type,
|
||||
=> return a == b,
|
||||
|
||||
builtin.TypeId.Pointer => |info| switch (info.size) {
|
||||
builtin.TypeInfo.Pointer.Size.One => @compileError("TODO auto eql for single item pointers"),
|
||||
builtin.TypeInfo.Pointer.Size.Many => @compileError("TODO auto eql for many item pointers"),
|
||||
builtin.TypeInfo.Pointer.Size.C => @compileError("TODO auto eql for C pointers"),
|
||||
builtin.TypeInfo.Pointer.Size.Slice => {
|
||||
if (a.len != b.len) return false;
|
||||
for (a) |a_item, i| {
|
||||
if (!autoEql(a_item, b[i])) return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
|
||||
builtin.TypeId.Optional => @compileError("TODO auto eql for optionals"),
|
||||
builtin.TypeId.Array => @compileError("TODO auto eql for arrays"),
|
||||
builtin.TypeId.Struct => @compileError("TODO auto eql for structs"),
|
||||
builtin.TypeId.Union => @compileError("TODO auto eql for unions"),
|
||||
builtin.TypeId.ErrorUnion => @compileError("TODO auto eql for unions"),
|
||||
builtin.TypeId.Vector => @compileError("TODO auto eql for vectors"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,9 +102,19 @@ test "HeaderEntry" {
|
||||
testing.expectEqualSlices(u8, "x", e.value);
|
||||
}
|
||||
|
||||
fn stringEql(a: []const u8, b: []const u8) bool {
|
||||
if (a.len != b.len) return false;
|
||||
if (a.ptr == b.ptr) return true;
|
||||
return mem.compare(u8, a, b) == .Equal;
|
||||
}
|
||||
|
||||
fn stringHash(s: []const u8) u32 {
|
||||
return @truncate(u32, std.hash.Wyhash.hash(0, s));
|
||||
}
|
||||
|
||||
const HeaderList = std.ArrayList(HeaderEntry);
|
||||
const HeaderIndexList = std.ArrayList(usize);
|
||||
const HeaderIndex = std.AutoHashMap([]const u8, HeaderIndexList);
|
||||
const HeaderIndex = std.HashMap([]const u8, HeaderIndexList, stringHash, stringEql);
|
||||
|
||||
pub const Headers = struct {
|
||||
// the owned header field name is stored in the index as part of the key
|
||||
|
||||
70
std/math.zig
70
std/math.zig
@ -242,12 +242,76 @@ pub fn floatExponentBits(comptime T: type) comptime_int {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn min(x: var, y: var) @typeOf(x + y) {
|
||||
return if (x < y) x else y;
|
||||
/// Given two types, returns the smallest one which is capable of holding the
|
||||
/// full range of the minimum value.
|
||||
pub fn Min(comptime A: type, comptime B: type) type {
|
||||
switch (@typeInfo(A)) {
|
||||
.Int => |a_info| switch (@typeInfo(B)) {
|
||||
.Int => |b_info| if (!a_info.is_signed and !b_info.is_signed) {
|
||||
if (a_info.bits < b_info.bits) {
|
||||
return A;
|
||||
} else {
|
||||
return B;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
return @typeOf(A(0) + B(0));
|
||||
}
|
||||
|
||||
/// Returns the smaller number. When one of the parameter's type's full range fits in the other,
|
||||
/// the return type is the smaller type.
|
||||
pub fn min(x: var, y: var) Min(@typeOf(x), @typeOf(y)) {
|
||||
const Result = Min(@typeOf(x), @typeOf(y));
|
||||
if (x < y) {
|
||||
// TODO Zig should allow this as an implicit cast because x is immutable and in this
|
||||
// scope it is known to fit in the return type.
|
||||
switch (@typeInfo(Result)) {
|
||||
.Int => return @intCast(Result, x),
|
||||
else => return x,
|
||||
}
|
||||
} else {
|
||||
// TODO Zig should allow this as an implicit cast because y is immutable and in this
|
||||
// scope it is known to fit in the return type.
|
||||
switch (@typeInfo(Result)) {
|
||||
.Int => return @intCast(Result, y),
|
||||
else => return y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "math.min" {
|
||||
testing.expect(min(i32(-1), i32(2)) == -1);
|
||||
{
|
||||
var a: u16 = 999;
|
||||
var b: u32 = 10;
|
||||
var result = min(a, b);
|
||||
testing.expect(@typeOf(result) == u16);
|
||||
testing.expect(result == 10);
|
||||
}
|
||||
{
|
||||
var a: f64 = 10.34;
|
||||
var b: f32 = 999.12;
|
||||
var result = min(a, b);
|
||||
testing.expect(@typeOf(result) == f64);
|
||||
testing.expect(result == 10.34);
|
||||
}
|
||||
{
|
||||
var a: i8 = -127;
|
||||
var b: i16 = -200;
|
||||
var result = min(a, b);
|
||||
testing.expect(@typeOf(result) == i16);
|
||||
testing.expect(result == -200);
|
||||
}
|
||||
{
|
||||
const a = 10.34;
|
||||
var b: f32 = 999.12;
|
||||
var result = min(a, b);
|
||||
testing.expect(@typeOf(result) == f32);
|
||||
testing.expect(result == 10.34);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max(x: var, y: var) @typeOf(x + y) {
|
||||
@ -309,7 +373,7 @@ test "math.shl" {
|
||||
}
|
||||
|
||||
/// Shifts right. Overflowed bits are truncated.
|
||||
/// A negative shift amount results in a lefft shift.
|
||||
/// A negative shift amount results in a left shift.
|
||||
pub fn shr(comptime T: type, a: T, shift_amt: var) T {
|
||||
const abs_shift_amt = absCast(shift_amt);
|
||||
const casted_shift_amt = if (abs_shift_amt >= T.bit_count) return 0 else @intCast(Log2Int(T), abs_shift_amt);
|
||||
|
||||
@ -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 {
|
||||
|
||||
13
std/os.zig
13
std/os.zig
@ -120,6 +120,19 @@ pub fn getrandom(buf: []u8) GetRandomError!void {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (freebsd.is_the_target) {
|
||||
while (true) {
|
||||
const err = std.c.getErrno(std.c.getrandom(buf.ptr, buf.len, 0));
|
||||
|
||||
switch (err) {
|
||||
0 => return,
|
||||
EINVAL => unreachable,
|
||||
EFAULT => unreachable,
|
||||
EINTR => continue,
|
||||
else => return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wasi.is_the_target) {
|
||||
switch (wasi.random_get(buf.ptr, buf.len)) {
|
||||
0 => return,
|
||||
|
||||
@ -760,3 +760,62 @@ pub const stack_t = extern struct {
|
||||
ss_size: isize,
|
||||
ss_flags: i32,
|
||||
};
|
||||
|
||||
pub const S_IFMT = 0o170000;
|
||||
|
||||
pub const S_IFIFO = 0o010000;
|
||||
pub const S_IFCHR = 0o020000;
|
||||
pub const S_IFDIR = 0o040000;
|
||||
pub const S_IFBLK = 0o060000;
|
||||
pub const S_IFREG = 0o100000;
|
||||
pub const S_IFLNK = 0o120000;
|
||||
pub const S_IFSOCK = 0o140000;
|
||||
pub const S_IFWHT = 0o160000;
|
||||
|
||||
pub const S_ISUID = 0o4000;
|
||||
pub const S_ISGID = 0o2000;
|
||||
pub const S_ISVTX = 0o1000;
|
||||
pub const S_IRWXU = 0o700;
|
||||
pub const S_IRUSR = 0o400;
|
||||
pub const S_IWUSR = 0o200;
|
||||
pub const S_IXUSR = 0o100;
|
||||
pub const S_IRWXG = 0o070;
|
||||
pub const S_IRGRP = 0o040;
|
||||
pub const S_IWGRP = 0o020;
|
||||
pub const S_IXGRP = 0o010;
|
||||
pub const S_IRWXO = 0o007;
|
||||
pub const S_IROTH = 0o004;
|
||||
pub const S_IWOTH = 0o002;
|
||||
pub const S_IXOTH = 0o001;
|
||||
|
||||
pub fn S_ISFIFO(m: u32) bool {
|
||||
return m & S_IFMT == S_IFIFO;
|
||||
}
|
||||
|
||||
pub fn S_ISCHR(m: u32) bool {
|
||||
return m & S_IFMT == S_IFCHR;
|
||||
}
|
||||
|
||||
pub fn S_ISDIR(m: u32) bool {
|
||||
return m & S_IFMT == S_IFDIR;
|
||||
}
|
||||
|
||||
pub fn S_ISBLK(m: u32) bool {
|
||||
return m & S_IFMT == S_IFBLK;
|
||||
}
|
||||
|
||||
pub fn S_ISREG(m: u32) bool {
|
||||
return m & S_IFMT == S_IFREG;
|
||||
}
|
||||
|
||||
pub fn S_ISLNK(m: u32) bool {
|
||||
return m & S_IFMT == S_IFLNK;
|
||||
}
|
||||
|
||||
pub fn S_ISSOCK(m: u32) bool {
|
||||
return m & S_IFMT == S_IFSOCK;
|
||||
}
|
||||
|
||||
pub fn S_IWHT(m: u32) bool {
|
||||
return m & S_IFMT == S_IFWHT;
|
||||
}
|
||||
|
||||
@ -2,3 +2,4 @@ const builtin = @import("builtin");
|
||||
const std = @import("../std.zig");
|
||||
pub const is_the_target = builtin.os == .netbsd;
|
||||
pub usingnamespace std.c;
|
||||
pub usingnamespace @import("bits.zig");
|
||||
|
||||
@ -138,10 +138,19 @@ pub const RtlGenRandomError = error{Unexpected};
|
||||
/// https://github.com/rust-lang-nursery/rand/issues/111
|
||||
/// https://bugzilla.mozilla.org/show_bug.cgi?id=504270
|
||||
pub fn RtlGenRandom(output: []u8) RtlGenRandomError!void {
|
||||
if (advapi32.RtlGenRandom(output.ptr, output.len) == 0) {
|
||||
switch (kernel32.GetLastError()) {
|
||||
else => |err| return unexpectedError(err),
|
||||
var total_read: usize = 0;
|
||||
var buff: []u8 = output[0..];
|
||||
const max_read_size: ULONG = maxInt(ULONG);
|
||||
|
||||
while (total_read < output.len) {
|
||||
const to_read: ULONG = math.min(buff.len, max_read_size);
|
||||
|
||||
if (advapi32.RtlGenRandom(buff.ptr, to_read) == 0) {
|
||||
return unexpectedError(kernel32.GetLastError());
|
||||
}
|
||||
|
||||
total_read += to_read;
|
||||
buff = buff[to_read..];
|
||||
}
|
||||
}
|
||||
|
||||
@ -866,7 +875,6 @@ pub fn unexpectedError(err: DWORD) std.os.UnexpectedError {
|
||||
return error.Unexpected;
|
||||
}
|
||||
|
||||
|
||||
/// Call this when you made a windows NtDll call
|
||||
/// and you get an unexpected status.
|
||||
pub fn unexpectedStatus(status: NTSTATUS) std.os.UnexpectedError {
|
||||
|
||||
@ -19,5 +19,5 @@ pub extern "advapi32" stdcallcc fn RegQueryValueExW(
|
||||
|
||||
// RtlGenRandom is known as SystemFunction036 under advapi32
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa387694.aspx */
|
||||
pub extern "advapi32" stdcallcc fn SystemFunction036(output: [*]u8, length: usize) BOOL;
|
||||
pub extern "advapi32" stdcallcc fn SystemFunction036(output: [*]u8, length: ULONG) BOOL;
|
||||
pub const RtlGenRandom = SystemFunction036;
|
||||
|
||||
@ -209,7 +209,7 @@ pub const FILE_INFORMATION_CLASS = extern enum {
|
||||
FileLinkInformationExBypassAccessCheck,
|
||||
FileStorageReserveIdInformation,
|
||||
FileCaseSensitiveInformationForceAccessCheck,
|
||||
FileMaximumInformation
|
||||
FileMaximumInformation,
|
||||
};
|
||||
|
||||
pub const OVERLAPPED = extern struct {
|
||||
|
||||
@ -1,7 +1,13 @@
|
||||
usingnamespace @import("bits.zig");
|
||||
|
||||
pub extern "NtDll" stdcallcc fn RtlCaptureStackBackTrace(FramesToSkip: DWORD, FramesToCapture: DWORD, BackTrace: **c_void, BackTraceHash: ?*DWORD) WORD;
|
||||
pub extern "NtDll" stdcallcc fn NtQueryInformationFile(FileHandle: HANDLE, IoStatusBlock: *IO_STATUS_BLOCK, FileInformation: *c_void, Length: ULONG, FileInformationClass: FILE_INFORMATION_CLASS,) NTSTATUS;
|
||||
pub extern "NtDll" stdcallcc fn NtQueryInformationFile(
|
||||
FileHandle: HANDLE,
|
||||
IoStatusBlock: *IO_STATUS_BLOCK,
|
||||
FileInformation: *c_void,
|
||||
Length: ULONG,
|
||||
FileInformationClass: FILE_INFORMATION_CLASS,
|
||||
) NTSTATUS;
|
||||
pub extern "NtDll" stdcallcc fn NtCreateFile(
|
||||
FileHandle: *HANDLE,
|
||||
DesiredAccess: ACCESS_MASK,
|
||||
|
||||
@ -549,7 +549,6 @@ test "rb" {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
test "inserting and looking up" {
|
||||
var tree: Tree = undefined;
|
||||
tree.init(testCompare);
|
||||
|
||||
@ -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,
|
||||
@ -434,7 +434,6 @@ pub const Node = struct {
|
||||
ErrorTag,
|
||||
AsmInput,
|
||||
AsmOutput,
|
||||
AsyncAttribute,
|
||||
ParamDecl,
|
||||
FieldInitializer,
|
||||
};
|
||||
@ -838,36 +837,6 @@ pub const Node = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const AsyncAttribute = struct {
|
||||
base: Node,
|
||||
async_token: TokenIndex,
|
||||
allocator_type: ?*Node,
|
||||
rangle_bracket: ?TokenIndex,
|
||||
|
||||
pub fn iterate(self: *AsyncAttribute, index: usize) ?*Node {
|
||||
var i = index;
|
||||
|
||||
if (self.allocator_type) |allocator_type| {
|
||||
if (i < 1) return allocator_type;
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn firstToken(self: *const AsyncAttribute) TokenIndex {
|
||||
return self.async_token;
|
||||
}
|
||||
|
||||
pub fn lastToken(self: *const AsyncAttribute) TokenIndex {
|
||||
if (self.rangle_bracket) |rangle_bracket| {
|
||||
return rangle_bracket;
|
||||
}
|
||||
|
||||
return self.async_token;
|
||||
}
|
||||
};
|
||||
|
||||
pub const FnProto = struct {
|
||||
base: Node,
|
||||
doc_comments: ?*DocComment,
|
||||
@ -879,7 +848,6 @@ pub const Node = struct {
|
||||
var_args_token: ?TokenIndex,
|
||||
extern_export_inline_token: ?TokenIndex,
|
||||
cc_token: ?TokenIndex,
|
||||
async_attr: ?*AsyncAttribute,
|
||||
body_node: ?*Node,
|
||||
lib_name: ?*Node, // populated if this is an extern declaration
|
||||
align_expr: ?*Node, // populated if align(A) is present
|
||||
@ -935,7 +903,6 @@ pub const Node = struct {
|
||||
|
||||
pub fn firstToken(self: *const FnProto) TokenIndex {
|
||||
if (self.visib_token) |visib_token| return visib_token;
|
||||
if (self.async_attr) |async_attr| return async_attr.firstToken();
|
||||
if (self.extern_export_inline_token) |extern_export_inline_token| return extern_export_inline_token;
|
||||
assert(self.lib_name == null);
|
||||
if (self.cc_token) |cc_token| return cc_token;
|
||||
@ -952,9 +919,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 +929,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 +940,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;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1699,7 +1666,7 @@ pub const Node = struct {
|
||||
|
||||
pub const Call = struct {
|
||||
params: ParamList,
|
||||
async_attr: ?*AsyncAttribute,
|
||||
async_token: ?TokenIndex,
|
||||
|
||||
pub const ParamList = SegmentedList(*Node, 2);
|
||||
};
|
||||
@ -1752,7 +1719,7 @@ pub const Node = struct {
|
||||
|
||||
pub fn firstToken(self: *const SuffixOp) TokenIndex {
|
||||
switch (self.op) {
|
||||
.Call => |*call_info| if (call_info.async_attr) |async_attr| return async_attr.firstToken(),
|
||||
.Call => |*call_info| if (call_info.async_token) |async_token| return async_token,
|
||||
else => {},
|
||||
}
|
||||
return self.lhs.firstToken();
|
||||
|
||||
@ -277,7 +277,7 @@ fn parseTopLevelDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node
|
||||
|
||||
/// FnProto <- FnCC? KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? EXCLAMATIONMARK? (KEYWORD_var / TypeExpr)
|
||||
fn parseFnProto(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
||||
const cc = try parseFnCC(arena, it, tree);
|
||||
const cc = parseFnCC(arena, it, tree);
|
||||
const fn_token = eatToken(it, .Keyword_fn) orelse {
|
||||
if (cc == null) return null else return error.ParseError;
|
||||
};
|
||||
@ -320,7 +320,6 @@ fn parseFnProto(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
||||
.var_args_token = var_args_token,
|
||||
.extern_export_inline_token = null,
|
||||
.cc_token = null,
|
||||
.async_attr = null,
|
||||
.body_node = null,
|
||||
.lib_name = null,
|
||||
.align_expr = align_expr,
|
||||
@ -331,7 +330,6 @@ fn parseFnProto(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
||||
switch (kind) {
|
||||
.CC => |token| fn_proto_node.cc_token = token,
|
||||
.Extern => |token| fn_proto_node.extern_export_inline_token = token,
|
||||
.Async => |node| fn_proto_node.async_attr = node,
|
||||
}
|
||||
}
|
||||
|
||||
@ -814,7 +812,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 +836,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 },
|
||||
@ -1107,10 +1090,19 @@ fn parseErrorUnionExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*No
|
||||
}
|
||||
|
||||
/// SuffixExpr
|
||||
/// <- AsyncPrefix PrimaryTypeExpr SuffixOp* FnCallArguments
|
||||
/// <- KEYWORD_async PrimaryTypeExpr SuffixOp* FnCallArguments
|
||||
/// / PrimaryTypeExpr (SuffixOp / FnCallArguments)*
|
||||
fn parseSuffixExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
||||
if (try parseAsyncPrefix(arena, it, tree)) |async_node| {
|
||||
if (eatToken(it, .Keyword_async)) |async_token| {
|
||||
if (eatToken(it, .Keyword_fn)) |token_fn| {
|
||||
// HACK: If we see the keyword `fn`, then we assume that
|
||||
// we are parsing an async fn proto, and not a call.
|
||||
// We therefore put back all tokens consumed by the async
|
||||
// prefix...
|
||||
putBackToken(it, token_fn);
|
||||
putBackToken(it, async_token);
|
||||
return parsePrimaryTypeExpr(arena, it, tree);
|
||||
}
|
||||
// TODO: Implement hack for parsing `async fn ...` in ast_parse_suffix_expr
|
||||
var res = try expectNode(arena, it, tree, parsePrimaryTypeExpr, AstError{
|
||||
.ExpectedPrimaryTypeExpr = AstError.ExpectedPrimaryTypeExpr{ .token = it.index },
|
||||
@ -1131,7 +1123,6 @@ fn parseSuffixExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
||||
});
|
||||
return null;
|
||||
};
|
||||
|
||||
const node = try arena.create(Node.SuffixOp);
|
||||
node.* = Node.SuffixOp{
|
||||
.base = Node{ .id = .SuffixOp },
|
||||
@ -1139,14 +1130,13 @@ fn parseSuffixExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
||||
.op = Node.SuffixOp.Op{
|
||||
.Call = Node.SuffixOp.Op.Call{
|
||||
.params = params.list,
|
||||
.async_attr = async_node.cast(Node.AsyncAttribute).?,
|
||||
.async_token = async_token,
|
||||
},
|
||||
},
|
||||
.rtoken = params.rparen,
|
||||
};
|
||||
return &node.base;
|
||||
}
|
||||
|
||||
if (try parsePrimaryTypeExpr(arena, it, tree)) |expr| {
|
||||
var res = expr;
|
||||
|
||||
@ -1168,7 +1158,7 @@ fn parseSuffixExpr(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
||||
.op = Node.SuffixOp.Op{
|
||||
.Call = Node.SuffixOp.Op.Call{
|
||||
.params = params.list,
|
||||
.async_attr = null,
|
||||
.async_token = null,
|
||||
},
|
||||
},
|
||||
.rtoken = params.rparen,
|
||||
@ -1201,7 +1191,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 +1246,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;
|
||||
@ -1668,36 +1658,18 @@ fn parseLinkSection(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node
|
||||
/// <- KEYWORD_nakedcc
|
||||
/// / KEYWORD_stdcallcc
|
||||
/// / KEYWORD_extern
|
||||
/// / KEYWORD_async (LARROW TypeExpr RARROW)?
|
||||
fn parseFnCC(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?FnCC {
|
||||
/// / KEYWORD_async
|
||||
fn parseFnCC(arena: *Allocator, it: *TokenIterator, tree: *Tree) ?FnCC {
|
||||
if (eatToken(it, .Keyword_nakedcc)) |token| return FnCC{ .CC = token };
|
||||
if (eatToken(it, .Keyword_stdcallcc)) |token| return FnCC{ .CC = token };
|
||||
if (eatToken(it, .Keyword_extern)) |token| return FnCC{ .Extern = token };
|
||||
if (eatToken(it, .Keyword_async)) |token| {
|
||||
const node = try arena.create(Node.AsyncAttribute);
|
||||
node.* = Node.AsyncAttribute{
|
||||
.base = Node{ .id = .AsyncAttribute },
|
||||
.async_token = token,
|
||||
.allocator_type = null,
|
||||
.rangle_bracket = null,
|
||||
};
|
||||
if (eatToken(it, .AngleBracketLeft)) |_| {
|
||||
const type_expr = try expectNode(arena, it, tree, parseTypeExpr, AstError{
|
||||
.ExpectedTypeExpr = AstError.ExpectedTypeExpr{ .token = it.index },
|
||||
});
|
||||
const rarrow = try expectToken(it, tree, .AngleBracketRight);
|
||||
node.allocator_type = type_expr;
|
||||
node.rangle_bracket = rarrow;
|
||||
}
|
||||
return FnCC{ .Async = node };
|
||||
}
|
||||
if (eatToken(it, .Keyword_async)) |token| return FnCC{ .CC = token };
|
||||
return null;
|
||||
}
|
||||
|
||||
const FnCC = union(enum) {
|
||||
CC: TokenIndex,
|
||||
Extern: TokenIndex,
|
||||
Async: *Node.AsyncAttribute,
|
||||
};
|
||||
|
||||
/// ParamDecl <- (KEYWORD_noalias / KEYWORD_comptime)? (IDENTIFIER COLON)? ParamType
|
||||
@ -2194,7 +2166,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 +2181,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
|
||||
},
|
||||
@ -2424,28 +2396,6 @@ fn parseSuffixOp(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
||||
return &node.base;
|
||||
}
|
||||
|
||||
/// AsyncPrefix <- KEYWORD_async (LARROW PrefixExpr RARROW)?
|
||||
fn parseAsyncPrefix(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
||||
const async_token = eatToken(it, .Keyword_async) orelse return null;
|
||||
var rangle_bracket: ?TokenIndex = null;
|
||||
const expr_node = if (eatToken(it, .AngleBracketLeft)) |_| blk: {
|
||||
const prefix_expr = try expectNode(arena, it, tree, parsePrefixExpr, AstError{
|
||||
.ExpectedPrefixExpr = AstError.ExpectedPrefixExpr{ .token = it.index },
|
||||
});
|
||||
rangle_bracket = try expectToken(it, tree, .AngleBracketRight);
|
||||
break :blk prefix_expr;
|
||||
} else null;
|
||||
|
||||
const node = try arena.create(Node.AsyncAttribute);
|
||||
node.* = Node.AsyncAttribute{
|
||||
.base = Node{ .id = .AsyncAttribute },
|
||||
.async_token = async_token,
|
||||
.allocator_type = expr_node,
|
||||
.rangle_bracket = rangle_bracket,
|
||||
};
|
||||
return &node.base;
|
||||
}
|
||||
|
||||
/// FnCallArguments <- LPAREN ExprList RPAREN
|
||||
/// ExprList <- (Expr COMMA)* Expr?
|
||||
fn parseFnCallArguments(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?AnnotatedParamList {
|
||||
@ -2903,8 +2853,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 +2872,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 },
|
||||
});
|
||||
|
||||
@ -8,6 +8,18 @@ test "zig fmt: change use to usingnamespace" {
|
||||
);
|
||||
}
|
||||
|
||||
test "zig fmt: async function" {
|
||||
try testCanonical(
|
||||
\\pub const Server = struct {
|
||||
\\ handleRequestFn: async fn (*Server, *const std.net.Address, File) void,
|
||||
\\};
|
||||
\\test "hi" {
|
||||
\\ var ptr = @ptrCast(async fn (i32) void, other);
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
}
|
||||
|
||||
test "zig fmt: whitespace fixes" {
|
||||
try testTransform("test \"\" {\r\n\tconst hi = x;\r\n}\n// zig fmt: off\ntest \"\"{\r\n\tconst a = b;}\r\n",
|
||||
\\test "" {
|
||||
@ -210,7 +222,7 @@ test "zig fmt: spaces around slice operator" {
|
||||
test "zig fmt: async call in if condition" {
|
||||
try testCanonical(
|
||||
\\comptime {
|
||||
\\ if (async<a> b()) {
|
||||
\\ if (async b()) {
|
||||
\\ a();
|
||||
\\ }
|
||||
\\}
|
||||
@ -1118,7 +1130,7 @@ test "zig fmt: first line comment in struct initializer" {
|
||||
\\pub async fn acquire(self: *Self) HeldLock {
|
||||
\\ return HeldLock{
|
||||
\\ // guaranteed allocation elision
|
||||
\\ .held = await (async self.lock.acquire() catch unreachable),
|
||||
\\ .held = self.lock.acquire(),
|
||||
\\ .value = &self.private_data,
|
||||
\\ };
|
||||
\\}
|
||||
@ -1183,7 +1195,7 @@ test "zig fmt: resume from suspend block" {
|
||||
try testCanonical(
|
||||
\\fn foo() void {
|
||||
\\ suspend {
|
||||
\\ resume @handle();
|
||||
\\ resume @frame();
|
||||
\\ }
|
||||
\\}
|
||||
\\
|
||||
@ -2103,7 +2115,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 +2123,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;
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
|
||||
@ -284,20 +284,6 @@ fn renderExpression(
|
||||
return renderExpression(allocator, stream, tree, indent, start_col, comptime_node.expr, space);
|
||||
},
|
||||
|
||||
ast.Node.Id.AsyncAttribute => {
|
||||
const async_attr = @fieldParentPtr(ast.Node.AsyncAttribute, "base", base);
|
||||
|
||||
if (async_attr.allocator_type) |allocator_type| {
|
||||
try renderToken(tree, stream, async_attr.async_token, indent, start_col, Space.None); // async
|
||||
|
||||
try renderToken(tree, stream, tree.nextToken(async_attr.async_token), indent, start_col, Space.None); // <
|
||||
try renderExpression(allocator, stream, tree, indent, start_col, allocator_type, Space.None); // allocator
|
||||
return renderToken(tree, stream, tree.nextToken(allocator_type.lastToken()), indent, start_col, space); // >
|
||||
} else {
|
||||
return renderToken(tree, stream, async_attr.async_token, indent, start_col, space); // async
|
||||
}
|
||||
},
|
||||
|
||||
ast.Node.Id.Suspend => {
|
||||
const suspend_node = @fieldParentPtr(ast.Node.Suspend, "base", base);
|
||||
|
||||
@ -459,8 +445,8 @@ fn renderExpression(
|
||||
|
||||
switch (suffix_op.op) {
|
||||
@TagType(ast.Node.SuffixOp.Op).Call => |*call_info| {
|
||||
if (call_info.async_attr) |async_attr| {
|
||||
try renderExpression(allocator, stream, tree, indent, start_col, &async_attr.base, Space.Space);
|
||||
if (call_info.async_token) |async_token| {
|
||||
try renderToken(tree, stream, async_token, indent, start_col, Space.Space);
|
||||
}
|
||||
|
||||
try renderExpression(allocator, stream, tree, indent, start_col, suffix_op.lhs, Space.None);
|
||||
@ -1121,10 +1107,6 @@ fn renderExpression(
|
||||
try renderToken(tree, stream, cc_token, indent, start_col, Space.Space); // stdcallcc
|
||||
}
|
||||
|
||||
if (fn_proto.async_attr) |async_attr| {
|
||||
try renderExpression(allocator, stream, tree, indent, start_col, &async_attr.base, Space.Space);
|
||||
}
|
||||
|
||||
const lparen = if (fn_proto.name_token) |name_token| blk: {
|
||||
try renderToken(tree, stream, fn_proto.fn_token, indent, start_col, Space.Space); // fn
|
||||
try renderToken(tree, stream, name_token, indent, start_col, Space.None); // name
|
||||
@ -1205,15 +1187,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,220 @@ const tests = @import("tests.zig");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pub fn addCases(cases: *tests.CompileErrorContext) void {
|
||||
cases.add(
|
||||
"result location incompatibility mismatching handle_is_ptr (generic call)",
|
||||
\\export fn entry() void {
|
||||
\\ var damn = Container{
|
||||
\\ .not_optional = getOptional(i32),
|
||||
\\ };
|
||||
\\}
|
||||
\\pub fn getOptional(comptime T: type) ?T {
|
||||
\\ return 0;
|
||||
\\}
|
||||
\\pub const Container = struct {
|
||||
\\ not_optional: i32,
|
||||
\\};
|
||||
,
|
||||
"tmp.zig:3:36: error: expected type 'i32', found '?i32'",
|
||||
);
|
||||
|
||||
cases.add(
|
||||
"result location incompatibility mismatching handle_is_ptr",
|
||||
\\export fn entry() void {
|
||||
\\ var damn = Container{
|
||||
\\ .not_optional = getOptional(),
|
||||
\\ };
|
||||
\\}
|
||||
\\pub fn getOptional() ?i32 {
|
||||
\\ return 0;
|
||||
\\}
|
||||
\\pub const Container = struct {
|
||||
\\ not_optional: i32,
|
||||
\\};
|
||||
,
|
||||
"tmp.zig:3:36: error: expected type 'i32', found '?i32'",
|
||||
);
|
||||
|
||||
cases.add(
|
||||
"const frame cast to anyframe",
|
||||
\\export fn a() void {
|
||||
\\ const f = async func();
|
||||
\\ resume f;
|
||||
\\}
|
||||
\\export fn b() void {
|
||||
\\ const f = async func();
|
||||
\\ var x: anyframe = &f;
|
||||
\\}
|
||||
\\fn func() void {
|
||||
\\ suspend;
|
||||
\\}
|
||||
,
|
||||
"tmp.zig:3:12: error: expected type 'anyframe', found '*const @Frame(func)'",
|
||||
"tmp.zig:7:24: error: expected type 'anyframe', found '*const @Frame(func)'",
|
||||
);
|
||||
|
||||
cases.add(
|
||||
"prevent bad implicit casting of anyframe types",
|
||||
\\export fn a() void {
|
||||
\\ var x: anyframe = undefined;
|
||||
\\ var y: anyframe->i32 = x;
|
||||
\\}
|
||||
\\export fn b() void {
|
||||
\\ var x: i32 = undefined;
|
||||
\\ var y: anyframe->i32 = x;
|
||||
\\}
|
||||
\\export fn c() void {
|
||||
\\ var x: @Frame(func) = undefined;
|
||||
\\ var y: anyframe->i32 = &x;
|
||||
\\}
|
||||
\\fn func() void {}
|
||||
,
|
||||
"tmp.zig:3:28: error: expected type 'anyframe->i32', found 'anyframe'",
|
||||
"tmp.zig:7:28: error: expected type 'anyframe->i32', found 'i32'",
|
||||
"tmp.zig:11:29: error: expected type 'anyframe->i32', found '*@Frame(func)'",
|
||||
);
|
||||
|
||||
cases.add(
|
||||
"wrong frame type used for async call",
|
||||
\\export fn entry() void {
|
||||
\\ var frame: @Frame(foo) = undefined;
|
||||
\\ frame = async bar();
|
||||
\\}
|
||||
\\fn foo() void {
|
||||
\\ suspend;
|
||||
\\}
|
||||
\\fn bar() void {
|
||||
\\ suspend;
|
||||
\\}
|
||||
,
|
||||
"tmp.zig:3:5: error: expected type '*@Frame(bar)', found '*@Frame(foo)'",
|
||||
);
|
||||
|
||||
cases.add(
|
||||
"@Frame() of generic function",
|
||||
\\export fn entry() void {
|
||||
\\ var frame: @Frame(func) = undefined;
|
||||
\\}
|
||||
\\fn func(comptime T: type) void {
|
||||
\\ var x: T = undefined;
|
||||
\\}
|
||||
,
|
||||
"tmp.zig:2:16: error: @Frame() of generic function",
|
||||
);
|
||||
|
||||
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 +1533,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 +1916,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 +1926,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 +1968,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(
|
||||
@ -3225,14 +3422,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
|
||||
"tmp.zig:3:17: note: value 8 cannot fit into type u3",
|
||||
);
|
||||
|
||||
cases.add(
|
||||
"incompatible number literals",
|
||||
\\const x = 2 == 2.0;
|
||||
\\export fn entry() usize { return @sizeOf(@typeOf(x)); }
|
||||
,
|
||||
"tmp.zig:1:11: error: integer value 2 cannot be implicitly casted to type 'comptime_float'",
|
||||
);
|
||||
|
||||
cases.add(
|
||||
"missing function call param",
|
||||
\\const Foo = struct {
|
||||
@ -3315,7 +3504,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);
|
||||
}
|
||||
|
||||
819
test/stage1/behavior/async_fn.zig
Normal file
819
test/stage1/behavior/async_fn.zig
Normal file
@ -0,0 +1,819 @@
|
||||
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" {
|
||||
var frame = async simpleAsyncFn();
|
||||
expect(global_x == 2);
|
||||
resume frame;
|
||||
expect(global_x == 3);
|
||||
const af: anyframe->void = &frame;
|
||||
resume frame;
|
||||
expect(global_x == 4);
|
||||
}
|
||||
fn simpleAsyncFn() void {
|
||||
global_x += 1;
|
||||
suspend;
|
||||
global_x += 1;
|
||||
suspend;
|
||||
global_x += 1;
|
||||
}
|
||||
|
||||
var global_y: i32 = 1;
|
||||
|
||||
test "pass parameter to coroutine" {
|
||||
var p = async simpleAsyncFnWithArg(2);
|
||||
expect(global_y == 3);
|
||||
resume p;
|
||||
expect(global_y == 5);
|
||||
}
|
||||
fn simpleAsyncFnWithArg(delta: i32) void {
|
||||
global_y += delta;
|
||||
suspend;
|
||||
global_y += delta;
|
||||
}
|
||||
|
||||
test "suspend at end of function" {
|
||||
const S = struct {
|
||||
var x: i32 = 1;
|
||||
|
||||
fn doTheTest() void {
|
||||
expect(x == 1);
|
||||
const p = async suspendAtEnd();
|
||||
expect(x == 2);
|
||||
}
|
||||
|
||||
fn suspendAtEnd() void {
|
||||
x += 1;
|
||||
suspend;
|
||||
}
|
||||
};
|
||||
S.doTheTest();
|
||||
}
|
||||
|
||||
test "local variable in async function" {
|
||||
const S = struct {
|
||||
var x: i32 = 0;
|
||||
|
||||
fn doTheTest() void {
|
||||
expect(x == 0);
|
||||
var p = async add(1, 2);
|
||||
expect(x == 0);
|
||||
resume p;
|
||||
expect(x == 0);
|
||||
resume p;
|
||||
expect(x == 0);
|
||||
resume p;
|
||||
expect(x == 3);
|
||||
}
|
||||
|
||||
fn add(a: i32, b: i32) void {
|
||||
var accum: i32 = 0;
|
||||
suspend;
|
||||
accum += a;
|
||||
suspend;
|
||||
accum += b;
|
||||
suspend;
|
||||
x = accum;
|
||||
}
|
||||
};
|
||||
S.doTheTest();
|
||||
}
|
||||
|
||||
test "calling an inferred async function" {
|
||||
const S = struct {
|
||||
var x: i32 = 1;
|
||||
var other_frame: *@Frame(other) = undefined;
|
||||
|
||||
fn doTheTest() void {
|
||||
_ = async first();
|
||||
expect(x == 1);
|
||||
resume other_frame.*;
|
||||
expect(x == 2);
|
||||
}
|
||||
|
||||
fn first() void {
|
||||
other();
|
||||
}
|
||||
fn other() void {
|
||||
other_frame = @frame();
|
||||
suspend;
|
||||
x += 1;
|
||||
}
|
||||
};
|
||||
S.doTheTest();
|
||||
}
|
||||
|
||||
test "@frameSize" {
|
||||
const S = struct {
|
||||
fn doTheTest() void {
|
||||
{
|
||||
var ptr = @ptrCast(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');
|
||||
var p = async await_amain();
|
||||
await_seq('f');
|
||||
resume await_a_promise;
|
||||
await_seq('i');
|
||||
expect(await_final_result == 1234);
|
||||
expect(std.mem.eql(u8, await_points, "abcdefghi"));
|
||||
}
|
||||
async fn await_amain() void {
|
||||
await_seq('b');
|
||||
var 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');
|
||||
var 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');
|
||||
var 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();
|
||||
}
|
||||
|
||||
test "no reason to resolve frame still works" {
|
||||
_ = async simpleNothing();
|
||||
}
|
||||
fn simpleNothing() void {
|
||||
var x: i32 = 1234;
|
||||
}
|
||||
|
||||
test "async call a generic function" {
|
||||
const S = struct {
|
||||
fn doTheTest() void {
|
||||
var f = async func(i32, 2);
|
||||
const result = await f;
|
||||
expect(result == 3);
|
||||
}
|
||||
|
||||
fn func(comptime T: type, inc: T) T {
|
||||
var x: T = 1;
|
||||
suspend {
|
||||
resume @frame();
|
||||
}
|
||||
x += inc;
|
||||
return x;
|
||||
}
|
||||
};
|
||||
_ = async S.doTheTest();
|
||||
}
|
||||
|
||||
test "return from suspend block" {
|
||||
const S = struct {
|
||||
fn doTheTest() void {
|
||||
expect(func() == 1234);
|
||||
}
|
||||
fn func() i32 {
|
||||
suspend {
|
||||
return 1234;
|
||||
}
|
||||
}
|
||||
};
|
||||
_ = async S.doTheTest();
|
||||
}
|
||||
|
||||
test "struct parameter to async function is copied to the frame" {
|
||||
const S = struct {
|
||||
const Point = struct {
|
||||
x: i32,
|
||||
y: i32,
|
||||
};
|
||||
|
||||
var frame: anyframe = undefined;
|
||||
|
||||
fn doTheTest() void {
|
||||
_ = async atest();
|
||||
resume frame;
|
||||
}
|
||||
|
||||
fn atest() void {
|
||||
var f: @Frame(foo) = undefined;
|
||||
bar(&f);
|
||||
clobberStack(10);
|
||||
}
|
||||
|
||||
fn clobberStack(x: i32) void {
|
||||
if (x == 0) return;
|
||||
clobberStack(x - 1);
|
||||
var y: i32 = x;
|
||||
}
|
||||
|
||||
fn bar(f: *@Frame(foo)) void {
|
||||
var pt = Point{ .x = 1, .y = 2 };
|
||||
f.* = async foo(pt);
|
||||
var result = await f;
|
||||
expect(result == 1);
|
||||
}
|
||||
|
||||
fn foo(point: Point) i32 {
|
||||
suspend {
|
||||
frame = @frame();
|
||||
}
|
||||
return point.x;
|
||||
}
|
||||
};
|
||||
S.doTheTest();
|
||||
}
|
||||
@ -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;
|
||||
var 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;
|
||||
var 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;
|
||||
}
|
||||
@ -508,7 +508,7 @@ test "peer type resolution: unreachable, null, slice" {
|
||||
}
|
||||
|
||||
test "peer type resolution: unreachable, error set, unreachable" {
|
||||
const Error = error {
|
||||
const Error = error{
|
||||
FileDescriptorAlreadyPresentInSet,
|
||||
OperationCausesCircularLoop,
|
||||
FileDescriptorNotRegistered,
|
||||
@ -529,3 +529,8 @@ test "peer type resolution: unreachable, error set, unreachable" {
|
||||
};
|
||||
expect(transformed_err == error.SystemResources);
|
||||
}
|
||||
|
||||
test "implicit cast comptime_int to comptime_float" {
|
||||
comptime expect(comptime_float(10) == f32(10));
|
||||
expect(2 == 2.0);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -982,3 +982,14 @@ test "enum literal casting to tagged union" {
|
||||
else => @panic("fail"),
|
||||
}
|
||||
}
|
||||
|
||||
test "enum with one member and custom tag type" {
|
||||
const E = enum(u2) {
|
||||
One,
|
||||
};
|
||||
expect(@enumToInt(E.One) == 0);
|
||||
const E2 = enum(u2) {
|
||||
One = 2,
|
||||
};
|
||||
expect(@enumToInt(E2.One) == 2);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -504,6 +504,9 @@ const Contents = struct {
|
||||
}
|
||||
};
|
||||
|
||||
comptime {
|
||||
@compileError("the behavior of std.AutoHashMap changed and []const u8 will be treated as a pointer. will need to update the hash maps to actually do some kind of hashing on the slices.");
|
||||
}
|
||||
const HashToContents = std.AutoHashMap([]const u8, Contents);
|
||||
const TargetToHash = std.HashMap(DestTarget, []const u8, DestTarget.hash, DestTarget.eql);
|
||||
const PathTable = std.AutoHashMap([]const u8, *TargetToHash);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user