Skip to content

Commit 22e5c55

Browse files
authored
Merge pull request #24717 from atrick/cache-field-number
Cache struct/class field offsets in SIL.
2 parents 0283e4d + 8f84429 commit 22e5c55

File tree

8 files changed

+125
-71
lines changed

8 files changed

+125
-71
lines changed

include/swift/Basic/Statistics.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ FRONTEND_STATISTIC(AST, NumPrefixOperators)
141141
/// Number of precedence groups in the AST context.
142142
FRONTEND_STATISTIC(AST, NumPrecedenceGroups)
143143

144+
/// Number of precedence groups in the AST context.
145+
FRONTEND_STATISTIC(AST, NumStoredPropertiesQueries)
146+
144147
/// Number of full function bodies parsed.
145148
FRONTEND_STATISTIC(Parse, NumFunctionsParsed)
146149

include/swift/SIL/Projection.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,9 @@ class Projection {
289289

290290
/// Apply this projection to \p BaseType and return the relevant subfield's
291291
/// SILType if BaseField has less subtypes than projection's offset.
292+
///
293+
/// WARNING: This is not a constant time operation because it is implemented
294+
/// in terms of getVarDecl, which requests all BaseType's stored properties.
292295
SILType getType(SILType BaseType, SILModule &M) const {
293296
assert(isValid());
294297
switch (getKind()) {
@@ -314,6 +317,8 @@ class Projection {
314317
llvm_unreachable("Unhandled ProjectionKind in switch.");
315318
}
316319

320+
/// WARNING: This is not a constant time operation because it requests all
321+
/// BaseType's stored properties.
317322
VarDecl *getVarDecl(SILType BaseType) const {
318323
assert(isValid());
319324
assert((getKind() == ProjectionKind::Struct ||

include/swift/SIL/SILInstruction.h

Lines changed: 65 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -5625,39 +5625,74 @@ class TupleElementAddrInst
56255625
}
56265626
};
56275627

5628-
/// Extract a physical, fragile field out of a value of struct type.
5629-
class StructExtractInst
5630-
: public UnaryInstructionBase<SILInstructionKind::StructExtractInst,
5631-
SingleValueInstruction>
5632-
{
5633-
friend SILBuilder;
5634-
5635-
VarDecl *Field;
5628+
/// A common base for instructions that require a cached field index.
5629+
///
5630+
/// "Field" is a term used here to refer to the ordered, accessible stored
5631+
/// properties of a class or struct.
5632+
///
5633+
/// The field's ordinal value is the basis of efficiently comparing and sorting
5634+
/// access paths in SIL. For example, whenever a Projection object is created,
5635+
/// it stores the field index. Finding the field index initially requires
5636+
/// searching the type declaration's array of all stored properties. If this
5637+
/// index is not cached, it will cause widespread quadratic complexity in any
5638+
/// pass that queries projections, including the SIL verifier.
5639+
///
5640+
/// FIXME: This cache may not be necessary if the Decl TypeChecker instead
5641+
/// caches a field index in the VarDecl itself. This solution would be superior
5642+
/// because it would allow constant time lookup of either the VarDecl or the
5643+
/// index from a single pointer without referring back to a projection
5644+
/// instruction.
5645+
class FieldIndexCacheBase : public SingleValueInstruction {
5646+
enum : unsigned { InvalidFieldIndex = ~unsigned(0) };
56365647

5637-
StructExtractInst(SILDebugLocation DebugLoc, SILValue Operand,
5638-
VarDecl *Field, SILType ResultTy)
5639-
: UnaryInstructionBase(DebugLoc, Operand, ResultTy), Field(Field) {}
5648+
VarDecl *field;
56405649

56415650
public:
5642-
VarDecl *getField() const { return Field; }
5651+
FieldIndexCacheBase(SILInstructionKind kind, SILDebugLocation loc,
5652+
SILType type, VarDecl *field)
5653+
: SingleValueInstruction(kind, loc, type), field(field) {
5654+
SILInstruction::Bits.FieldIndexCacheBase.FieldIndex = InvalidFieldIndex;
5655+
// This needs to be a concrete class to hold bitfield information. However,
5656+
// it should only be extended by UnaryInstructions.
5657+
assert(getNumOperands() == 1);
5658+
}
56435659

5660+
VarDecl *getField() const { return field; }
5661+
5662+
// FIXME: this should be called getFieldIndex().
56445663
unsigned getFieldNo() const {
5645-
unsigned i = 0;
5646-
for (VarDecl *D : getStructDecl()->getStoredProperties()) {
5647-
if (Field == D)
5648-
return i;
5649-
++i;
5650-
}
5651-
llvm_unreachable("A struct_extract's structdecl has at least 1 field, the "
5652-
"field of the struct_extract.");
5664+
unsigned idx = SILInstruction::Bits.FieldIndexCacheBase.FieldIndex;
5665+
if (idx != InvalidFieldIndex)
5666+
return idx;
5667+
5668+
return const_cast<FieldIndexCacheBase *>(this)->cacheFieldIndex();
56535669
}
56545670

5655-
StructDecl *getStructDecl() const {
5656-
auto s = getOperand()->getType().getStructOrBoundGenericStruct();
5671+
NominalTypeDecl *getParentDecl() const {
5672+
auto s = getOperand(0)->getType().getNominalOrBoundGenericNominal();
56575673
assert(s);
56585674
return s;
56595675
}
56605676

5677+
private:
5678+
unsigned cacheFieldIndex();
5679+
};
5680+
5681+
/// Extract a physical, fragile field out of a value of struct type.
5682+
class StructExtractInst
5683+
: public UnaryInstructionBase<SILInstructionKind::StructExtractInst,
5684+
FieldIndexCacheBase> {
5685+
friend SILBuilder;
5686+
5687+
StructExtractInst(SILDebugLocation DebugLoc, SILValue Operand, VarDecl *Field,
5688+
SILType ResultTy)
5689+
: UnaryInstructionBase(DebugLoc, Operand, ResultTy, Field) {}
5690+
5691+
public:
5692+
StructDecl *getStructDecl() const {
5693+
return cast<StructDecl>(getParentDecl());
5694+
}
5695+
56615696
/// Returns true if this is a trivial result of a struct that is non-trivial
56625697
/// and represents one RCID.
56635698
bool isTrivialFieldOfOneRCIDStruct() const;
@@ -5670,71 +5705,33 @@ class StructExtractInst
56705705

56715706
/// Derive the address of a physical field from the address of a struct.
56725707
class StructElementAddrInst
5673-
: public UnaryInstructionBase<SILInstructionKind::StructElementAddrInst,
5674-
SingleValueInstruction>
5675-
{
5708+
: public UnaryInstructionBase<SILInstructionKind::StructElementAddrInst,
5709+
FieldIndexCacheBase> {
56765710
friend SILBuilder;
56775711

5678-
VarDecl *Field;
5679-
56805712
StructElementAddrInst(SILDebugLocation DebugLoc, SILValue Operand,
56815713
VarDecl *Field, SILType ResultTy)
5682-
: UnaryInstructionBase(DebugLoc, Operand, ResultTy), Field(Field) {}
5714+
: UnaryInstructionBase(DebugLoc, Operand, ResultTy, Field) {}
56835715

56845716
public:
5685-
VarDecl *getField() const { return Field; }
5686-
5687-
unsigned getFieldNo() const {
5688-
unsigned i = 0;
5689-
for (auto *D : getStructDecl()->getStoredProperties()) {
5690-
if (Field == D)
5691-
return i;
5692-
++i;
5693-
}
5694-
llvm_unreachable("A struct_element_addr's structdecl has at least 1 field, "
5695-
"the field of the struct_element_addr.");
5696-
}
5697-
56985717
StructDecl *getStructDecl() const {
5699-
auto s = getOperand()->getType().getStructOrBoundGenericStruct();
5700-
assert(s);
5701-
return s;
5718+
return cast<StructDecl>(getParentDecl());
57025719
}
57035720
};
57045721

57055722
/// RefElementAddrInst - Derive the address of a named element in a reference
57065723
/// type instance.
57075724
class RefElementAddrInst
5708-
: public UnaryInstructionBase<SILInstructionKind::RefElementAddrInst,
5709-
SingleValueInstruction>
5710-
{
5725+
: public UnaryInstructionBase<SILInstructionKind::RefElementAddrInst,
5726+
FieldIndexCacheBase> {
57115727
friend SILBuilder;
57125728

5713-
VarDecl *Field;
5714-
57155729
RefElementAddrInst(SILDebugLocation DebugLoc, SILValue Operand,
57165730
VarDecl *Field, SILType ResultTy)
5717-
: UnaryInstructionBase(DebugLoc, Operand, ResultTy), Field(Field) {}
5731+
: UnaryInstructionBase(DebugLoc, Operand, ResultTy, Field) {}
57185732

57195733
public:
5720-
VarDecl *getField() const { return Field; }
5721-
5722-
unsigned getFieldNo() const {
5723-
unsigned i = 0;
5724-
for (auto *D : getClassDecl()->getStoredProperties()) {
5725-
if (Field == D)
5726-
return i;
5727-
++i;
5728-
}
5729-
llvm_unreachable("A ref_element_addr's classdecl has at least 1 field, the "
5730-
"field of the ref_element_addr.");
5731-
}
5732-
5733-
ClassDecl *getClassDecl() const {
5734-
auto s = getOperand()->getType().getClassOrBoundGenericClass();
5735-
assert(s);
5736-
return s;
5737-
}
5734+
ClassDecl *getClassDecl() const { return cast<ClassDecl>(getParentDecl()); }
57385735
};
57395736

57405737
/// RefTailAddrInst - Derive the address of the first element of the first

include/swift/SIL/SILNode.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,10 @@ class alignas(8) SILNode {
304304
FieldNo : 32
305305
);
306306

307+
SWIFT_INLINE_BITFIELD_FULL(FieldIndexCacheBase, SingleValueInstruction, 32,
308+
: NumPadBits,
309+
FieldIndex : 32);
310+
307311
SWIFT_INLINE_BITFIELD_EMPTY(MethodInst, SingleValueInstruction);
308312
// Ensure that WitnessMethodInst bitfield does not overflow.
309313
IBWTO_BITFIELD_EMPTY(WitnessMethodInst, MethodInst);

lib/AST/Decl.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3370,6 +3370,14 @@ void NominalTypeDecl::addExtension(ExtensionDecl *extension) {
33703370

33713371
auto NominalTypeDecl::getStoredProperties(bool skipInaccessible) const
33723372
-> StoredPropertyRange {
3373+
// This should be called at most once per SIL instruction that accesses a
3374+
// VarDecl.
3375+
//
3376+
// FIXME: Once VarDecl itself caches its field index, it should be called at
3377+
// most once per finalized VarDecl.
3378+
if (getASTContext().Stats)
3379+
getASTContext().Stats->getFrontendCounters().NumStoredPropertiesQueries++;
3380+
33733381
// Clang-imported classes never have stored properties.
33743382
if (hasClangNode() && isa<ClassDecl>(this))
33753383
return StoredPropertyRange(DeclRange(nullptr, nullptr),

lib/SIL/SILInstructions.cpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,21 @@ bool TupleExtractInst::isEltOnlyNonTrivialElt() const {
990990
return true;
991991
}
992992

993+
unsigned FieldIndexCacheBase::cacheFieldIndex() {
994+
unsigned i = 0;
995+
for (VarDecl *property : getParentDecl()->getStoredProperties()) {
996+
if (field == property) {
997+
SILInstruction::Bits.FieldIndexCacheBase.FieldIndex = i;
998+
return i;
999+
}
1000+
++i;
1001+
}
1002+
llvm_unreachable("The field decl for a struct_extract, struct_element_addr, "
1003+
"or ref_element_addr must be an accessible stored property "
1004+
"of the operand's type");
1005+
}
1006+
1007+
// FIXME: this should be cached during cacheFieldIndex().
9931008
bool StructExtractInst::isTrivialFieldOfOneRCIDStruct() const {
9941009
auto *F = getFunction();
9951010

@@ -1011,7 +1026,7 @@ bool StructExtractInst::isTrivialFieldOfOneRCIDStruct() const {
10111026
// For each element index of the tuple...
10121027
for (VarDecl *D : getStructDecl()->getStoredProperties()) {
10131028
// If the field is the one we are extracting, skip it...
1014-
if (Field == D)
1029+
if (getField() == D)
10151030
continue;
10161031

10171032
// Otherwise check if we have a non-trivial type. If we don't have one,
@@ -1040,6 +1055,8 @@ bool StructExtractInst::isTrivialFieldOfOneRCIDStruct() const {
10401055
/// Return true if we are extracting the only non-trivial field of out parent
10411056
/// struct. This implies that a ref count operation on the aggregate is
10421057
/// equivalent to a ref count operation on this field.
1058+
///
1059+
/// FIXME: this should be cached during cacheFieldIndex().
10431060
bool StructExtractInst::isFieldOnlyNonTrivialField() const {
10441061
auto *F = getFunction();
10451062

@@ -1053,7 +1070,7 @@ bool StructExtractInst::isFieldOnlyNonTrivialField() const {
10531070
// Ok, we are visiting a non-trivial field. Then for every stored field...
10541071
for (VarDecl *D : getStructDecl()->getStoredProperties()) {
10551072
// If we are visiting our own field continue.
1056-
if (Field == D)
1073+
if (getField() == D)
10571074
continue;
10581075

10591076
// Ok, we have a field that is not equal to the field we are

utils/scale-test

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,8 @@ def run_once_with_primary(args, ast, rng, primary_idx):
178178
else:
179179
r = merge_all_jobstats(load_stats_dir(d)).stats
180180
finally:
181-
shutil.rmtree(d)
181+
if not args.save_temps:
182+
shutil.rmtree(d)
182183

183184
return {k: v for (k, v) in r.items() if args.select in k}
184185

@@ -754,6 +755,9 @@ def main():
754755
parser.add_argument(
755756
'--tmpdir', type=str,
756757
default=None, help='directory to create tempfiles in')
758+
parser.add_argument(
759+
'--save-temps', action='store_true',
760+
default=False, help='save files in tempfiles')
757761
parser.add_argument(
758762
'--select',
759763
default="", help='substring of counters/symbols to limit attention to')
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// RUN: %scale-test -O --begin 5 --end 21 --step 5 --select NumStoredPropertiesQueries %s
2+
// REQUIRES: OS=macosx
3+
// REQUIRES: asserts
4+
//
5+
// Single file with many stored properties.
6+
//
7+
// Check that SIL passes don't exhibit cubic behavior by repeatedly
8+
// asking for all the stored properties (if the
9+
// NumStoredPropertiesQueries stat scales quadratically, then the
10+
// behavior is cubic).
11+
12+
public class LazyProperties {
13+
%for i in range(N):
14+
public lazy var prop${i}: String = { return "${i}" }()
15+
%end
16+
}

0 commit comments

Comments
 (0)