Skip to content

Commit c5863ac

Browse files
committed
SILOptimizer: Constant fold the _kvcKeyPathString of literal key paths.
Eliminate the intermediate key path object when a literal key path is passed to a function that just wants its KVC string to pass down to an ObjC API.
1 parent de6d393 commit c5863ac

File tree

13 files changed

+183
-29
lines changed

13 files changed

+183
-29
lines changed

include/swift/AST/ASTContext.h

+4
Original file line numberDiff line numberDiff line change
@@ -1027,6 +1027,10 @@ class ASTContext final {
10271027

10281028
/// Retrieve the IRGen specific SIL passes.
10291029
SILTransformCtors getIRGenSILTransforms() const;
1030+
1031+
/// Check whether a given string would be considered "pure ASCII" by the
1032+
/// standard library's String implementation.
1033+
bool isASCIIString(StringRef s) const;
10301034

10311035
private:
10321036
friend Decl;

include/swift/AST/Attr.h

+13
Original file line numberDiff line numberDiff line change
@@ -2209,6 +2209,19 @@ class DeclAttributes {
22092209
make_range(begin(), end()), ToAttributeKind<ATTR, AllowInvalid>());
22102210
}
22112211

2212+
/// Return the range of semantics attributes attached to this attribute set.
2213+
auto getSemanticsAttrs() const
2214+
-> decltype(getAttributes<SemanticsAttr>()) {
2215+
return getAttributes<SemanticsAttr>();
2216+
}
2217+
2218+
/// Return whether this attribute set includes the given semantics attribute.
2219+
bool hasSemanticsAttr(StringRef attrValue) const {
2220+
return llvm::any_of(getSemanticsAttrs(), [&](const SemanticsAttr *attr) {
2221+
return attrValue.equals(attr->Value);
2222+
});
2223+
}
2224+
22122225
// Remove the given attribute from the list of attributes. Used when
22132226
// the attribute was semantically invalid.
22142227
void removeAttribute(const DeclAttribute *attr) {

include/swift/AST/Decl.h

+3-5
Original file line numberDiff line numberDiff line change
@@ -3524,14 +3524,12 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {
35243524

35253525
/// Return the range of semantics attributes attached to this NominalTypeDecl.
35263526
auto getSemanticsAttrs() const
3527-
-> decltype(getAttrs().getAttributes<SemanticsAttr>()) {
3528-
return getAttrs().getAttributes<SemanticsAttr>();
3527+
-> decltype(getAttrs().getSemanticsAttrs()) {
3528+
return getAttrs().getSemanticsAttrs();
35293529
}
35303530

35313531
bool hasSemanticsAttr(StringRef attrValue) const {
3532-
return llvm::any_of(getSemanticsAttrs(), [&](const SemanticsAttr *attr) {
3533-
return attrValue.equals(attr->Value);
3534-
});
3532+
return getAttrs().hasSemanticsAttr(attrValue);
35353533
}
35363534

35373535
/// Whether this declaration has a synthesized memberwise initializer.

include/swift/AST/SemanticAttrs.def

+2
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,7 @@ SEMANTICS_ATTR(SLOWPATH, "slowpath")
8787
SEMANTICS_ATTR(PROGRAMTERMINATION_POINT, "programtermination_point")
8888
SEMANTICS_ATTR(CONVERT_TO_OBJECTIVE_C, "convertToObjectiveC")
8989

90+
SEMANTICS_ATTR(KEYPATH_KVC_KEY_PATH_STRING, "keypath.kvcKeyPathString")
91+
9092
#undef SEMANTICS_ATTR
9193

include/swift/SILOptimizer/Utils/KeyPathProjector.h

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
namespace swift {
2626

27+
class KeyPathInst;
28+
2729
/// Projects a statically known key path expression to
2830
/// a direct property access.
2931
class KeyPathProjector {
@@ -53,6 +55,10 @@ class KeyPathProjector {
5355
static std::unique_ptr<KeyPathProjector>
5456
create(SILValue keyPath, SILValue root, SILLocation loc, SILBuilder &builder);
5557

58+
/// Extract the literal KeyPathInst underlying a value, or return null if there is none.
59+
static KeyPathInst *
60+
getLiteralKeyPath(SILValue keyPath);
61+
5662
/// Projects the key path to an address. Sets up the projection,
5763
/// invokes the callback, then tears down the projection.
5864
/// \param accessType The access type of the projected address.

lib/AST/ASTContext.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -4827,3 +4827,11 @@ llvm::LLVMContext &ASTContext::getIntrinsicScratchContext() const {
48274827
#endif
48284828
}
48294829

4830+
bool ASTContext::isASCIIString(StringRef s) const {
4831+
for (unsigned char c : s) {
4832+
if (c > 127) {
4833+
return false;
4834+
}
4835+
}
4836+
return true;
4837+
}

lib/SILGen/SILGenApply.cpp

+1-7
Original file line numberDiff line numberDiff line change
@@ -1614,13 +1614,7 @@ static PreparedArguments emitStringLiteral(SILGenFunction &SGF, Expr *E,
16141614
StringRef Str, SGFContext C,
16151615
StringLiteralExpr::Encoding encoding) {
16161616
uint64_t Length;
1617-
bool isASCII = true;
1618-
for (unsigned char c : Str) {
1619-
if (c > 127) {
1620-
isASCII = false;
1621-
break;
1622-
}
1623-
}
1617+
bool isASCII = SGF.getASTContext().isASCIIString(Str);
16241618
StringLiteralInst::Encoding instEncoding;
16251619
switch (encoding) {
16261620
case StringLiteralExpr::UTF8:

lib/SILOptimizer/SILCombiner/SILCombiner.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,9 @@ class SILCombiner :
241241

242242
bool tryOptimizeKeypath(ApplyInst *AI);
243243
bool tryOptimizeInoutKeypath(BeginApplyInst *AI);
244-
244+
bool tryOptimizeKeypathApplication(ApplyInst *AI, SILFunction *callee);
245+
bool tryOptimizeKeypathKVCString(ApplyInst *AI, SILDeclRef callee);
246+
245247
// Optimize concatenation of string literals.
246248
// Constant-fold concatenation of string literals known at compile-time.
247249
SILInstruction *optimizeConcatenationOfStringLiterals(ApplyInst *AI);

lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp

+95-5
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,8 @@ SILCombiner::optimizeApplyOfConvertFunctionInst(FullApplySite AI,
226226
/// %addr = struct_element_addr/ref_element_addr %root_object
227227
/// ...
228228
/// load/store %addr
229-
bool SILCombiner::tryOptimizeKeypath(ApplyInst *AI) {
230-
SILFunction *callee = AI->getReferencedFunctionOrNull();
231-
if (!callee)
232-
return false;
233-
229+
bool SILCombiner::tryOptimizeKeypathApplication(ApplyInst *AI,
230+
SILFunction *callee) {
234231
if (AI->getNumArguments() != 3)
235232
return false;
236233

@@ -274,6 +271,99 @@ bool SILCombiner::tryOptimizeKeypath(ApplyInst *AI) {
274271
return true;
275272
}
276273

274+
/// Try to optimize a keypath KVC string access on a literal key path.
275+
///
276+
/// Replace:
277+
/// %kp = keypath (objc "blah", ...)
278+
/// %string = apply %keypath_kvcString_method(%kp)
279+
/// With:
280+
/// %string = string_literal "blah"
281+
bool SILCombiner::tryOptimizeKeypathKVCString(ApplyInst *AI,
282+
SILDeclRef callee) {
283+
if (AI->getNumArguments() != 1) {
284+
return false;
285+
}
286+
if (!callee.hasDecl()) {
287+
return false;
288+
}
289+
auto calleeFn = dyn_cast<FuncDecl>(callee.getDecl());
290+
if (!calleeFn)
291+
return false;
292+
293+
if (!calleeFn->getAttrs()
294+
.hasSemanticsAttr(semantics::KEYPATH_KVC_KEY_PATH_STRING))
295+
return false;
296+
297+
// Method should return `String?`
298+
auto &C = calleeFn->getASTContext();
299+
auto objTy = AI->getType().getOptionalObjectType();
300+
if (!objTy || objTy.getStructOrBoundGenericStruct() != C.getStringDecl())
301+
return false;
302+
303+
KeyPathInst *kp
304+
= KeyPathProjector::getLiteralKeyPath(AI->getArgument(0));
305+
if (!kp || !kp->hasPattern())
306+
return false;
307+
308+
auto objcString = kp->getPattern()->getObjCString();
309+
310+
SILValue literalValue;
311+
if (objcString.empty()) {
312+
// Replace with a nil String value.
313+
literalValue = Builder.createEnum(AI->getLoc(), SILValue(),
314+
C.getOptionalNoneDecl(),
315+
AI->getType());
316+
} else {
317+
// Construct a literal String from the ObjC string.
318+
auto init = C.getStringBuiltinInitDecl(C.getStringDecl());
319+
if (!init)
320+
return false;
321+
auto initRef = SILDeclRef(init.getDecl(), SILDeclRef::Kind::Allocator);
322+
auto initFn = AI->getModule().findFunction(initRef.mangle(),
323+
SILLinkage::PublicExternal);
324+
if (!initFn)
325+
return false;
326+
327+
auto stringValue = Builder.createStringLiteral(AI->getLoc(), objcString,
328+
StringLiteralInst::Encoding::UTF8);
329+
auto stringLen = Builder.createIntegerLiteral(AI->getLoc(),
330+
SILType::getBuiltinWordType(C),
331+
objcString.size());
332+
auto isAscii = Builder.createIntegerLiteral(AI->getLoc(),
333+
SILType::getBuiltinIntegerType(1, C),
334+
C.isASCIIString(objcString));
335+
auto metaTy =
336+
CanMetatypeType::get(objTy.getASTType(), MetatypeRepresentation::Thin);
337+
auto self = Builder.createMetatype(AI->getLoc(),
338+
SILType::getPrimitiveObjectType(metaTy));
339+
340+
auto initFnRef = Builder.createFunctionRef(AI->getLoc(), initFn);
341+
auto string = Builder.createApply(AI->getLoc(),
342+
initFnRef, {},
343+
{stringValue, stringLen, isAscii, self});
344+
345+
literalValue = Builder.createEnum(AI->getLoc(), string,
346+
C.getOptionalSomeDecl(), AI->getType());
347+
}
348+
349+
AI->replaceAllUsesWith(literalValue);
350+
eraseInstFromFunction(*AI);
351+
++NumOptimizedKeypaths;
352+
return true;
353+
}
354+
355+
bool SILCombiner::tryOptimizeKeypath(ApplyInst *AI) {
356+
if (SILFunction *callee = AI->getReferencedFunctionOrNull()) {
357+
return tryOptimizeKeypathApplication(AI, callee);
358+
}
359+
360+
if (auto method = dyn_cast<ClassMethodInst>(AI->getCallee())) {
361+
return tryOptimizeKeypathKVCString(AI, method->getMember());
362+
}
363+
364+
return false;
365+
}
366+
277367
/// Try to optimize a keypath application with an apply instruction.
278368
///
279369
/// Replaces (simplified SIL):

lib/SILOptimizer/Utils/KeyPathProjector.cpp

+9-4
Original file line numberDiff line numberDiff line change
@@ -656,14 +656,19 @@ class CompleteKeyPathProjector : public KeyPathProjector {
656656
}
657657
};
658658

659+
KeyPathInst *
660+
KeyPathProjector::getLiteralKeyPath(SILValue keyPath) {
661+
if (auto *upCast = dyn_cast<UpcastInst>(keyPath))
662+
keyPath = upCast->getOperand();
663+
// TODO: Look through other conversions, copies, etc.?
664+
return dyn_cast<KeyPathInst>(keyPath);
665+
}
666+
659667
std::unique_ptr<KeyPathProjector>
660668
KeyPathProjector::create(SILValue keyPath, SILValue root,
661669
SILLocation loc, SILBuilder &builder) {
662-
if (auto *upCast = dyn_cast<UpcastInst>(keyPath))
663-
keyPath = upCast->getOperand();
664-
665670
// Is it a keypath instruction at all?
666-
auto *kpInst = dyn_cast<KeyPathInst>(keyPath);
671+
auto *kpInst = getLiteralKeyPath(keyPath);
667672
if (!kpInst || !kpInst->hasPattern())
668673
return nullptr;
669674

lib/Sema/ConstantnessSemaDiagnostics.cpp

+1-5
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,7 @@ using namespace swift;
3535
/// Check whether a given \p decl has a @_semantics attribute with the given
3636
/// attribute name \c attrName.
3737
static bool hasSemanticsAttr(ValueDecl *decl, StringRef attrName) {
38-
for (auto semantics : decl->getAttrs().getAttributes<SemanticsAttr>()) {
39-
if (semantics->Value.equals(attrName))
40-
return true;
41-
}
42-
return false;
38+
return decl->getAttrs().hasSemanticsAttr(attrName);
4339
}
4440

4541
/// Return true iff the given \p structDecl has a name that matches one of the

stdlib/public/core/KeyPath.swift

+5-2
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,12 @@ public class AnyKeyPath: Hashable, _AppendKeyPath {
122122
// SPI for the Foundation overlay to allow interop with KVC keypath-based
123123
// APIs.
124124
public var _kvcKeyPathString: String? {
125-
guard let ptr = _kvcKeyPathStringPtr else { return nil }
125+
@_semantics("keypath.kvcKeyPathString")
126+
get {
127+
guard let ptr = _kvcKeyPathStringPtr else { return nil }
126128

127-
return String(validatingUTF8: ptr)
129+
return String(validatingUTF8: ptr)
130+
}
128131
}
129132

130133
// MARK: Implementation details
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -primary-file %s -O -sil-verify-all -emit-sil >%t/output.sil
3+
// RUN: %FileCheck %s < %t/output.sil
4+
5+
// REQUIRES: objc_interop
6+
7+
import Foundation
8+
9+
class Foo: NSObject {
10+
@objc(doesIndeedHaveAKVCString) var hasKVCString: Int = 0
11+
12+
var noKVCString: Int? = 0
13+
}
14+
15+
// CHECK-LABEL: sil hidden @{{.*}}21optimize_keypath_objc12hasKVCString
16+
func hasKVCString() -> String? {
17+
// CHECK-NOT: = keypath
18+
// CHECK: string_literal utf8 "doesIndeedHaveAKVCString"
19+
// CHECK-NOT: = keypath
20+
// CHECK: [[RESULT:%.*]] = enum $Optional<String>, #Optional.some
21+
// CHECK-NEXT: return [[RESULT:%.*]]
22+
// CHECK-NEXT: }
23+
return (\Foo.hasKVCString)._kvcKeyPathString
24+
}
25+
26+
// CHECK-LABEL: sil hidden @{{.*}}21optimize_keypath_objc11noKVCString
27+
func noKVCString() -> String? {
28+
// CHECK-NOT: = keypath
29+
// CHECK: [[RESULT:%.*]] = enum $Optional<String>, #Optional.none
30+
// CHECK-NEXT: return [[RESULT:%.*]]
31+
// CHECK-NEXT: }
32+
return (\Foo.noKVCString)._kvcKeyPathString
33+
}

0 commit comments

Comments
 (0)