Skip to content

Don't double-emit the source expression when erasing an archetype to Error #4308

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 1 commit into from
Aug 16, 2016
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
8 changes: 3 additions & 5 deletions lib/SILGen/SILGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,15 +319,13 @@ VarDecl *SILGenModule::getNSErrorRequirement(SILLocation loc) {
return found;
}

ProtocolConformance *
Optional<ProtocolConformanceRef>
SILGenModule::getConformanceToBridgedStoredNSError(SILLocation loc, Type type) {
auto proto = getBridgedStoredNSError(loc);
if (!proto) return nullptr;
if (!proto) return None;

// Find the conformance to _BridgedStoredNSError.
auto result = SwiftModule->lookupConformance(type, proto, nullptr);
if (result) return result->getConcrete();
return nullptr;
return SwiftModule->lookupConformance(type, proto, nullptr);
}

ProtocolConformance *SILGenModule::getNSErrorConformanceToError() {
Expand Down
4 changes: 2 additions & 2 deletions lib/SILGen/SILGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -390,8 +390,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor<SILGenModule> {

/// Find the conformance of the given Swift type to the
/// _BridgedStoredNSError protocol.
ProtocolConformance *getConformanceToBridgedStoredNSError(SILLocation loc,
Type type);
Optional<ProtocolConformanceRef>
getConformanceToBridgedStoredNSError(SILLocation loc, Type type);

/// Retrieve the conformance of NSError to the Error protocol.
ProtocolConformance *getNSErrorConformanceToError();
Expand Down
29 changes: 19 additions & 10 deletions lib/SILGen/SILGenBridging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -738,20 +738,29 @@ ManagedValue SILGenFunction::emitBridgedToNativeValue(SILLocation loc,
llvm_unreachable("bad CC");
}

/// Bridge an optional foreign error type to Error.
/// Bridge a possibly-optional foreign error type to Error.
ManagedValue SILGenFunction::emitBridgedToNativeError(SILLocation loc,
ManagedValue bridgedError) {
// If the incoming error is non-optional, inject it into an optional.
Type bridgedErrorTy = bridgedError.getType().getSwiftRValueType();
if (!bridgedErrorTy->getAnyOptionalObjectType()) {
SILType loweredOptErrorTy =
SGM.getLoweredType(OptionalType::get(bridgedErrorTy));
auto *someDecl = getASTContext().getOptionalSomeDecl();
auto *enumInst = B.createEnum(loc, bridgedError.getValue(), someDecl,
loweredOptErrorTy);
bridgedError = ManagedValue(enumInst, bridgedError.getCleanup());
// If the incoming error is non-optional, just do an existential erasure.
CanType bridgedErrorTy = bridgedError.getType().getSwiftRValueType();
if (!bridgedErrorTy.getAnyOptionalObjectType()) {
auto nativeErrorTy = SILType::getExceptionType(getASTContext());

auto conformance = SGM.getNSErrorConformanceToError();
if (!conformance) return emitUndef(loc, nativeErrorTy);
ProtocolConformanceRef conformanceArray[] = {
ProtocolConformanceRef(conformance)
};
auto conformances = getASTContext().AllocateCopy(conformanceArray);

SILValue nativeError =
B.createInitExistentialRef(loc, nativeErrorTy, bridgedErrorTy,
bridgedError.forward(*this), conformances);
return emitManagedRValueWithCleanup(nativeError);
}

// Otherwise, we need to call a runtime function to potential substitute
// a standard error for a nil NSError.
auto bridgeFn = emitGlobalFunctionRef(loc, SGM.getNSErrorToErrorFn());
auto bridgeFnType = bridgeFn->getType().castTo<SILFunctionType>();
assert(bridgeFnType->getNumAllResults() == 1);
Expand Down
272 changes: 134 additions & 138 deletions lib/SILGen/SILGenConvert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -459,147 +459,145 @@ ManagedValue SILGenFunction::emitExistentialErasure(
// If we're erasing to the 'Error' type, we might be able to get an NSError
// representation more efficiently.
auto &ctx = getASTContext();
auto nsError = ctx.getNSErrorDecl();
if (allowEmbeddedNSError && nsError &&
existentialTL.getSemanticType().getSwiftRValueType()->getAnyNominal() ==
ctx.getErrorDecl()) {
// Check whether the concrete type conforms to the _BridgedStoredNSError
// protocol. In that case, call the _nsError witness getter to extract the
// NSError directly.
auto conformance =
SGM.getConformanceToBridgedStoredNSError(loc, concreteFormalType);

CanType nsErrorType =
nsError->getDeclaredInterfaceType()->getCanonicalType();

ProtocolConformanceRef nsErrorConformances[1] = {
ProtocolConformanceRef(SGM.getNSErrorConformanceToError())
};

if (conformance && nsError && SGM.getNSErrorConformanceToError()) {
if (auto witness =
conformance->getWitness(SGM.getNSErrorRequirement(loc), nullptr)) {
// Create a reference to the getter witness.
SILDeclRef getter =
getGetterDeclRef(cast<VarDecl>(witness.getDecl()),
/*isDirectAccessorUse=*/true);

// Compute the substitutions.
ArrayRef<Substitution> substitutions =
concreteFormalType->gatherAllSubstitutions(
SGM.SwiftModule, nullptr);

// Emit the erasure, through the getter to _nsError.
return emitExistentialErasure(
loc, nsErrorType,
getTypeLowering(nsErrorType),
existentialTL,
ctx.AllocateCopy(nsErrorConformances),
C,
[&](SGFContext innerC) -> ManagedValue {
// Call the getter.
return emitGetAccessor(loc, getter, substitutions,
ArgumentSource(loc,
RValue(*this, loc,
concreteFormalType,
F(SGFContext()))),
/*isSuper=*/false,
/*isDirectAccessorUse=*/true,
RValue(), innerC)
.getAsSingleValue(*this, loc);
});
if (conformances.size() == 1 &&
conformances[0].getRequirement() == ctx.getErrorDecl() &&
ctx.getNSErrorDecl()) {
auto nsErrorDecl = ctx.getNSErrorDecl();

// If the concrete type is NSError or a subclass thereof, just erase it
// directly.
auto nsErrorType = nsErrorDecl->getDeclaredType()->getCanonicalType();
if (nsErrorType->isExactSuperclassOf(concreteFormalType, nullptr)) {
ManagedValue nsError = F(SGFContext());
if (nsErrorType != concreteFormalType) {
nsError = ManagedValue(B.createUpcast(loc, nsError.getValue(),
getLoweredType(nsErrorType)),
nsError.getCleanup());
}
return emitBridgedToNativeError(loc, nsError);
}

// Check whether the concrete type is an archetype. If so, call the
// _getEmbeddedNSError() witness to try to dig out the embedded NSError.
if (auto archetypeType = concreteFormalType->getAs<ArchetypeType>()) {
if (std::find(archetypeType->getConformsTo().begin(),
archetypeType->getConformsTo().end(),
ctx.getErrorDecl())
!= archetypeType->getConformsTo().end()) {
auto contBB = createBasicBlock();
auto isNotPresentBB = createBasicBlock();
auto isPresentBB = createBasicBlock();

SILValue existentialResult =
contBB->createBBArg(existentialTL.getLoweredType());

ProtocolConformanceRef trivialErrorConformances[1] = {
ProtocolConformanceRef(ctx.getErrorDecl())
};

Substitution substitutions[1] = {
Substitution(concreteFormalType,
ctx.AllocateCopy(trivialErrorConformances))
};

// Call swift_stdlib_getErrorEmbeddedNSError to attempt to extract an
// NSError from the value.
ManagedValue concreteValue = F(SGFContext());
ManagedValue potentialNSError =
emitApplyOfLibraryIntrinsic(loc,
SGM.getGetErrorEmbeddedNSError(loc),
ctx.AllocateCopy(substitutions),
{ concreteValue },
SGFContext())
.getAsSingleValue(*this, loc);

// Check whether we got an NSError back.
SILValue hasNSError =
emitDoesOptionalHaveValue(loc, potentialNSError.getValue());

B.createCondBranch(loc, hasNSError, isPresentBB, isNotPresentBB);

// If we did get an NSError, emit the existential erasure from that
// NSError.
B.emitBlock(isPresentBB);
SILValue branchArg;
{
// Don't allow cleanups to escape the conditional block.
FullExpr presentScope(Cleanups, CleanupLocation::get(loc));

// Emit the existential erasure from the NSError.
branchArg = emitExistentialErasure(
loc, nsErrorType,
getTypeLowering(nsErrorType),
existentialTL,
ctx.AllocateCopy(nsErrorConformances),
C,
[&](SGFContext innerC) -> ManagedValue {
// Pull the NSError object out of the optional result.
auto &inputTL = getTypeLowering(potentialNSError.getType());
auto nsErrorValue =
emitUncheckedGetOptionalValueFrom(loc, potentialNSError,
inputTL);


// Perform an unchecked cast down to NSError, because it was typed
// as 'AnyObject' for layering reasons.
return ManagedValue(B.createUncheckedRefCast(
loc,
nsErrorValue.getValue(),
getLoweredType(nsErrorType)),
nsErrorValue.getCleanup());

}).forward(*this);
// If the concrete type is known to conform to _BridgedStoredNSError,
// call the _nsError witness getter to extract the NSError directly,
// then just erase the NSError.
if (auto storedNSErrorConformance =
SGM.getConformanceToBridgedStoredNSError(loc, concreteFormalType)) {
auto nsErrorVar = SGM.getNSErrorRequirement(loc);
if (!nsErrorVar) return emitUndef(loc, existentialTL.getLoweredType());

ArrayRef<Substitution> nsErrorVarSubstitutions;

// Devirtualize. Maybe this should be done implicitly by
// emitPropertyLValue?
if (storedNSErrorConformance->isConcrete()) {
if (auto witnessVar = storedNSErrorConformance->getConcrete()
->getWitness(nsErrorVar, nullptr)) {
nsErrorVar = cast<VarDecl>(witnessVar.getDecl());
nsErrorVarSubstitutions = witnessVar.getSubstitutions();
}
B.createBranch(loc, contBB, branchArg);
}

// If we did not get an NSError, just directly emit the existential
// (recursively).
B.emitBlock(isNotPresentBB);
auto nativeError = F(SGFContext());

WritebackScope writebackScope(*this);
auto nsError =
emitRValueForPropertyLoad(loc, nativeError, concreteFormalType,
/*super*/ false, nsErrorVar,
nsErrorVarSubstitutions,
AccessSemantics::Ordinary, nsErrorType,
SGFContext())
.getAsSingleValue(*this, loc);

return emitBridgedToNativeError(loc, nsError);
}

// Otherwise, if it's an archetype, try calling the _getEmbeddedNSError()
// witness to try to dig out the embedded NSError. But don't do this
// when we're being called recursively.
if (isa<ArchetypeType>(concreteFormalType) && allowEmbeddedNSError) {
auto contBB = createBasicBlock();
auto isNotPresentBB = createBasicBlock();
auto isPresentBB = createBasicBlock();

// Call swift_stdlib_getErrorEmbeddedNSError to attempt to extract an
// NSError from the value.
auto getEmbeddedNSErrorFn = SGM.getGetErrorEmbeddedNSError(loc);
if (!getEmbeddedNSErrorFn)
return emitUndef(loc, existentialTL.getLoweredType());

Substitution getEmbeddedNSErrorSubstitutions[1] = {
Substitution(concreteFormalType, conformances)
};

ManagedValue concreteValue = F(SGFContext());
ManagedValue potentialNSError =
emitApplyOfLibraryIntrinsic(loc,
getEmbeddedNSErrorFn,
getEmbeddedNSErrorSubstitutions,
{ concreteValue.copy(*this, loc) },
SGFContext())
.getAsSingleValue(*this, loc);

// We're going to consume 'concreteValue' in exactly one branch,
// so kill its cleanup now and recreate it on both branches.
(void) concreteValue.forward(*this);

// Check whether we got an NSError back.
std::pair<EnumElementDecl*, SILBasicBlock*> cases[] = {
{ ctx.getOptionalSomeDecl(), isPresentBB },
{ ctx.getOptionalNoneDecl(), isNotPresentBB }
};
B.createSwitchEnum(loc, potentialNSError.forward(*this),
/*default*/ nullptr, cases);

// If we did get an NSError, emit the existential erasure from that
// NSError.
B.emitBlock(isPresentBB);
SILValue branchArg;
{
// Don't allow cleanups to escape the conditional block.
FullExpr presentScope(Cleanups, CleanupLocation::get(loc));
enterDestroyCleanup(concreteValue.getValue());

// Receive the error value. It's typed as an 'AnyObject' for
// layering reasons, so perform an unchecked cast down to NSError.
OptionalTypeKind optKind;
SILType anyObjectTy =
potentialNSError.getType().getAnyOptionalObjectType(SGM.M, optKind);
SILValue nsError = isPresentBB->createBBArg(anyObjectTy);
nsError = B.createUncheckedRefCast(loc, nsError,
getLoweredType(nsErrorType));

branchArg = emitBridgedToNativeError(loc,
emitManagedRValueWithCleanup(nsError))
.forward(*this);
}
B.createBranch(loc, contBB, branchArg);

// If we did not get an NSError, just directly emit the existential.
// Since this is a recursive call, make sure we don't end up in this
// path again.
B.emitBlock(isNotPresentBB);
{
FullExpr presentScope(Cleanups, CleanupLocation::get(loc));
concreteValue = emitManagedRValueWithCleanup(concreteValue.getValue());
branchArg = emitExistentialErasure(loc, concreteFormalType, concreteTL,
existentialTL, conformances,
SGFContext(), F,
SGFContext(),
[&](SGFContext C) {
return concreteValue;
},
/*allowEmbeddedNSError=*/false)
.forward(*this);
B.createBranch(loc, contBB, branchArg);

// Continue.
B.emitBlock(contBB);
return emitManagedRValueWithCleanup(existentialResult, existentialTL);
}
B.createBranch(loc, contBB, branchArg);

// Continue.
B.emitBlock(contBB);

SILValue existentialResult =
contBB->createBBArg(existentialTL.getLoweredType());
return emitManagedRValueWithCleanup(existentialResult, existentialTL);
}
}

Expand Down Expand Up @@ -641,14 +639,12 @@ ManagedValue SILGenFunction::emitExistentialErasure(
concreteTL.getLoweredType(),
existential);
// Initialize the concrete value in-place.
InitializationPtr init(
new ExistentialInitialization(existential, valueAddr, concreteFormalType,
ExistentialRepresentation::Boxed,
*this));
ManagedValue mv = F(SGFContext(init.get()));
ExistentialInitialization init(existential, valueAddr, concreteFormalType,
ExistentialRepresentation::Boxed, *this);
ManagedValue mv = F(SGFContext(&init));
if (!mv.isInContext()) {
mv.forwardInto(*this, loc, init->getAddress());
init->finishInitialization(*this);
mv.forwardInto(*this, loc, init.getAddress());
init.finishInitialization(*this);
}

return emitManagedRValueWithCleanup(existential);
Expand Down
Loading