Skip to content

Instantly share code, notes, and snippets.

@hamishknight
Last active October 20, 2022 07:19
Show Gist options
  • Save hamishknight/c1fc4a1b9fa8135870095ca07ad1f89b to your computer and use it in GitHub Desktop.
Save hamishknight/c1fc4a1b9fa8135870095ca07ad1f89b to your computer and use it in GitHub Desktop.

The SIL here was emitted for a -Onone Swift 3.1 build, and is completely subject to change with optimisations and future versions of Swift.

The use of temporary variables for pointer passing

For the following code:

func aaa(_ key: UnsafeRawPointer) {}

class A {
    var key = "aaa"
}

let a = A()
aaa(&a.key)

A simplified look at the SIL emitted (we're not considering an extra parameter for now – as it doesn't make a difference in unoptimised builds) is as follows for the call to aaa:

// get reference to function 'aaa'.
%13 = function_ref @main.aaa (Swift.UnsafeRawPointer) -> ()

// load the reference to the instance of A from the global variable 'a'.
%14 = load %8 : $*A

// create temporary variable to hold a.key
%15 = alloc_stack $String

// get a reference to a.key's getter
%16 = class_method %14 : $A, #A.key!getter.1 : (A) -> () -> String

// call a.key's getter
%17 = apply %16(%14) : $(A) -> String

// store the result to the temporary variable.
store %17 to %15 : $*String

// get the address of the temporary variable.
%19 = address_to_pointer %15 : $*String to $Builtin.RawPointer

// wrap the address in a UnsafeRawPointer.
%20 = struct $UnsafeRawPointer (%19 : $Builtin.RawPointer)

// finally, call 'aaa' with the pointer as the argument.
%23 = apply %13(%20) : $(UnsafeRawPointer) -> ()

Direct pointer passing when the property is final

For the following code:

func aaa(_ key: UnsafeRawPointer) {}

class A {
    final var key = "aaa"
}

let a = A()
aaa(&a.key)

A simplified look at the SIL emitted for the call to aaa:

// get reference to function 'aaa'.
%13 = function_ref @main.aaa (Swift.UnsafeRawPointer) -> ()

// load the reference to the instance of A from the global variable 'a'.
%14 = load %8 : $*A

// get the address of the 'key' property on the instance.
%15 = ref_element_addr %14 : $A, #A.key

// convert that address to a Builtin.RawPointer.
%16 = address_to_pointer %15 : $*String to $Builtin.RawPointer

// wrap the pointer in an UnsafeRawPointer.
%17 = struct $UnsafeRawPointer (%16 : $Builtin.RawPointer)

// finally, call 'aaa' with the pointer as the argument.
%20 = apply %13(%17) : $(UnsafeRawPointer) -> ()

The use of materializeForSet for inout

For the following code:

func aaa(_ key: inout String) {}

class A {
    var key = "aaa"
}

let a = A()
aaa(&a.key)

A 'simplified' look at the SIL emitted for the call to aaa:

  // get reference to function 'aaa'.
  %8 = function_ref @main.aaa (inout Swift.String) -> ()

  // load the reference to the instance of A from the global variable 'a'.
  %9 = load %3 : $*A

  // create temporary UnsafeValueBuffer (I'm actually not too sure what this is used for).
  %10 = alloc_stack $Builtin.UnsafeValueBuffer

  // create temporary variable to potentially store the value from a.key's getter,
  // if a direct pointer to the storage cannot be provided.
  %11 = alloc_stack $String

  // get a pointer to the temporary variable.
  %12 = address_to_pointer %11 : $*String to $Builtin.RawPointer

  // get a reference to a.key's materializeForSet accessor.
  %13 = class_method %9 : $A, #A.key!materializeForSet.1 : $(Builtin.RawPointer, @inout Builtin.UnsafeValueBuffer, A) -> (Builtin.RawPointer, Optional<Builtin.RawPointer>)

  // call materializeForSet with the pointer to the temporary storage for a.key, along with the UnsafeValueBuffer and instance of A.
  %14 = apply %13(%12, %10, %9) : $(Builtin.RawPointer, @inout Builtin.UnsafeValueBuffer, A) -> (Builtin.RawPointer, Optional<Builtin.RawPointer>)

  // get the first element of the tuple returned – which is the pointer to a.key,
  // this is either a direct pointer, or a pointer to the temporary variable (in this case, it's the former).
  %15 = tuple_extract %14 : $(Builtin.RawPointer, Optional<Builtin.RawPointer>), 0

  // get the second element of the tuple returned – simply a flag to know if we have a direct pointer.
  %16 = tuple_extract %14 : $(Builtin.RawPointer, Optional<Builtin.RawPointer>), 1

  // convert the pointer address to a *String address.
  %17 = pointer_to_address %15 : $Builtin.RawPointer to [strict] $*String

  // ensure that the instance of A is not deallocated until after we're done using the pointer to a.key.
  %18 = mark_dependence %17 : $*String on %9 : $A

  // call 'aaa' with the reference to a.key, passed as an @inout argument (which is by-reference).
  %19 = apply %8(%18) : (@inout String) -> ()

  // switch over the flag of whether we have a direct pointer to a.key.
  //
  // If we do have a direct pointer (flag will be .none), go to bb1 (which skips to bb3).
  //
  // If we don't (flag will be .some, with a wrapped value of a reference to the setter),
  // go to bb2, which handles writing the value back to a.key's setter.
  //
  switch_enum %16 : $Optional<Builtin.RawPointer>,
    case #Optional.some!enumelt.1: bb2,
    case #Optional.none!enumelt: bb1

bb1:
  br bb3

bb2(%22 : $Builtin.RawPointer):

  // cast the wrapped value to a reference to a.key's setter, then apply with the pointer to the temporary variable,
  // as well as the UnsafeValueBuffer, instance of A, and metatype of A.
  %23 = pointer_to_thin_function %22 : $Builtin.RawPointer to $(Builtin.RawPointer, @inout Builtin.UnsafeValueBuffer, @inout A, @thick A.Type) -> ()
  %24 = alloc_stack $A
  store %9 to %24 : $*A
  %26 = metatype $@thick A.Type
  %27 = address_to_pointer %18 : $*String to $Builtin.RawPointer
  %28 = apply %23(%27, %10, %24, %26) : $(Builtin.RawPointer, @inout Builtin.UnsafeValueBuffer, @inout A, @thick A.Type) -> ()
  dealloc_stack %24 : $*A
  br bb3

bb3:
  dealloc_stack %11 : $*String
  dealloc_stack %10 : $*Builtin.UnsafeValueBuffer

And the SIL for a.key's materializeForSet acccessor:

// A.key.materializeForSet
sil hidden @main.A.key.materializeForSet : Swift.String : $(Builtin.RawPointer, @inout Builtin.UnsafeValueBuffer, @guaranteed A) -> (Builtin.RawPointer, Optional<Builtin.RawPointer>) {
bb0(%0 : $Builtin.RawPointer, %1 : $*Builtin.UnsafeValueBuffer, %2 : $A):

  // simply get a reference to the instance's storage for the 'key' property.
  %3 = ref_element_addr %2 : $A, #A.key

  // convert this reference to a Builtin.RawPointer.
  %4 = address_to_pointer %3 : $*String to $Builtin.RawPointer

  // simply return the pointer, along with a flag to indicate that it's a direct pointer to the storage.
  %5 = enum $Optional<Builtin.RawPointer>, #Optional.none!enumelt
  %6 = tuple (%4 : $Builtin.RawPointer, %5 : $Optional<Builtin.RawPointer>)
  return %6 : $(Builtin.RawPointer, Optional<Builtin.RawPointer>)
}

Which shows that we're getting a direct pointer to a.key's storage.

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