Skip to content

Commit 44d211f

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 9f09add commit 44d211f

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;
@@ -207,92 +209,6 @@ SILCombiner::optimizeApplyOfConvertFunctionInst(FullApplySite AI,
207209
return NAI;
208210
}
209211

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

396-
// Replace the projected address.
397-
valueAddr->replaceAllUsesWith(projectedAddr);
398-
399-
Builder.setInsertionPoint(endApply);
400-
insertEndAccess(beginAccess, isModify, Builder);
401326
eraseInstFromFunction(*endApply);
402327
eraseInstFromFunction(*AI);
403328
++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)