Skip to content

Commit 9bac363

Browse files
authored
Merge pull request #33232 from eeckstein/string-optimization
StringOptimization: handle static let variables for String constant folding.
2 parents 0685b28 + e3f3b75 commit 9bac363

File tree

9 files changed

+157
-42
lines changed

9 files changed

+157
-42
lines changed

include/swift/SIL/SILGlobalVariable.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,7 @@ SILFunction *getCalleeOfOnceCall(BuiltinInst *BI);
260260
/// Given an addressor, AddrF, find the call to the global initializer if
261261
/// present, otherwise return null. If an initializer is returned, then
262262
/// `CallToOnce` is initialized to the corresponding builtin "once" call.
263-
SILFunction *findInitializer(SILModule *Module, SILFunction *AddrF,
264-
BuiltinInst *&CallToOnce);
263+
SILFunction *findInitializer(SILFunction *AddrF, BuiltinInst *&CallToOnce);
265264

266265
/// Helper for getVariableOfGlobalInit(), so GlobalOpts can deeply inspect and
267266
/// rewrite the initialization pattern.

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,8 @@ PASS(SILCombine, "sil-combine",
299299
"Combine SIL Instructions via Peephole Optimization")
300300
PASS(SILDebugInfoGenerator, "sil-debuginfo-gen",
301301
"Generate Debug Information with Source Locations into Textual SIL")
302+
PASS(EarlySROA, "early-sroa",
303+
"Scalar Replacement of Aggregate Stack Objects on high-level SIL")
302304
PASS(SROA, "sroa",
303305
"Scalar Replacement of Aggregate Stack Objects")
304306
PASS(SROABBArgs, "sroa-bb-args",

lib/SIL/IR/SILGlobalVariable.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,9 @@ SILGlobalVariable *swift::getVariableOfGlobalInit(SILFunction *AddrF) {
220220
// and the globalinit_func is called by "once" from a single location,
221221
// continue; otherwise bail.
222222
BuiltinInst *CallToOnce;
223-
auto *InitF = findInitializer(&AddrF->getModule(), AddrF, CallToOnce);
223+
auto *InitF = findInitializer(AddrF, CallToOnce);
224224

225-
if (!InitF || !InitF->getName().startswith("globalinit_"))
225+
if (!InitF)
226226
return nullptr;
227227

228228
// If the globalinit_func is trivial, continue; otherwise bail.
@@ -247,8 +247,8 @@ SILFunction *swift::getCalleeOfOnceCall(BuiltinInst *BI) {
247247
}
248248

249249
// Find the globalinit_func by analyzing the body of the addressor.
250-
SILFunction *swift::findInitializer(SILModule *Module, SILFunction *AddrF,
251-
BuiltinInst *&CallToOnce) {
250+
SILFunction *swift::findInitializer(SILFunction *AddrF,
251+
BuiltinInst *&CallToOnce) {
252252
// We only handle a single SILBasicBlock for now.
253253
if (AddrF->size() != 1)
254254
return nullptr;
@@ -272,7 +272,10 @@ SILFunction *swift::findInitializer(SILModule *Module, SILFunction *AddrF,
272272
}
273273
if (!CallToOnce)
274274
return nullptr;
275-
return getCalleeOfOnceCall(CallToOnce);
275+
SILFunction *callee = getCalleeOfOnceCall(CallToOnce);
276+
if (!callee->getName().startswith("globalinit_"))
277+
return nullptr;
278+
return callee;
276279
}
277280

278281
SILGlobalVariable *

lib/SILOptimizer/IPO/GlobalOpt.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,9 +431,8 @@ bool SILGlobalOpt::optimizeInitializer(SILFunction *AddrF,
431431
// If the addressor contains a single "once" call, it calls globalinit_func,
432432
// and the globalinit_func is called by "once" from a single location,
433433
// continue; otherwise bail.
434-
auto *InitF = findInitializer(Module, AddrF, CallToOnce);
435-
if (!InitF || !InitF->getName().startswith("globalinit_") ||
436-
InitializerCount[InitF] > 1)
434+
auto *InitF = findInitializer(AddrF, CallToOnce);
435+
if (!InitF || InitializerCount[InitF] > 1)
437436
return false;
438437

439438
// If the globalinit_func is trivial, continue; otherwise bail.

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ void addHighLevelLoopOptPasses(SILPassPipelinePlan &P) {
222222
// Perform classic SSA optimizations for cleanup.
223223
P.addLowerAggregateInstrs();
224224
P.addSILCombine();
225-
P.addSROA();
225+
P.addEarlySROA();
226226
P.addMem2Reg();
227227
P.addDCE();
228228
P.addSILCombine();
@@ -290,7 +290,11 @@ void addFunctionPasses(SILPassPipelinePlan &P,
290290
P.addLowerAggregateInstrs();
291291

292292
// Split up operations on stack-allocated aggregates (struct, tuple).
293-
P.addSROA();
293+
if (OpLevel == OptimizationLevelKind::HighLevel) {
294+
P.addEarlySROA();
295+
} else {
296+
P.addSROA();
297+
}
294298

295299
// Promote stack allocations to values.
296300
P.addMem2Reg();
@@ -489,6 +493,8 @@ static void addHighLevelFunctionPipeline(SILPassPipelinePlan &P) {
489493
addFunctionPasses(P, OptimizationLevelKind::HighLevel);
490494

491495
addHighLevelLoopOptPasses(P);
496+
497+
P.addStringOptimization();
492498
}
493499

494500
// After "high-level" function passes have processed the entire call tree, run
@@ -525,8 +531,6 @@ static void addSerializePipeline(SILPassPipelinePlan &P) {
525531
static void addMidLevelFunctionPipeline(SILPassPipelinePlan &P) {
526532
P.startPipeline("MidLevel,Function", true /*isFunctionPassPipeline*/);
527533

528-
P.addStringOptimization();
529-
530534
addFunctionPasses(P, OptimizationLevelKind::MidLevel);
531535

532536
// Specialize partially applied functions with dead arguments as a preparation

lib/SILOptimizer/Transforms/SILMem2Reg.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -375,15 +375,15 @@ static void replaceLoad(LoadInst *LI, SILValue val, AllocStackInst *ASI) {
375375

376376
static void replaceDestroy(DestroyAddrInst *DAI, SILValue NewValue) {
377377
SILFunction *F = DAI->getFunction();
378+
auto Ty = DAI->getOperand()->getType();
378379

379-
assert(DAI->getOperand()->getType().isLoadable(*F) &&
380+
assert(Ty.isLoadable(*F) &&
380381
"Unexpected promotion of address-only type!");
381382

382-
assert(NewValue && "Expected a value to release!");
383+
assert(NewValue || (Ty.is<TupleType>() && Ty.getAs<TupleType>()->getNumElements() == 0));
383384

384385
SILBuilderWithScope Builder(DAI);
385386

386-
auto Ty = DAI->getOperand()->getType();
387387
auto &TL = F->getTypeLowering(Ty);
388388

389389
bool expand = shouldExpand(DAI->getModule(),

lib/SILOptimizer/Transforms/SILSROA.cpp

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,18 +301,33 @@ void SROAMemoryUseAnalyzer::chopUpAlloca(std::vector<AllocStackInst *> &Worklist
301301
eraseFromParentWithDebugInsts(AI);
302302
}
303303

304-
static bool runSROAOnFunction(SILFunction &Fn) {
304+
/// Returns true, if values of \ty should be ignored, because \p ty is known
305+
/// by a high-level SIL optimization. Values of that type must not be split
306+
/// so that those high-level optimizations can analyze the code.
307+
static bool isSemanticType(ASTContext &ctxt, SILType ty) {
308+
NominalTypeDecl *stringDecl = ctxt.getStringDecl();
309+
if (ty.getStructOrBoundGenericStruct() == stringDecl)
310+
return true;
311+
return false;
312+
}
313+
314+
static bool runSROAOnFunction(SILFunction &Fn, bool splitSemanticTypes) {
305315
std::vector<AllocStackInst *> Worklist;
306316
bool Changed = false;
317+
ASTContext &ctxt = Fn.getModule().getASTContext();
307318

308319
// For each basic block BB in Fn...
309320
for (auto &BB : Fn)
310321
// For each instruction in BB...
311322
for (auto &I : BB)
312323
// If the instruction is an alloc stack inst, add it to the worklist.
313-
if (auto *AI = dyn_cast<AllocStackInst>(&I))
324+
if (auto *AI = dyn_cast<AllocStackInst>(&I)) {
325+
if (!splitSemanticTypes && isSemanticType(ctxt, AI->getElementType()))
326+
continue;
327+
314328
if (shouldExpand(Fn.getModule(), AI->getElementType()))
315329
Worklist.push_back(AI);
330+
}
316331

317332
while (!Worklist.empty()) {
318333
AllocStackInst *AI = Worklist.back();
@@ -332,6 +347,11 @@ static bool runSROAOnFunction(SILFunction &Fn) {
332347
namespace {
333348
class SILSROA : public SILFunctionTransform {
334349

350+
bool splitSemanticTypes;
351+
352+
public:
353+
SILSROA(bool splitSemanticTypes) : splitSemanticTypes(splitSemanticTypes) { }
354+
335355
/// The entry point to the transformation.
336356
void run() override {
337357
SILFunction *F = getFunction();
@@ -343,7 +363,7 @@ class SILSROA : public SILFunctionTransform {
343363
LLVM_DEBUG(llvm::dbgs() << "***** SROA on function: " << F->getName()
344364
<< " *****\n");
345365

346-
if (runSROAOnFunction(*F))
366+
if (runSROAOnFunction(*F, splitSemanticTypes))
347367
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
348368
}
349369

@@ -352,5 +372,9 @@ class SILSROA : public SILFunctionTransform {
352372

353373

354374
SILTransform *swift::createSROA() {
355-
return new SILSROA();
375+
return new SILSROA(/*splitSemanticTypes*/ true);
376+
}
377+
378+
SILTransform *swift::createEarlySROA() {
379+
return new SILSROA(/*splitSemanticTypes*/ false);
356380
}

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)