Skip to content

Instantly share code, notes, and snippets.

@hamishknight
Created January 3, 2017 14:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hamishknight/6188acb60ddf9787fa66f4b5c5df32f6 to your computer and use it in GitHub Desktop.
Save hamishknight/6188acb60ddf9787fa66f4b5c5df32f6 to your computer and use it in GitHub Desktop.
For the code:
class Foo {}
struct Bar {
var foo = Foo()
public mutating func bar() {
isKnownUniquelyReferenced(&foo) // I removed the print() as it generates a bunch of unrelated SIL.
func nestedFunc() {
_ = self // capture self
}
nestedFunc()
}
}
var b = Bar()
b.bar() // false ?!
The relevant part of the canonical SIL generated, the bar() method (xcrun swiftc -emit-sil main.swift | xcrun swift-demangle > main.silgen):
sil hidden @main.Bar.bar () -> () : $@convention(method) (@inout Bar) -> () {
// %0 // users: %10, %3
bb0(%0 : $*Bar):
// create new heap-allocated box, and store self in it.
// this is where the problem stems from – there are now two copies of the Bar instance, thus isKnownUniquelyReferenced will return false.
%1 = alloc_box $Bar, var, name "self", argno 1, loc "main.swift":15:26, scope 9 // users: %11, %9, %7, %2
%2 = project_box %1 : $@box Bar, loc "main.swift":15:26, scope 9 // users: %10, %5, %3
copy_addr %0 to [initialization] %2 : $*Bar, scope 9 // id: %3
// call isKnownUniquelyReferenced.
// function_ref isKnownUniquelyReferenced<A where ...> (inout A) -> Bool
%4 = function_ref @Swift.isKnownUniquelyReferenced <A where A: Swift.AnyObject> (inout A) -> Swift.Bool : $@convention(thin) <τ_0_0 where τ_0_0 : AnyObject> (@inout τ_0_0) -> Bool, loc "main.swift":17:9, scope 10 // user: %6
%5 = struct_element_addr %2 : $*Bar, #Bar.foo, loc "main.swift":17:35, scope 10 // user: %6
%6 = apply %4<Foo>(%5) : $@convention(thin) <τ_0_0 where τ_0_0 : AnyObject> (@inout τ_0_0) -> Bool, loc "main.swift":17:39, scope 10
// retain the heap-allocated box containing self, in preparation for applying nestedFunc() with it.
// (as it's passed as an @owned parameter).
strong_retain %1 : $@box Bar, loc "main.swift":27:9, scope 10 // id: %7
// call the nested function with the box as the argument.
// function_ref Bar.(bar() -> ()).(nestedFunc #1)() -> ()
%8 = function_ref @main.Bar.(bar () -> ()).(nestedFunc #1) () -> () : $@convention(thin) (@owned @box Bar) -> (), loc "main.swift":27:9, scope 10 // user: %9
%9 = apply %8(%1) : $@convention(thin) (@owned @box Bar) -> (), loc "main.swift":27:20, scope 10
// once called, copy the contents of the box back to the address of the Bar instance that was passed in, and release the box.
copy_addr %2 to %0 : $*Bar, scope 10 // id: %10
strong_release %1 : $@box Bar, loc "main.swift":29:5, scope 10 // id: %11
// so cute.
%12 = tuple (), loc "main.swift":29:5, scope 10 // user: %13
return %12 : $(), loc "main.swift":29:5, scope 10 // id: %13
}
Full SIL for reference:
sil_stage canonical
import Builtin
import Swift
import SwiftShims
// b
sil_global hidden @main.b : main.Bar : $Bar
// static CommandLine._argc
sil_global [fragile] @static Swift.CommandLine._argc : Swift.Int32 : $Int32
// globalinit_33_FD9A49A256BEB6AF7C48013347ADC3BA_token4
sil_global private_external [fragile] @globalinit_33_FD9A49A256BEB6AF7C48013347ADC3BA_token4 : $Builtin.Word
// static CommandLine._unsafeArgv
sil_global [fragile] @static Swift.CommandLine._unsafeArgv : Swift.UnsafeMutablePointer<Swift.UnsafeMutablePointer<Swift.Int8>?> : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>
sil_scope 1 { parent @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 }
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
// %0 // user: %3
// %1 // user: %9
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
%2 = global_addr @static Swift.CommandLine._argc : Swift.Int32 : $*Int32, scope 1 // user: %3
store %0 to %2 : $*Int32, scope 1 // id: %3
%4 = global_addr @globalinit_33_FD9A49A256BEB6AF7C48013347ADC3BA_token4 : $*Builtin.Word, scope 1 // user: %5
%5 = address_to_pointer %4 : $*Builtin.Word to $Builtin.RawPointer, scope 1 // user: %7
// function_ref globalinit_33_FD9A49A256BEB6AF7C48013347ADC3BA_func4
%6 = function_ref @globalinit_33_FD9A49A256BEB6AF7C48013347ADC3BA_func4 : $@convention(thin) () -> (), scope 1 // user: %7
%7 = builtin "once"(%5 : $Builtin.RawPointer, %6 : $@convention(thin) () -> ()) : $(), scope 1
%8 = global_addr @static Swift.CommandLine._unsafeArgv : Swift.UnsafeMutablePointer<Swift.UnsafeMutablePointer<Swift.Int8>?> : $*UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>, scope 1 // user: %9
store %1 to %8 : $*UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>, scope 1 // id: %9
%10 = tuple (), scope 1
// create a new Bar instance.
alloc_global @main.b : main.Bar, loc "main.swift":32:5, scope 1 // id: %11
%12 = global_addr @main.b : main.Bar : $*Bar, loc "main.swift":32:5, scope 1 // users: %18, %16
// function_ref Bar.init() -> Bar
%13 = function_ref @main.Bar.init () -> main.Bar : $@convention(method) (@thin Bar.Type) -> @owned Bar, loc "main.swift":32:9, scope 1 // user: %15
%14 = metatype $@thin Bar.Type, loc "main.swift":32:9, scope 1 // user: %15
%15 = apply %13(%14) : $@convention(method) (@thin Bar.Type) -> @owned Bar, loc "main.swift":32:13, scope 1 // user: %16
store %15 to %12 : $*Bar, loc "main.swift":32:13, scope 1 // id: %16
// calling bar() on the Bar instance.
// as it's a mutating method, the Bar instance is passed in by reference (@inout).
// function_ref Bar.bar() -> ()
%17 = function_ref @main.Bar.bar () -> () : $@convention(method) (@inout Bar) -> (), loc "main.swift":33:3, scope 1 // user: %18
%18 = apply %17(%12) : $@convention(method) (@inout Bar) -> (), loc "main.swift":33:7, scope 1
%19 = integer_literal $Builtin.Int32, 0, scope 1 // user: %20
%20 = struct $Int32 (%19 : $Builtin.Int32), scope 1 // user: %21
return %20 : $Int32, scope 1 // id: %21
}
sil_scope 2 { parent @Swift._stdlib_didEnterMain (argc : Swift.Int32, argv : Swift.UnsafeMutablePointer<Swift.UnsafeMutablePointer<Swift.Int8>?>) -> () : $@convention(thin) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> () }
// _stdlib_didEnterMain(argc : Int32, argv : UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>) -> ()
sil public_external [transparent] [fragile] @Swift._stdlib_didEnterMain (argc : Swift.Int32, argv : Swift.UnsafeMutablePointer<Swift.UnsafeMutablePointer<Swift.Int8>?>) -> () : $@convention(thin) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> () {
// %0 // users: %5, %2
// %1 // users: %11, %3
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
debug_value %0 : $Int32, scope 2 // id: %2
debug_value %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>, scope 2 // id: %3
%4 = global_addr @static Swift.CommandLine._argc : Swift.Int32 : $*Int32, scope 2 // user: %5
store %0 to %4 : $*Int32, scope 2 // id: %5
%6 = global_addr @globalinit_33_FD9A49A256BEB6AF7C48013347ADC3BA_token4 : $*Builtin.Word, scope 2 // user: %7
%7 = address_to_pointer %6 : $*Builtin.Word to $Builtin.RawPointer, scope 2 // user: %9
// function_ref globalinit_33_FD9A49A256BEB6AF7C48013347ADC3BA_func4
%8 = function_ref @globalinit_33_FD9A49A256BEB6AF7C48013347ADC3BA_func4 : $@convention(thin) () -> (), scope 2 // user: %9
%9 = builtin "once"(%7 : $Builtin.RawPointer, %8 : $@convention(thin) () -> ()) : $(), scope 2
%10 = global_addr @static Swift.CommandLine._unsafeArgv : Swift.UnsafeMutablePointer<Swift.UnsafeMutablePointer<Swift.Int8>?> : $*UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>, scope 2 // user: %11
store %1 to %10 : $*UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>, scope 2 // id: %11
%12 = tuple (), scope 2 // user: %13
return %12 : $(), scope 2 // id: %13
}
sil_scope 3 { loc "main.swift":9:7 parent @main.Foo.__deallocating_deinit : $@convention(method) (@owned Foo) -> () }
// Foo.__deallocating_deinit
sil hidden @main.Foo.__deallocating_deinit : $@convention(method) (@owned Foo) -> () {
// %0 // users: %3, %1
bb0(%0 : $Foo):
debug_value %0 : $Foo, let, name "self", argno 1, loc "main.swift":9:7, scope 3 // id: %1
// function_ref Foo.deinit
%2 = function_ref @main.Foo.deinit : $@convention(method) (@guaranteed Foo) -> @owned Builtin.NativeObject, scope 3 // user: %3
%3 = apply %2(%0) : $@convention(method) (@guaranteed Foo) -> @owned Builtin.NativeObject, scope 3 // user: %4
%4 = unchecked_ref_cast %3 : $Builtin.NativeObject to $Foo, scope 3 // user: %5
dealloc_ref %4 : $Foo, scope 3 // id: %5
%6 = tuple (), scope 3 // user: %7
return %6 : $(), scope 3 // id: %7
}
sil_scope 4 { loc "main.swift":9:7 parent @main.Foo.deinit : $@convention(method) (@guaranteed Foo) -> @owned Builtin.NativeObject }
sil_scope 5 { loc "main.swift":9:7 parent 4 }
// Foo.deinit
sil hidden @main.Foo.deinit : $@convention(method) (@guaranteed Foo) -> @owned Builtin.NativeObject {
// %0 // users: %2, %1
bb0(%0 : $Foo):
debug_value %0 : $Foo, let, name "self", argno 1, loc "main.swift":9:7, scope 4 // id: %1
%2 = unchecked_ref_cast %0 : $Foo to $Builtin.NativeObject, scope 5 // user: %3
return %2 : $Builtin.NativeObject, scope 5 // id: %3
}
sil_scope 6 { loc "main.swift":9:7 parent @main.Foo.init () -> main.Foo : $@convention(method) (@owned Foo) -> @owned Foo }
sil_scope 7 { parent 6 }
// Foo.init() -> Foo
sil hidden @main.Foo.init () -> main.Foo : $@convention(method) (@owned Foo) -> @owned Foo {
// %0 // users: %2, %1
bb0(%0 : $Foo):
debug_value %0 : $Foo, let, name "self", loc "main.swift":9:7, scope 6 // id: %1
return %0 : $Foo, loc "main.swift":9:7, scope 7 // id: %2
}
sil_scope 8 { loc "main.swift":9:7 parent @main.Foo.__allocating_init () -> main.Foo : $@convention(method) (@thick Foo.Type) -> @owned Foo }
// Foo.__allocating_init() -> Foo
sil hidden @main.Foo.__allocating_init () -> main.Foo : $@convention(method) (@thick Foo.Type) -> @owned Foo {
bb0(%0 : $@thick Foo.Type):
%1 = alloc_ref $Foo, scope 8 // user: %3
// function_ref Foo.init() -> Foo
%2 = function_ref @main.Foo.init () -> main.Foo : $@convention(method) (@owned Foo) -> @owned Foo, scope 8 // user: %3
%3 = apply %2(%1) : $@convention(method) (@owned Foo) -> @owned Foo, scope 8 // user: %4
return %3 : $Foo, scope 8 // id: %4
}
sil_scope 9 { loc "main.swift":15:26 parent @main.Bar.bar () -> () : $@convention(method) (@inout Bar) -> () }
sil_scope 10 { loc "main.swift":29:5 parent 9 }
// Bar.bar() -> ()
sil hidden @main.Bar.bar () -> () : $@convention(method) (@inout Bar) -> () {
// %0 // users: %10, %3
bb0(%0 : $*Bar):
// create new heap-allocated box, and store self in it.
// this is where the problem stems from – there are now two copies of the Bar instance, thus isKnownUniquelyReferenced will return false.
%1 = alloc_box $Bar, var, name "self", argno 1, loc "main.swift":15:26, scope 9 // users: %11, %9, %7, %2
%2 = project_box %1 : $@box Bar, loc "main.swift":15:26, scope 9 // users: %10, %5, %3
copy_addr %0 to [initialization] %2 : $*Bar, scope 9 // id: %3
// call isKnownUniquelyReferenced.
// function_ref isKnownUniquelyReferenced<A where ...> (inout A) -> Bool
%4 = function_ref @Swift.isKnownUniquelyReferenced <A where A: Swift.AnyObject> (inout A) -> Swift.Bool : $@convention(thin) <τ_0_0 where τ_0_0 : AnyObject> (@inout τ_0_0) -> Bool, loc "main.swift":17:9, scope 10 // user: %6
%5 = struct_element_addr %2 : $*Bar, #Bar.foo, loc "main.swift":17:35, scope 10 // user: %6
%6 = apply %4<Foo>(%5) : $@convention(thin) <τ_0_0 where τ_0_0 : AnyObject> (@inout τ_0_0) -> Bool, loc "main.swift":17:39, scope 10
// retain the heap-allocated box containing self, in preparation for applying nestedFunc() with it.
// (as it's passed as an @owned parameter).
strong_retain %1 : $@box Bar, loc "main.swift":27:9, scope 10 // id: %7
// call the nested function with the box as the argument.
// function_ref Bar.(bar() -> ()).(nestedFunc #1)() -> ()
%8 = function_ref @main.Bar.(bar () -> ()).(nestedFunc #1) () -> () : $@convention(thin) (@owned @box Bar) -> (), loc "main.swift":27:9, scope 10 // user: %9
%9 = apply %8(%1) : $@convention(thin) (@owned @box Bar) -> (), loc "main.swift":27:20, scope 10
// once called, copy the contents of the box back to the address of the Bar instance that was passed in, and release the box.
copy_addr %2 to %0 : $*Bar, scope 10 // id: %10
strong_release %1 : $@box Bar, loc "main.swift":29:5, scope 10 // id: %11
// so cute.
%12 = tuple (), loc "main.swift":29:5, scope 10 // user: %13
return %12 : $(), loc "main.swift":29:5, scope 10 // id: %13
}
// isKnownUniquelyReferenced<A where ...> (inout A) -> Bool
sil [fragile] @Swift.isKnownUniquelyReferenced <A where A: Swift.AnyObject> (inout A) -> Swift.Bool : $@convention(thin) <T where T : AnyObject> (@inout T) -> Bool
sil_scope 11 { loc "main.swift":23:14 parent @main.Bar.(bar () -> ()).(nestedFunc #1) () -> () : $@convention(thin) (@owned @box Bar) -> () }
sil_scope 12 { loc "main.swift":25:9 parent 11 }
// Bar.(bar() -> ()).(nestedFunc #1)() -> ()
sil shared @main.Bar.(bar () -> ()).(nestedFunc #1) () -> () : $@convention(thin) (@owned @box Bar) -> () {
// %0 // users: %3, %1
bb0(%0 : $@box Bar):
%1 = project_box %0 : $@box Bar, loc "main.swift":15:26, scope 11 // user: %2
debug_value_addr %1 : $*Bar, var, name "self", argno 1, loc "main.swift":15:26, scope 11 // id: %2
strong_release %0 : $@box Bar, loc "main.swift":25:9, scope 12 // id: %3
%4 = tuple (), loc "main.swift":25:9, scope 12 // user: %5
return %4 : $(), loc "main.swift":25:9, scope 12 // id: %5
}
sil_scope 13 { loc "main.swift":13:9 parent @main.Bar.foo.getter : main.Foo : $@convention(method) (@guaranteed Bar) -> @owned Foo }
sil_scope 14 { loc "main.swift":13:9 parent 13 }
// Bar.foo.getter
sil hidden [transparent] @main.Bar.foo.getter : main.Foo : $@convention(method) (@guaranteed Bar) -> @owned Foo {
// %0 // users: %2, %1
bb0(%0 : $Bar):
debug_value %0 : $Bar, let, name "self", argno 1, loc "main.swift":13:9, scope 13 // id: %1
%2 = struct_extract %0 : $Bar, #Bar.foo, scope 14 // users: %4, %3
strong_retain %2 : $Foo, scope 14 // id: %3
return %2 : $Foo, loc "main.swift":13:9, scope 14 // id: %4
}
sil_scope 15 { loc "main.swift":13:9 parent @main.Bar.foo.setter : main.Foo : $@convention(method) (@owned Foo, @inout Bar) -> () }
sil_scope 16 { loc "main.swift":13:9 parent 15 }
// Bar.foo.setter
sil hidden [transparent] @main.Bar.foo.setter : main.Foo : $@convention(method) (@owned Foo, @inout Bar) -> () {
// %0 // users: %7, %9, %4, %3
// %1 // users: %2, %5
bb0(%0 : $Foo, %1 : $*Bar):
debug_value_addr %1 : $*Bar, var, name "self", argno 2, loc "main.swift":13:9, scope 15 // id: %2
debug_value %0 : $Foo, let, name "value", argno 1, loc "main.swift":13:9, scope 15 // id: %3
strong_retain %0 : $Foo, scope 16 // id: %4
%5 = struct_element_addr %1 : $*Bar, #Bar.foo, scope 16 // users: %7, %6
%6 = load %5 : $*Foo, scope 16 // user: %8
store %0 to %5 : $*Foo, scope 16 // id: %7
strong_release %6 : $Foo, scope 16 // id: %8
strong_release %0 : $Foo, loc "main.swift":13:9, scope 16 // id: %9
%10 = tuple (), loc "main.swift":13:9, scope 16 // user: %11
return %10 : $(), loc "main.swift":13:9, scope 16 // id: %11
}
sil_scope 17 { loc "main.swift":13:9 parent @main.Bar.foo.materializeForSet : main.Foo : $@convention(method) (Builtin.RawPointer, @inout Builtin.UnsafeValueBuffer, @inout Bar) -> (Builtin.RawPointer, Optional<Builtin.RawPointer>) }
// Bar.foo.materializeForSet
sil hidden [transparent] @main.Bar.foo.materializeForSet : main.Foo : $@convention(method) (Builtin.RawPointer, @inout Builtin.UnsafeValueBuffer, @inout Bar) -> (Builtin.RawPointer, Optional<Builtin.RawPointer>) {
// %2 // user: %3
bb0(%0 : $Builtin.RawPointer, %1 : $*Builtin.UnsafeValueBuffer, %2 : $*Bar):
%3 = struct_element_addr %2 : $*Bar, #Bar.foo, scope 17 // user: %4
%4 = address_to_pointer %3 : $*Foo to $Builtin.RawPointer, scope 17 // user: %6
%5 = enum $Optional<Builtin.RawPointer>, #Optional.none!enumelt, scope 17 // user: %6
%6 = tuple (%4 : $Builtin.RawPointer, %5 : $Optional<Builtin.RawPointer>), scope 17 // user: %7
return %6 : $(Builtin.RawPointer, Optional<Builtin.RawPointer>), scope 17 // id: %7
}
sil_scope 18 { loc "main.swift":11:8 parent @main.Bar.init (foo : main.Foo) -> main.Bar : $@convention(method) (@owned Foo, @thin Bar.Type) -> @owned Bar }
// Bar.init(foo : Foo) -> Bar
sil hidden @main.Bar.init (foo : main.Foo) -> main.Bar : $@convention(method) (@owned Foo, @thin Bar.Type) -> @owned Bar {
// %0 // user: %2
bb0(%0 : $Foo, %1 : $@thin Bar.Type):
%2 = struct $Bar (%0 : $Foo), scope 18 // user: %3
return %2 : $Bar, scope 18 // id: %3
}
sil_scope 19 { loc "main.swift":11:8 parent @main.Bar.init () -> main.Bar : $@convention(method) (@thin Bar.Type) -> @owned Bar }
sil_scope 20 { parent 19 }
// Bar.init() -> Bar
sil hidden @main.Bar.init () -> main.Bar : $@convention(method) (@thin Bar.Type) -> @owned Bar {
bb0(%0 : $@thin Bar.Type):
%1 = alloc_stack $Bar, var, name "self", argno 1, loc "main.swift":11:8, scope 19 // users: %5, %11
// function_ref Foo.__allocating_init() -> Foo
%2 = function_ref @main.Foo.__allocating_init () -> main.Foo : $@convention(method) (@thick Foo.Type) -> @owned Foo, loc "main.swift":13:15, scope 19 // user: %4
%3 = metatype $@thick Foo.Type, loc "main.swift":13:15, scope 19 // user: %4
%4 = apply %2(%3) : $@convention(method) (@thick Foo.Type) -> @owned Foo, loc "main.swift":13:19, scope 19 // users: %9, %7, %6
%5 = struct_element_addr %1 : $*Bar, #Bar.foo, loc "main.swift":13:9, scope 19 // user: %6
store %4 to %5 : $*Foo, loc "main.swift":13:9, scope 19 // id: %6
%7 = struct $Bar (%4 : $Foo), loc "main.swift":11:8, scope 20 // users: %8, %12
retain_value %7 : $Bar, loc "main.swift":11:8, scope 20 // id: %8
%9 = struct $Bar (%4 : $Foo), loc "main.swift":11:8, scope 20 // user: %10
release_value %9 : $Bar, loc "main.swift":11:8, scope 20 // id: %10
dealloc_stack %1 : $*Bar, loc "main.swift":11:8, scope 20 // id: %11
return %7 : $Bar, loc "main.swift":11:8, scope 20 // id: %12
}
// globalinit_33_FD9A49A256BEB6AF7C48013347ADC3BA_func4
sil private_external [fragile] @globalinit_33_FD9A49A256BEB6AF7C48013347ADC3BA_func4 : $@convention(thin) () -> ()
sil_vtable Foo {
#Foo.deinit!deallocator: main.Foo.__deallocating_deinit // Foo.__deallocating_deinit
#Foo.init!initializer.1: main.Foo.init () -> main.Foo // Foo.init() -> Foo
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment