Skip to content

Commit dda544b

Browse files
authored
Merge pull request #24768 from atrick/5.1-cache-field-index
[5.1] Merge pull request #24717 from atrick/cache-field-number
2 parents 230f776 + d3eb2ae commit dda544b

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
@@ -144,6 +144,9 @@ FRONTEND_STATISTIC(AST, NumPrecedenceGroups)
144144
/// Number of conformances used by code processed by this frontend job.
145145
FRONTEND_STATISTIC(AST, NumUsedConformances)
146146

147+
/// Number of precedence groups in the AST context.
148+
FRONTEND_STATISTIC(AST, NumStoredPropertiesQueries)
149+
147150
/// Number of full function bodies parsed.
148151
FRONTEND_STATISTIC(Parse, NumFunctionsParsed)
149152

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
@@ -5535,39 +5535,74 @@ class TupleElementAddrInst
55355535
}
55365536
};
55375537

5538-
/// Extract a physical, fragile field out of a value of struct type.
5539-
class StructExtractInst
5540-
: public UnaryInstructionBase<SILInstructionKind::StructExtractInst,
5541-
SingleValueInstruction>
5542-
{
5543-
friend SILBuilder;
5544-
5545-
VarDecl *Field;
5538+
/// A common base for instructions that require a cached field index.
5539+
///
5540+
/// "Field" is a term used here to refer to the ordered, accessible stored
5541+
/// properties of a class or struct.
5542+
///
5543+
/// The field's ordinal value is the basis of efficiently comparing and sorting
5544+
/// access paths in SIL. For example, whenever a Projection object is created,
5545+
/// it stores the field index. Finding the field index initially requires
5546+
/// searching the type declaration's array of all stored properties. If this
5547+
/// index is not cached, it will cause widespread quadratic complexity in any
5548+
/// pass that queries projections, including the SIL verifier.
5549+
///
5550+
/// FIXME: This cache may not be necessary if the Decl TypeChecker instead
5551+
/// caches a field index in the VarDecl itself. This solution would be superior
5552+
/// because it would allow constant time lookup of either the VarDecl or the
5553+
/// index from a single pointer without referring back to a projection
5554+
/// instruction.
5555+
class FieldIndexCacheBase : public SingleValueInstruction {
5556+
enum : unsigned { InvalidFieldIndex = ~unsigned(0) };
55465557

5547-
StructExtractInst(SILDebugLocation DebugLoc, SILValue Operand,
5548-
VarDecl *Field, SILType ResultTy)
5549-
: UnaryInstructionBase(DebugLoc, Operand, ResultTy), Field(Field) {}
5558+
VarDecl *field;
55505559

55515560
public:
5552-
VarDecl *getField() const { return Field; }
5561+
FieldIndexCacheBase(SILInstructionKind kind, SILDebugLocation loc,
5562+
SILType type, VarDecl *field)
5563+
: SingleValueInstruction(kind, loc, type), field(field) {
5564+
SILInstruction::Bits.FieldIndexCacheBase.FieldIndex = InvalidFieldIndex;
5565+
// This needs to be a concrete class to hold bitfield information. However,
5566+
// it should only be extended by UnaryInstructions.
5567+
assert(getNumOperands() == 1);
5568+
}
55535569

5570+
VarDecl *getField() const { return field; }
5571+
5572+
// FIXME: this should be called getFieldIndex().
55545573
unsigned getFieldNo() const {
5555-
unsigned i = 0;
5556-
for (VarDecl *D : getStructDecl()->getStoredProperties()) {
5557-
if (Field == D)
5558-
return i;
5559-
++i;
5560-
}
5561-
llvm_unreachable("A struct_extract's structdecl has at least 1 field, the "
5562-
"field of the struct_extract.");
5574+
unsigned idx = SILInstruction::Bits.FieldIndexCacheBase.FieldIndex;
5575+
if (idx != InvalidFieldIndex)
5576+
return idx;
5577+
5578+
return const_cast<FieldIndexCacheBase *>(this)->cacheFieldIndex();
55635579
}
55645580

5565-
StructDecl *getStructDecl() const {
5566-
auto s = getOperand()->getType().getStructOrBoundGenericStruct();
5581+
NominalTypeDecl *getParentDecl() const {
5582+
auto s = getOperand(0)->getType().getNominalOrBoundGenericNominal();
55675583
assert(s);
55685584
return s;
55695585
}
55705586

5587+
private:
5588+
unsigned cacheFieldIndex();
5589+
};
5590+
5591+
/// Extract a physical, fragile field out of a value of struct type.
5592+
class StructExtractInst
5593+
: public UnaryInstructionBase<SILInstructionKind::StructExtractInst,
5594+
FieldIndexCacheBase> {
5595+
friend SILBuilder;
5596+
5597+
StructExtractInst(SILDebugLocation DebugLoc, SILValue Operand, VarDecl *Field,
5598+
SILType ResultTy)
5599+
: UnaryInstructionBase(DebugLoc, Operand, ResultTy, Field) {}
5600+
5601+
public:
5602+
StructDecl *getStructDecl() const {
5603+
return cast<StructDecl>(getParentDecl());
5604+
}
5605+
55715606
/// Returns true if this is a trivial result of a struct that is non-trivial
55725607
/// and represents one RCID.
55735608
bool isTrivialFieldOfOneRCIDStruct() const;
@@ -5580,71 +5615,33 @@ class StructExtractInst
55805615

55815616
/// Derive the address of a physical field from the address of a struct.
55825617
class StructElementAddrInst
5583-
: public UnaryInstructionBase<SILInstructionKind::StructElementAddrInst,
5584-
SingleValueInstruction>
5585-
{
5618+
: public UnaryInstructionBase<SILInstructionKind::StructElementAddrInst,
5619+
FieldIndexCacheBase> {
55865620
friend SILBuilder;
55875621

5588-
VarDecl *Field;
5589-
55905622
StructElementAddrInst(SILDebugLocation DebugLoc, SILValue Operand,
55915623
VarDecl *Field, SILType ResultTy)
5592-
: UnaryInstructionBase(DebugLoc, Operand, ResultTy), Field(Field) {}
5624+
: UnaryInstructionBase(DebugLoc, Operand, ResultTy, Field) {}
55935625

55945626
public:
5595-
VarDecl *getField() const { return Field; }
5596-
5597-
unsigned getFieldNo() const {
5598-
unsigned i = 0;
5599-
for (auto *D : getStructDecl()->getStoredProperties()) {
5600-
if (Field == D)
5601-
return i;
5602-
++i;
5603-
}
5604-
llvm_unreachable("A struct_element_addr's structdecl has at least 1 field, "
5605-
"the field of the struct_element_addr.");
5606-
}
5607-
56085627
StructDecl *getStructDecl() const {
5609-
auto s = getOperand()->getType().getStructOrBoundGenericStruct();
5610-
assert(s);
5611-
return s;
5628+
return cast<StructDecl>(getParentDecl());
56125629
}
56135630
};
56145631

56155632
/// RefElementAddrInst - Derive the address of a named element in a reference
56165633
/// type instance.
56175634
class RefElementAddrInst
5618-
: public UnaryInstructionBase<SILInstructionKind::RefElementAddrInst,
5619-
SingleValueInstruction>
5620-
{
5635+
: public UnaryInstructionBase<SILInstructionKind::RefElementAddrInst,
5636+
FieldIndexCacheBase> {
56215637
friend SILBuilder;
56225638

5623-
VarDecl *Field;
5624-
56255639
RefElementAddrInst(SILDebugLocation DebugLoc, SILValue Operand,
56265640
VarDecl *Field, SILType ResultTy)
5627-
: UnaryInstructionBase(DebugLoc, Operand, ResultTy), Field(Field) {}
5641+
: UnaryInstructionBase(DebugLoc, Operand, ResultTy, Field) {}
56285642

56295643
public:
5630-
VarDecl *getField() const { return Field; }
5631-
5632-
unsigned getFieldNo() const {
5633-
unsigned i = 0;
5634-
for (auto *D : getClassDecl()->getStoredProperties()) {
5635-
if (Field == D)
5636-
return i;
5637-
++i;
5638-
}
5639-
llvm_unreachable("A ref_element_addr's classdecl has at least 1 field, the "
5640-
"field of the ref_element_addr.");
5641-
}
5642-
5643-
ClassDecl *getClassDecl() const {
5644-
auto s = getOperand()->getType().getClassOrBoundGenericClass();
5645-
assert(s);
5646-
return s;
5647-
}
5644+
ClassDecl *getClassDecl() const { return cast<ClassDecl>(getParentDecl()); }
56485645
};
56495646

56505647
/// 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
@@ -300,6 +300,10 @@ class alignas(8) SILNode {
300300
FieldNo : 32
301301
);
302302

303+
SWIFT_INLINE_BITFIELD_FULL(FieldIndexCacheBase, SingleValueInstruction, 32,
304+
: NumPadBits,
305+
FieldIndex : 32);
306+
303307
SWIFT_INLINE_BITFIELD_EMPTY(MethodInst, SingleValueInstruction);
304308
// Ensure that WitnessMethodInst bitfield does not overflow.
305309
IBWTO_BITFIELD_EMPTY(WitnessMethodInst, MethodInst);

lib/AST/Decl.cpp

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

32923292
auto NominalTypeDecl::getStoredProperties(bool skipInaccessible) const
32933293
-> StoredPropertyRange {
3294+
// This should be called at most once per SIL instruction that accesses a
3295+
// VarDecl.
3296+
//
3297+
// FIXME: Once VarDecl itself caches its field index, it should be called at
3298+
// most once per finalized VarDecl.
3299+
if (getASTContext().Stats)
3300+
getASTContext().Stats->getFrontendCounters().NumStoredPropertiesQueries++;
3301+
32943302
// Clang-imported classes never have stored properties.
32953303
if (hasClangNode() && isa<ClassDecl>(this))
32963304
return StoredPropertyRange(DeclRange(nullptr, nullptr),

lib/SIL/SILInstructions.cpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,21 @@ bool TupleExtractInst::isEltOnlyNonTrivialElt() const {
979979
return true;
980980
}
981981

982+
unsigned FieldIndexCacheBase::cacheFieldIndex() {
983+
unsigned i = 0;
984+
for (VarDecl *property : getParentDecl()->getStoredProperties()) {
985+
if (field == property) {
986+
SILInstruction::Bits.FieldIndexCacheBase.FieldIndex = i;
987+
return i;
988+
}
989+
++i;
990+
}
991+
llvm_unreachable("The field decl for a struct_extract, struct_element_addr, "
992+
"or ref_element_addr must be an accessible stored property "
993+
"of the operand's type");
994+
}
995+
996+
// FIXME: this should be cached during cacheFieldIndex().
982997
bool StructExtractInst::isTrivialFieldOfOneRCIDStruct() const {
983998
auto *F = getFunction();
984999

@@ -1000,7 +1015,7 @@ bool StructExtractInst::isTrivialFieldOfOneRCIDStruct() const {
10001015
// For each element index of the tuple...
10011016
for (VarDecl *D : getStructDecl()->getStoredProperties()) {
10021017
// If the field is the one we are extracting, skip it...
1003-
if (Field == D)
1018+
if (getField() == D)
10041019
continue;
10051020

10061021
// Otherwise check if we have a non-trivial type. If we don't have one,
@@ -1029,6 +1044,8 @@ bool StructExtractInst::isTrivialFieldOfOneRCIDStruct() const {
10291044
/// Return true if we are extracting the only non-trivial field of out parent
10301045
/// struct. This implies that a ref count operation on the aggregate is
10311046
/// equivalent to a ref count operation on this field.
1047+
///
1048+
/// FIXME: this should be cached during cacheFieldIndex().
10321049
bool StructExtractInst::isFieldOnlyNonTrivialField() const {
10331050
auto *F = getFunction();
10341051

@@ -1042,7 +1059,7 @@ bool StructExtractInst::isFieldOnlyNonTrivialField() const {
10421059
// Ok, we are visiting a non-trivial field. Then for every stored field...
10431060
for (VarDecl *D : getStructDecl()->getStoredProperties()) {
10441061
// If we are visiting our own field continue.
1045-
if (Field == D)
1062+
if (getField() == D)
10461063
continue;
10471064

10481065
// 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)