Skip to content

SIL: Lower fields that are conditionally addressable because of a dependency. #80038

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

Merged
merged 1 commit into from
Mar 18, 2025
Merged
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
47 changes: 40 additions & 7 deletions include/swift/AST/LifetimeDependence.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,25 @@ enum class ParsedLifetimeDependenceKind : uint8_t {
enum class LifetimeDependenceKind : uint8_t { Inherit = 0, Scope };

struct LifetimeDescriptor {
enum IsAddressable_t {
IsNotAddressable,
IsConditionallyAddressable,
IsAddressable,
};

union Value {
struct {
Identifier name;
} Named;
struct {
unsigned index;
bool isAddress;
IsAddressable_t isAddress;
} Ordered;
struct {
} Self;
Value(Identifier name) : Named({name}) {}
Value(unsigned index, bool isAddress) : Ordered({index, isAddress}) {}
Value(unsigned index, IsAddressable_t isAddress)
: Ordered({index, isAddress}) {}
Value() : Self() {}
} value;

Expand All @@ -72,7 +79,7 @@ struct LifetimeDescriptor {
SourceLoc loc)
: value{name}, kind(DescriptorKind::Named),
parsedLifetimeDependenceKind(parsedLifetimeDependenceKind), loc(loc) {}
LifetimeDescriptor(unsigned index, bool isAddress,
LifetimeDescriptor(unsigned index, IsAddressable_t isAddress,
ParsedLifetimeDependenceKind parsedLifetimeDependenceKind,
SourceLoc loc)
: value{index, isAddress}, kind(DescriptorKind::Ordered),
Expand All @@ -93,7 +100,7 @@ struct LifetimeDescriptor {
forOrdered(unsigned index,
ParsedLifetimeDependenceKind parsedLifetimeDependenceKind,
SourceLoc loc,
bool isAddress = false) {
IsAddressable_t isAddress = IsNotAddressable) {
return {index, isAddress, parsedLifetimeDependenceKind, loc};
}
static LifetimeDescriptor
Expand All @@ -116,10 +123,10 @@ struct LifetimeDescriptor {
return value.Ordered.index;
}

bool isAddressable() const {
IsAddressable_t isAddressable() const {
return kind == DescriptorKind::Ordered
? value.Ordered.isAddress
: false;
: IsNotAddressable;
}

DescriptorKind getDescriptorKind() const { return kind; }
Expand Down Expand Up @@ -216,6 +223,8 @@ class LifetimeDependenceInfo {
IndexSubset *scopeLifetimeParamIndices;
llvm::PointerIntPair<IndexSubset *, 1, bool>
addressableParamIndicesAndImmortal;
IndexSubset *conditionallyAddressableParamIndices;

unsigned targetIndex;

static LifetimeDependenceInfo getForIndex(AbstractFunctionDecl *afd,
Expand Down Expand Up @@ -249,16 +258,23 @@ class LifetimeDependenceInfo {
IndexSubset *scopeLifetimeParamIndices,
unsigned targetIndex, bool isImmortal,
// set during SIL type lowering
IndexSubset *addressableParamIndices = nullptr)
IndexSubset *addressableParamIndices = nullptr,
IndexSubset *conditionallyAddressableParamIndices = nullptr)
: inheritLifetimeParamIndices(inheritLifetimeParamIndices),
scopeLifetimeParamIndices(scopeLifetimeParamIndices),
addressableParamIndicesAndImmortal(addressableParamIndices, isImmortal),
conditionallyAddressableParamIndices(conditionallyAddressableParamIndices),
targetIndex(targetIndex) {
assert(this->isImmortal() || inheritLifetimeParamIndices ||
scopeLifetimeParamIndices);
assert(!inheritLifetimeParamIndices ||
!inheritLifetimeParamIndices->isEmpty());
assert(!scopeLifetimeParamIndices || !scopeLifetimeParamIndices->isEmpty());
assert((!conditionallyAddressableParamIndices
|| (addressableParamIndices
&& conditionallyAddressableParamIndices
->isSubsetOf(addressableParamIndices)))
&& "conditionally-addressable params not a subset of addressable params?");
}

operator bool() const { return !empty(); }
Expand Down Expand Up @@ -286,9 +302,26 @@ class LifetimeDependenceInfo {

IndexSubset *getScopeIndices() const { return scopeLifetimeParamIndices; }

/// Return the set of parameters which have addressable dependencies.
///
/// This indicates that any dependency on the parameter value is dependent
/// not only on the value, but the memory location of a particular instance
/// of the value.
IndexSubset *getAddressableIndices() const {
return addressableParamIndicesAndImmortal.getPointer();
}
/// Return the set of parameters which may have addressable dependencies
/// depending on the type of the parameter.
///
/// Generic parameters need to be conservatively treated as addressable in
/// situations where the substituted type may end up being addressable-for-
/// dependencies. If substitution at a call site or specialization results
/// in the type becoming concretely non-addressable-for-dependencies,
/// then the lifetime dependency can be considered a normal value
/// dependency.
IndexSubset *getConditionallyAddressableIndices() const {
return conditionallyAddressableParamIndices;
}

bool checkInherit(int index) const {
return inheritLifetimeParamIndices
Expand Down
21 changes: 19 additions & 2 deletions lib/AST/LifetimeDependence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ getLifetimeDependenceFor(ArrayRef<LifetimeDependenceInfo> lifetimeDependencies,
std::string LifetimeDependenceInfo::getString() const {
std::string lifetimeDependenceString = "@lifetime(";
auto addressable = getAddressableIndices();
auto condAddressable = getConditionallyAddressableIndices();

auto getSourceString = [&](IndexSubset *bitvector, StringRef kind) {
std::string result;
Expand All @@ -61,7 +62,11 @@ std::string LifetimeDependenceInfo::getString() const {
}
result += kind;
if (addressable && addressable->contains(i)) {
result += "address ";
if (condAddressable && condAddressable->contains(i)) {
result += "address_for_deps ";
} else {
result += "address ";
}
}
result += std::to_string(i);
isFirstSetBit = false;
Expand Down Expand Up @@ -452,6 +457,7 @@ std::optional<LifetimeDependenceInfo> LifetimeDependenceInfo::fromDependsOn(
SmallBitVector inheritLifetimeParamIndices(capacity);
SmallBitVector scopeLifetimeParamIndices(capacity);
SmallBitVector addressableLifetimeParamIndices(capacity);
SmallBitVector conditionallyAddressableLifetimeParamIndices(capacity);

auto updateLifetimeDependenceInfo = [&](LifetimeDescriptor descriptor,
unsigned paramIndexToSet,
Expand Down Expand Up @@ -494,8 +500,16 @@ std::optional<LifetimeDependenceInfo> LifetimeDependenceInfo::fromDependsOn(
if (updateLifetimeDependenceInfo(source, index, paramConvention)) {
return std::nullopt;
}
if (source.isAddressable()) {
switch (source.isAddressable()) {
case LifetimeDescriptor::IsNotAddressable:
break;
case LifetimeDescriptor::IsConditionallyAddressable:
conditionallyAddressableLifetimeParamIndices.set(index);
addressableLifetimeParamIndices.set(index);
break;
case LifetimeDescriptor::IsAddressable:
addressableLifetimeParamIndices.set(index);
break;
}
break;
}
Expand Down Expand Up @@ -523,6 +537,9 @@ std::optional<LifetimeDependenceInfo> LifetimeDependenceInfo::fromDependsOn(
/*isImmortal*/ false,
addressableLifetimeParamIndices.any()
? IndexSubset::get(ctx, addressableLifetimeParamIndices)
: nullptr,
conditionallyAddressableLifetimeParamIndices.any()
? IndexSubset::get(ctx, conditionallyAddressableLifetimeParamIndices)
: nullptr);
}

Expand Down
6 changes: 4 additions & 2 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2517,7 +2517,7 @@ parseLifetimeDescriptor(Parser &P,
// In SIL, lifetimes explicitly state whether they are dependent on a
// memory location in addition to the value stored at that location.
if (P.isInSILMode()
&& name.str() == "address"
&& (name.str() == "address" || name.str() == "address_for_deps")
&& P.Tok.is(tok::integer_literal)) {
SourceLoc orderedLoc;
unsigned index;
Expand All @@ -2527,7 +2527,9 @@ parseLifetimeDescriptor(Parser &P,
return std::nullopt;
}
return LifetimeDescriptor::forOrdered(index, lifetimeDependenceKind, loc,
/*addressable*/ true);
name.str() == "address_for_deps"
? LifetimeDescriptor::IsConditionallyAddressable
: LifetimeDescriptor::IsAddressable);
}

return LifetimeDescriptor::forNamed(name, lifetimeDependenceKind, loc);
Expand Down
46 changes: 34 additions & 12 deletions lib/SIL/IR/SILFunctionType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1578,6 +1578,7 @@ class DestructureInputs {
SmallVectorImpl<SILParameterInfo> &Inputs;
SmallVectorImpl<int> &ParameterMap;
SmallBitVector &AddressableLoweredParameters;
SmallBitVector &ConditionallyAddressableLoweredParameters;
unsigned NextOrigParamIndex = 0;

void addLoweredParameter(SILParameterInfo parameter,
Expand All @@ -1593,11 +1594,13 @@ class DestructureInputs {
std::optional<ActorIsolation> isolationInfo,
SmallVectorImpl<SILParameterInfo> &inputs,
SmallVectorImpl<int> &parameterMap,
SmallBitVector &addressableParams)
: expansion(expansion), TC(TC), Convs(conventions), Foreign(foreign),
IsolationInfo(isolationInfo), Inputs(inputs),
ParameterMap(parameterMap),
AddressableLoweredParameters(addressableParams)
SmallBitVector &addressableParams,
SmallBitVector &conditionallyAddressableParams)
: expansion(expansion), TC(TC), Convs(conventions), Foreign(foreign),
IsolationInfo(isolationInfo), Inputs(inputs),
ParameterMap(parameterMap),
AddressableLoweredParameters(addressableParams),
ConditionallyAddressableLoweredParameters(conditionallyAddressableParams)
{}

void destructure(AbstractionPattern origType,
Expand Down Expand Up @@ -1768,7 +1771,9 @@ class DestructureInputs {

// Any parameters not yet marked addressable shouldn't be.
assert(AddressableLoweredParameters.size() <= ParameterMap.size());
assert(ConditionallyAddressableLoweredParameters.size() <= ParameterMap.size());
AddressableLoweredParameters.resize(ParameterMap.size(), false);
ConditionallyAddressableLoweredParameters.resize(ParameterMap.size(), false);
}

void visit(AbstractionPattern origType, AnyFunctionType::Param substParam,
Expand Down Expand Up @@ -1809,8 +1814,8 @@ class DestructureInputs {
if (origFlags.isAddressable()) {
origType = AbstractionPattern::getOpaque();

// Remember that this lowered parameter is addressable in the
// addressable parameters vector.
// Remember that this lowered parameter is unconditionally addressable in
// the addressable parameters vector.
AddressableLoweredParameters.resize(ParameterMap.size() + 1, false);
AddressableLoweredParameters[ParameterMap.size()] = true;
} else if (hasScopedDependency) {
Expand All @@ -1821,10 +1826,14 @@ class DestructureInputs {
if (initialSubstTL.getRecursiveProperties().isAddressableForDependencies()) {
origType = AbstractionPattern::getOpaque();

// Remember that this lowered parameter is addressable in the
// addressable parameters vector.
// Remember that this lowered parameter is conditionally addressable in
// the addressable parameters vector.
AddressableLoweredParameters.resize(ParameterMap.size() + 1, false);
AddressableLoweredParameters[ParameterMap.size()] = true;

ConditionallyAddressableLoweredParameters
.resize(ParameterMap.size() + 1, false);
ConditionallyAddressableLoweredParameters[ParameterMap.size()] = true;
}
}

Expand Down Expand Up @@ -2565,6 +2574,7 @@ static CanSILFunctionType getSILFunctionType(
SmallVector<SILParameterInfo, 8> inputs;
SmallVector<int, 8> parameterMap;
SmallBitVector addressableParams;
SmallBitVector conditionallyAddressableParams;
{
std::optional<ActorIsolation> actorIsolation;
if (constant) {
Expand All @@ -2587,7 +2597,9 @@ static CanSILFunctionType getSILFunctionType(
}
DestructureInputs destructurer(expansionContext, TC, conventions,
foreignInfo, actorIsolation, inputs,
parameterMap, addressableParams);
parameterMap,
addressableParams,
conditionallyAddressableParams);
destructurer.destructure(origType, substFnInterfaceType.getParams(),
extInfoBuilder, unimplementable);
}
Expand Down Expand Up @@ -2668,11 +2680,19 @@ static CanSILFunctionType getSILFunctionType(
IndexSubset *addressableSet = addressableDeps.any()
? IndexSubset::get(TC.Context, addressableDeps)
: nullptr;

SmallBitVector condAddressableDeps = scopeIndicesSet
? scopeIndicesSet->getBitVector() & conditionallyAddressableParams
: SmallBitVector(1, false);
IndexSubset *condAddressableSet = condAddressableDeps.any()
? IndexSubset::get(TC.Context, condAddressableDeps)
: nullptr;

return LifetimeDependenceInfo(inheritIndicesSet,
scopeIndicesSet,
target, /*immortal*/ false,
addressableSet);
addressableSet,
condAddressableSet);
};
// Lower parameter dependencies.
for (unsigned i = 0; i < parameterMap.size(); ++i) {
Expand Down Expand Up @@ -2759,10 +2779,12 @@ static CanSILFunctionType getSILFunctionTypeForInitAccessor(
std::optional<ActorIsolation> actorIsolation; // For now always null.
SmallVector<int, 8> unusedParameterMap;
SmallBitVector unusedAddressableParams;
SmallBitVector unusedConditionalAddressableParams;
DestructureInputs destructurer(context, TC, conventions, foreignInfo,
actorIsolation, inputs,
unusedParameterMap,
unusedAddressableParams);
unusedAddressableParams,
unusedConditionalAddressableParams);
destructurer.destructure(
origType, substAccessorType.getParams(),
extInfoBuilder.withRepresentation(SILFunctionTypeRepresentation::Thin),
Expand Down
2 changes: 1 addition & 1 deletion test/SIL/lifetime_dependence_generics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public func test(pview: borrowing PView) -> View {
return pview.getDefault()
}

// CHECK-LABEL: sil hidden @$s28lifetime_dependence_generics1PPAAE10getDefault1EQzyF : $@convention(method) <Self where Self : P> (@in_guaranteed Self) -> @lifetime(borrow address 0) @out Self.E {
// CHECK-LABEL: sil hidden @$s28lifetime_dependence_generics1PPAAE10getDefault1EQzyF : $@convention(method) <Self where Self : P> (@in_guaranteed Self) -> @lifetime(borrow address_for_deps 0) @out Self.E {

// CHECK-LABEL: sil hidden @$s28lifetime_dependence_generics5PViewV4getEAA4ViewVyF : $@convention(method) (PView) -> @lifetime(immortal) @owned View {

Expand Down
6 changes: 3 additions & 3 deletions test/SILGen/lifetime_dependence_lowering.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,21 +90,21 @@ struct Butt {
@_addressableForDependencies
struct AddressableForDeps {
// CHECK-LABEL: sil{{.*}} @$s{{.*}}6test16{{.*}} : $
// CHECK-SAME: -> @lifetime(borrow address 3) @owned Foo
// CHECK-SAME: -> @lifetime(borrow address_for_deps 3) @owned Foo
@lifetime(borrow self)
func test16(tuple: (AddressableForDeps, AddressableForDeps),
other: AddressableForDeps) -> Foo {}

// The dependency makes the tuple pass as a single indirect argument.
// CHECK-LABEL: sil{{.*}} @$s{{.*}}6test17{{.*}} : $
// CHECK-SAME: -> @lifetime(borrow address 0) @owned Foo
// CHECK-SAME: -> @lifetime(borrow address_for_deps 0) @owned Foo
@lifetime(borrow tuple)
func test17(tuple: (AddressableForDeps, AddressableForDeps),
other: AddressableForDeps) -> Foo {}

// The tuple destructures as usual, but `other` is passed indirectly.
// CHECK-LABEL: sil{{.*}} @$s{{.*}}6test18{{.*}} : $
// CHECK-SAME: -> @lifetime(borrow address 2) @owned Foo
// CHECK-SAME: -> @lifetime(borrow address_for_deps 2) @owned Foo
@lifetime(borrow other)
func test18(tuple: (AddressableForDeps, AddressableForDeps),
other: AddressableForDeps) -> Foo {}
Expand Down