diff --git a/include/swift/AST/KnownDecls.def b/include/swift/AST/KnownDecls.def index 146d95877fc..5d6135dabe9 100644 --- a/include/swift/AST/KnownDecls.def +++ b/include/swift/AST/KnownDecls.def @@ -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") diff --git a/include/swift/Runtime/Error.h b/include/swift/Runtime/Error.h index 080b907ae40..185f9dec9f7 100644 --- a/include/swift/Runtime/Error.h +++ b/include/swift/Runtime/Error.h @@ -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 diff --git a/lib/SILGen/SILGenStmt.cpp b/lib/SILGen/SILGenStmt.cpp index 1a935e59506..245c385a72f 100644 --- a/lib/SILGen/SILGenStmt.cpp +++ b/lib/SILGen/SILGenStmt.cpp @@ -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) { - // 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"), - SGM.Types.getEmptyTupleType(), {}, {exn}); + // 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. + + // 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); } } diff --git a/stdlib/public/core/EmbeddedRuntime.swift b/stdlib/public/core/EmbeddedRuntime.swift index a0b518be0ca..bdb889b1bfb 100644 --- a/stdlib/public/core/EmbeddedRuntime.swift +++ b/stdlib/public/core/EmbeddedRuntime.swift @@ -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(_ error: E) { +} + @_extern(c, "arc4random_buf") func arc4random_buf(buf: UnsafeMutableRawPointer, nbytes: Int) diff --git a/stdlib/public/core/ErrorType.swift b/stdlib/public/core/ErrorType.swift index 69c135ddd3e..3713475b673 100644 --- a/stdlib/public/core/ErrorType.swift +++ b/stdlib/public/core/ErrorType.swift @@ -177,6 +177,30 @@ internal func _getErrorDefaultUserInfo(_ 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(_ 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(_ 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") diff --git a/stdlib/public/runtime/ErrorObjectCommon.cpp b/stdlib/public/runtime/ErrorObjectCommon.cpp index e1bc4be4850..1f146e6b895 100644 --- a/stdlib/public/runtime/ErrorObjectCommon.cpp +++ b/stdlib/public/runtime/ErrorObjectCommon.cpp @@ -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(boxedError.object); + (* handler)(errorBox); + + // Release the error box. + swift_errorRelease(errorBox); + } +} diff --git a/test/SILGen/typed_throws.swift b/test/SILGen/typed_throws.swift index 7bcc26dce3c..94d68e6de52 100644 --- a/test/SILGen/typed_throws.swift +++ b/test/SILGen/typed_throws.swift @@ -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]]([[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]]([[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) (@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]]([[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(_ 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]+]] diff --git a/test/abi/macOS/arm64/stdlib.swift b/test/abi/macOS/arm64/stdlib.swift index da6769fb1ab..91e706ca191 100644 --- a/test/abi/macOS/arm64/stdlib.swift +++ b/test/abi/macOS/arm64/stdlib.swift @@ -254,3 +254,4 @@ Added: __swift_pod_destroy Added: __swift_pod_direct_initializeBufferWithCopyOfBuffer Added: __swift_pod_indirect_initializeBufferWithCopyOfBuffer Added: __swift_validatePrespecializedMetadata +Added: _swift_willThrowTypedImpl diff --git a/test/abi/macOS/x86_64/stdlib.swift b/test/abi/macOS/x86_64/stdlib.swift index c28f8d12241..543eb05bacc 100644 --- a/test/abi/macOS/x86_64/stdlib.swift +++ b/test/abi/macOS/x86_64/stdlib.swift @@ -254,3 +254,4 @@ Added: __swift_pod_destroy Added: __swift_pod_direct_initializeBufferWithCopyOfBuffer Added: __swift_pod_indirect_initializeBufferWithCopyOfBuffer Added: __swift_validatePrespecializedMetadata +Added: _swift_willThrowTypedImpl diff --git a/test/stdlib/Error.swift b/test/stdlib/Error.swift index 77e259621d1..7737a19e6b8 100644 --- a/test/stdlib/Error.swift +++ b/test/stdlib/Error.swift @@ -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