Skip to content

Commit e3f3b75

Browse files
committed
StringOptimization: handle static let variables for String constant folding.
For example, constant fold: struct Str { static let s = "hello" } ... let x = "<\(Str.s)>"
1 parent 4d03eb4 commit e3f3b75

File tree

2 files changed

+105
-21
lines changed

2 files changed

+105
-21
lines changed

lib/SILOptimizer/Transforms/StringOptimization.cpp

Lines changed: 91 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "swift/SILOptimizer/Analysis/ValueTracking.h"
2020
#include "swift/SIL/SILFunction.h"
2121
#include "swift/SIL/SILBasicBlock.h"
22+
#include "swift/SIL/SILGlobalVariable.h"
2223
#include "swift/SIL/SILBuilder.h"
2324
#include "swift/AST/SemanticAttrs.h"
2425
#include "swift/AST/ParameterList.h"
@@ -58,13 +59,15 @@ class StringOptimization {
5859
StringRef str;
5960

6061
/// Negative means: not constant
61-
int numCodeUnits = -1;
62-
63-
/// Not 0 for the empty-string initializer which reserves a capacity.
6462
int reservedCapacity = 0;
6563

66-
bool isConstant() const { return numCodeUnits >= 0; }
64+
StringInfo(StringRef str, int reservedCapacity = 0) :
65+
str(str), reservedCapacity(reservedCapacity) { }
66+
67+
bool isConstant() const { return reservedCapacity >= 0; }
6768
bool isEmpty() const { return isConstant() && str.empty(); }
69+
70+
static StringInfo unknown() { return StringInfo(StringRef(), -1); }
6871
};
6972

7073
/// The stdlib's String type.
@@ -96,6 +99,8 @@ class StringOptimization {
9699
static void invalidateModifiedObjects(SILInstruction *inst,
97100
llvm::DenseMap<SILValue, SILValue> &storedStrings);
98101
static StringInfo getStringInfo(SILValue value);
102+
static StringInfo getStringFromStaticLet(SILValue value);
103+
99104
static Optional<int> getIntConstant(SILValue value);
100105
static void replaceAppendWith(ApplyInst *appendCall, SILValue newValue,
101106
bool copyNewValue);
@@ -368,32 +373,31 @@ void StringOptimization::invalidateModifiedObjects(SILInstruction *inst,
368373

369374
/// Returns information about value if it's a constant string.
370375
StringOptimization::StringInfo StringOptimization::getStringInfo(SILValue value) {
371-
// Start with a non-constant result.
372-
StringInfo result;
376+
if (!value)
377+
return StringInfo::unknown();
373378

374-
auto *apply = dyn_cast_or_null<ApplyInst>(value);
375-
if (!apply)
376-
return result;
379+
auto *apply = dyn_cast<ApplyInst>(value);
380+
if (!apply) {
381+
return getStringFromStaticLet(value);
382+
}
377383

378384
SILFunction *callee = apply->getReferencedFunctionOrNull();
379385
if (!callee)
380-
return result;
386+
return StringInfo::unknown();
381387

382388
if (callee->hasSemanticsAttr(semantics::STRING_INIT_EMPTY)) {
383389
// An empty string initializer.
384-
result.numCodeUnits = 0;
385-
return result;
390+
return StringInfo("");
386391
}
387392

388393
if (callee->hasSemanticsAttr(semantics::STRING_INIT_EMPTY_WITH_CAPACITY)) {
389394
// An empty string initializer with initial capacity.
390-
result.numCodeUnits = 0;
391-
result.reservedCapacity = std::numeric_limits<int>::max();
395+
int reservedCapacity = std::numeric_limits<int>::max();
392396
if (apply->getNumArguments() > 0) {
393397
if (Optional<int> capacity = getIntConstant(apply->getArgument(0)))
394-
result.reservedCapacity = capacity.getValue();
398+
reservedCapacity = capacity.getValue();
395399
}
396-
return result;
400+
return StringInfo("", reservedCapacity);
397401
}
398402

399403
if (callee->hasSemanticsAttr(semantics::STRING_MAKE_UTF8)) {
@@ -404,13 +408,79 @@ StringOptimization::StringInfo StringOptimization::getStringInfo(SILValue value)
404408
auto *intLiteral = dyn_cast<IntegerLiteralInst>(lengthVal);
405409
if (intLiteral && stringLiteral &&
406410
// For simplicity, we only support UTF8 string literals.
407-
stringLiteral->getEncoding() == StringLiteralInst::Encoding::UTF8) {
408-
result.str = stringLiteral->getValue();
409-
result.numCodeUnits = intLiteral->getValue().getSExtValue();
410-
return result;
411+
stringLiteral->getEncoding() == StringLiteralInst::Encoding::UTF8 &&
412+
// This passed number of code units should always match the size of the
413+
// string in the string literal. Just to be on the safe side, check it.
414+
intLiteral->getValue() == stringLiteral->getValue().size()) {
415+
return StringInfo(stringLiteral->getValue());
411416
}
412417
}
413-
return result;
418+
return StringInfo::unknown();
419+
}
420+
421+
/// Return the string if \p value is a load from a global static let, which is
422+
/// initialized with a String constant.
423+
StringOptimization::StringInfo
424+
StringOptimization::getStringFromStaticLet(SILValue value) {
425+
// Match the pattern
426+
// %ptr_to_global = apply %addressor()
427+
// %global_addr = pointer_to_address %ptr_to_global
428+
// %value = load %global_addr
429+
auto *load = dyn_cast<LoadInst>(value);
430+
if (!load)
431+
return StringInfo::unknown();
432+
433+
auto *pta = dyn_cast<PointerToAddressInst>(load->getOperand());
434+
if (!pta)
435+
return StringInfo::unknown();
436+
437+
auto *addressorCall = dyn_cast<ApplyInst>(pta->getOperand());
438+
if (!addressorCall)
439+
return StringInfo::unknown();
440+
441+
SILFunction *addressorFunc = addressorCall->getReferencedFunctionOrNull();
442+
if (!addressorFunc)
443+
return StringInfo::unknown();
444+
445+
// The addressor function has a builtin.once call to the initializer.
446+
BuiltinInst *onceCall = nullptr;
447+
SILFunction *initializer = findInitializer(addressorFunc, onceCall);
448+
if (!initializer)
449+
return StringInfo::unknown();
450+
451+
if (initializer->size() != 1)
452+
return StringInfo::unknown();
453+
454+
// Match the pattern
455+
// %addr = global_addr @staticStringLet
456+
// ...
457+
// %str = apply %stringInitializer(...)
458+
// store %str to %addr
459+
GlobalAddrInst *gAddr = nullptr;
460+
for (SILInstruction &inst : initializer->front()) {
461+
if (auto *ga = dyn_cast<GlobalAddrInst>(&inst)) {
462+
if (gAddr)
463+
return StringInfo::unknown();
464+
gAddr = ga;
465+
}
466+
}
467+
if (!gAddr || !gAddr->getReferencedGlobal()->isLet())
468+
return StringInfo::unknown();
469+
470+
Operand *gUse = gAddr->getSingleUse();
471+
auto *store = dyn_cast<StoreInst>(gUse->getUser());
472+
if (!store || store->getDest() != gAddr)
473+
return StringInfo::unknown();
474+
475+
SILValue initVal = store->getSrc();
476+
477+
// This check is probably not needed, but let's be on the safe side:
478+
// it prevents an infinite recursion if the initializer of the global is
479+
// itself a load of another global, and so on.
480+
if (isa<LoadInst>(initVal))
481+
return StringInfo::unknown();
482+
483+
return getStringInfo(initVal);
414484
}
415485

416486
/// Returns the constant integer value if \a value is an Int or Bool struct with

test/SILOptimizer/string_optimization.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import Foundation
1212

1313
struct Outer {
1414
struct Inner { }
15+
16+
static let staticString = "static"
1517
}
1618

1719
// More types are tested in test/stdlib/TypeName.swift and
@@ -36,6 +38,15 @@ public func testFoldCompleteInterpolation() -> String {
3638
return "-\([Int].self) \(s) \("cool")+"
3739
}
3840

41+
// CHECK-LABEL: sil [noinline] @$s4test0A13FoldStaticLetSSyF
42+
// CHECK-NOT: apply
43+
// CHECK-NOT: bb1
44+
// CHECK: } // end sil function '$s4test0A13FoldStaticLetSSyF'
45+
@inline(never)
46+
public func testFoldStaticLet() -> String {
47+
return "-\(Outer.staticString)+"
48+
}
49+
3950
// CHECK-LABEL: sil [noinline] @$s4test0A19UnqualifiedTypeNameSSyF
4051
// CHECK-NOT: apply
4152
// CHECK-NOT: bb1
@@ -92,6 +103,9 @@ printEmbeeded(testTypeNameInterpolation())
92103
// CHECK-OUTPUT: <-Array<Int> is cool+>
93104
printEmbeeded(testFoldCompleteInterpolation())
94105

106+
// CHECK-OUTPUT: <-static+>
107+
printEmbeeded(testFoldStaticLet())
108+
95109
// CHECK-OUTPUT: <Inner>
96110
printEmbeeded(testUnqualifiedTypeName())
97111

0 commit comments

Comments
 (0)