Skip to content

Commit e1ceb4f

Browse files
committed
[SILOptimizer] Generalize optimization of static keypaths
We have an optimization in SILCombiner that "inlines" the use of compile-time constant key paths by performing the property access directly instead of calling a runtime function (leading to huge performance gains e.g. for heavy use of @dynamicMemberLookup). However, this optimization previously only supported key paths which solely access stored properties, so computed properties, optional chaining, etc. still had to call a runtime function. This commit generalizes the optimization to support all types of key paths.
1 parent 0c2208e commit e1ceb4f

File tree

5 files changed

+1033
-112
lines changed

5 files changed

+1033
-112
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//===-- KeyPathProjector.h - Project a static key path ----------*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
///
13+
/// \file
14+
/// Utility class to project a statically known key path
15+
/// expression to a direct property access sequence.
16+
///
17+
//===----------------------------------------------------------------------===//
18+
19+
#ifndef SWIFT_SILOPTIMIZER_UTILS_KEYPATHPROJECTOR_H
20+
#define SWIFT_SILOPTIMIZER_UTILS_KEYPATHPROJECTOR_H
21+
22+
#include "swift/SIL/SILBuilder.h"
23+
#include <memory>
24+
25+
namespace swift {
26+
27+
/// Projects a statically known key path expression to
28+
/// a direct property access.
29+
class KeyPathProjector {
30+
public:
31+
/// The type of a key path access.
32+
enum class AccessType {
33+
/// A get-only access (i.e. swift_getAtKeyPath).
34+
Get,
35+
36+
/// A set-only access (i.e. swift_setAtWritableKeyPath).
37+
Set,
38+
39+
/// A modification (i.e. swift_modifyAtWritableKeyPath).
40+
Modify
41+
};
42+
43+
/// Creates a key path projector for a key path.
44+
///
45+
/// Returns nullptr if \p keyPath is not a keypath instruction or if there is
46+
/// any other reason why the optimization cannot be done.
47+
///
48+
/// \param keyPath The key path to project. Must be the result of either
49+
/// a keypath instruction or an upcast of a key path instruction.
50+
/// \param root The address of the object the key path is applied to.
51+
/// \param loc The location of the key path application.
52+
/// \param builder The SILBuilder to use.
53+
static std::unique_ptr<KeyPathProjector>
54+
create(SILValue keyPath, SILValue root, SILLocation loc, SILBuilder &builder);
55+
56+
/// Projects the key path to an address. Sets up the projection,
57+
/// invokes the callback, then tears down the projection.
58+
/// \param accessType The access type of the projected address.
59+
/// \param callback A callback to invoke with the projected adddress.
60+
/// The projected address is only valid from within \p callback.
61+
virtual void project(AccessType accessType,
62+
std::function<void(SILValue addr)> callback) = 0;
63+
64+
virtual ~KeyPathProjector() {};
65+
66+
/// Whether this projection returns a struct.
67+
virtual bool isStruct() = 0;
68+
protected:
69+
KeyPathProjector(SILLocation loc, SILBuilder &builder)
70+
: loc(loc), builder(builder) {}
71+
72+
/// The location of the key path application.
73+
SILLocation loc;
74+
75+
/// The SILBuilder to use.
76+
SILBuilder &builder;
77+
};
78+
79+
} // end namespace swift
80+
81+
#endif /* KeyPathProjector_h */

lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp

Lines changed: 37 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@
2828
#include "swift/SILOptimizer/Analysis/ValueTracking.h"
2929
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
3030
#include "swift/SILOptimizer/Utils/Existential.h"
31+
#include "swift/SILOptimizer/Utils/KeyPathProjector.h"
3132
#include "swift/SILOptimizer/Utils/ValueLifetime.h"
3233
#include "llvm/ADT/DenseMap.h"
3334
#include "llvm/ADT/SmallPtrSet.h"
3435
#include "llvm/ADT/SmallVector.h"
3536
#include "llvm/ADT/Statistic.h"
37+
#include <utility>
3638

3739
using namespace swift;
3840
using namespace swift::PatternMatch;
@@ -199,92 +201,6 @@ SILCombiner::optimizeApplyOfConvertFunctionInst(FullApplySite AI,
199201
return NAI;
200202
}
201203

202-
/// Ends the begin_access "scope" if a begin_access was inserted for optimizing
203-
/// a keypath pattern.
204-
static void insertEndAccess(BeginAccessInst *&beginAccess, bool isModify,
205-
SILBuilder &builder) {
206-
if (beginAccess) {
207-
builder.createEndAccess(beginAccess->getLoc(), beginAccess,
208-
/*aborted*/ false);
209-
if (isModify)
210-
beginAccess->setAccessKind(SILAccessKind::Modify);
211-
beginAccess = nullptr;
212-
}
213-
}
214-
215-
/// Creates the projection pattern for a keypath instruction.
216-
///
217-
/// Currently only the StoredProperty pattern is handled.
218-
/// TODO: handle other patterns, like getters/setters, optional chaining, etc.
219-
///
220-
/// Returns false if \p keyPath is not a keypath instruction or if there is any
221-
/// other reason why the optimization cannot be done.
222-
static SILValue createKeypathProjections(SILValue keyPath, SILValue root,
223-
SILLocation loc,
224-
BeginAccessInst *&beginAccess,
225-
SILBuilder &builder) {
226-
if (auto *upCast = dyn_cast<UpcastInst>(keyPath))
227-
keyPath = upCast->getOperand();
228-
229-
// Is it a keypath instruction at all?
230-
auto *kpInst = dyn_cast<KeyPathInst>(keyPath);
231-
if (!kpInst || !kpInst->hasPattern())
232-
return SILValue();
233-
234-
auto components = kpInst->getPattern()->getComponents();
235-
236-
// Check if the keypath only contains patterns which we support.
237-
for (const KeyPathPatternComponent &comp : components) {
238-
if (comp.getKind() != KeyPathPatternComponent::Kind::StoredProperty)
239-
return SILValue();
240-
}
241-
242-
SILValue addr = root;
243-
for (const KeyPathPatternComponent &comp : components) {
244-
assert(comp.getKind() == KeyPathPatternComponent::Kind::StoredProperty);
245-
VarDecl *storedProperty = comp.getStoredPropertyDecl();
246-
SILValue elementAddr;
247-
if (addr->getType().getStructOrBoundGenericStruct()) {
248-
addr = builder.createStructElementAddr(loc, addr, storedProperty);
249-
} else if (addr->getType().getClassOrBoundGenericClass()) {
250-
SingleValueInstruction *Ref = builder.createLoad(loc, addr,
251-
LoadOwnershipQualifier::Unqualified);
252-
insertEndAccess(beginAccess, /*isModify*/ false, builder);
253-
254-
// Handle the case where the storedProperty is in a super class.
255-
while (Ref->getType().getClassOrBoundGenericClass() !=
256-
storedProperty->getDeclContext()) {
257-
SILType superCl = Ref->getType().getSuperclass();
258-
if (!superCl) {
259-
// This should never happen, because the property should be in the
260-
// decl or in a superclass of it. Just handle this to be on the safe
261-
// side.
262-
return SILValue();
263-
}
264-
Ref = builder.createUpcast(loc, Ref, superCl);
265-
}
266-
267-
addr = builder.createRefElementAddr(loc, Ref, storedProperty);
268-
269-
// Class members need access enforcement.
270-
if (builder.getModule().getOptions().EnforceExclusivityDynamic) {
271-
beginAccess = builder.createBeginAccess(loc, addr, SILAccessKind::Read,
272-
SILAccessEnforcement::Dynamic,
273-
/*noNestedConflict*/ false,
274-
/*fromBuiltin*/ false);
275-
addr = beginAccess;
276-
}
277-
} else {
278-
// This should never happen, as a stored-property pattern can only be
279-
// applied to classes and structs. But to be safe - and future prove -
280-
// let's handle this case and bail.
281-
insertEndAccess(beginAccess, /*isModify*/ false, builder);
282-
return SILValue();
283-
}
284-
}
285-
return addr;
286-
}
287-
288204
/// Try to optimize a keypath application with an apply instruction.
289205
///
290206
/// Replaces (simplified SIL):
@@ -317,22 +233,26 @@ bool SILCombiner::tryOptimizeKeypath(ApplyInst *AI) {
317233
} else {
318234
return false;
319235
}
320-
321-
BeginAccessInst *beginAccess = nullptr;
322-
SILValue projectedAddr = createKeypathProjections(keyPath, rootAddr,
323-
AI->getLoc(), beginAccess,
324-
Builder);
325-
if (!projectedAddr)
236+
237+
auto projector = KeyPathProjector::create(keyPath, rootAddr,
238+
AI->getLoc(), Builder);
239+
if (!projector)
326240
return false;
327-
328-
if (isModify) {
329-
Builder.createCopyAddr(AI->getLoc(), valueAddr, projectedAddr,
330-
IsTake, IsNotInitialization);
331-
} else {
332-
Builder.createCopyAddr(AI->getLoc(), projectedAddr, valueAddr,
333-
IsNotTake, IsInitialization);
334-
}
335-
insertEndAccess(beginAccess, isModify, Builder);
241+
242+
KeyPathProjector::AccessType accessType;
243+
if (isModify) accessType = KeyPathProjector::AccessType::Set;
244+
else accessType = KeyPathProjector::AccessType::Get;
245+
246+
projector->project(accessType, [&](SILValue projectedAddr) {
247+
if (isModify) {
248+
Builder.createCopyAddr(AI->getLoc(), valueAddr, projectedAddr,
249+
IsTake, IsNotInitialization);
250+
} else {
251+
Builder.createCopyAddr(AI->getLoc(), projectedAddr, valueAddr,
252+
IsNotTake, IsInitialization);
253+
}
254+
});
255+
336256
eraseInstFromFunction(*AI);
337257
++NumOptimizedKeypaths;
338258
return true;
@@ -377,19 +297,24 @@ bool SILCombiner::tryOptimizeInoutKeypath(BeginApplyInst *AI) {
377297
EndApplyInst *endApply = dyn_cast<EndApplyInst>(AIUse->getUser());
378298
if (!endApply)
379299
return false;
380-
381-
BeginAccessInst *beginAccess = nullptr;
382-
SILValue projectedAddr = createKeypathProjections(keyPath, rootAddr,
383-
AI->getLoc(), beginAccess,
384-
Builder);
385-
if (!projectedAddr)
300+
301+
auto projector = KeyPathProjector::create(keyPath, rootAddr,
302+
AI->getLoc(), Builder);
303+
if (!projector)
386304
return false;
305+
306+
KeyPathProjector::AccessType accessType;
307+
if (isModify) accessType = KeyPathProjector::AccessType::Modify;
308+
else accessType = KeyPathProjector::AccessType::Get;
309+
310+
projector->project(accessType, [&](SILValue projectedAddr) {
311+
// Replace the projected address.
312+
valueAddr->replaceAllUsesWith(projectedAddr);
313+
314+
// Skip to the end of the key path application before cleaning up.
315+
Builder.setInsertionPoint(endApply);
316+
});
387317

388-
// Replace the projected address.
389-
valueAddr->replaceAllUsesWith(projectedAddr);
390-
391-
Builder.setInsertionPoint(endApply);
392-
insertEndAccess(beginAccess, isModify, Builder);
393318
eraseInstFromFunction(*endApply);
394319
eraseInstFromFunction(*AI);
395320
++NumOptimizedKeypaths;

lib/SILOptimizer/Utils/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ silopt_register_sources(
1111
GenericCloner.cpp
1212
Generics.cpp
1313
InstOptUtils.cpp
14+
KeyPathProjector.cpp
1415
LoadStoreOptUtils.cpp
1516
LoopUtils.cpp
1617
OptimizerStatsUtils.cpp

0 commit comments

Comments
 (0)