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

result locations: unwrap optional and error unions so that the payload can be non-copied #2761

Open
andrewrk opened this issue Jun 26, 2019 · 2 comments
Labels
accepted This proposal is planned. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@andrewrk
Copy link
Member

andrewrk commented Jun 26, 2019

This issue is split from #287. What the result location mechanism has accomplished is that expressions do not introduce copies for intermediate values. For example:

const Point = struct {
    x: i32,
    y: i32,
};

fn foo() Point {
    return bar();
}

fn bar() Point {
    return Point{
        .x = 1,
        .y = 2,
    };
}

test "result location" {
    var point = foo();
}

Previously, the code return bar() would introduce an extra copy, so the body of the function foo would needlessly copy the point before returning it. This copying would happen at every expression, recursively when the type is an aggregate type (such as struct). Now that the result location mechanism is merged into master, you can see that the foo function does not introduce an extra copy:

define internal fastcc void @foo(%Point* nonnull sret) unnamed_addr #2 !dbg !35 {
Entry:
  call fastcc void @bar(%Point* sret %0), !dbg !44
  ret void, !dbg !46
}

However, if the expression unwraps an optional:

fn foo() Point {
    return bar().?;
}

fn bar() ?Point {
    return Point{
        .x = 1,
        .y = 2,
    };
}

Now there must be a temporary stack variable and a memcpy:

define internal fastcc void @foo(%Point* nonnull sret) unnamed_addr #2 !dbg !35 {
Entry:
  %1 = alloca { %Point, i1 }, align 4
  call fastcc void @bar({ %Point, i1 }* sret %1), !dbg !44
  %2 = getelementptr inbounds { %Point, i1 }, { %Point, i1 }* %1, i32 0, i32 1, !dbg !46
  %3 = load i1, i1* %2, align 1, !dbg !46
  br i1 %3, label %UnwrapOptionalOk, label %UnwrapOptionalFail, !dbg !46

UnwrapOptionalFail:                               ; preds = %Entry
  tail call fastcc void @panic(%"[]u8"* @1, %builtin.StackTrace* null), !dbg !46
  unreachable, !dbg !46

UnwrapOptionalOk:                                 ; preds = %Entry
  %4 = getelementptr inbounds { %Point, i1 }, { %Point, i1 }* %1, i32 0, i32 0, !dbg !46
  %5 = bitcast %Point* %4 to i8*, !dbg !46
  %6 = bitcast %Point* %0 to i8*, !dbg !46
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 4 %6, i8* align 4 %5, i64 8, i1 false), !dbg !46
  ret void, !dbg !47
}

The equivalent is true for the zig code with catch:

fn foo() Point {
    return bar() catch unreachable;
}

fn bar() anyerror!Point {
    return Point{
        .x = 1,
        .y = 2,
    };
}

This means that when writing a function that returns type ?T or !T, one cannot be sure that the return result location will not be copied.

That's what this issue is meant to address. A solution to this issue will mean that calling such functions will not require a temporary variable or memcpy the result.

One thing to note is that wrapping an optional or error union does not have a copy; it is only unwrapping that is in question here:

fn foo() ?Point {
    return bar();
}

fn bar() Point {
    return Point{
        .x = 1,
        .y = 2,
    };
}

You can see there is no copy here:

define internal fastcc void @foo({ %Point, i1 }* nonnull sret) unnamed_addr #2 !dbg !35 {
Entry:
  %1 = getelementptr inbounds { %Point, i1 }, { %Point, i1 }* %0, i32 0, i32 1, !dbg !49
  store i1 true, i1* %1, !dbg !49
  %2 = getelementptr inbounds { %Point, i1 }, { %Point, i1 }* %0, i32 0, i32 0, !dbg !49
  call fastcc void @bar(%Point* sret %2), !dbg !49
  ret void, !dbg !51
}

Related issue: #2765

@andrewrk andrewrk added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Jun 26, 2019
@andrewrk andrewrk added this to the 0.6.0 milestone Jun 26, 2019
@andrewrk andrewrk modified the milestones: 0.6.0, 0.7.0 Jan 8, 2020
@andrewrk andrewrk added the accepted This proposal is planned. label Feb 18, 2020
@andrewrk andrewrk modified the milestones: 0.7.0, 0.8.0 Oct 17, 2020
@Airtz
Copy link

Airtz commented Mar 23, 2021

Does this also covers other forms of payload capture?

That is, will this allow copy elision for capturing loops, switch and if statements?

@andrewrk andrewrk modified the milestones: 0.8.0, 0.9.0 May 19, 2021
@andrewrk andrewrk modified the milestones: 0.9.0, 0.10.0 Nov 20, 2021
@andrewrk andrewrk modified the milestones: 0.10.0, 0.11.0 Apr 16, 2022
@andrewrk andrewrk modified the milestones: 0.11.0, 0.12.0 Apr 9, 2023
@andrewrk andrewrk removed this from the 0.13.0 milestone Jun 29, 2023
@andrewrk andrewrk added this to the 0.12.0 milestone Jun 29, 2023
@mlugg
Copy link
Member

mlugg commented Jul 31, 2023

Is there any plan for how to actually implement this? My guess would be that a function with return type, say, E!?T is passed two result locations: one for T, and one for an E!bool representing the other part. (Alternatively, since it seems a bit unnecessary, that part of the return value need not go through the result location mechanism, and can instead be returned via normal means.) However, this would kinda break #2765, so that feature would need to be aware of these unwrapping semantics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted This proposal is planned. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

3 participants