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

Part 1 of well-defined copy-elision (second attempt) #1682

Closed
wants to merge 199 commits into from

Conversation

andrewrk
Copy link
Member

@andrewrk andrewrk commented Oct 25, 2018

Not satisfied with how the code was looking in #1652, I'm tossing out that work and starting over. This branch uses the IR system directly to implement copy elision, instead of awkwardly patching over it.

The goals of this branch are the same:

This does not solve the problem of how to access the result location of a function, (e.g. #287 (comment)). That is left for a future change.

Note that clang already generates code like this. This PR is actually Zig catching up with Clang in this particular area.

Checklist:

  • scalar variable declaration
  • scalar variable declaration to undefined
  • scalar variable declaration with no explicit type
  • fix alignment of variables
  • var decl initializing to global const
  • var decl initializing to aggregate runtime var
  • aggregate variable declaration
  • variable declaration with undefined
  • function returning result of another function
  • assignment of function call
  • assignment of undefined
  • implicit cast optional wrap
  • implicit cast optional wrap when payload is integer
  • return const struct
  • if bool with aggregate types
  • if bool with scalar types
  • if optional with aggregate types
  • if optional with scalar types
  • if error with aggregate types
  • if error with scalar types
  • while bool with aggregate types
  • while bool with scalar types
  • while optional with aggregate types
  • while optional with scalar types
  • while error with aggregate types
  • while error with scalar types
  • for with aggregate types
  • for with scalar types
  • switch on scalar type
  • switch on aggregate type
  • result locations that require stack allocations
  • returning a scalar value
  • implicit cast payload to error union
  • implicit cast error code to error union comptime
  • implicit cast error code to error union runtime
  • double implicit cast
  • double implicit cast using fn call syntax
  • implicit *[N]T to []T
  • assignment of non function call
  • double implicit cast when payload is integer, fn call
  • implicit array to slice
  • double implicit cast when payload is integer, no fn call
  • runtime union init
  • comptime union init
  • runtime struct init
  • comptime struct init
  • runtime array init
  • comptime array init
  • fn call in parameter expressions with implicit casting to parameters
  • cmpxchg
  • @sliceToBytes
  • slicing syntax
  • orelse
  • catch with identifier expression
  • catch with fn call expression
  • catch with other expression which requires stack alloca
  • catch access of the error code
  • .?
  • try
  • catch unreachable with identifier expression
  • catch unreachable with fn call expression
  • catch unreachable with other expression which requires stack alloca
  • fn call parameters
  • @noInlineCall / @inlineCall parameters
  • labeled block
  • comptime code
  • fn ptr vs method call
  • audit all usage of ir_lval_wrap
  • write a new kind of test that looks at the LLVM IR output and add cases for all the above
  • fix test regressions
  • look over the diff and identify any more test cases
  • add behavior test case for @sizeOf(?error)
  • look over the diff and delete commented out code and no longer used Zig IR instructions.
  • figure out what the llvm ir fn signature should be for error unions and optionals. sret? should the error value / null value be a pointer argument?
  • re-enable stack traces in bootstrap and in the default panic handler

See each commit message in this branch for example Zig/LLVM code.

Some interesting tidbits:

  • a catch b is no longer syntactic sugar for if (a) |x| x else |_| b. The former provides copy elision; the latter does not. The if expression creates a result location for a, whereas the catch expression does not create a result location.
  • try a is no longer syntactic sugar for if (a) |x| x else |e| return e. Same explanation.
  • a orelse b is no longer syntactic sugar for if (a) |x| x else b. However, a.? is still syntactic sugar for a orelse unreachable.

@andrewrk andrewrk added the work in progress This pull request is not ready for review yet. label Oct 25, 2018
@andrewrk andrewrk added this to the 0.4.0 milestone Oct 25, 2018
```zig
export fn entry() void {
    var x: i32 = 1234;
}
```

```llvm
define void @entry() #2 !dbg !41 {
Entry:
  %x = alloca i32, align 4
  store i32 1234, i32* %x, align 4, !dbg !48
  call void @llvm.dbg.declare(metadata i32* %x, metadata !45, metadata !DIExpression()), !dbg !48
  ret void, !dbg !49
}
```
```zig
export fn entry() void {
    var x: i32 = undefined;
}
```

```llvm
define void @entry() #2 !dbg !41 {
Entry:
  %x = alloca i32, align 4
  %0 = bitcast i32* %x to i8*, !dbg !48
  call void @llvm.memset.p0i8.i64(i8* align 4 %0, i8 -86, i64 4, i1 false), !dbg !48
  call void @llvm.dbg.declare(metadata i32* %x, metadata !45, metadata !DIExpression()), !dbg !48
  ret void, !dbg !49
}
```
```zig
export fn entry() void {
    var x = i32(1234);
}
```

```llvm
define void @entry() #2 !dbg !41 {
Entry:
  %x = alloca i32, align 4
  store i32 1234, i32* %x, align 4, !dbg !48
  %0 = bitcast i32* %x to i8*, !dbg !48
  call void @llvm.memset.p0i8.i64(i8* align 4 %0, i8 -86, i64 4, i1 false), !dbg !48
  call void @llvm.dbg.declare(metadata i32* %x, metadata !45, metadata !DIExpression()), !dbg !48
  ret void, !dbg !49
}
```
```zig
export fn entry() void {
    var x = Foo.{
        .x = 1,
        .y = 2,
    };
}
```

```llvm
define void @entry() #2 !dbg !41 {
Entry:
  %x = alloca %Foo, align 4
  %0 = bitcast %Foo* %x to i8*, !dbg !52
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 4 %0, i8* align 4 bitcast (%Foo* @0 to i8*), i64 8, i1 false), !dbg !52
  call void @llvm.dbg.declare(metadata %Foo* %x, metadata !45, metadata !DIExpression()), !dbg !52
  ret void, !dbg !53
}
```
```zig
export fn entry() void {
    var y = foo();
}

fn foo() Foo {
    return Foo.{
        .x = 1,
        .y = 2,
    };
}
```

```llvm
define void @entry() #2 !dbg !41 {
Entry:
  %y = alloca %Foo, align 4
  call fastcc void @foo(%Foo* sret %y), !dbg !52
  call void @llvm.dbg.declare(metadata %Foo* %y, metadata !45, metadata !DIExpression()), !dbg !53
  ret void, !dbg !54
}

define internal fastcc void @foo(%Foo* nonnull sret) unnamed_addr #2 !dbg !55 {
Entry:
  %1 = bitcast %Foo* %0 to i8*, !dbg !60
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 4 %1, i8* align 4 bitcast (%Foo* @0 to i8*), i64 8, i1 false), !dbg !60
  ret void, !dbg !60
}
```
```zig
fn bar() Foo {
    return foo();
}
```

```llvm
define internal fastcc void @bar(%Foo* nonnull sret) unnamed_addr #2 !dbg !55 {
Entry:
  call fastcc void @foo(%Foo* sret %0), !dbg !60
  ret void, !dbg !62
}
```
@tgschultz tgschultz mentioned this pull request Oct 28, 2018
```zig
export fn entry() void {
    var x = bar();
    var y = x;
}
```

```llvm
define void @entry() #2 !dbg !41 {
Entry:
  %x = alloca %Foo, align 4
  %y = alloca %Foo, align 4
  call fastcc void @bar(%Foo* sret %x), !dbg !54
  call void @llvm.dbg.declare(metadata %Foo* %x, metadata !45, metadata !DIExpression()), !dbg !55
  %0 = bitcast %Foo* %x to i8*, !dbg !56
  %1 = bitcast %Foo* %y to i8*, !dbg !56
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 4 %1, i8* align 4 %0, i64 8, i1 false), !dbg !56
  call void @llvm.dbg.declare(metadata %Foo* %y, metadata !52, metadata !DIExpression()), !dbg !57
  ret void, !dbg !58
}
```
```zig
export fn entry() void {
    var x: ?Foo = foo();
}
```

```llvm
define void @entry() #2 !dbg !41 {
Entry:
  %x = alloca { %Foo, i1 }, align 4
  %0 = getelementptr inbounds { %Foo, i1 }, { %Foo, i1 }* %x, i32 0, i32 1, !dbg !57
  store i1 true, i1* %0, align 1, !dbg !57
  %1 = getelementptr inbounds { %Foo, i1 }, { %Foo, i1 }* %x, i32 0, i32 0, !dbg !57
  call fastcc void @foo(%Foo* sret %1), !dbg !58
  call void @llvm.dbg.declare(metadata { %Foo, i1 }* %x, metadata !45, metadata !DIExpression()), !dbg !57
  ret void, !dbg !59
}
```
```zig
export fn entry() void {
    var a: i32 = 1234;
    var b: ?i32 = a;
}
```

```llvm
define void @entry() #2 !dbg !41 {
Entry:
  %a = alloca i32, align 4
  %b = alloca { i32, i1 }, align 4
  store i32 1234, i32* %a, align 4, !dbg !55
  call void @llvm.dbg.declare(metadata i32* %a, metadata !45, metadata !DIExpression()), !dbg !55
  %0 = load i32, i32* %a, align 4, !dbg !56
  %1 = getelementptr inbounds { i32, i1 }, { i32, i1 }* %b, i32 0, i32 1, !dbg !57
  store i1 true, i1* %1, align 1, !dbg !57
  %2 = getelementptr inbounds { i32, i1 }, { i32, i1 }* %b, i32 0, i32 0, !dbg !57
  store i32 %0, i32* %2, align 4, !dbg !57
  call void @llvm.dbg.declare(metadata { i32, i1 }* %b, metadata !48, metadata !DIExpression()), !dbg !57
  ret void, !dbg !58
}
```
```zig
export fn entry() void {
    var y = true;
    var z = bar();
    var x = if (y) foo() else z;
}
```

```llvm
define void @entry() #2 !dbg !41 {
Entry:
  %y = alloca i1, align 1
  %z = alloca %Foo, align 4
  %x = alloca %Foo, align 4
  store i1 true, i1* %y, align 1, !dbg !57
  call void @llvm.dbg.declare(metadata i1* %y, metadata !45, metadata !DIExpression()), !dbg !58
  call fastcc void @bar(%Foo* sret %z), !dbg !59
  call void @llvm.dbg.declare(metadata %Foo* %z, metadata !48, metadata !DIExpression()), !dbg !60
  %0 = load i1, i1* %y, align 1, !dbg !61
  br i1 %0, label %Then, label %Else, !dbg !61

Then:                                             ; preds = %Entry
  call fastcc void @foo(%Foo* sret %x), !dbg !62
  br label %EndIf, !dbg !63

Else:                                             ; preds = %Entry
  %1 = bitcast %Foo* %z to i8*, !dbg !64
  %2 = bitcast %Foo* %x to i8*, !dbg !64
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 4 %2, i8* align 4 %1, i64 8, i1 false), !dbg !64
  br label %EndIf, !dbg !63

EndIf:                                            ; preds = %Else, %Then
  call void @llvm.dbg.declare(metadata %Foo* %x, metadata !55, metadata !DIExpression()), !dbg !65
  ret void, !dbg !66
}
```
```zig
export fn entry() void {
    var x: error!Foo = foo();
}
```

```llvm
define void @entry() #2 !dbg !41 {
Entry:
  %x = alloca { i16, %Foo }, align 4
  %0 = getelementptr inbounds { i16, %Foo }, { i16, %Foo }* %x, i32 0, i32 0, !dbg !56
  store i16 0, i16* %0, align 2, !dbg !56
  %1 = getelementptr inbounds { i16, %Foo }, { i16, %Foo }* %x, i32 0, i32 1, !dbg !56
  call fastcc void @foo(%Foo* sret %1), !dbg !57
  call void @llvm.dbg.declare(metadata { i16, %Foo }* %x, metadata !45, metadata !DIExpression()), !dbg !56
  ret void, !dbg !58
}
```
```zig
export fn entry() void {
    var x: error!Foo = fail();
}
```

```llvm
define void @entry() #2 !dbg !42 {
Entry:
  %x = alloca { i16, %Foo }, align 4
  %0 = getelementptr inbounds { i16, %Foo }, { i16, %Foo }* %x, i32 0, i32 0, !dbg !57
  %1 = call fastcc i16 @fail(), !dbg !58
  store i16 %1, i16* %0, align 2, !dbg !58
  ret void, !dbg !59
}
```
```zig
export fn entry() void {
    var x: error!?Foo = bar();
}
```

```llvm
define void @entry() #2 !dbg !41 {
Entry:
  %x = alloca { i16, { %Foo, i1 } }, align 4
  %0 = getelementptr inbounds { i16, { %Foo, i1 } }, { i16, { %Foo, i1 } }* %x, i32 0, i32 0, !dbg !61
  store i16 0, i16* %0, align 2, !dbg !61
  %1 = getelementptr inbounds { i16, { %Foo, i1 } }, { i16, { %Foo, i1 } }* %x, i32 0, i32 1, !dbg !61
  %2 = getelementptr inbounds { %Foo, i1 }, { %Foo, i1 }* %1, i32 0, i32 1, !dbg !61
  store i1 true, i1* %2, align 1, !dbg !61
  %3 = getelementptr inbounds { %Foo, i1 }, { %Foo, i1 }* %1, i32 0, i32 0, !dbg !61
  call fastcc void @bar(%Foo* sret %3), !dbg !62
  ret void, !dbg !63
}
```
```zig
export fn entry() void {
    var x = Foo.{
        .x = 1,
        .y = bar(),
    };
}
```

```llvm
define void @entry() #2 !dbg !41 {
Entry:
  %x = alloca %Foo, align 4
  %0 = getelementptr inbounds %Foo, %Foo* %x, i32 0, i32 0, !dbg !56
  store i32 1, i32* %0, align 4, !dbg !57
  %1 = getelementptr inbounds %Foo, %Foo* %x, i32 0, i32 1, !dbg !58
  call fastcc void @bar(%Bar* sret %1), !dbg !59
  ret void, !dbg !60
}
```
```zig
export fn entry() void {
    var x: error!Bar = fail();
    var z = x catch unreachable;
}
```

```llvm
define void @entry() #2 !dbg !42 {
Entry:
  %error_return_trace_addresses = alloca [30 x i64], align 8
  %error_return_trace = alloca %StackTrace, align 8
  %x = alloca { i16, %Bar }, align 4
  %z = alloca %Bar, align 4
  %0 = getelementptr inbounds %StackTrace, %StackTrace* %error_return_trace, i32 0, i32 0
  store i64 0, i64* %0, align 8
  %1 = getelementptr inbounds %StackTrace, %StackTrace* %error_return_trace, i32 0, i32 1
  %2 = getelementptr inbounds %"[]usize", %"[]usize"* %1, i32 0, i32 0
  %3 = getelementptr inbounds [30 x i64], [30 x i64]* %error_return_trace_addresses, i64 0, i64 0
  store i64* %3, i64** %2, align 8
  %4 = getelementptr inbounds %"[]usize", %"[]usize"* %1, i32 0, i32 1
  store i64 30, i64* %4, align 8
  %5 = getelementptr inbounds { i16, %Bar }, { i16, %Bar }* %x, i32 0, i32 0, !dbg !59
  %6 = call fastcc i16 @fail(%StackTrace* %error_return_trace), !dbg !60
  store i16 %6, i16* %5, align 2, !dbg !60
  call void @llvm.dbg.declare(metadata { i16, %Bar }* %x, metadata !46, metadata !DIExpression()), !dbg !59
  %7 = getelementptr inbounds { i16, %Bar }, { i16, %Bar }* %x, i32 0, i32 1, !dbg !61
  %8 = bitcast %Bar* %7 to i8*, !dbg !61
  %9 = bitcast %Bar* %z to i8*, !dbg !61
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 4 %9, i8* align 4 %8, i64 8, i1 false), !dbg !61
  %10 = getelementptr inbounds { i16, %Bar }, { i16, %Bar }* %x, i32 0, i32 0, !dbg !61
  %11 = load i16, i16* %10, align 2, !dbg !61
  %12 = icmp eq i16 %11, 0, !dbg !62
  br i1 %12, label %UnwrapErrOk, label %UnwrapErrError, !dbg !62

UnwrapErrError:                                   ; preds = %Entry
  tail call fastcc void @__zig_fail_unwrap(%StackTrace* %error_return_trace, i16 %11), !dbg !62
  unreachable, !dbg !62

UnwrapErrOk:                                      ; preds = %Entry
  call void @llvm.dbg.declare(metadata %Bar* %z, metadata !57, metadata !DIExpression()), !dbg !63
  ret void, !dbg !64
}
```
This commit also makes `@sizeOf(?error) == @sizeof(error)`.
Since 0 is not a valid error code, 0 can be used for the null
value, in the same way that 0 is used for the null value of
pointers.

```zig
export fn entry() void {
    var x: error!Bar = fail();
    var y = x catch bar2();
}
```

```llvm
define void @entry() #2 !dbg !42 {
Entry:
  %error_return_trace_addresses = alloca [30 x i64], align 8
  %error_return_trace = alloca %StackTrace, align 8
  %x = alloca { i16, %Bar }, align 4
  %y = alloca %Bar, align 4
  %0 = getelementptr inbounds %StackTrace, %StackTrace* %error_return_trace, i32 0, i32 0
  store i64 0, i64* %0, align 8
  %1 = getelementptr inbounds %StackTrace, %StackTrace* %error_return_trace, i32 0, i32 1
  %2 = getelementptr inbounds %"[]usize", %"[]usize"* %1, i32 0, i32 0
  %3 = getelementptr inbounds [30 x i64], [30 x i64]* %error_return_trace_addresses, i64 0, i64 0
  store i64* %3, i64** %2, align 8
  %4 = getelementptr inbounds %"[]usize", %"[]usize"* %1, i32 0, i32 1
  store i64 30, i64* %4, align 8
  %5 = getelementptr inbounds { i16, %Bar }, { i16, %Bar }* %x, i32 0, i32 0, !dbg !59
  %6 = call fastcc i16 @fail(%StackTrace* %error_return_trace), !dbg !60
  store i16 %6, i16* %5, align 2, !dbg !60
  call void @llvm.dbg.declare(metadata { i16, %Bar }* %x, metadata !46, metadata !DIExpression()), !dbg !59
  %7 = getelementptr inbounds { i16, %Bar }, { i16, %Bar }* %x, i32 0, i32 1, !dbg !61
  %8 = bitcast %Bar* %7 to i8*, !dbg !61
  %9 = bitcast %Bar* %y to i8*, !dbg !61
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 4 %9, i8* align 4 %8, i64 8, i1 false), !dbg !61
  %10 = getelementptr inbounds { i16, %Bar }, { i16, %Bar }* %x, i32 0, i32 0, !dbg !61
  %11 = load i16, i16* %10, align 2, !dbg !62
  %12 = icmp ne i16 %11, 0, !dbg !62
  br i1 %12, label %CatchError, label %CatchEnd, !dbg !62

CatchError:                                       ; preds = %Entry
  call fastcc void @bar2(%Bar* sret %y), !dbg !63
  br label %CatchEnd, !dbg !62

CatchEnd:                                         ; preds = %CatchError, %Entry
  call void @llvm.dbg.declare(metadata %Bar* %y, metadata !57, metadata !DIExpression()), !dbg !64
  ret void, !dbg !65
}
```
```zig
export fn entry() void {
    var c = true;
    var y: i32 = if (c) 1234 else 5678;
}
```

```llvm
define void @entry() #2 !dbg !41 {
Entry:
  %c = alloca i1, align 1
  %y = alloca i32, align 4
  store i1 true, i1* %c, align 1, !dbg !51
  call void @llvm.dbg.declare(metadata i1* %c, metadata !45, metadata !DIExpression()), !dbg !52
  %0 = load i1, i1* %c, align 1, !dbg !53
  br i1 %0, label %Then, label %Else, !dbg !53

Then:                                             ; preds = %Entry
  store i32 1234, i32* %y, align 4, !dbg !54
  br label %EndIf, !dbg !55

Else:                                             ; preds = %Entry
  store i32 5678, i32* %y, align 4, !dbg !56
  br label %EndIf, !dbg !55

EndIf:                                            ; preds = %Else, %Then
  call void @llvm.dbg.declare(metadata i32* %y, metadata !48, metadata !DIExpression()), !dbg !57
  ret void, !dbg !58
}
```
```zig
export fn entry() void {
    var x: ?Foo = foo();
    var y = if (x) |a| a.y else bar();
}
```

```llvm
define void @entry() #2 !dbg !41 {
Entry:
  %x = alloca { %Foo, i1 }, align 4
  %y = alloca %Bar, align 4
  %0 = getelementptr inbounds { %Foo, i1 }, { %Foo, i1 }* %x, i32 0, i32 1, !dbg !64
  store i1 true, i1* %0, align 1, !dbg !64
  %1 = getelementptr inbounds { %Foo, i1 }, { %Foo, i1 }* %x, i32 0, i32 0, !dbg !64
  call fastcc void @foo(%Foo* sret %1), !dbg !65
  call void @llvm.dbg.declare(metadata { %Foo, i1 }* %x, metadata !45, metadata !DIExpression()), !dbg !64
  %2 = getelementptr inbounds { %Foo, i1 }, { %Foo, i1 }* %x, i32 0, i32 1, !dbg !66
  %3 = load i1, i1* %2, align 1, !dbg !66
  br i1 %3, label %OptionalThen, label %OptionalElse, !dbg !66

OptionalThen:                                     ; preds = %Entry
  %4 = getelementptr inbounds { %Foo, i1 }, { %Foo, i1 }* %x, i32 0, i32 0, !dbg !66
  call void @llvm.dbg.declare(metadata %Foo* %4, metadata !61, metadata !DIExpression()), !dbg !66
  %5 = getelementptr inbounds %Foo, %Foo* %4, i32 0, i32 1, !dbg !67
  %6 = bitcast %Bar* %5 to i8*, !dbg !67
  %7 = bitcast %Bar* %y to i8*, !dbg !67
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 4 %7, i8* align 4 %6, i64 8, i1 false), !dbg !67
  br label %OptionalEndIf, !dbg !66

OptionalElse:                                     ; preds = %Entry
  call fastcc void @bar(%Bar* sret %y), !dbg !69
  br label %OptionalEndIf, !dbg !66

OptionalEndIf:                                    ; preds = %OptionalElse, %OptionalThen
  call void @llvm.dbg.declare(metadata %Bar* %y, metadata !63, metadata !DIExpression()), !dbg !70
  ret void, !dbg !71
}
```
```zig
export fn entry() void {
    var x: error!Foo = foo();
    var y = if (x) |a| a.y else |e| bar();
}
```

```llvm
define void @entry() #2 !dbg !41 {
Entry:
  %x = alloca { i16, %Foo }, align 4
  %y = alloca %Bar, align 4
  %0 = getelementptr inbounds { i16, %Foo }, { i16, %Foo }* %x, i32 0, i32 0, !dbg !64
  store i16 0, i16* %0, align 2, !dbg !64
  %1 = getelementptr inbounds { i16, %Foo }, { i16, %Foo }* %x, i32 0, i32 1, !dbg !64
  call fastcc void @foo(%Foo* sret %1), !dbg !65
  call void @llvm.dbg.declare(metadata { i16, %Foo }* %x, metadata !45, metadata !DIExpression()), !dbg !64
  %2 = getelementptr inbounds { i16, %Foo }, { i16, %Foo }* %x, i32 0, i32 0, !dbg !66
  %3 = load i16, i16* %2, align 2, !dbg !66
  %4 = icmp ne i16 %3, 0, !dbg !66
  br i1 %4, label %IfErrElse, label %IfErrThen, !dbg !66

IfErrThen:                                        ; preds = %Entry
  %5 = getelementptr inbounds { i16, %Foo }, { i16, %Foo }* %x, i32 0, i32 1, !dbg !66
  call void @llvm.dbg.declare(metadata %Foo* %5, metadata !60, metadata !DIExpression()), !dbg !66
  %6 = getelementptr inbounds %Foo, %Foo* %5, i32 0, i32 1, !dbg !67
  %7 = bitcast %Bar* %6 to i8*, !dbg !67
  %8 = bitcast %Bar* %y to i8*, !dbg !67
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 4 %8, i8* align 4 %7, i64 8, i1 false), !dbg !67
  br label %IfErrEnd, !dbg !66

IfErrElse:                                        ; preds = %Entry
  %9 = load i16, i16* %2, align 2, !dbg !66
  call void @llvm.dbg.declare(metadata i16* %2, metadata !62, metadata !DIExpression()), !dbg !66
  call fastcc void @bar(%Bar* sret %y), !dbg !69
  br label %IfErrEnd, !dbg !66

IfErrEnd:                                         ; preds = %IfErrElse, %IfErrThen
  call void @llvm.dbg.declare(metadata %Bar* %y, metadata !63, metadata !DIExpression()), !dbg !71
  ret void, !dbg !72
}
```
```zig
export fn entry() void {
    var x = error.Failure;
    var y: error!Foo = x;
}
```

```llvm
define void @entry() #2 !dbg !42 {
Entry:
  %x = alloca i16, align 2
  %y = alloca { i16, %Foo }, align 4
  store i16 1, i16* %x, align 2, !dbg !63
  call void @llvm.dbg.declare(metadata i16* %x, metadata !46, metadata !DIExpression()), !dbg !64
  %0 = load i16, i16* %x, align 2, !dbg !65
  %1 = getelementptr inbounds { i16, %Foo }, { i16, %Foo }* %y, i32 0, i32 0, !dbg !66
  store i16 %0, i16* %1, align 2, !dbg !65
  call void @llvm.dbg.declare(metadata { i16, %Foo }* %y, metadata !48, metadata !DIExpression()), !dbg !66
  ret void, !dbg !67
}
```
```zig
export fn entry() void {
    var x: error!i32 = 1234;
    var y = if (x) |a| a else |e| 5678;
}
```

```llvm
define void @entry() #2 !dbg !41 {
Entry:
  %x = alloca { i16, i32 }, align 4
  %y = alloca i32, align 4
  %0 = bitcast { i16, i32 }* %x to i8*, !dbg !56
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 4 %0, i8* align 4 bitcast ({ i16, i32 }* @0 to i8*), i64 8, i1 false), !dbg !56
  call void @llvm.dbg.declare(metadata { i16, i32 }* %x, metadata !45, metadata !DIExpression()), !dbg !57
  %1 = getelementptr inbounds { i16, i32 }, { i16, i32 }* %x, i32 0, i32 0, !dbg !58
  %2 = load i16, i16* %1, align 2, !dbg !58
  %3 = icmp ne i16 %2, 0, !dbg !58
  br i1 %3, label %IfErrElse, label %IfErrThen, !dbg !58

IfErrThen:                                        ; preds = %Entry
  %4 = getelementptr inbounds { i16, i32 }, { i16, i32 }* %x, i32 0, i32 1, !dbg !58
  call void @llvm.dbg.declare(metadata i32* %4, metadata !52, metadata !DIExpression()), !dbg !58
  %5 = load i32, i32* %4, align 4, !dbg !59
  store i32 %5, i32* %y, align 4, !dbg !59
  br label %IfErrEnd, !dbg !58

IfErrElse:                                        ; preds = %Entry
  %6 = load i16, i16* %1, align 2, !dbg !58
  call void @llvm.dbg.declare(metadata i16* %1, metadata !54, metadata !DIExpression()), !dbg !58
  store i32 5678, i32* %y, align 4, !dbg !61
  br label %IfErrEnd, !dbg !58

IfErrEnd:                                         ; preds = %IfErrElse, %IfErrThen
  call void @llvm.dbg.declare(metadata i32* %y, metadata !55, metadata !DIExpression()), !dbg !63
  ret void, !dbg !64
}
```
```zig
fn fail() error {
    return error.Failure;
}
```

```llvm
define internal fastcc i16 @fail(%StackTrace* nonnull) unnamed_addr #2 !dbg !56 {
Entry:
  call fastcc void @__zig_return_error(%StackTrace* %0), !dbg !60
  ret i16 1, !dbg !60
}
```
```zig
export fn entry() void {
    var c = false;
    var x: ?Foo = foo();
    var z = while (x) |a| {
        _ = if (c) break a.y;
    } else bar();
}
```

```llvm
define void @entry() #2 !dbg !41 {
Entry:
  %c = alloca i1, align 1
  %x = alloca { %Foo, i1 }, align 4
  %z = alloca %Bar, align 4
  store i1 false, i1* %c, align 1, !dbg !66
  call void @llvm.dbg.declare(metadata i1* %c, metadata !45, metadata !DIExpression()), !dbg !67
  %0 = getelementptr inbounds { %Foo, i1 }, { %Foo, i1 }* %x, i32 0, i32 1, !dbg !68
  store i1 true, i1* %0, align 1, !dbg !68
  %1 = getelementptr inbounds { %Foo, i1 }, { %Foo, i1 }* %x, i32 0, i32 0, !dbg !68
  call fastcc void @foo(%Foo* sret %1), !dbg !69
  call void @llvm.dbg.declare(metadata { %Foo, i1 }* %x, metadata !48, metadata !DIExpression()), !dbg !68
  br label %WhileCond, !dbg !70

WhileCond:                                        ; preds = %Else, %Entry
  %2 = getelementptr inbounds { %Foo, i1 }, { %Foo, i1 }* %x, i32 0, i32 1, !dbg !71
  %3 = load i1, i1* %2, align 1, !dbg !71
  br i1 %3, label %WhileBody, label %WhileElse, !dbg !71

WhileBody:                                        ; preds = %WhileCond
  %4 = getelementptr inbounds { %Foo, i1 }, { %Foo, i1 }* %x, i32 0, i32 0, !dbg !72
  call void @llvm.dbg.declare(metadata %Foo* %4, metadata !63, metadata !DIExpression()), !dbg !70
  %5 = load i1, i1* %c, align 1, !dbg !74
  br i1 %5, label %Then, label %Else, !dbg !74

Then:                                             ; preds = %WhileBody
  %6 = getelementptr inbounds %Foo, %Foo* %4, i32 0, i32 1, !dbg !76
  %7 = bitcast %Bar* %6 to i8*, !dbg !76
  %8 = bitcast %Bar* %z to i8*, !dbg !76
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 4 %8, i8* align 4 %7, i64 8, i1 false), !dbg !76
  br label %WhileEnd, !dbg !77

Else:                                             ; preds = %WhileBody
  br label %WhileCond, !dbg !72

WhileElse:                                        ; preds = %WhileCond
  call fastcc void @bar(%Bar* sret %z), !dbg !78
  br label %WhileEnd, !dbg !70

WhileEnd:                                         ; preds = %WhileElse, %Then
  call void @llvm.dbg.declare(metadata %Bar* %z, metadata !65, metadata !DIExpression()), !dbg !79
  ret void, !dbg !80
}
```
All behavior tests are now passing in the copy elision branch.
also restore default panic handler code
...const slice cast in array literal
...pointer return value

I had to put the panic handler stub back in since
some code was being incorrectly comptime eliminated.
@andrewrk
Copy link
Member Author

This branch also started going in a design direction and with a code quality that I was not comfortable with. I have backported all the extra stuff from this branch back into master in 581edd6 and when tackling copy elision I will try a third approach.

The third approach involves breaking IR pass 2 (the analysis pass) into sub-phases:

  1. Each instruction is given the type and is_comptime of each of its parameters, and it must return its type and is_comptime of itself, and whether it needs a result location.
  2. Each instruction is given the value of each of its parameters, and given a result location if requested, told whether it should emit a comptime or untime value, and it must return its value.

With this framework in place, in IR pass 1, result location instructions will be generated to track result locations throughout expressions. However unlike in this pull request, they will be lazy. Pass 1 will only create the tree structure of result locations, correlated to the corresponding instructions. In the dual phased Pass 2, result locations will be resolved with full type information, which is a key difference.

The separate is_comptime computation helps when there are "peer" expressions, for example an if expression or a switch expression. Each "prong" has the is_comptime value computed, and if all of them are comptime-known, then all of them are analyzed as comptime. Otherwise all of them are analyzed as runtime.

This will also fix this particularly tricky test case, as mentioned in #1682 (comment)

export fn entry(cond: bool, a: i8, b: i32) void {
    const x = if (cond) a else b;
}

So the plan is to close this branch, and then branch again from master. However, I think there are some important things to work on before embarking on a third copy elision attempt, such as merging the outstanding pull requests, and some other features to get into 0.4.0 besides copy elision.

Master branch is open for business.

@andrewrk andrewrk closed this Jan 30, 2019
@andrewrk andrewrk deleted the copy-elision-2 branch June 26, 2019 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
work in progress This pull request is not ready for review yet.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants