Skip to content

Commit 3fa07a0

Browse files
committed
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.
1 parent e7be8f3 commit 3fa07a0

File tree

10 files changed

+168
-19
lines changed

10 files changed

+168
-19
lines changed

include/swift/AST/KnownDecls.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ FUNC_DECL(BridgeAnyObjectToAny,
6464

6565
FUNC_DECL(ConvertToAnyHashable, "_convertToAnyHashable")
6666

67+
FUNC_DECL(WillThrowTyped, "_willThrowTyped")
68+
6769
FUNC_DECL(DiagnoseUnexpectedError, "_unexpectedError")
6870
FUNC_DECL(DiagnoseUnexpectedErrorTyped, "_unexpectedErrorTyped")
6971
FUNC_DECL(ErrorInMainTyped, "_errorInMainTyped")

include/swift/Runtime/Error.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ SWIFT_RUNTIME_STDLIB_API void
7171
swift_willThrow(SWIFT_CONTEXT void *unused,
7272
SWIFT_ERROR_RESULT SwiftError **object);
7373

74+
/// Called when throwing a typed error. Serves as a breakpoint hook
75+
/// for debuggers.
76+
SWIFT_CC(swift)
77+
SWIFT_RUNTIME_STDLIB_API void
78+
swift_willThrowTypedImpl(OpaqueValue *value,
79+
const Metadata *type,
80+
const WitnessTable *errorConformance);
81+
7482
/// Called when an error is thrown out of the top level of a script.
7583
SWIFT_CC(swift)
7684
SWIFT_RUNTIME_STDLIB_API SWIFT_NORETURN void

lib/SILGen/SILGenStmt.cpp

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,24 +1560,57 @@ void SILGenFunction::emitThrow(SILLocation loc, ManagedValue exnMV,
15601560

15611561
SILValue exn;
15621562
if (!exnMV.isInContext()) {
1563-
// Claim the exception value. If we need to handle throwing
1564-
// cleanups, the correct thing to do here is to recreate the
1565-
// exception's cleanup when emitting each cleanup we branch through.
1566-
// But for now we aren't bothering.
1567-
exn = exnMV.forward(*this);
1568-
15691563
// Whether the thrown exception is already an Error existential box.
15701564
SILType existentialBoxType = SILType::getExceptionType(getASTContext());
1571-
bool isExistentialBox = exn->getType() == existentialBoxType;
1572-
1573-
// FIXME: Right now, we suppress emission of the willThrow builtin if the
1574-
// error isn't already the error existential, because swift_willThrow expects
1575-
// the existential box.
1576-
if (emitWillThrow && isExistentialBox) {
1577-
// Generate a call to the 'swift_willThrow' runtime function to allow the
1578-
// debugger to catch the throw event.
1579-
B.createBuiltin(loc, SGM.getASTContext().getIdentifier("willThrow"),
1580-
SGM.Types.getEmptyTupleType(), {}, {exn});
1565+
bool isExistentialBox = exnMV.getType() == existentialBoxType;
1566+
1567+
// If we are supposed to emit a call to swift_willThrow(Typed), do so now.
1568+
if (emitWillThrow) {
1569+
ASTContext &ctx = SGM.getASTContext();
1570+
if (isExistentialBox) {
1571+
// Generate a call to the 'swift_willThrow' runtime function to allow the
1572+
// debugger to catch the throw event.
1573+
1574+
// Claim the exception value.
1575+
exn = exnMV.forward(*this);
1576+
1577+
B.createBuiltin(loc,
1578+
ctx.getIdentifier("willThrow"),
1579+
SGM.Types.getEmptyTupleType(), {}, {exn});
1580+
} else {
1581+
// Call the _willThrowTyped entrypoint, which handles
1582+
// arbitrary error types.
1583+
SILValue tmpBuffer;
1584+
SILValue error;
1585+
1586+
FuncDecl *entrypoint = ctx.getWillThrowTyped();
1587+
auto genericSig = entrypoint->getGenericSignature();
1588+
SubstitutionMap subMap = SubstitutionMap::get(
1589+
genericSig, [&](SubstitutableType *dependentType) {
1590+
return exnMV.getType().getASTType();
1591+
}, LookUpConformanceInModule(getModule().getSwiftModule()));
1592+
1593+
// Generic errors are passed indirectly.
1594+
if (!exnMV.getType().isAddress()) {
1595+
// Materialize the error so we can pass the address down to the
1596+
// swift_willThrowTyped.
1597+
exnMV = exnMV.materialize(*this, loc);
1598+
error = exnMV.getValue();
1599+
exn = exnMV.forward(*this);
1600+
} else {
1601+
// Claim the exception value.
1602+
exn = exnMV.forward(*this);
1603+
error = exn;
1604+
}
1605+
1606+
emitApplyOfLibraryIntrinsic(
1607+
loc, entrypoint, subMap,
1608+
{ ManagedValue::forForwardedRValue(*this, error) },
1609+
SGFContext());
1610+
}
1611+
} else {
1612+
// Claim the exception value.
1613+
exn = exnMV.forward(*this);
15811614
}
15821615
}
15831616

stdlib/public/core/EmbeddedRuntime.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,11 @@ public func swift_deletedMethodError() -> Never {
302302
public func swift_willThrow() throws {
303303
}
304304

305+
/// Called when a typed error will be thrown.
306+
@_silgen_name("swift_willThrowTyped")
307+
public func _willThrowTyped<E: Error>(_ error: E) {
308+
}
309+
305310
@_extern(c, "arc4random_buf")
306311
func arc4random_buf(buf: UnsafeMutableRawPointer, nbytes: Int)
307312

stdlib/public/core/ErrorType.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,30 @@ internal func _getErrorDefaultUserInfo<T: Error>(_ error: T) -> AnyObject?
177177
public func _bridgeErrorToNSError(_ error: __owned Error) -> AnyObject
178178
#endif
179179

180+
/// Called to indicate that a typed error will be thrown.
181+
@_silgen_name("swift_willThrowTypedImpl")
182+
@available(SwiftStdlib 5.11, *)
183+
@usableFromInline
184+
func _willThrowTypedImpl<E: Error>(_ error: E)
185+
186+
#if !$Embedded
187+
/// Called when a typed error will be thrown.
188+
///
189+
/// On new-enough platforms, this will call through to the runtime to invoke
190+
/// the thrown error handler (if one is set).
191+
///
192+
/// On older platforms, the error will not be passed into the runtime, because
193+
/// doing so would require memory allocation (to create the 'any Error').
194+
@inlinable
195+
@_alwaysEmitIntoClient
196+
@_silgen_name("swift_willThrowTyped")
197+
public func _willThrowTyped<E: Error>(_ error: E) {
198+
if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) {
199+
_willThrowTypedImpl(error)
200+
}
201+
}
202+
#endif
203+
180204
/// Invoked by the compiler when the subexpression of a `try!` expression
181205
/// throws an error.
182206
@_silgen_name("swift_unexpectedError")

stdlib/public/runtime/ErrorObjectCommon.cpp

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ void swift::_swift_setWillThrowHandler(void (* handler)(SwiftError *error)) {
3030
_swift_willThrow.store(handler, std::memory_order_release);
3131
}
3232

33-
/// Breakpoint hook for debuggers, and calls _swift_willThrow if set.
33+
/// Breakpoint hook for debuggers that is called for untyped throws, and
34+
/// calls _swift_willThrow if set.
3435
SWIFT_CC(swift) void
3536
swift::swift_willThrow(SWIFT_CONTEXT void *unused,
3637
SWIFT_ERROR_RESULT SwiftError **error) {
@@ -41,3 +42,27 @@ swift::swift_willThrow(SWIFT_CONTEXT void *unused,
4142
(* handler)(*error);
4243
}
4344
}
45+
46+
/// Breakpoint hook for debuggers that is called for typed throws, and calls
47+
/// _swift_willThrow if set. This implicitly boxes the typed error in an
48+
/// any Error for the call.
49+
SWIFT_CC(swift) void
50+
swift::swift_willThrowTypedImpl(OpaqueValue *value,
51+
const Metadata *type,
52+
const WitnessTable *errorConformance) {
53+
// Cheap check to bail out early, since we expect there to be no callbacks
54+
// the vast majority of the time.
55+
auto handler = _swift_willThrow.load(std::memory_order_acquire);
56+
if (SWIFT_UNLIKELY(handler)) {
57+
// Form an error box containing the error.
58+
BoxPair boxedError = swift_allocError(
59+
type, errorConformance, value, /*isTake=*/false);
60+
61+
// Hand the boxed error off to the handler.
62+
auto errorBox = reinterpret_cast<SwiftError *>(boxedError.object);
63+
(* handler)(errorBox);
64+
65+
// Release the error box.
66+
swift_errorRelease(errorBox);
67+
}
68+
}

test/SILGen/typed_throws.swift

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,45 @@ func doesNotThrowConcrete() throws(MyError) { }
1717
// CHECK-LABEL: sil hidden [ossa] @$s12typed_throws0B8ConcreteyyAA7MyErrorOYKF : $@convention(thin) () -> @error MyError
1818
func throwsConcrete() throws(MyError) {
1919
// CHECK: [[ERROR:%[0-9]+]] = enum $MyError, #MyError.fail!enumelt
20-
// CHECK-NOT: builtin "willThrow"
21-
// CHECK: throw [[ERROR]] : $MyError
20+
// CHECK: [[ERROR_ALLOC:%.*]] = alloc_stack $MyError
21+
// CHECK: store [[ERROR]] to [trivial] [[ERROR_ALLOC]] : $*MyError
22+
// CHECK: [[FN:%.*]] = function_ref @swift_willThrowTyped : $@convention(thin) <τ_0_0 where τ_0_0 : Error> (@in_guaranteed τ_0_0) -> ()
23+
// CHECK: apply [[FN]]<MyError>([[ERROR_ALLOC]]) : $@convention(thin) <τ_0_0 where τ_0_0 : Error> (@in_guaranteed τ_0_0) -> ()
24+
// CHECK: [[ERROR_RELOAD:%.*]] = load [trivial] [[ERROR_ALLOC]]
25+
// CHECK: dealloc_stack [[ERROR_ALLOC]] : $*MyError
26+
// CHECK: throw [[ERROR_RELOAD]] : $MyError
2227
throw .fail
2328
}
2429

30+
class ClassError: Error { }
31+
32+
// CHECK-LABEL: sil hidden [ossa] @$s12typed_throws0B10ClassErroryyAA0cD0CYKF : $@convention(thin) () -> @error ClassError
33+
// CHECK: [[META:%.*]] = metatype $@thick ClassError.Type
34+
// CHECK: [[INIT:%.*]] = function_ref @$s12typed_throws10ClassErrorCACycfC
35+
// CHECK: [[ERROR:%.*]] = apply [[INIT]]([[META]]) : $@convention(method) (@thick ClassError.Type) -> @owned ClassError
36+
// CHECK: [[ERROR_ALLOC:%.*]] = alloc_stack $ClassError
37+
// CHECK: store [[ERROR]] to [init] [[ERROR_ALLOC]] : $*ClassError
38+
// CHECK: [[FN:%.*]] = function_ref @swift_willThrowTyped : $@convention(thin) <τ_0_0 where τ_0_0 : Error> (@in_guaranteed τ_0_0) -> ()
39+
// CHECK: apply [[FN]]<ClassError>([[ERROR_ALLOC]]) : $@convention(thin) <τ_0_0 where τ_0_0 : Error> (@in_guaranteed τ_0_0) -> ()
40+
// CHECK: [[ERROR_RELOAD:%.*]] = load [take] [[ERROR_ALLOC]] : $*ClassError
41+
// CHECK: dealloc_stack [[ERROR_ALLOC]] : $*ClassError
42+
// CHECK: throw [[ERROR_RELOAD]] : $ClassError
43+
func throwsClassError() throws(ClassError) {
44+
throw ClassError()
45+
}
46+
47+
// CHECK-LABEL: sil hidden [ossa] @$s12typed_throws0B13IndirectErroryyxxYKs0D0RzlF : $@convention(thin) <E where E : Error> (@in_guaranteed E) -> @error_indirect E
48+
// CHECK: [[ERROR_ALLOC:%.*]] = alloc_stack $E
49+
// CHECK: copy_addr %1 to [init] [[ERROR_ALLOC]] : $*E
50+
// CHECK: [[FN:%.*]] = function_ref @swift_willThrowTyped : $@convention(thin) <τ_0_0 where τ_0_0 : Error> (@in_guaranteed τ_0_0) -> ()
51+
// CHECK: apply [[FN]]<E>([[ERROR_ALLOC]]) : $@convention(thin) <τ_0_0 where τ_0_0 : Error> (@in_guaranteed τ_0_0) -> ()
52+
// CHECK: copy_addr [take] [[ERROR_ALLOC]] to [init] %0 : $*E
53+
// CHECK: dealloc_stack [[ERROR_ALLOC]] : $*E
54+
// CHECK-NEXT: throw_addr
55+
func throwsIndirectError<E: Error>(_ error: E) throws(E) {
56+
throw error
57+
}
58+
2559
// CHECK-LABEL: sil hidden [ossa] @$s12typed_throws15rethrowConcreteyyAA7MyErrorOYKF
2660
func rethrowConcrete() throws(MyError) {
2761
// CHECK: try_apply [[FN:%[0-9]+]]() : $@convention(thin) () -> @error MyError, normal [[NORMALBB:bb[0-9]+]], error [[ERRORBB:bb[0-9]+]]

test/abi/macOS/arm64/stdlib.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,4 @@ Added: __swift_pod_destroy
254254
Added: __swift_pod_direct_initializeBufferWithCopyOfBuffer
255255
Added: __swift_pod_indirect_initializeBufferWithCopyOfBuffer
256256
Added: __swift_validatePrespecializedMetadata
257+
Added: _swift_willThrowTypedImpl

test/abi/macOS/x86_64/stdlib.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,4 @@ Added: __swift_pod_destroy
254254
Added: __swift_pod_direct_initializeBufferWithCopyOfBuffer
255255
Added: __swift_pod_indirect_initializeBufferWithCopyOfBuffer
256256
Added: __swift_validatePrespecializedMetadata
257+
Added: _swift_willThrowTypedImpl

test/stdlib/Error.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,11 @@ func throwJazzHands() throws {
220220
throw SillyError.JazzHands
221221
}
222222

223+
@inline(never)
224+
func throwJazzHandsTyped() throws(SillyError) {
225+
throw .JazzHands
226+
}
227+
223228
// Error isn't allowed in a @convention(c) function when ObjC interop is
224229
// not available, so pass it through an UnsafeRawPointer.
225230
@available(SwiftStdlib 5.8, *)
@@ -249,6 +254,17 @@ ErrorTests.test("willThrow") {
249254
} catch {}
250255
expectEqual(2, errors.count)
251256
expectEqual(SillyError.self, type(of: errors.last!))
257+
258+
// Typed errors introduced in Swift 5.11
259+
guard #available(SwiftStdlib 5.11, *) else {
260+
return
261+
}
262+
263+
do {
264+
try throwJazzHandsTyped()
265+
} catch {}
266+
expectEqual(3, errors.count)
267+
expectEqual(SillyError.self, type(of: errors.last!))
252268
}
253269
#endif
254270

0 commit comments

Comments
 (0)