Merge remote-tracking branch 'origin/master' into llvm9

This commit is contained in:
Andrew Kelley 2019-08-16 16:43:56 -04:00
commit 6529658ad8
No known key found for this signature in database
GPG key ID: 7C5F548F728501A9
81 changed files with 6783 additions and 5490 deletions

View file

@ -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"

View file

@ -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

View file

@ -1,6 +1,6 @@
![ZIG](https://ziglang.org/zig-logo.svg)
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

View file

@ -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);

View file

@ -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,

View file

@ -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
&lt;- AsmExpr
/ IfExpr
/ KEYWORD_break BreakLabel? Expr?
/ KEYWORD_cancel Expr
/ KEYWORD_comptime Expr
/ KEYWORD_continue BreakLabel?
/ KEYWORD_resume Expr
@ -9825,7 +9988,7 @@ TypeExpr &lt;- PrefixTypeOp* ErrorUnionExpr
ErrorUnionExpr &lt;- SuffixExpr (EXCLAMATIONMARK TypeExpr)?
SuffixExpr
&lt;- AsyncPrefix PrimaryTypeExpr SuffixOp* FnCallArguments
&lt;- KEYWORD_async PrimaryTypeExpr SuffixOp* FnCallArguments
/ PrimaryTypeExpr (SuffixOp / FnCallArguments)*
PrimaryTypeExpr
@ -9901,7 +10064,7 @@ FnCC
&lt;- KEYWORD_nakedcc
/ KEYWORD_stdcallcc
/ KEYWORD_extern
/ KEYWORD_async (LARROW TypeExpr RARROW)?
/ KEYWORD_async
ParamDecl &lt;- (KEYWORD_noalias / KEYWORD_comptime)? (IDENTIFIER COLON)? ParamType
@ -10006,8 +10169,6 @@ SuffixOp
/ DOTASTERISK
/ DOTQUESTIONMARK
AsyncPrefix &lt;- KEYWORD_async (LARROW PrefixExpr RARROW)?
FnCallArguments &lt;- LPAREN ExprList RPAREN
# Ptr specific
@ -10150,7 +10311,6 @@ KEYWORD_asm &lt;- 'asm' end_of_word
KEYWORD_async &lt;- 'async' end_of_word
KEYWORD_await &lt;- 'await' end_of_word
KEYWORD_break &lt;- 'break' end_of_word
KEYWORD_cancel &lt;- 'cancel' end_of_word
KEYWORD_catch &lt;- 'catch' end_of_word
KEYWORD_comptime &lt;- 'comptime' end_of_word
KEYWORD_const &lt;- 'const' end_of_word
@ -10195,7 +10355,7 @@ KEYWORD_volatile &lt;- 'volatile' end_of_word
KEYWORD_while &lt;- 'while' end_of_word
keyword &lt;- 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

View file

@ -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) {

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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,

View file

@ -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;
};

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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);
}

File diff suppressed because it is too large Load diff

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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");
}

View file

@ -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"

View file

@ -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;

View file

@ -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;
}

View file

@ -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

View file

@ -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";

View file

@ -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,

View file

@ -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:

View file

@ -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);

View file

@ -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;

View file

@ -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{

View file

@ -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 {

View file

@ -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);
}

File diff suppressed because it is too large Load diff

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -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,
};
}

View file

@ -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;

View file

@ -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 {

View file

@ -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;

View file

@ -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);
}
};

View file

@ -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();

View file

@ -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 {

View file

@ -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);

View file

@ -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
View 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));
}

View 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
View 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);
}

View file

@ -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"),
}
}

View file

@ -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

View file

@ -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);

View file

@ -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 {

View file

@ -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,

View file

@ -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;
}

View file

@ -5,4 +5,4 @@ pub const is_the_target = switch (builtin.os) {
else => false,
};
pub usingnamespace std.c;
pub usingnamespace @import("bits.zig");
pub usingnamespace @import("bits.zig");

View file

@ -2,4 +2,4 @@ const std = @import("../std.zig");
const builtin = @import("builtin");
pub const is_the_target = builtin.os == .freebsd;
pub usingnamespace std.c;
pub usingnamespace @import("bits.zig");
pub usingnamespace @import("bits.zig");

View file

@ -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");

View file

@ -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 {

View file

@ -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;

View file

@ -209,7 +209,7 @@ pub const FILE_INFORMATION_CLASS = extern enum {
FileLinkInformationExBypassAccessCheck,
FileStorageReserveIdInformation,
FileCaseSensitiveInformationForceAccessCheck,
FileMaximumInformation
FileMaximumInformation,
};
pub const OVERLAPPED = extern struct {
@ -731,4 +731,4 @@ pub const UNICODE_STRING = extern struct {
Length: USHORT,
MaximumLength: USHORT,
Buffer: [*]WCHAR,
};
};

View file

@ -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,
@ -15,4 +21,4 @@ pub extern "NtDll" stdcallcc fn NtCreateFile(
EaBuffer: *c_void,
EaLength: ULONG,
) NTSTATUS;
pub extern "NtDll" stdcallcc fn NtClose(Handle: HANDLE) NTSTATUS;
pub extern "NtDll" stdcallcc fn NtClose(Handle: HANDLE) NTSTATUS;

View file

@ -1,5 +1,5 @@
/// The operation completed successfully.
/// The operation completed successfully.
pub const SUCCESS = 0x00000000;
/// The data was too large to fit into the specified buffer.
pub const BUFFER_OVERFLOW = 0x80000005;
pub const BUFFER_OVERFLOW = 0x80000005;

View file

@ -549,7 +549,6 @@ test "rb" {
}
}
test "inserting and looking up" {
var tree: Tree = undefined;
tree.init(testCompare);

View file

@ -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);

View file

@ -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();

View file

@ -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 },
});

View file

@ -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;
\\}
\\
);

View file

@ -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
}
},

View file

@ -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,

View file

@ -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",
);

View file

@ -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;
\\}
);

View file

@ -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");
}

View file

@ -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);
}

View 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();
}

View file

@ -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 };

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -31,4 +31,4 @@ fn testMulAdd() void {
// var c: f128 = 6.25;
// expect(@mulAdd(f128, a, b, c) == 20);
//}
}
}

View file

@ -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,

View file

@ -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);