After some more messing around, I've found that when you extract it to a function, it matters what side of the function the .pointee
is on.
func typedMalloc<T>(_: T.Type) -> UnsafeMutablePointer<T> {
malloc(MemoryLayout<T>.size)!.bindMemory(to: T.self, capacity: 1)
}
typedMalloc(Never.self).pointee
print("Why am I still here")
func unsafeMakeUninitializedInstance<T>(_: T.Type) -> T {
malloc(MemoryLayout<T>.size)!.bindMemory(to: T.self, capacity: 1).pointee
}
unsafeMakeUninitializedInstance(Never.self)
print("This is eliminated as dead code")
I have two unrelated points to make here.
- A weird corollary to this is that the former can't be used to make a function that explicitly returns
Never
unless you wrap it correctly:
func typedMallocAndDereference<T>(_: T.Type) -> T {
typedMalloc(T.self).pointee
}
func thisDoesntEvenCompile() -> Never {
typedMalloc(Never.self).pointee
}
func butThisDoes() -> Never {
typedMallocAndDereference(Never.self)
}
I realize that typedMallocAndDereference
here (as well as unsafeMakeUninitializedInstance
above) is just asking for nasal demons, but this is a very weird inconsistency to me. It shouldn't matter whether the Never
comes from a function call or a property access, right?
- Under
-O
, unsafeMakeUninitializedInstance
is inlined in the SIL, which reflects it as this:
%59 = apply %4(%3) : $@convention(c) (Int) -> Optional<UnsafeMutableRawPointer>, loc "/app/example.swift":10:5, scope 28 // user: %60
switch_enum %59 : $Optional<UnsafeMutableRawPointer>, case #Optional.some!enumelt: bb3, case #Optional.none!enumelt: bb4, loc "/app/example.swift":10:33, scope 28 // id: %60
bb3(%61 : $UnsafeMutableRawPointer): // Preds: bb2
%62 = struct_extract %61 : $UnsafeMutableRawPointer, #UnsafeMutableRawPointer._rawValue, loc "/app/example.swift":10:35, scope 28 // user: %63
%63 = bind_memory %62 : $Builtin.RawPointer, %12 : $Builtin.Word to $*Never, loc "/app/example.swift":10:35, scope 28
unreachable , loc "/app/example.swift":12:1, scope 25 // id: %64
bb4: // Preds: bb2
%65 = integer_literal $Builtin.Int1, -1, loc "/app/example.swift":7:7, scope 6 // user: %66
cond_fail %65 : $Builtin.Int1, "Unexpectedly found nil while unwrapping an Optional value", loc "/app/example.swift":10:33, scope 28 // id: %66
unreachable , loc "/app/example.swift":10:33, scope 28 // id: %67
It actually calls malloc
and bind_memory
, but then is immediately followed by an unreachable
. I think it would be a viable solution to emit a trap instead of unreachable
here, but I wouldn't know how to check that.