For the following code:
struct Point {
var x: Float = 0
}
var p1 = Point()
var p2 = p1
p2.x += 1
The relevant IR emitted in a -O build:
@main.p1 : main.Point = hidden local_unnamed_addr global %V4main5Point zeroinitializer, align 4
@main.p2 : main.Point = hidden local_unnamed_addr global %V4main5Point zeroinitializer, align 4
// ...
define i32 @main(i32, i8** nocapture readnone) local_unnamed_addr #0 {
entry:
store float 0.000000e+00, float* getelementptr inbounds (%V4main5Point, %V4main5Point* @main.p1 : main.Point, i64 0, i32 0, i32 0), align 4
store float 1.000000e+00, float* getelementptr inbounds (%V4main5Point, %V4main5Point* @main.p2 : main.Point, i64 0, i32 0, i32 0), align 4
ret i32 0
}
For the following code:
struct Point {
var x: Float = 0
}
var x: Float = 0
@inline(never)
func foo() {
let p1 = Point()
var p2 = p1
p2.x += 1
x = p2.x
}
foo()
print(x)
The IR for the foo()
function in a -O build:
define hidden void @main.foo () -> ()() local_unnamed_addr #3 {
entry:
store float 1.000000e+00, float* getelementptr inbounds (%Sf, %Sf* @main.x : Swift.Float, i64 0, i32 0), align 4
ret void
}
For the following code:
struct Foo {
var a = 0
var b = 1
var c = 2
var d = 3
}
var foo = Foo()
@inline(never)
func bar(f: Foo) {
foo = f
}
bar(f: Foo(a: 5, b: 6, c: 7, d: 8))
print(foo)
The IR emitted for the bar()
function in a -O build:
define hidden void @main.bar (f : main.Foo) -> ()(%V4main3Foo* noalias nocapture readonly dereferenceable(32)) local_unnamed_addr #4 {
entry:
// copying the instance in two chunks of 128 bits.
%1 = bitcast %V4main3Foo* %0 to <2 x i64>*
%2 = load <2 x i64>, <2 x i64>* %1, align 8
%.c._value = getelementptr inbounds %V4main3Foo, %V4main3Foo* %0, i64 0, i32 2, i32 0
%3 = bitcast i64* %.c._value to <2 x i64>*
%4 = load <2 x i64>, <2 x i64>* %3, align 8
store <2 x i64> %2, <2 x i64>* bitcast (%V4main3Foo* @main.foo : main.Foo to <2 x i64>*), align 16
store <2 x i64> %4, <2 x i64>* bitcast (i64* getelementptr inbounds (%V4main3Foo, %V4main3Foo* @main.foo : main.Foo, i64 0, i32 2, i32 0) to <2 x i64>*), align 16
ret void
}
Then called as:
// allocate memory on the stack for a Foo instance.
%2 = alloca %V4main3Foo, align 16
// ...
// simply storing the values 5, 6, 7, 8 in chunks of 128 bits to the properties.
%3 = bitcast %V4main3Foo* %2 to <2 x i64>*
store <2 x i64> <i64 5, i64 6>, <2 x i64>* %3, align 16
%.c._value = getelementptr inbounds %V4main3Foo, %V4main3Foo* %2, i64 0, i32 2, i32 0
%4 = bitcast i64* %.c._value to <2 x i64>*
store <2 x i64> <i64 7, i64 8>, <2 x i64>* %4, align 16
// call by passing the instance of Foo by reference.
call void @main.bar (f : main.Foo) -> ()(%V4main3Foo* noalias nocapture nonnull dereferenceable(32) %2)
You can see that the Foo
instance is passed in by reference – and then copied in the bar()
function to store to main.foo
. If we were just reading from f
's
properties, no copying of the Foo
instance would have taken place upon calling the function – we can just dereference the pointer.
If we reduce the number of properties:
struct Foo {
var a = 0
var b = 1
var c = 2
}
var foo = Foo()
@inline(never)
func bar(f: Foo) {
foo = f
}
bar(f: Foo(a: 5, b: 6, c: 7))
print(foo)
Swift now no longer considers it worthwhile to pass by reference, and passes the 3 property values by value as individual parameters:
define hidden void @main.bar (f : main.Foo) -> ()(i64, i64, i64) local_unnamed_addr #3 {
entry:
store i64 %0, i64* getelementptr inbounds (%V4main3Foo, %V4main3Foo* @main.foo : main.Foo, i64 0, i32 0, i32 0), align 16
store i64 %1, i64* getelementptr inbounds (%V4main3Foo, %V4main3Foo* @main.foo : main.Foo, i64 0, i32 1, i32 0), align 8
store i64 %2, i64* getelementptr inbounds (%V4main3Foo, %V4main3Foo* @main.foo : main.Foo, i64 0, i32 2, i32 0), align 16
ret void
}
Simply called as:
tail call void @main.bar (f : main.Foo) -> ()(i64 5, i64 6, i64 7)
For the following code:
struct Foo {
var a = 0
var b = 1
var c = 2
}
var a = 0
@inline(never)
func bar(f: Foo) {
a = f.a
}
bar(f: Foo(a: 5, b: 6, c: 7))
print(a)
The IR emitted for the specialised bar()
function in a -O build:
define hidden void @function signature specialization <Arg[0] = Exploded> of main.bar (f : main.Foo) -> ()(i64) local_unnamed_addr #3 {
entry:
store i64 %0, i64* getelementptr inbounds (%Si, %Si* @main.a : Swift.Int, i64 0, i32 0), align 8
ret void
}
Which is then simply called as:
tail call void @function signature specialization <Arg[0] = Exploded> of main.bar (f : main.Foo) -> ()(i64 5)
Holy moly...