-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
syntax for guaranteed coroutine frame allocation elision #1260
Comments
New proposal:
Examples: var frame1 = async readFile("foo.txt");
var frame2 = async readFile("bar.txt");
const foo_data = await frame1;
const bar_data = await frame2;
With error handling: var frame1 = async readFile("foo.txt");
defer cancel frame1;
var frame2 = async readFile("bar.txt");
defer cancel frame2;
const foo_data = try await frame1;
const bar_data = try await frame2;
Here's the above example but using the var group = event.Group.init(loop);
defer group.deinit();
try group.call(readFile, "foo.txt"); // allocates a Frame(readFile) on the heap along with results[0]
try group.call(readFile, "bar.txt"); // allocates a Frame(readFile) on the heap along with results[1]
await async group.wait();
const foo_data = try group.results[0];
const bar_data = try group.results[1]; Chaining const foo_data = try await async readFile("foo.txt");
const bar_data = try await async readFile("bar.txt"); When we solve #157, this recursive function would be a compile error: fn fib(x: i32) i32 {
if (x <= 1) return x;
return fib(x - 1) + fib(x - 2); // error: unbounded stack growth
} This is solved with stackless functions (aka coroutines aka async functions): async fn fib(allocator: *Allocator, x: i32) !i32 {
if (x <= 1) return x;
const frame = try allocator.create(@Frame(fib));
defer allocator.destroy(frame);
frame.* = async fib(x - 1);
const value1 = await frame.*;
frame.* = async fib(x - 2);
const value2 = await frame.*;
return value1 + value2;
} Calls to this function grow heap memory, not stack memory. The function can return |
Fixed by the merge of #3033 |
When you
await
apromise
in the same stack frame or coroutine frame that you created it withasync
, we can guarantee that no memory allocation occurs. With status quo Zig, I've been usingasync foo() catch unreachable
, but this is problematic for a few reasons:catch unreachable
should stick out as a noticeable code smell. Guaranteed memory allocation elision for async calls is common, and should be used often. False positives like this harm people's respect for trying to avoidcatch unreachable
.Proposal: use
async<>
with empty angle brackets to require frame allocation elision.Using
async<>
asserts that the correspondingawait
orcancel
will occur in the same stack frame or coroutine frame.Implementation details:
It's certainly possible to make a runtime safety check for this. We can make a convention that passing a null pointer as the allocator means it must be elided, and then when asked to do an allocation, if the allocator is null, call panic.
Theoretically we should be able to have a compile error for not being able to do the elision. However this depends on LLVM IR passes. So it would require inspection into the LLVM IR module post-optimization. This is inherently tricky and would require research.
The text was updated successfully, but these errors were encountered: