Skip to content

Commit 6befb10

Browse files
committed
Cache struct/class field offsets in SIL.
The field's ordinal value is used by the Projection abstraction, which is the basis of efficiently comparing and sorting access paths in SIL. It must be cached before it is used by any SIL passes, including the verifier, or it causes widespread quadratic complexity. Fixes <rdar://problem/50353228> Swift compile time regression with optimizations enabled In production code, a file that was taking 40 minutes to compile now takes 1 minute, with more than half of the time in LLVM. Here's a short script that reproduces the problem. It used to take 30s and now takes 0.06s: // swift genlazyinit.swift > lazyinit.sil // sil-opt ./lazyinit.sil --access-enforcement-opts var NumProperties = 300 print(""" sil_stage canonical import Builtin import Swift import SwiftShims public class LazyProperties { """) for i in 0..<NumProperties { print(""" // public lazy var i\(i): Int { get set } @_hasStorage @_hasInitialValue final var __lazy_storage__i\(i): Int? { get set } """) } print(""" } // LazyProperties.init() sil @$s4lazy14LazyPropertiesCACycfc : $@convention(method) (@owned LazyProperties) -> @owned LazyProperties { bb0(%0 : $LazyProperties): %enum = enum $Optional<Int>, #Optional.none!enumelt """) for i in 0..<NumProperties { let adr = (i*4) + 2 let access = adr + 1 print(""" %\(adr) = ref_element_addr %0 : $LazyProperties, #LazyProperties.__lazy_storage__i\(i) %\(access) = begin_access [modify] [dynamic] %\(adr) : $*Optional<Int> store %enum to %\(access) : $*Optional<Int> end_access %\(access) : $*Optional<Int> """) } print(""" return %0 : $LazyProperties } // end sil function '$s4lazy14LazyPropertiesCACycfc' """)
1 parent cfc2dda commit 6befb10

File tree

4 files changed

+93
-70
lines changed

4 files changed

+93
-70
lines changed

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

0 commit comments

Comments
 (0)