mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Implement swift_willThrow variant for typed throws.
`swift_willThrow` is called with an error right before it is thrown. This existing entrypoint requires an already-boxed error existential; with typed errors, we don't have the error existential on hand, so we would need to allocate the box to throw a typed error. That's not okay. Introduce a new `swift_willThrowTypedImpl` entry point into the runtime that will first check for the presence of an error handler and, if one is present, box the error to provide to the error handler. This maintains the no-allocations path for typed errors while still allowing existing error handlers to work. This new entrypoint isn't available on older Swift runtimes, so create a back-deployable shim called by the compiler. On new-enough platforms, this will call through to `swift_willThrowTypedImpl`. On older platforms, we drop the error and don't call the registered will-throw handler at all. This is a compromise that avoids boxing when throwing typed errors, at the cost of a slightly different experience for this new feature on older runtimes. Fixes rdar://119828459.
This commit is contained in:
@@ -64,6 +64,8 @@ FUNC_DECL(BridgeAnyObjectToAny,
|
||||
|
||||
FUNC_DECL(ConvertToAnyHashable, "_convertToAnyHashable")
|
||||
|
||||
FUNC_DECL(WillThrowTyped, "_willThrowTyped")
|
||||
|
||||
FUNC_DECL(DiagnoseUnexpectedError, "_unexpectedError")
|
||||
FUNC_DECL(DiagnoseUnexpectedErrorTyped, "_unexpectedErrorTyped")
|
||||
FUNC_DECL(ErrorInMainTyped, "_errorInMainTyped")
|
||||
|
||||
@@ -71,6 +71,14 @@ SWIFT_RUNTIME_STDLIB_API void
|
||||
swift_willThrow(SWIFT_CONTEXT void *unused,
|
||||
SWIFT_ERROR_RESULT SwiftError **object);
|
||||
|
||||
/// Called when throwing a typed error. Serves as a breakpoint hook
|
||||
/// for debuggers.
|
||||
SWIFT_CC(swift)
|
||||
SWIFT_RUNTIME_STDLIB_API void
|
||||
swift_willThrowTypedImpl(OpaqueValue *value,
|
||||
const Metadata *type,
|
||||
const WitnessTable *errorConformance);
|
||||
|
||||
/// Called when an error is thrown out of the top level of a script.
|
||||
SWIFT_CC(swift)
|
||||
SWIFT_RUNTIME_STDLIB_API SWIFT_NORETURN void
|
||||
|
||||
@@ -1560,24 +1560,57 @@ void SILGenFunction::emitThrow(SILLocation loc, ManagedValue exnMV,
|
||||
|
||||
SILValue exn;
|
||||
if (!exnMV.isInContext()) {
|
||||
// Claim the exception value. If we need to handle throwing
|
||||
// cleanups, the correct thing to do here is to recreate the
|
||||
// exception's cleanup when emitting each cleanup we branch through.
|
||||
// But for now we aren't bothering.
|
||||
exn = exnMV.forward(*this);
|
||||
|
||||
// Whether the thrown exception is already an Error existential box.
|
||||
SILType existentialBoxType = SILType::getExceptionType(getASTContext());
|
||||
bool isExistentialBox = exn->getType() == existentialBoxType;
|
||||
bool isExistentialBox = exnMV.getType() == existentialBoxType;
|
||||
|
||||
// FIXME: Right now, we suppress emission of the willThrow builtin if the
|
||||
// error isn't already the error existential, because swift_willThrow expects
|
||||
// the existential box.
|
||||
if (emitWillThrow && isExistentialBox) {
|
||||
// If we are supposed to emit a call to swift_willThrow(Typed), do so now.
|
||||
if (emitWillThrow) {
|
||||
ASTContext &ctx = SGM.getASTContext();
|
||||
if (isExistentialBox) {
|
||||
// Generate a call to the 'swift_willThrow' runtime function to allow the
|
||||
// debugger to catch the throw event.
|
||||
B.createBuiltin(loc, SGM.getASTContext().getIdentifier("willThrow"),
|
||||
|
||||
// Claim the exception value.
|
||||
exn = exnMV.forward(*this);
|
||||
|
||||
B.createBuiltin(loc,
|
||||
ctx.getIdentifier("willThrow"),
|
||||
SGM.Types.getEmptyTupleType(), {}, {exn});
|
||||
} else {
|
||||
// Call the _willThrowTyped entrypoint, which handles
|
||||
// arbitrary error types.
|
||||
SILValue tmpBuffer;
|
||||
SILValue error;
|
||||
|
||||
FuncDecl *entrypoint = ctx.getWillThrowTyped();
|
||||
auto genericSig = entrypoint->getGenericSignature();
|
||||
SubstitutionMap subMap = SubstitutionMap::get(
|
||||
genericSig, [&](SubstitutableType *dependentType) {
|
||||
return exnMV.getType().getASTType();
|
||||
}, LookUpConformanceInModule(getModule().getSwiftModule()));
|
||||
|
||||
// Generic errors are passed indirectly.
|
||||
if (!exnMV.getType().isAddress()) {
|
||||
// Materialize the error so we can pass the address down to the
|
||||
// swift_willThrowTyped.
|
||||
exnMV = exnMV.materialize(*this, loc);
|
||||
error = exnMV.getValue();
|
||||
exn = exnMV.forward(*this);
|
||||
} else {
|
||||
// Claim the exception value.
|
||||
exn = exnMV.forward(*this);
|
||||
error = exn;
|
||||
}
|
||||
|
||||
emitApplyOfLibraryIntrinsic(
|
||||
loc, entrypoint, subMap,
|
||||
{ ManagedValue::forForwardedRValue(*this, error) },
|
||||
SGFContext());
|
||||
}
|
||||
} else {
|
||||
// Claim the exception value.
|
||||
exn = exnMV.forward(*this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -302,6 +302,11 @@ public func swift_deletedMethodError() -> Never {
|
||||
public func swift_willThrow() throws {
|
||||
}
|
||||
|
||||
/// Called when a typed error will be thrown.
|
||||
@_silgen_name("swift_willThrowTyped")
|
||||
public func _willThrowTyped<E: Error>(_ error: E) {
|
||||
}
|
||||
|
||||
@_extern(c, "arc4random_buf")
|
||||
func arc4random_buf(buf: UnsafeMutableRawPointer, nbytes: Int)
|
||||
|
||||
|
||||
@@ -177,6 +177,30 @@ internal func _getErrorDefaultUserInfo<T: Error>(_ error: T) -> AnyObject?
|
||||
public func _bridgeErrorToNSError(_ error: __owned Error) -> AnyObject
|
||||
#endif
|
||||
|
||||
/// Called to indicate that a typed error will be thrown.
|
||||
@_silgen_name("swift_willThrowTypedImpl")
|
||||
@available(SwiftStdlib 5.11, *)
|
||||
@usableFromInline
|
||||
func _willThrowTypedImpl<E: Error>(_ error: E)
|
||||
|
||||
#if !$Embedded
|
||||
/// Called when a typed error will be thrown.
|
||||
///
|
||||
/// On new-enough platforms, this will call through to the runtime to invoke
|
||||
/// the thrown error handler (if one is set).
|
||||
///
|
||||
/// On older platforms, the error will not be passed into the runtime, because
|
||||
/// doing so would require memory allocation (to create the 'any Error').
|
||||
@inlinable
|
||||
@_alwaysEmitIntoClient
|
||||
@_silgen_name("swift_willThrowTyped")
|
||||
public func _willThrowTyped<E: Error>(_ error: E) {
|
||||
if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) {
|
||||
_willThrowTypedImpl(error)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Invoked by the compiler when the subexpression of a `try!` expression
|
||||
/// throws an error.
|
||||
@_silgen_name("swift_unexpectedError")
|
||||
|
||||
@@ -30,7 +30,8 @@ void swift::_swift_setWillThrowHandler(void (* handler)(SwiftError *error)) {
|
||||
_swift_willThrow.store(handler, std::memory_order_release);
|
||||
}
|
||||
|
||||
/// Breakpoint hook for debuggers, and calls _swift_willThrow if set.
|
||||
/// Breakpoint hook for debuggers that is called for untyped throws, and
|
||||
/// calls _swift_willThrow if set.
|
||||
SWIFT_CC(swift) void
|
||||
swift::swift_willThrow(SWIFT_CONTEXT void *unused,
|
||||
SWIFT_ERROR_RESULT SwiftError **error) {
|
||||
@@ -41,3 +42,27 @@ swift::swift_willThrow(SWIFT_CONTEXT void *unused,
|
||||
(* handler)(*error);
|
||||
}
|
||||
}
|
||||
|
||||
/// Breakpoint hook for debuggers that is called for typed throws, and calls
|
||||
/// _swift_willThrow if set. This implicitly boxes the typed error in an
|
||||
/// any Error for the call.
|
||||
SWIFT_CC(swift) void
|
||||
swift::swift_willThrowTypedImpl(OpaqueValue *value,
|
||||
const Metadata *type,
|
||||
const WitnessTable *errorConformance) {
|
||||
// Cheap check to bail out early, since we expect there to be no callbacks
|
||||
// the vast majority of the time.
|
||||
auto handler = _swift_willThrow.load(std::memory_order_acquire);
|
||||
if (SWIFT_UNLIKELY(handler)) {
|
||||
// Form an error box containing the error.
|
||||
BoxPair boxedError = swift_allocError(
|
||||
type, errorConformance, value, /*isTake=*/false);
|
||||
|
||||
// Hand the boxed error off to the handler.
|
||||
auto errorBox = reinterpret_cast<SwiftError *>(boxedError.object);
|
||||
(* handler)(errorBox);
|
||||
|
||||
// Release the error box.
|
||||
swift_errorRelease(errorBox);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,45 @@ func doesNotThrowConcrete() throws(MyError) { }
|
||||
// CHECK-LABEL: sil hidden [ossa] @$s12typed_throws0B8ConcreteyyAA7MyErrorOYKF : $@convention(thin) () -> @error MyError
|
||||
func throwsConcrete() throws(MyError) {
|
||||
// CHECK: [[ERROR:%[0-9]+]] = enum $MyError, #MyError.fail!enumelt
|
||||
// CHECK-NOT: builtin "willThrow"
|
||||
// CHECK: throw [[ERROR]] : $MyError
|
||||
// CHECK: [[ERROR_ALLOC:%.*]] = alloc_stack $MyError
|
||||
// CHECK: store [[ERROR]] to [trivial] [[ERROR_ALLOC]] : $*MyError
|
||||
// CHECK: [[FN:%.*]] = function_ref @swift_willThrowTyped : $@convention(thin) <τ_0_0 where τ_0_0 : Error> (@in_guaranteed τ_0_0) -> ()
|
||||
// CHECK: apply [[FN]]<MyError>([[ERROR_ALLOC]]) : $@convention(thin) <τ_0_0 where τ_0_0 : Error> (@in_guaranteed τ_0_0) -> ()
|
||||
// CHECK: [[ERROR_RELOAD:%.*]] = load [trivial] [[ERROR_ALLOC]]
|
||||
// CHECK: dealloc_stack [[ERROR_ALLOC]] : $*MyError
|
||||
// CHECK: throw [[ERROR_RELOAD]] : $MyError
|
||||
throw .fail
|
||||
}
|
||||
|
||||
class ClassError: Error { }
|
||||
|
||||
// CHECK-LABEL: sil hidden [ossa] @$s12typed_throws0B10ClassErroryyAA0cD0CYKF : $@convention(thin) () -> @error ClassError
|
||||
// CHECK: [[META:%.*]] = metatype $@thick ClassError.Type
|
||||
// CHECK: [[INIT:%.*]] = function_ref @$s12typed_throws10ClassErrorCACycfC
|
||||
// CHECK: [[ERROR:%.*]] = apply [[INIT]]([[META]]) : $@convention(method) (@thick ClassError.Type) -> @owned ClassError
|
||||
// CHECK: [[ERROR_ALLOC:%.*]] = alloc_stack $ClassError
|
||||
// CHECK: store [[ERROR]] to [init] [[ERROR_ALLOC]] : $*ClassError
|
||||
// CHECK: [[FN:%.*]] = function_ref @swift_willThrowTyped : $@convention(thin) <τ_0_0 where τ_0_0 : Error> (@in_guaranteed τ_0_0) -> ()
|
||||
// CHECK: apply [[FN]]<ClassError>([[ERROR_ALLOC]]) : $@convention(thin) <τ_0_0 where τ_0_0 : Error> (@in_guaranteed τ_0_0) -> ()
|
||||
// CHECK: [[ERROR_RELOAD:%.*]] = load [take] [[ERROR_ALLOC]] : $*ClassError
|
||||
// CHECK: dealloc_stack [[ERROR_ALLOC]] : $*ClassError
|
||||
// CHECK: throw [[ERROR_RELOAD]] : $ClassError
|
||||
func throwsClassError() throws(ClassError) {
|
||||
throw ClassError()
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil hidden [ossa] @$s12typed_throws0B13IndirectErroryyxxYKs0D0RzlF : $@convention(thin) <E where E : Error> (@in_guaranteed E) -> @error_indirect E
|
||||
// CHECK: [[ERROR_ALLOC:%.*]] = alloc_stack $E
|
||||
// CHECK: copy_addr %1 to [init] [[ERROR_ALLOC]] : $*E
|
||||
// CHECK: [[FN:%.*]] = function_ref @swift_willThrowTyped : $@convention(thin) <τ_0_0 where τ_0_0 : Error> (@in_guaranteed τ_0_0) -> ()
|
||||
// CHECK: apply [[FN]]<E>([[ERROR_ALLOC]]) : $@convention(thin) <τ_0_0 where τ_0_0 : Error> (@in_guaranteed τ_0_0) -> ()
|
||||
// CHECK: copy_addr [take] [[ERROR_ALLOC]] to [init] %0 : $*E
|
||||
// CHECK: dealloc_stack [[ERROR_ALLOC]] : $*E
|
||||
// CHECK-NEXT: throw_addr
|
||||
func throwsIndirectError<E: Error>(_ error: E) throws(E) {
|
||||
throw error
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil hidden [ossa] @$s12typed_throws15rethrowConcreteyyAA7MyErrorOYKF
|
||||
func rethrowConcrete() throws(MyError) {
|
||||
// CHECK: try_apply [[FN:%[0-9]+]]() : $@convention(thin) () -> @error MyError, normal [[NORMALBB:bb[0-9]+]], error [[ERRORBB:bb[0-9]+]]
|
||||
|
||||
@@ -254,3 +254,4 @@ Added: __swift_pod_destroy
|
||||
Added: __swift_pod_direct_initializeBufferWithCopyOfBuffer
|
||||
Added: __swift_pod_indirect_initializeBufferWithCopyOfBuffer
|
||||
Added: __swift_validatePrespecializedMetadata
|
||||
Added: _swift_willThrowTypedImpl
|
||||
|
||||
@@ -254,3 +254,4 @@ Added: __swift_pod_destroy
|
||||
Added: __swift_pod_direct_initializeBufferWithCopyOfBuffer
|
||||
Added: __swift_pod_indirect_initializeBufferWithCopyOfBuffer
|
||||
Added: __swift_validatePrespecializedMetadata
|
||||
Added: _swift_willThrowTypedImpl
|
||||
|
||||
@@ -220,6 +220,11 @@ func throwJazzHands() throws {
|
||||
throw SillyError.JazzHands
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
func throwJazzHandsTyped() throws(SillyError) {
|
||||
throw .JazzHands
|
||||
}
|
||||
|
||||
// Error isn't allowed in a @convention(c) function when ObjC interop is
|
||||
// not available, so pass it through an UnsafeRawPointer.
|
||||
@available(SwiftStdlib 5.8, *)
|
||||
@@ -249,6 +254,17 @@ ErrorTests.test("willThrow") {
|
||||
} catch {}
|
||||
expectEqual(2, errors.count)
|
||||
expectEqual(SillyError.self, type(of: errors.last!))
|
||||
|
||||
// Typed errors introduced in Swift 5.11
|
||||
guard #available(SwiftStdlib 5.11, *) else {
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try throwJazzHandsTyped()
|
||||
} catch {}
|
||||
expectEqual(3, errors.count)
|
||||
expectEqual(SillyError.self, type(of: errors.last!))
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
Reference in New Issue
Block a user