swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.3k stars 10.34k forks source link

[SR-16112] Fast class casting does not work on Windows #58371

Open eeckstein opened 2 years ago

eeckstein commented 2 years ago
Previous ID SR-16112
Radar None
Original Reporter @eeckstein
Type Bug
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 0 | |Component/s | Compiler | |Labels | Bug | |Assignee | None | |Priority | Medium | md5: 348f16b24ead7791d1d5c72261b86e2f

Issue Description:

Fast class casting is introduced here: https://github.com/apple/swift/pull/41784
and fails here: https://ci-external.swift.org/job/oss-swift-windows-toolchain-x86_64-vs2019/447/console

atrick commented 2 years ago

@compnerd shared the crash info with me. From the point of the crash, I wasn't that I wasn't able to pinpoint the issue, but I'll explain what I can here. Descending along the call chain to the crash:

Foundation.Bundle.localizedInfoDictionary.getter instantiates a CFDictionary, passing it to __SwiftValue.fetch

@Bundle.swift:374
let cfDict: CFDictionary? = CFBundleGetInfoDictionary(_bundle)
 __SwiftValue.fetch(cfDict)

__SwiftValue.fetch attempts to cast to _StructBridgeable.

@Bridging.swift:160

static func fetch(nonOptional object: AnyObject) -> Any {
 //...
 } else if let val = object as? _StructBridgeable {
 return val._bridgeToAny() <============

[aside: Casting to _StructBridgeable apparently succeeds with or without optimization, but I don't see any explicit conformance from CFDictionary to _StructBridgeable. Presumably CFDictionary is merely a type alias for NSDictionary in corelibs, which does conform]

_StructTypeBridgeable.bridgeToAny() eventually calls _conditionallyBridgeFromObjectiveC. Here the conversion iterates over key/value pairs, at which point, __SwiftValue.fetch is called recursively, this time on a key object, presumably an NSString.

Dictionary.swift @ 68
let key = __SwiftValue.fetch(nonOptional: unsafeBitCast(keys.advanced(by: idx).pointee!, to: AnyObject.self)) 

[unsafeBitCast should never be done on a reference! But that's a digression]

__SwiftValue.fetch calls swift_dynamicCast for the attempted cast from NSString to _StructBridgeable.

@Bridging.swift:160
static func fetch(nonOptional object: AnyObject) -> Any {
 //...
 } else if let val = object as? _StructBridgeable { <============ 

Ultimiately this crashes deep within the runtime

swiftCore!std::_Atomic_storage<swift::PrivateMetadataTrackingInfo,8>::load (Inline Function @ 00007ffc`5b5e24bf) [C:\Program Files\Microsoft Visual Studio\2022\Preview\VC\Tools\MSVC\14.32.31302\include\atomic @ 1045] 
swiftCore!swift::MetadataCacheEntryBase<(anonymous namespace)::SingletonMetadataCacheEntry,int>::awaitSatisfyingState+0x2f [S:\SourceCache\swift\stdlib\public\runtime\MetadataCache.h @ 1223] 
swiftCore!swift::MetadataCacheEntryBase<(anonymous namespace)::SingletonMetadataCacheEntry,int>::await+0xb (Inline Function @ 00007ffc`5b5db86f) [S:\SourceCache\swift\stdlib\public\runtime\MetadataCache.h @ 953] 
swiftCore!swift::LockingConcurrentMap<(anonymous namespace)::SingletonMetadataCacheEntry,(anonymous namespace)::SingletonMetadataCacheStorage>::await+0x16 (Inline Function @ 00007ffc`5b5db86f) [S:\SourceCache\swift\stdlib\public\runtime\MetadataCache.h @ 287] 
swiftCore!swift_checkMetadataState::CheckStateCallbacks::forSingletonMetadata+0x6c (Inline Function @ 00007ffc`5b5db86f) [S:\SourceCache\swift\stdlib\public\runtime\Metadata.cpp @ 5789] 
swiftCore!performOnMetadataCache<swift::MetadataResponse,CheckStateCallbacks>+0x29f [S:\SourceCache\swift\stdlib\public\runtime\Metadata.cpp @ 5750] 
swiftCore!swift_checkMetadataState+0x29 [S:\SourceCache\swift\stdlib\public\runtime\Metadata.cpp @ 5803] 
swiftCore!tryGetCompleteMetadataNonblocking+0xd (Inline Function @ 00007ffc`5b5ff7f8) [S:\SourceCache\swift\stdlib\public\runtime\ProtocolConformance.cpp @ 187] 
swiftCore!getSuperclassForMaybeIncompleteMetadata+0x98 (Inline Function @ 00007ffc`5b5ff7f8) [S:\SourceCache\swift\stdlib\public\runtime\ProtocolConformance.cpp @ 230] 
swiftCore!MaybeIncompleteSuperclassIterator::operator+++0x98 (Inline Function @ 00007ffc`5b5ff7f8) [S:\SourceCache\swift\stdlib\public\runtime\ProtocolConformance.cpp @ 271] 
swiftCore!searchInConformanceCache+0x197 (Inline Function @ 00007ffc`5b5ff7f8) [S:\SourceCache\swift\stdlib\public\runtime\ProtocolConformance.cpp @ 637] 
swiftCore!swift_conformsToProtocolMaybeInstantiateSuperclasses+0x278 [S:\SourceCache\swift\stdlib\public\runtime\ProtocolConformance.cpp @ 990] 
swiftCore!swift_conformsToProtocolImpl+0xe (Inline Function @ 00007ffc`5b5ff53f) [S:\SourceCache\swift\stdlib\public\runtime\ProtocolConformance.cpp @ 1112] 
swiftCore!swift_conformsToProtocol+0x2f [S:\SourceCache\swift\stdlib\public\CompatibilityOverride\CompatibilityOverrideRuntime.def @ 140] 
swiftCore!swift::_conformsToProtocol+0x17 [S:\SourceCache\swift\stdlib\public\runtime\Casting.cpp @ 519] 
swiftCore!_conformsToProtocols+0x94 (Inline Function @ 00007ffc`5b5c6762) [S:\SourceCache\swift\stdlib\public\runtime\DynamicCast.cpp @ 1358] 
swiftCore!tryCastToConstrainedOpaqueExistential+0xb2 [S:\SourceCache\swift\stdlib\public\runtime\DynamicCast.cpp @ 1415] 
swiftCore!tryCast+0x270 [S:\SourceCache\swift\stdlib\public\runtime\DynamicCast.cpp @ 2073] 
swiftCore!tryCastUnwrappingExistentialSource+0x123 (Inline Function @ 00007ffc`5b5c5827) [S:\SourceCache\swift\stdlib\public\runtime\DynamicCast.cpp @ 1695] 
swiftCore!tryCast+0x487 [S:\SourceCache\swift\stdlib\public\runtime\DynamicCast.cpp @ 2153] 
swiftCore!swift_dynamicCastImpl+0x42 (Inline Function @ 00007ffc`5b5c5329) [S:\SourceCache\swift\stdlib\public\runtime\DynamicCast.cpp @ 2353] 
swiftCore!swift_dynamicCast+0x69 [S:\SourceCache\swift\stdlib\public\CompatibilityOverride\CompatibilityOverrideRuntime.def @ 80]

All I can guess from this is that the metadata cache is not properly initialized.

atrick commented 2 years ago

To relating the issue back to the original optimization... I'd like to see the output produced by tracing the new code in IRGen and rebuilding Foundation:

// TODO: we could use the ClassHierarchyAnalysis do also handle "effectively"
 // final classes, e.g. not-subclassed internal classes in WMO.
 // This would need some re-architecting of ClassHierarchyAnalysis to make it
 // available in IRGen.
 ClassDecl *toClass = classTy->getDecl();
 if (!toClass->isFinal())
 return nullptr;
<========= let's print both the class name and function being optimized. It should be easy to narrow down from there.

@compnerd says the optimization is only performed for the `Operation` class. But this shouldn't happen because that class is non-final:

open class Operation : NSObject {

If `isFinal` returns an incorrect value in certain modes on certain platforms, that's a serious problem.

atrick commented 2 years ago

@swift-cicreate

compnerd commented 2 years ago

From an instrumented compiler, these are all the instances being triggered in a build of Foundation:
(class name :: function name)

 _BarrierOperation :: $s10Foundation9OperationC12dependenciesSayACGvg
 _BarrierOperation :: $s10Foundation14OperationQueueC18_operationFinishedyyAA0B0C_AF18__NSOperationStateOtF
 _BarrierOperation :: $s10Foundation14OperationQueueC9_scheduleyyF
 _BarrierOperation :: $s10Foundation14OperationQueueC8_executeyyAA0B0CF
 _BarrierOperation :: $s10Foundation14OperationQueueC15_operationCountSivg
 _BarrierOperation :: $s10Foundation14OperationQueueC11_operations17includingBarriersSayAA0B0CGSb_tF
compnerd commented 2 years ago

IRGen: enable the dynamic cast optimization on Windows by compnerd · Pull Request #42286 · apple/swift (github.com) does seem to repair the test cases. Hopefully we can keep the optimization enabled on all platforms.