Skip to content

SILGen: Lowering 'try?' and 'try!' with typed throws #69565

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions lib/SILGen/JumpDest.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,14 @@ namespace Lowering {

struct LLVM_LIBRARY_VISIBILITY ThrownErrorInfo {
SILValue IndirectErrorResult;
bool Discard;

explicit ThrownErrorInfo(SILValue IndirectErrorResult)
: IndirectErrorResult(IndirectErrorResult) {}
explicit ThrownErrorInfo(SILValue indirectErrorAddr, bool discard=false)
: IndirectErrorResult(indirectErrorAddr), Discard(discard) {}

static ThrownErrorInfo forDiscard() {
return ThrownErrorInfo(SILValue(), /*discard=*/true);
}
};

/// The destination of a direct jump. Swift currently does not
Expand Down
77 changes: 46 additions & 31 deletions lib/SILGen/SILGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1148,14 +1148,15 @@ SILGenFunction::ForceTryEmission::ForceTryEmission(SILGenFunction &SGF,
// Set up a "catch" block for when an error occurs.
SILBasicBlock *catchBB = SGF.createBasicBlock(FunctionSection::Postmatter);

// FIXME: typed throws
ASTContext &ctx = SGF.getASTContext();
(void) catchBB->createPhiArgument(SILType::getExceptionType(ctx),
OwnershipKind::Owned);
auto &errorTL = SGF.getTypeLowering(loc->getThrownError());
if (!errorTL.isAddressOnly()) {
(void) catchBB->createPhiArgument(errorTL.getLoweredType(),
OwnershipKind::Owned);
}

SGF.ThrowDest = JumpDest(catchBB, SGF.Cleanups.getCleanupsDepth(),
CleanupLocation(loc),
ThrownErrorInfo(/*indirectErrorAddr=*/nullptr));
ThrownErrorInfo::forDiscard());
}

void SILGenFunction::ForceTryEmission::finish() {
Expand All @@ -1171,26 +1172,34 @@ void SILGenFunction::ForceTryEmission::finish() {
// Otherwise, we need to emit it.
SILGenSavedInsertionPoint scope(SGF, catchBB, FunctionSection::Postmatter);

// FIXME: typed throws
auto error = ManagedValue::forForwardedRValue(SGF, catchBB->getArgument(0));

ASTContext &ctx = SGF.getASTContext();
if (auto diagnoseError = ctx.getDiagnoseUnexpectedError()) {
auto args = SGF.emitSourceLocationArgs(Loc->getExclaimLoc(), Loc);

SGF.emitApplyOfLibraryIntrinsic(
Loc,
diagnoseError,
SubstitutionMap(),
{
error,
args.filenameStartPointer,
args.filenameLength,
args.filenameIsAscii,
args.line
},
SGFContext());

// Consume the thrown error.
if (catchBB->getNumArguments() == 1) {
auto error = ManagedValue::forForwardedRValue(SGF, catchBB->getArgument(0));

// FIXME: for typed throws, we need a new version of this entry point that
// takes a generic rather than an existential.
if (error.getType() == SILType::getExceptionType(ctx)) {
if (auto diagnoseError = ctx.getDiagnoseUnexpectedError()) {
auto args = SGF.emitSourceLocationArgs(Loc->getExclaimLoc(), Loc);

SGF.emitApplyOfLibraryIntrinsic(
Loc,
diagnoseError,
SubstitutionMap(),
{
error,
args.filenameStartPointer,
args.filenameLength,
args.filenameIsAscii,
args.line
},
SGFContext());
}
}
}

SGF.B.createUnreachable(Loc);
}

Expand Down Expand Up @@ -1241,19 +1250,22 @@ RValue RValueEmitter::visitOptionalTryExpr(OptionalTryExpr *E, SGFContext C) {
if (isByAddress)
optAddr = optInit->getAddressForInPlaceInitialization(SGF, E);

FullExpr localCleanups(SGF.Cleanups, E);

// Set up a "catch" block for when an error occurs.
SILBasicBlock *catchBB = SGF.createBasicBlock(FunctionSection::Postmatter);

// FIXME: typed throws
auto *errorArg = catchBB->createPhiArgument(
SILType::getExceptionType(SGF.getASTContext()), OwnershipKind::Owned);
// FIXME: opaque values
auto &errorTL = SGF.getTypeLowering(E->getThrownError());
if (!errorTL.isAddressOnly()) {
(void) catchBB->createPhiArgument(errorTL.getLoweredType(),
OwnershipKind::Owned);
}

FullExpr localCleanups(SGF.Cleanups, E);

llvm::SaveAndRestore<JumpDest> throwDest{
SGF.ThrowDest,
JumpDest(catchBB, SGF.Cleanups.getCleanupsDepth(), E,
ThrownErrorInfo(/*indirectErrorAddr=*/nullptr))};
ThrownErrorInfo::forDiscard())};

SILValue branchArg;
if (shouldWrapInOptional) {
Expand Down Expand Up @@ -1308,11 +1320,14 @@ RValue RValueEmitter::visitOptionalTryExpr(OptionalTryExpr *E, SGFContext C) {
else
SGF.B.createBranch(E, contBB, branchArg);

// If control branched to the failure block, inject .None into the
// If control branched to the failure block, inject .none into the
// result type.
SGF.B.emitBlock(catchBB);
FullExpr catchCleanups(SGF.Cleanups, E);
(void) SGF.emitManagedRValueWithCleanup(errorArg);

// Consume the thrown error.
if (!errorTL.isAddressOnly())
(void) SGF.emitManagedRValueWithCleanup(catchBB->getArgument(0));
catchCleanups.pop();

if (isByAddress) {
Expand Down
4 changes: 4 additions & 0 deletions lib/SILGen/SILGenStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1583,6 +1583,10 @@ void SILGenFunction::emitThrow(SILLocation loc, ManagedValue exnMV,

// A direct error value is passed to the epilog block as a BB argument.
args.push_back(exn);
} else if (ThrowDest.getThrownError().Discard) {
assert(!indirectErrorAddr);
if (exn)
B.createDestroyAddr(loc, exn);
} else {
assert(indirectErrorAddr);

Expand Down
61 changes: 59 additions & 2 deletions test/SILGen/typed_throws_generic.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// RUN: %target-swift-emit-silgen %s -enable-experimental-feature TypedThrows -enable-experimental-feature FullTypedThrows | %FileCheck %s

public func genericThrow<E>(e: E) throws(E) -> () {
public func genericThrow<E>(e: E) throws(E) {
throw e
}


// CHECK-LABEL: sil [ossa] @$s20typed_throws_generic0C5Throw1eyx_txYKs5ErrorRzlF : $@convention(thin) <E where E : Error> (@in_guaranteed E) -> @error_indirect E {

// CHECK: bb0(%0 : $*E, %1 : $*E):
Expand All @@ -14,7 +15,7 @@ public func genericThrow<E>(e: E) throws(E) -> () {
// CHECK: throw_addr


public func genericTryApply<E>(fn: () throws(E) -> ()) throws(E) -> () {
public func genericTryApply<E>(fn: () throws(E) -> ()) throws(E) {
try fn()
}

Expand All @@ -38,3 +39,59 @@ public func genericTryApply<E>(fn: () throws(E) -> ()) throws(E) -> () {
// CHECK: dealloc_stack [[ERROR]]
// CHECK: destroy_value [[FN]]
// CHECK: throw_addr


public func genericOptionalTry<E>(fn: () throws(E) -> ()) -> ()? {
return try? fn()
}

// CHECK-LABEL: sil [ossa] @$s20typed_throws_generic0C11OptionalTry2fnytSgyyxYKXE_ts5ErrorRzlF : $@convention(thin) <E where E : Error> (@guaranteed @noescape @callee_guaranteed @substituted <τ_0_0> () -> @error_indirect τ_0_0 for <E>) -> Optional<()> {
// CHECK: bb0(%0 : @guaranteed $@noescape @callee_guaranteed @substituted <τ_0_0> () -> @error_indirect τ_0_0 for <E>):
// CHECK: [[FN:%.*]] = copy_value %0 : $@noescape @callee_guaranteed @substituted <τ_0_0> () -> @error_indirect τ_0_0 for <E>
// CHECK: [[ERROR:%.*]] = alloc_stack $E
// CHECK: [[FN_BORROW:%.*]] = begin_borrow %2 : $@noescape @callee_guaranteed @substituted <τ_0_0> () -> @error_indirect τ_0_0 for <E>
// CHECK: try_apply [[FN_BORROW]]([[ERROR]]) : $@noescape @callee_guaranteed @substituted <τ_0_0> () -> @error_indirect τ_0_0 for <E>, normal bb1, error bb3

// CHECK: bb1({{.*}} : $()):
// CHECK: end_borrow [[FN_BORROW]] : $@noescape @callee_guaranteed @substituted <τ_0_0> () -> @error_indirect τ_0_0 for <E>
// CHECK: dealloc_stack [[ERROR]] : $*E
// CHECK: [[RESULT:%.*]] = tuple ()
// CHECK: [[OPT:%.*]] = enum $Optional<()>, #Optional.some!enumelt, [[RESULT]] : $()
// CHECK: destroy_value [[FN]] : $@noescape @callee_guaranteed @substituted <τ_0_0> () -> @error_indirect τ_0_0 for <E>
// CHECK: br bb2([[OPT]] : $Optional<()>)

// CHECK: bb2([[RESULT:%.*]] : $Optional<()>):
// CHECK: return [[RESULT]] : $Optional<()>

// CHECK: bb3:
// CHECK: destroy_addr [[ERROR]] : $*E
// CHECK: end_borrow [[FN_BORROW]] : $@noescape @callee_guaranteed @substituted <τ_0_0> () -> @error_indirect τ_0_0 for <E>
// CHECK: dealloc_stack [[ERROR]] : $*E
// CHECK: destroy_value [[FN]] : $@noescape @callee_guaranteed @substituted <τ_0_0> () -> @error_indirect τ_0_0 for <E>
// CHECK: [[RESULT:%.*]] = enum $Optional<()>, #Optional.none!enumelt
// CHECK: br bb2([[RESULT]] : $Optional<()>)


public func genericForceTry<E>(fn: () throws(E) -> ()) {
try! fn()
}

// CHECK-LABEL: sil [ossa] @$s20typed_throws_generic0C8ForceTry2fnyyyxYKXE_ts5ErrorRzlF : $@convention(thin) <E where E : Error> (@guaranteed @noescape @callee_guaranteed @substituted <τ_0_0> () -> @error_indirect τ_0_0 for <E>) -> () {
// CHECK: bb0(%0 : @guaranteed $@noescape @callee_guaranteed @substituted <τ_0_0> () -> @error_indirect τ_0_0 for <E>):
// CHECK: [[FN:%.*]] = copy_value %0 : $@noescape @callee_guaranteed @substituted <τ_0_0> () -> @error_indirect τ_0_0 for <E>
// CHECK: [[ERROR:%.*]] = alloc_stack $E
// CHECK: [[FN_BORROW:%.*]] = begin_borrow %2 : $@noescape @callee_guaranteed @substituted <τ_0_0> () -> @error_indirect τ_0_0 for <E>
// CHECK: try_apply [[FN_BORROW]]([[ERROR]]) : $@noescape @callee_guaranteed @substituted <τ_0_0> () -> @error_indirect τ_0_0 for <E>, normal bb1, error bb2

// CHECK: bb1({{.*}} : $()):
// CHECK: end_borrow [[FN_BORROW]] : $@noescape @callee_guaranteed @substituted <τ_0_0> () -> @error_indirect τ_0_0 for <E>
// CHECK: dealloc_stack [[ERROR]] : $*E
// CHECK: [[RESULT:%.*]] = tuple ()
// CHECK: return [[RESULT]] : $()

// CHECK: bb2:
// CHECK: destroy_addr [[ERROR]] : $*E
// CHECK: end_borrow [[FN_BORROW]] : $@noescape @callee_guaranteed @substituted <τ_0_0> () -> @error_indirect τ_0_0 for <E>
// CHECK: dealloc_stack [[ERROR]] : $*E
// CHECK: destroy_value [[FN]] : $@noescape @callee_guaranteed @substituted <τ_0_0> () -> @error_indirect τ_0_0 for <E>
// CHECK: unreachable