Skip to content
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

Should these two snippets be equivalent ? #8188

Closed
TibboddiT opened this issue Mar 9, 2021 · 2 comments
Closed

Should these two snippets be equivalent ? #8188

TibboddiT opened this issue Mar 9, 2021 · 2 comments
Milestone

Comments

@TibboddiT
Copy link

I've started today with zig, sorry if this is the wrong place to ask.
Maybe there is something I don't understand.

Given this code (which I know is not idiomatic):

const std = @import("std");

const TaskContext = struct {
    info: u32,
};

pub fn main() void {
    _ = async amain1();
    _ = async amain2();
}

fn amain1() void {
    std.log.info("amain1", .{});

    var frames: [3]@Frame(task) = undefined;

    var i: u32 = 0;
    while (i < frames.len) {
        std.log.info("creating frame: {d}", .{i});

        const frame = async task(.{
            .info = i,
        });
        frames[i] = frame;

        i += 1;
    }

    std.log.info("awaiting frames...", .{});

    i = 0;
    while (i < frames.len) {
        const frameResult = await frames[i];
        std.log.info("frame {d} returned {d}", .{i, frameResult});
        i += 1;
    }
}

fn amain2() void {
    std.log.info("amain2", .{});

    var frames: [3]@Frame(task) = undefined;

    var i: u32 = 0;
    while (i < frames.len) {
        std.log.info("creating frame: {d}", .{i});

        frames[i] = async task(.{
            .info = i,
        });

        i += 1;
    }

    std.log.info("awaiting frames...", .{});

    i = 0;
    while (i < frames.len) {
        const frameResult = await frames[i];
        std.log.info("frame {d} returned {d}", .{i, frameResult});
        i += 1;
    }
}

fn task(context: TaskContext) u32 {
    return context.info;
}

I expected functions amain1 and amain2 to behave the same, the only difference between them being:

  • amain1
        const frame = async task(.{
            .info = i,
        });
        frames[i] = frame;
  • amain2
        frames[i] = async task(.{
            .info = i,
        });

But as you can see on output:

info: amain1
info: creating frame: 0
info: creating frame: 1
info: creating frame: 2
info: awaiting frames...
info: frame 0 returned 2
info: frame 1 returned 2
info: frame 2 returned 2
info: amain2
info: creating frame: 0
info: creating frame: 1
info: creating frame: 2
info: awaiting frames...
info: frame 0 returned 0
info: frame 1 returned 1
info: frame 2 returned 2

the first version seems to store the last frame for all indexes in the frames array. The second version behaves the way I expected.

If this is a correct behavior, I don't mind getting an explanation :)

@SpexGuy
Copy link
Contributor

SpexGuy commented Mar 9, 2021

This output is normal. It's undefined behavior to copy a frame from one place to another. Zig uses Result Location Semantics (#287, #2765, #2761), so frames[i] = async task(); actually constructs the frame in frames[i] and no copy is performed. Frames are planned to be marked pinned (#7769), which will make it a compile error to copy them.

The reason this breaks in this case is also due to result location semantics. The frame contains a pointer to the location where it should store its result. This pointer is initialized at the async keyword, to point to a space reserved within the frame. (it can be made to point elsewhere with the @asyncCall builtin, or by calling an async function without the async keyword.) In amain1, it is initialized to point to stack memory in the frame variable. Since all three functions are initialized over the same memory, all of their result locations point to this variable, and the third one writes the value 2 last. Then the results are retrieved through the result location pointer at the await. These pointers were copied by value, so they still point to the stack memory for frame. This memory is no longer valid, but it hasn't been reused, so it still contains the value 2. All three frames read this value.

I've started today with zig, sorry if this is the wrong place to ask

Np, welcome! The preferred place for questions like this is a community location like the IRC or Discord.

@TibboddiT
Copy link
Author

Thanks for the quick feedback !

I got it, and after reading a little about Result Location Semantics in linked issues it is pretty clear.
IMHO issue #2809 is an important one too !

Next time I'll use Discord.

@andrewrk andrewrk added this to the 0.8.0 milestone Jun 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants