Skip to content

[DNM] compute the field index with a request instead of caching it in the instruction #37831

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4788,8 +4788,12 @@ enum class PropertyWrapperSynthesizedPropertyKind {
/// VarDecl - 'var' and 'let' declarations.
class VarDecl : public AbstractStorageDecl {
friend class NamingPatternRequest;
friend class GetFieldIndexRequest;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetFieldIndexRequest doesn't appear to be defined

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch. I'll remove it

friend class ComputeFieldIndicesRequest;
NamedPattern *NamingPattern = nullptr;

int fieldIndex = -1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unfortunate that we'd still need to cache index in the decl but it would be expensive to compute indexes with request for an individual property since there could be quite a few stored properties...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This used to be cached as an unsigned, are there any issues here in halving the range of values it can store?

It might also be worth moving into the inline bitfield for VarDecl, though that would mean you'd likely have to add a private getter and setter for the cached value so the request can access it.


public:
enum class Introducer : uint8_t {
Let = 0,
Expand Down Expand Up @@ -4821,6 +4825,8 @@ class VarDecl : public AbstractStorageDecl {
return hasName() ? getBaseIdentifier().str() : "_";
}

int getFieldIndex() const;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this should probably have a slightly scarier name to convey it might entail a non-trivial amount of work (such as synthesising backing storage vars) – something like getABIStoredPropertyIndex perhaps? Or even just getABIFieldIndex? I'd be interested to hear what others think.


/// Get the type of the variable within its context. If the context is generic,
/// this will use archetypes.
Type getType() const;
Expand Down
14 changes: 14 additions & 0 deletions include/swift/AST/SILOptimizerRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,20 @@ class LoweredSILRequest
ASTLoweringDescriptor desc) const;
};

class ComputeFieldIndicesRequest :
public SimpleRequest<ComputeFieldIndicesRequest,
std::tuple<>(NominalTypeDecl *),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this has to be void?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC the evaluator doesn't currently support void return types, we usually use evaluator::SideEffect for that (which is an alias for std::tuple<>)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah interesting, since the definition accepted void I got confused...

Copy link
Contributor

@hamishknight hamishknight Jun 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I believe the signature written in the SWIFT_REQUEST macro isn't actually used for anything, so isn't currently checked for consistency 😬

RequestFlags::Uncached> {
public:
using SimpleRequest::SimpleRequest;

private:
friend SimpleRequest;

// Evaluation.
std::tuple<> evaluate(Evaluator &evaluator, NominalTypeDecl *decl) const;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here as well?

};

/// Report that a request of the given kind is being evaluated, so it
/// can be recorded by the stats reporter.
template <typename Request>
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/SILOptimizerTypeIDZone.def
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ SWIFT_REQUEST(SILOptimizer, ExecuteSILPipelineRequest,
SWIFT_REQUEST(SILOptimizer, LoweredSILRequest,
std::unique_ptr<SILModule>(ASTLoweringDescriptor),
Uncached, NoLocationInfo)
SWIFT_REQUEST(SILOptimizer, ComputeFieldIndicesRequest,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, ideally we'd have a SIL request evaluator zone for this. I think for now at least it might be better off placed in the TypeChecker zone, as it shouldn't depend on anything outside of Sema.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. I can move it to the type checker

void(NominalTypeDecl *),
Cached, NoLocationInfo)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can make it Uncached since it doesn't return anything?

108 changes: 31 additions & 77 deletions include/swift/SIL/SILInstruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -6265,89 +6265,29 @@ unsigned getFieldIndex(NominalTypeDecl *decl, VarDecl *property);
/// Precondition: \p decl must be a non-resilient struct or class.
VarDecl *getIndexedField(NominalTypeDecl *decl, unsigned index);

/// A common base for instructions that require a cached field index.
///
/// "Field" is a term used here to refer to the ordered, accessible stored
/// properties of a class or struct.
///
/// The field's ordinal value is the basis of efficiently comparing and sorting
/// access paths in SIL. For example, whenever a Projection object is created,
/// it stores the field index. Finding the field index initially requires
/// searching the type declaration's array of all stored properties. If this
/// index is not cached, it will cause widespread quadratic complexity in any
/// pass that queries projections, including the SIL verifier.
///
/// FIXME: This cache may not be necessary if the Decl TypeChecker instead
/// caches a field index in the VarDecl itself. This solution would be superior
/// because it would allow constant time lookup of either the VarDecl or the
/// index from a single pointer without referring back to a projection
/// instruction.
template <typename ParentTy>
class FieldIndexCacheBase : public ParentTy {
enum : unsigned { InvalidFieldIndex = ~unsigned(0) };

VarDecl *field;

public:
template <typename... ArgTys>
FieldIndexCacheBase(SILInstructionKind kind, SILDebugLocation loc,
SILType type, VarDecl *field, ArgTys &&... extraArgs)
: ParentTy(kind, loc, type, std::forward<ArgTys>(extraArgs)...),
field(field) {
SILNode::Bits.FieldIndexCacheBase.FieldIndex = InvalidFieldIndex;
// This needs to be a concrete class to hold bitfield information. However,
// it should only be extended by UnaryInstructions.
assert(ParentTy::getNumOperands() == 1);
}

VarDecl *getField() const { return field; }

unsigned getFieldIndex() const {
unsigned idx = SILNode::Bits.FieldIndexCacheBase.FieldIndex;
if (idx != InvalidFieldIndex)
return idx;

return const_cast<FieldIndexCacheBase *>(this)->cacheFieldIndex();
}

NominalTypeDecl *getParentDecl() const {
auto s =
ParentTy::getOperand(0)->getType().getNominalOrBoundGenericNominal();
assert(s);
return s;
}

static bool classof(SILNodePointer node) {
SILNodeKind kind = node->getKind();
return kind == SILNodeKind::StructExtractInst ||
kind == SILNodeKind::StructElementAddrInst ||
kind == SILNodeKind::RefElementAddrInst;
}

private:
unsigned cacheFieldIndex() {
unsigned index = swift::getFieldIndex(getParentDecl(), getField());
SILNode::Bits.FieldIndexCacheBase.FieldIndex = index;
return index;
}
};

/// Extract a physical, fragile field out of a value of struct type.
class StructExtractInst
: public UnaryInstructionBase<
SILInstructionKind::StructExtractInst,
FieldIndexCacheBase<GuaranteedFirstArgForwardingSingleValueInst>> {
GuaranteedFirstArgForwardingSingleValueInst> {
friend SILBuilder;

VarDecl *field;

StructExtractInst(SILDebugLocation DebugLoc, SILValue Operand, VarDecl *Field,
SILType ResultTy,
ValueOwnershipKind forwardingOwnershipKind)
: UnaryInstructionBase(DebugLoc, Operand, ResultTy, Field,
forwardingOwnershipKind) {}
: UnaryInstructionBase(DebugLoc, Operand, ResultTy,
forwardingOwnershipKind),
field(Field) {}

public:
VarDecl *getField() const { return field; }

unsigned getFieldIndex() const { return getField()->getFieldIndex(); }

StructDecl *getStructDecl() const {
return cast<StructDecl>(getParentDecl());
return getOperand()->getType().getStructOrBoundGenericStruct();
}

/// Returns true if this is a trivial result of a struct that is non-trivial
Expand All @@ -6363,34 +6303,48 @@ class StructExtractInst
/// Derive the address of a physical field from the address of a struct.
class StructElementAddrInst
: public UnaryInstructionBase<SILInstructionKind::StructElementAddrInst,
FieldIndexCacheBase<SingleValueInstruction>> {
SingleValueInstruction> {
friend SILBuilder;

VarDecl *field;

StructElementAddrInst(SILDebugLocation DebugLoc, SILValue Operand,
VarDecl *Field, SILType ResultTy)
: UnaryInstructionBase(DebugLoc, Operand, ResultTy, Field) {}
: UnaryInstructionBase(DebugLoc, Operand, ResultTy), field(Field) {}

public:
VarDecl *getField() const { return field; }

unsigned getFieldIndex() const { return getField()->getFieldIndex(); }

StructDecl *getStructDecl() const {
return cast<StructDecl>(getParentDecl());
return getOperand()->getType().getStructOrBoundGenericStruct();
}
};

/// RefElementAddrInst - Derive the address of a named element in a reference
/// type instance.
class RefElementAddrInst
: public UnaryInstructionBase<SILInstructionKind::RefElementAddrInst,
FieldIndexCacheBase<SingleValueInstruction>> {
SingleValueInstruction> {
friend SILBuilder;

VarDecl *field;

RefElementAddrInst(SILDebugLocation DebugLoc, SILValue Operand,
VarDecl *Field, SILType ResultTy, bool IsImmutable)
: UnaryInstructionBase(DebugLoc, Operand, ResultTy, Field) {
: UnaryInstructionBase(DebugLoc, Operand, ResultTy), field(Field) {
setImmutable(IsImmutable);
}

public:
ClassDecl *getClassDecl() const { return cast<ClassDecl>(getParentDecl()); }
VarDecl *getField() const { return field; }

unsigned getFieldIndex() const { return getField()->getFieldIndex(); }

ClassDecl *getClassDecl() const {
return getOperand()->getType().getClassOrBoundGenericClass();
}

/// Returns true if all loads of the same instance variable from the same
/// class reference operand are guaranteed to yield the same value.
Expand Down
6 changes: 0 additions & 6 deletions include/swift/SIL/SILNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -348,12 +348,6 @@ class alignas(8) SILNode {
KeepUnique : 1
);

SWIFT_INLINE_BITFIELD_FULL_TEMPLATE(FieldIndexCacheBase,
SingleValueInstruction, 32,
: NumPadBits,
FieldIndex : 32
);

SWIFT_INLINE_BITFIELD_EMPTY(MethodInst, SingleValueInstruction);
// Ensure that WitnessMethodInst bitfield does not overflow.
IBWTO_BITFIELD_EMPTY(WitnessMethodInst, MethodInst);
Expand Down
11 changes: 11 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "swift/AST/Module.h"
#include "swift/AST/NameLookup.h"
#include "swift/AST/NameLookupRequests.h"
#include "swift/AST/SILOptimizerRequests.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/ParseRequests.h"
#include "swift/AST/Pattern.h"
Expand Down Expand Up @@ -5518,6 +5519,16 @@ VarDecl::VarDecl(DeclKind kind, bool isStatic, VarDecl::Introducer introducer,
Bits.VarDecl.IsTopLevelGlobal = false;
}

int VarDecl::getFieldIndex() const {
if (fieldIndex < 0) {
NominalTypeDecl *parent = getDeclContext()->getSelfNominalTypeDecl();
evaluateOrDefault(getASTContext().evaluator,
ComputeFieldIndicesRequest{parent}, {});
assert(fieldIndex >= 0);
}
Comment on lines +5523 to +5528
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We generally want to try and avoid having side-effecting requests like this. Ideally ComputeFieldIndicesRequest would be a SeparatelyCached cached request, and get and set the cached value in its getCachedResult/cacheResult methods.

You could also fill in the other field indices for the other properties as you iterate by calling cacheOutput on the evaluator for those properties, similar to what DefaultArgumentInitContextRequest does.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, would a better approach here be to remove field from the decl and just store everything in a cached DenseMap instead? That would use more memory but at least wouldn't have any side-effects.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's going into a DenseMap then that would basically be more or less equivalent to just using the evaluator's internal cache, which we could possibly do here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I could also add a DenseMap for this in SILModule without using the evaluator machinery. But the field indices are something which are used in a critical path in the optimizer (for computing projections). So the purpose of this exercise is to avoid the overhead of a hash lookup.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, the index of all stored properties should be computed and cached for a type during a single request. This should result in a unique index for each property in an object. This is something that we can't currently rely on but would greatly simplify analysis because the index would have meaning independent of the address type.
@eeckstein is right that the index should be directly loadable from an instruction without any hash lookup. That was the point of the instruction cache, although I'm happy to see it go.

return fieldIndex;
}

Type VarDecl::getType() const {
return getDeclContext()->mapTypeIntoContext(getInterfaceType());
}
Expand Down
17 changes: 1 addition & 16 deletions lib/SIL/IR/SILInstructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1293,22 +1293,7 @@ bool TupleExtractInst::isEltOnlyNonTrivialElt() const {

/// Get a unique index for a struct or class field in layout order.
unsigned swift::getFieldIndex(NominalTypeDecl *decl, VarDecl *field) {
unsigned index = 0;
if (auto *classDecl = dyn_cast<ClassDecl>(decl)) {
for (auto *superDecl = classDecl->getSuperclassDecl(); superDecl != nullptr;
superDecl = superDecl->getSuperclassDecl()) {
index += superDecl->getStoredProperties().size();
}
}
for (VarDecl *property : decl->getStoredProperties()) {
if (field == property) {
return index;
}
++index;
}
llvm_unreachable("The field decl for a struct_extract, struct_element_addr, "
"or ref_element_addr must be an accessible stored "
"property of the operand type");
return field->getFieldIndex();
}

/// Get the property for a struct or class by its unique index.
Expand Down
7 changes: 6 additions & 1 deletion lib/SIL/Verifier/SILVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2985,6 +2985,8 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
requireSameType(loweredFieldTy, EI->getType(),
"result of struct_extract does not match type of field");
}
require(EI->getFieldIndex() < sd->getStoredProperties().size(),
"invalid field index for struct_extract instruction");
}

void checkTupleElementAddrInst(TupleElementAddrInst *EI) {
Expand Down Expand Up @@ -3033,6 +3035,8 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
loweredFieldTy, EI->getType(),
"result of struct_element_addr does not match type of field");
}
require(EI->getFieldIndex() < sd->getStoredProperties().size(),
"invalid field index for struct_element_addr instruction");
}

void checkRefElementAddrInst(RefElementAddrInst *EI) {
Expand Down Expand Up @@ -3060,7 +3064,8 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
loweredFieldTy, EI->getType(),
"result of ref_element_addr does not match type of field");
}
EI->getFieldIndex(); // Make sure we can access the field without crashing.
require(EI->getFieldIndex() < cd->getStoredProperties().size(),
"invalid field index for ref_element_addr instruction");
}

void checkRefTailAddrInst(RefTailAddrInst *RTAI) {
Expand Down
16 changes: 16 additions & 0 deletions lib/SILOptimizer/PassManager/SILOptimizerRequests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,22 @@ LoweredSILRequest::evaluate(Evaluator &evaluator,
return silMod;
}

std::tuple<>
ComputeFieldIndicesRequest::evaluate(Evaluator &evaluator, NominalTypeDecl *decl) const {
unsigned index = 0;
if (auto *classDecl = dyn_cast<ClassDecl>(decl)) {
for (auto *superDecl = classDecl->getSuperclassDecl(); superDecl != nullptr;
superDecl = superDecl->getSuperclassDecl()) {
index += superDecl->getStoredProperties().size();
}
}
for (VarDecl *property : decl->getStoredProperties()) {
assert(property->fieldIndex < 0);
property->fieldIndex = index++;
}
return {};
}

// Define request evaluation functions for each of the SILGen requests.
static AbstractRequestFunction *silOptimizerRequestFunctions[] = {
#define SWIFT_REQUEST(Zone, Name, Sig, Caching, LocOptions) \
Expand Down