-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[stdlib] AutoreleasingUnsafeMutablePointer: eliminate questionable pointer use #26760
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The head ref may contain hidden characters: "\u266A-hold-me-use-me-autorelease-me-\u266C"
[stdlib] AutoreleasingUnsafeMutablePointer: eliminate questionable pointer use #26760
Conversation
…inter use AutoreleasingUnsafeMutablePointer is pointing to a +0 reference, but in its pointee property’s getter/setter implementations, it is loading the pointer into regular Unsafe[Mutable]Pointers. Those are assuming that the addressed memory contain a +1 reference, which can mislead the compiler into doing optimizations that aren’t valid. Change the getter/setter implementations so that they use UnsafeRawPointer and load/store Unmanaged values instead. As long as Unmanaged.passUnretained(_:) and Unmanaged.takeUnretainedValue() do the right thing, then AutoreleasingUnsafeMutablePointer won’t have issues, either. (This boils down to ensuring that loading a strong reference out of an unmanaged(unsafe) value works correctly.)
This is still somewhat of a WIP - AutoreleasingUnsafeMutablePointer doesn't seem to have much test coverage yet, so perhaps this is the right PR to add those. |
@swift-ci benchmark |
Performance: -O
Code size: -OPerformance: -Osize
Code size: -OsizePerformance: -Onone
Code size: -swiftlibsHow to read the dataThe tables contain differences in performance which are larger than 8% and differences in code size which are larger than 1%.If you see any unexpected regressions, you should consider fixing the Noise: Sometimes the performance results (not code size!) contain false Hardware Overview
|
// The memory addressed by this pointer contains a non-owning reference, | ||
// therefore we *must not* point an `UnsafePointer<AnyObject>` to | ||
// it---otherwise we would allow the compiler to assume it has a +1 | ||
// refcount, enabling some optimizations that wouldn't be valid. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense but is kinda terrifying. I need to think carefully about some of the NSDictionary bridging stuff…
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All +0 references in memory are hazards; they must only be handled through the Unmanaged
glovebox. E.g., we'll need to audit any and all NSFastEnumeration implementations & usages from Swift code.
(Currently Unmanaged doesn't quite get this right either, but Michael is already working on fixing that.)
The benchmark results seem irrelevant; I don't see how any of these benchmarks would touch However, this new version does generate a little bit worse code: // SOURCE:
class Foo {}
func foo(_ p: AutoreleasingUnsafeMutablePointer<Foo>) -> Foo {
return p.pointee
} // ORIGINAL SIL:
sil hidden [signature_optimized_thunk] [always_inline] @$s6output3fooyAA3FooCSAyADGF : $@convention(thin) (AutoreleasingUnsafeMutablePointer<Foo>) -> @owned Foo {
bb0(%0 : $AutoreleasingUnsafeMutablePointer<Foo>):
debug_value %0 : $AutoreleasingUnsafeMutablePointer<Foo>, let, name "p", argno 1 // id: %1
%2 = struct_extract %0 : $AutoreleasingUnsafeMutablePointer<Foo>, #AutoreleasingUnsafeMutablePointer._rawValue // user: %3
%3 = pointer_to_address %2 : $Builtin.RawPointer to [strict] $*Foo // user: %4
%4 = load %3 : $*Foo // users: %5, %6
strong_retain %4 : $Foo // id: %5
return %4 : $Foo // id: %6
} // end sil function '$s6output3fooyAA3FooCSAyADGF'
// NEW SIL:
sil hidden @$s6output3fooyAA3FooCSAyADGF : $@convention(thin) (AutoreleasingUnsafeMutablePointer<Foo>) -> @owned Foo {
bb0(%0 : $AutoreleasingUnsafeMutablePointer<Foo>):
debug_value %0 : $AutoreleasingUnsafeMutablePointer<Foo>, let, name "p", argno 1 // id: %1
%2 = struct_extract %0 : $AutoreleasingUnsafeMutablePointer<Foo>, #AutoreleasingUnsafeMutablePointer._rawValue // user: %3
%3 = pointer_to_address %2 : $Builtin.RawPointer to $*Optional<Unmanaged<AnyObject>> // user: %4
%4 = load %3 : $*Optional<Unmanaged<AnyObject>> // user: %5
switch_enum %4 : $Optional<Unmanaged<AnyObject>>, case #Optional.some!enumelt.1: bb1, case #Optional.none!enumelt: bb2 // id: %5
bb1(%6 : $Unmanaged<AnyObject>): // Preds: bb0
%7 = struct_extract %6 : $Unmanaged<AnyObject>, #Unmanaged._value // user: %8
%8 = unmanaged_to_ref %7 : $@sil_unmanaged AnyObject to $AnyObject // users: %10, %9
%9 = enum $Optional<AnyObject>, #Optional.some!enumelt.1, %8 : $AnyObject // user: %11
strong_retain %8 : $AnyObject // id: %10
br bb3(%9 : $Optional<AnyObject>) // id: %11
bb2: // Preds: bb0
%12 = enum $Optional<AnyObject>, #Optional.none!enumelt // user: %13
br bb3(%12 : $Optional<AnyObject>) // id: %13
bb3(%14 : $Optional<AnyObject>): // Preds: bb2 bb1
%15 = metatype $@thick Optional<AnyObject>.Type // user: %16
%16 = builtin "sizeof"<Optional<AnyObject>>(%15 : $@thick Optional<AnyObject>.Type) : $Builtin.Word // user: %17
%17 = builtin "sextOrBitCast_Word_Int64"(%16 : $Builtin.Word) : $Builtin.Int64 // user: %21
%18 = metatype $@thick Foo.Type // user: %19
%19 = builtin "sizeof"<Foo>(%18 : $@thick Foo.Type) : $Builtin.Word // user: %20
%20 = builtin "sextOrBitCast_Word_Int64"(%19 : $Builtin.Word) : $Builtin.Int64 // user: %21
%21 = builtin "cmp_eq_Int64"(%17 : $Builtin.Int64, %20 : $Builtin.Int64) : $Builtin.Int1 // user: %23
%22 = integer_literal $Builtin.Int1, -1 // user: %23
%23 = builtin "xor_Int1"(%21 : $Builtin.Int1, %22 : $Builtin.Int1) : $Builtin.Int1 // user: %24
cond_fail %23 : $Builtin.Int1, "Can't unsafeBitCast between types of different sizes" // id: %24
%25 = unchecked_ref_cast %14 : $Optional<AnyObject> to $Foo // user: %26
return %25 : $Foo // id: %26
} // end sil function '$s6output3fooyAA3FooCSAyADGF' // Original x86_64 assembly:
output.foo(Swift.AutoreleasingUnsafeMutablePointer<output.Foo>) -> output.Foo:
push rbp
mov rbp, rsp
mov rdi, qword ptr [rdi]
pop rbp
jmp _swift_retain
// New x86_64 assembly:
output.foo(Swift.AutoreleasingUnsafeMutablePointer<output.Foo>) -> output.Foo:
push rbp
mov rbp, rsp
push rbx
push rax
mov rbx, qword ptr [rdi]
test rbx, rbx
je LBB5_1
mov rdi, rbx
call _swift_unknownObjectRetain
jmp LBB5_3
LBB5_1:
xor ebx, ebx
LBB5_3:
mov rax, rbx
add rsp, 8
pop rbx
pop rbp
ret
|
…parent This improves codegen in debug builds.
… than UnsafeRawPointer This improves code generation in -Onone mode. (UnsafeRawPointer.load/.storeBytes isn’t @_transparent and it doesn’t get specialized in unoptimized builds.)
The latest commits get rid of unspecialized generic calls (to |
(And they also use my new favorite type, |
@lorentey thanks for figuring out the semantics and documenting it. Just to limit the number of times |
…eCast for extra validation
@swift-ci test |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
@swift-ci test |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
The latest commit adds some rudimentary high-level functional tests for I think this is ready to land now. |
AutoreleasingUnsafeMutablePointer
is pointing to a +0 reference, but in itspointee
property’s getter/setter implementations, it is loading the pointer into regularUnsafe[Mutable]Pointer
s. Those are assuming that the addressed memory contains a +1 reference, which can mislead the compiler into doing optimizations that aren’t valid.Change the getter/setter implementations so that they use
UnsafeRawPointer
and load/storeUnmanaged
values instead. As long asUnmanaged.passUnretained(_:)
andUnmanaged.takeUnretainedValue()
do the right thing, thenAutoreleasingUnsafeMutablePointer
won’t have issues, either. (This boils down to ensuring that loading a strong reference out of anunmanaged(unsafe)
value works correctly.)