Skip to content

[Swift 3.0] Eliminate double-wrapping of NSErrors in _SwiftNativeNSError #3976

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
2 changes: 1 addition & 1 deletion include/swift/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ class ASTContext {
PrecedenceGroupDecl *right) const;

/// Retrieve the declaration of Swift.Error.
NominalTypeDecl *getErrorDecl() const;
ProtocolDecl *getErrorDecl() const;
CanType getExceptionType() const;

/// Retrieve the declaration of Swift.Bool.
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/KnownDecls.def
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,6 @@ FUNC_DECL(BridgeAnythingToObjectiveC,
FUNC_DECL(DidEnterMain, "_stdlib_didEnterMain")
FUNC_DECL(DiagnoseUnexpectedNilOptional, "_diagnoseUnexpectedNilOptional")

FUNC_DECL(GetErrorEmbeddedNSError, "_stdlib_getErrorEmbeddedNSError")

#undef FUNC_DECL
2 changes: 1 addition & 1 deletion lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ CanType ASTContext::getExceptionType() const {
}
}

NominalTypeDecl *ASTContext::getErrorDecl() const {
ProtocolDecl *ASTContext::getErrorDecl() const {
return getProtocol(KnownProtocolKind::Error);
}

Expand Down
4 changes: 2 additions & 2 deletions lib/SIL/SILType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -465,8 +465,8 @@ static bool isBridgedErrorClass(SILModule &M,
t = archetypeType->getSuperclass();

// NSError (TODO: and CFError) can be bridged.
auto errorType = M.Types.getNSErrorType();
if (t && errorType && t->isEqual(errorType)) {
auto nsErrorType = M.Types.getNSErrorType();
if (t && nsErrorType && nsErrorType->isExactSuperclassOf(t, nullptr)) {
return true;
}

Expand Down
77 changes: 77 additions & 0 deletions lib/SILGen/SILGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,83 @@ SILGenModule::getConformanceToObjectiveCBridgeable(SILLocation loc, Type type) {
return nullptr;
}

ProtocolDecl *SILGenModule::getBridgedStoredNSError(SILLocation loc) {
if (BridgedStoredNSError)
return *BridgedStoredNSError;

// Find the _BridgedStoredNSError protocol.
auto &ctx = getASTContext();
auto proto = ctx.getProtocol(KnownProtocolKind::BridgedStoredNSError);
BridgedStoredNSError = proto;
return proto;
}

VarDecl *SILGenModule::getNSErrorRequirement(SILLocation loc) {
if (NSErrorRequirement)
return *NSErrorRequirement;

// Find the _BridgedStoredNSError protocol.
auto proto = getBridgedStoredNSError(loc);
if (!proto) {
NSErrorRequirement = nullptr;
return nullptr;
}

// Look for _nsError.
auto &ctx = getASTContext();
VarDecl *found = nullptr;
for (auto member : proto->lookupDirect(ctx.Id_nsError, true)) {
if (auto var = dyn_cast<VarDecl>(member)) {
found = var;
break;
}
}

NSErrorRequirement = found;
return found;
}

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

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

ProtocolConformance *SILGenModule::getNSErrorConformanceToError() {
if (NSErrorConformanceToError)
return *NSErrorConformanceToError;

auto &ctx = getASTContext();
auto nsError = ctx.getNSErrorDecl();
if (!nsError) {
NSErrorConformanceToError = nullptr;
return nullptr;
}

auto error = ctx.getErrorDecl();
if (!error) {
NSErrorConformanceToError = nullptr;
return nullptr;
}

auto conformance =
SwiftModule->lookupConformance(nsError->getDeclaredInterfaceType(),
cast<ProtocolDecl>(error),
nullptr);

if (conformance && conformance->isConcrete())
NSErrorConformanceToError = conformance->getConcrete();
else
NSErrorConformanceToError = nullptr;
return *NSErrorConformanceToError;
}


SILFunction *SILGenModule::emitTopLevelFunction(SILLocation Loc) {
ASTContext &C = M.getASTContext();
auto extInfo = SILFunctionType::ExtInfo()
Expand Down
19 changes: 19 additions & 0 deletions lib/SILGen/SILGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor<SILGenModule> {
Optional<FuncDecl*> UnconditionallyBridgeFromObjectiveCRequirement;
Optional<AssociatedTypeDecl*> BridgedObjectiveCType;

Optional<ProtocolDecl*> BridgedStoredNSError;
Optional<VarDecl*> NSErrorRequirement;

Optional<ProtocolConformance *> NSErrorConformanceToError;

public:
SILGenModule(SILModule &M, Module *SM, bool makeModuleFragile);
~SILGenModule();
Expand Down Expand Up @@ -377,6 +382,20 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor<SILGenModule> {
ProtocolConformance *getConformanceToObjectiveCBridgeable(SILLocation loc,
Type type);

/// Retrieve the _BridgedStoredNSError protocol definition.
ProtocolDecl *getBridgedStoredNSError(SILLocation loc);

/// Retrieve the _BridgedStoredNSError._nsError requirement.
VarDecl *getNSErrorRequirement(SILLocation loc);

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

/// Retrieve the conformance of NSError to the Error protocol.
ProtocolConformance *getNSErrorConformanceToError();

/// Report a diagnostic.
template<typename...T, typename...U>
InFlightDiagnostic diagnose(SourceLoc loc, Diag<T...> diag,
Expand Down
150 changes: 149 additions & 1 deletion lib/SILGen/SILGenConvert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -450,11 +450,159 @@ ManagedValue SILGenFunction::emitExistentialErasure(
const TypeLowering &existentialTL,
ArrayRef<ProtocolConformanceRef> conformances,
SGFContext C,
llvm::function_ref<ManagedValue (SGFContext)> F) {
llvm::function_ref<ManagedValue (SGFContext)> F,
bool allowEmbeddedNSError) {
// Mark the needed conformances as used.
for (auto conformance : conformances)
SGM.useConformance(conformance);

// 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);
});
}
}

// 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);
}
B.createBranch(loc, contBB, branchArg);

// If we did not get an NSError, just directly emit the existential
// (recursively).
B.emitBlock(isNotPresentBB);
branchArg = emitExistentialErasure(loc, concreteFormalType, concreteTL,
existentialTL, conformances,
SGFContext(), F,
/*allowEmbeddedNSError=*/false)
.forward(*this);
B.createBranch(loc, contBB, branchArg);

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

switch (existentialTL.getLoweredType().getObjectType()
.getPreferredExistentialRepresentation(SGM.M, concreteFormalType)) {
case ExistentialRepresentation::None:
Expand Down
3 changes: 2 additions & 1 deletion lib/SILGen/SILGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -944,7 +944,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
const TypeLowering &existentialTL,
ArrayRef<ProtocolConformanceRef> conformances,
SGFContext C,
llvm::function_ref<ManagedValue (SGFContext)> F);
llvm::function_ref<ManagedValue (SGFContext)> F,
bool allowEmbeddedNSError = true);

//===--------------------------------------------------------------------===//
// Recursive entry points
Expand Down
15 changes: 15 additions & 0 deletions stdlib/public/SDK/Foundation/NSError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,11 @@ extension NSError : Error {
public var _domain: String { return domain }
public var _code: Int { return code }
public var _userInfo: Any? { return userInfo }

/// The "embedded" NSError is itself.
public func _getEmbeddedNSError() -> AnyObject? {
return self
}
}

extension CFError : Error {
Expand All @@ -255,6 +260,11 @@ extension CFError : Error {
public var _userInfo: Any? {
return CFErrorCopyUserInfo(self) as Any
}

/// The "embedded" NSError is itself.
public func _getEmbeddedNSError() -> AnyObject? {
return self
}
}

// An error value to use when an Objective-C API indicates error
Expand Down Expand Up @@ -496,6 +506,11 @@ extension _ErrorCodeProtocol where Self._ErrorType: _BridgedStoredNSError {
}

extension _BridgedStoredNSError {
/// Retrieve the embedded NSError from a bridged, stored NSError.
public func _getEmbeddedNSError() -> AnyObject? {
return _nsError
}

public static func == (lhs: Self, rhs: Self) -> Bool {
return lhs._nsError.isEqual(rhs._nsError)
}
Expand Down
Loading