Skip to content

Instantly share code, notes, and snippets.

@hamishknight
Last active June 23, 2023 14:46
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save hamishknight/edbcc3b9cc92158a35488ce28108fe9f to your computer and use it in GitHub Desktop.
Save hamishknight/edbcc3b9cc92158a35488ce28108fe9f to your computer and use it in GitHub Desktop.

Constant evalutation at compile time

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
}

Optimising value type parameters to pass by reference

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)

Specialising functions to only take the property values of a structure that they need

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)
@isaac-weisberg
Copy link

Holy moly...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment