Skip to content

Insert end_cow_mutation_addr for lifetime dependent values dependent on mutable addresses #81043

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 4 commits into from
May 1, 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
1 change: 1 addition & 0 deletions SwiftCompilerSources/Sources/AST/Type.swift
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ extension TypeProperties {
public var hasLocalArchetype: Bool { rawType.bridged.hasLocalArchetype() }
public var isEscapable: Bool { rawType.bridged.isEscapable() }
public var isNoEscape: Bool { rawType.bridged.isNoEscape() }
public var isBuiltinType: Bool { rawType.bridged.isBuiltinType() }
public var archetypeRequiresClass: Bool { rawType.bridged.archetypeRequiresClass() }

public var representationOfMetatype: AST.`Type`.MetatypeRepresentation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,102 @@ let lifetimeDependenceScopeFixupPass = FunctionPass(
}
let args = scopeExtension.findArgumentDependencies()

// If the scope cannot be extended to the caller, this must be the outermost dependency level.
// Insert end_cow_mutation_addr if needed.
if args.isEmpty {
createEndCOWMutationIfNeeded(lifetimeDep: newLifetimeDep, context)
}

// Redirect the dependence base to the function arguments. This may create additional mark_dependence instructions.
markDep.redirectFunctionReturn(to: args, context)
}
}

private extension Type {
func mayHaveMutableSpan(in function: Function, _ context: FunctionPassContext) -> Bool {
if hasArchetype {
return true
}
if isBuiltinType {
return false
}
// Only result types that are nominal can have a MutableSpan derived from an inout array access.
if nominal == nil {
return false
}
if nominal == context.swiftMutableSpan {
return true
}
if isStruct {
guard let fields = getNominalFields(in: function) else {
return false
}
return fields.contains { $0.mayHaveMutableSpan(in: function, context) }
}
if isTuple {
return tupleElements.contains { $0.mayHaveMutableSpan(in: function, context) }
}
if isEnum {
guard let cases = getEnumCases(in: function) else {
return true
}
return cases.contains { $0.payload?.mayHaveMutableSpan(in: function, context) ?? false }
}
// Classes cannot be ~Escapable, therefore cannot hold a MutableSpan.
if isClass {
return false
}
return false
}
}

/// Insert end_cow_mutation_addr for lifetime dependent values that maybe of type MutableSpan and depend on a mutable address.
private func createEndCOWMutationIfNeeded(lifetimeDep: LifetimeDependence, _ context: FunctionPassContext) {
var scoped : ScopedInstruction

// Handle cases which generate mutable addresses: begin_access [modify] and yield &
switch lifetimeDep.scope {
case let .access(beginAccess):
if beginAccess.accessKind != .modify {
return
}
scoped = beginAccess
case let .yield(value):
let beginApply = value.definingInstruction as! BeginApplyInst
if value == beginApply.token {
return
}
if beginApply.convention(of: value as! MultipleValueInstructionResult) != .indirectInout {
return
}
scoped = beginApply
// None of the below cases can generate a mutable address.
case let .owned:
fallthrough
case let .borrowed:
fallthrough
case let .local:
fallthrough
case let .initialized:
fallthrough
case let .caller:
fallthrough
case let .global:
fallthrough
case let .unknown:
return
}

guard lifetimeDep.dependentValue.type.mayHaveMutableSpan(in: lifetimeDep.dependentValue.parentFunction, context) else {
return
}

for endInstruction in scoped.endInstructions {
let builder = Builder(before: endInstruction, context)
builder.createEndCOWMutationAddr(address: lifetimeDep.parentValue)
}
}

private extension MarkDependenceInstruction {
/// Rewrite the mark_dependence base operand to ignore inner borrow scopes (begin_borrow, load_borrow).
///
Expand Down Expand Up @@ -194,7 +285,7 @@ private extension MarkDependenceAddrInst {
}
}

/// A scope extension is a set of nested scopes and their owners. The owner is a value that represents ownerhip of
/// A scope extension is a set of nested scopes and their owners. The owner is a value that represents ownership of
/// the outermost scopes, which cannot be extended; it limits how far the nested scopes can be extended.
private struct ScopeExtension {
let context: FunctionPassContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,10 @@ struct FunctionPassContext : MutatingContext {
_bridged.getSwiftArrayDecl().getAs(NominalTypeDecl.self)
}

var swiftMutableSpan: NominalTypeDecl {
_bridged.getSwiftMutableSpanDecl().getAs(NominalTypeDecl.self)
}

func loadFunction(name: StaticString, loadCalleesRecursively: Bool) -> Function? {
return name.withUTF8Buffer { (nameBuffer: UnsafeBufferPointer<UInt8>) in
let nameStr = BridgedStringRef(data: nameBuffer.baseAddress, count: nameBuffer.count)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ private extension Instruction {
is BeginAccessInst,
is EndAccessInst,
is EndCOWMutationInst,
is EndCOWMutationAddrInst,
is CopyValueInst,
is DestroyValueInst,
is StrongReleaseInst,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ extension AddressUseVisitor {
is DestroyAddrInst, is DeallocStackInst,
is DeinitExistentialAddrInst,
is IsUniqueInst, is MarkFunctionEscapeInst,
is PackElementSetInst:
is PackElementSetInst, is EndCOWMutationAddrInst:
return leafAddressUse(of: operand)

case is LoadInst, is LoadUnownedInst, is LoadWeakInst, is ValueMetatypeInst, is ExistentialMetatypeInst,
Expand Down
5 changes: 5 additions & 0 deletions SwiftCompilerSources/Sources/SIL/Builder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,11 @@ public struct Builder {
return notifyNew(endMutation.getAs(EndCOWMutationInst.self))
}

public func createEndCOWMutationAddr(address: Value) -> EndCOWMutationAddrInst {
let endMutation = bridged.createEndCOWMutationAddr(address.bridged)
return notifyNew(endMutation.getAs(EndCOWMutationAddrInst.self))
}

public func createMarkDependence(value: Value, base: Value, kind: MarkDependenceKind) -> MarkDependenceInst {
let markDependence = bridged.createMarkDependence(value.bridged, base.bridged,
BridgedInstruction.MarkDependenceKind(rawValue: kind.rawValue)!)
Expand Down
4 changes: 4 additions & 0 deletions SwiftCompilerSources/Sources/SIL/Instruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1226,6 +1226,10 @@ final public class EndCOWMutationInst : SingleValueInstruction, UnaryInstruction
public var doKeepUnique: Bool { bridged.EndCOWMutationInst_doKeepUnique() }
}

final public class EndCOWMutationAddrInst : Instruction, UnaryInstruction {
public var address: Value { operand.value }
}

final public
class ClassifyBridgeObjectInst : SingleValueInstruction, UnaryInstruction {}

Expand Down
1 change: 1 addition & 0 deletions SwiftCompilerSources/Sources/SIL/Registration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ public func registerSILClasses() {
register(MoveValueInst.self)
register(DropDeinitInst.self)
register(EndCOWMutationInst.self)
register(EndCOWMutationAddrInst.self)
register(ClassifyBridgeObjectInst.self)
register(PartialApplyInst.self)
register(ApplyInst.self)
Expand Down
16 changes: 16 additions & 0 deletions docs/SIL/Instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2146,6 +2146,22 @@ not replace this reference with a not uniquely reference object.

For details see [Copy-on-Write Representation](SIL.md#Copy-on-Write-Representation).

### end_cow_mutation_addr

```
sil-instruction ::= 'end_cow_mutation_addr' sil-operand
end_cow_mutation_addr %0 : $*T
// %0 must be of an address $*T type
```

Copy link
Contributor

Choose a reason for hiding this comment

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

IIUC, the operand is not necessarily the address of a single class reference. It can e.g. be an opaque archetype or a structure, etc.
Can you point that out? And describe what it means, e.g. that it refers to all class instances contained in the type, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to:

sil-instruction ::= 'end_cow_mutation_addr' sil-operand

end_cow_mutation_addr %0 : $*T
// %0 must be of an address $*T type

This instruction marks the end of mutation of an address that may contain MutableSpan.
The address could be an opaque archetype, a struct type, tuple type or enum type and
the end_cow_mutation_addr will apply to all members contained within these types.
It is currently only generated in cases where is it is not possible to schedule an
end_cow_mutation in the standard library automatically. Ex: Array.mutableSpan

Copy link
Contributor

Choose a reason for hiding this comment

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

We should be clear that the type of %0 could be anything that an Array might depend on. Whether it contains MutableSpan seems irrelevant. You could talk about MutableSpan just to describe why the instruction is needed, but make it clear that the dependent value is different from %0!

This instruction marks the end of mutation of an address. The address could be
an opaque archetype, a struct, tuple or enum type and the end_cow_mutation_addr
will apply to all members contained within it.
It is currently only generated in cases where we maybe deriving a MutableSpan from
`%0` since it is not possible to schedule an `end_cow_mutation` in the standard
library automatically for Array.mutableSpan etc.

### destroy_not_escaped_closure

```
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/ASTBridging.h
Original file line number Diff line number Diff line change
Expand Up @@ -3105,6 +3105,7 @@ struct BridgedASTType {
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedASTType getBuiltinVectorElementType() const;
BRIDGED_INLINE bool isBuiltinFixedWidthInteger(SwiftInt width) const;
BRIDGED_INLINE bool isOptional() const;
BRIDGED_INLINE bool isBuiltinType() const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE OptionalBridgedDeclObj getNominalOrBoundGenericNominal() const;
BRIDGED_INLINE TraitResult canBeClass() const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE OptionalBridgedDeclObj getAnyNominal() const;
Expand Down
4 changes: 4 additions & 0 deletions include/swift/AST/ASTBridgingImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,10 @@ bool BridgedASTType::isUnownedStorageType() const {
return unbridged()->is<swift::UnownedStorageType>();
}

bool BridgedASTType::isBuiltinType() const {
return unbridged()->isBuiltinType();
}

OptionalBridgedDeclObj BridgedASTType::getNominalOrBoundGenericNominal() const {
return {unbridged()->getNominalOrBoundGenericNominal()};
}
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/KnownStdlibTypes.def
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,6 @@ KNOWN_STDLIB_TYPE_DECL(InlineArray, NominalTypeDecl, 2)

KNOWN_STDLIB_TYPE_DECL(SwiftSetting, NominalTypeDecl, 0)

KNOWN_STDLIB_TYPE_DECL(MutableSpan, NominalTypeDecl, 1)

#undef KNOWN_STDLIB_TYPE_DECL
7 changes: 7 additions & 0 deletions include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,9 @@ class alignas(1 << TypeAlignInBits) TypeBase
bool is##NAME();
#include "swift/AST/KnownStdlibTypes.def"

/// Check if this type is from the Builtin module.
bool isBuiltinType();

/// Check if this type is equal to Builtin.IntN.
bool isBuiltinIntegerType(unsigned bitWidth);

Expand Down Expand Up @@ -8199,6 +8202,10 @@ inline GenericTypeDecl *TypeBase::getAnyGeneric() {
return getCanonicalType().getAnyGeneric();
}

inline bool TypeBase::isBuiltinType() {
return isa<BuiltinType>(getCanonicalType());
}

inline bool TypeBase::isBuiltinIntegerType(unsigned n) {
if (auto intTy = dyn_cast<BuiltinIntegerType>(getCanonicalType()))
return intTy->getWidth().isFixedWidth()
Expand Down
2 changes: 1 addition & 1 deletion include/swift/SIL/AddressWalker.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ TransitiveAddressWalker<Impl>::walk(SILValue projectedAddress) {
isa<PackElementSetInst>(user) || isa<PackElementGetInst>(user) ||
isa<DeinitExistentialAddrInst>(user) || isa<LoadBorrowInst>(user) ||
isa<TupleAddrConstructorInst>(user) || isa<DeallocPackInst>(user) ||
isa<MergeIsolationRegionInst>(user)) {
isa<MergeIsolationRegionInst>(user) || isa<EndCOWMutationAddrInst>(user)) {
callVisitUse(op);
continue;
}
Expand Down
1 change: 1 addition & 0 deletions include/swift/SIL/SILBridging.h
Original file line number Diff line number Diff line change
Expand Up @@ -1280,6 +1280,7 @@ struct BridgedBuilder{
BridgedASTType::MetatypeRepresentation representation) const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedInstruction createEndCOWMutation(BridgedValue instance,
bool keepUnique) const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedInstruction createEndCOWMutationAddr(BridgedValue instance) const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedInstruction createMarkDependence(
BridgedValue value, BridgedValue base, BridgedInstruction::MarkDependenceKind dependenceKind) const;

Expand Down
6 changes: 6 additions & 0 deletions include/swift/SIL/SILBridgingImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2498,6 +2498,12 @@ BridgedInstruction BridgedBuilder::createEndCOWMutation(BridgedValue instance, b
keepUnique)};
}

BridgedInstruction
BridgedBuilder::createEndCOWMutationAddr(BridgedValue instance) const {
return {unbridged().createEndCOWMutationAddr(regularLoc(),
instance.getSILValue())};
}

BridgedInstruction BridgedBuilder::createMarkDependence(BridgedValue value, BridgedValue base, BridgedInstruction::MarkDependenceKind kind) const {
return {unbridged().createMarkDependence(regularLoc(), value.getSILValue(), base.getSILValue(), swift::MarkDependenceKind(kind))};
}
Expand Down
5 changes: 5 additions & 0 deletions include/swift/SIL/SILBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -2383,6 +2383,11 @@ class SILBuilder {
return insert(new (getModule()) EndCOWMutationInst(getSILDebugLocation(Loc),
operand, keepUnique));
}
EndCOWMutationAddrInst *createEndCOWMutationAddr(SILLocation Loc,
SILValue operand) {
return insert(new (getModule()) EndCOWMutationAddrInst(
getSILDebugLocation(Loc), operand));
}
DestroyNotEscapedClosureInst *createDestroyNotEscapedClosure(SILLocation Loc,
SILValue operand,
unsigned VerificationType) {
Expand Down
8 changes: 8 additions & 0 deletions include/swift/SIL/SILCloner.h
Original file line number Diff line number Diff line change
Expand Up @@ -3167,6 +3167,14 @@ void SILCloner<ImplClass>::visitEndCOWMutationInst(EndCOWMutationInst *Inst) {
getOpValue(Inst->getOperand()), Inst->doKeepUnique()));
}
template <typename ImplClass>
void SILCloner<ImplClass>::visitEndCOWMutationAddrInst(
EndCOWMutationAddrInst *Inst) {
getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope()));
recordClonedInstruction(
Inst, getBuilder().createEndCOWMutationAddr(
getOpLocation(Inst->getLoc()), getOpValue(Inst->getOperand())));
}
template <typename ImplClass>
void SILCloner<ImplClass>::visitDestroyNotEscapedClosureInst(
DestroyNotEscapedClosureInst *Inst) {
getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope()));
Expand Down
8 changes: 8 additions & 0 deletions include/swift/SIL/SILInstruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -9580,6 +9580,14 @@ class EndCOWMutationInst
sharedUInt8().EndCOWMutationInst.keepUnique = keepUnique;
}
};
class EndCOWMutationAddrInst
: public UnaryInstructionBase<SILInstructionKind::EndCOWMutationAddrInst,
NonValueInstruction> {
friend SILBuilder;

EndCOWMutationAddrInst(SILDebugLocation debugLoc, SILValue address)
: UnaryInstructionBase(debugLoc, address) {}
};

/// Given an escaping closure return true iff it has a non-nil context and the
/// context has a strong reference count greater than 1.
Expand Down
5 changes: 3 additions & 2 deletions include/swift/SIL/SILNodes.def
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,6 @@ ABSTRACT_VALUE_AND_INST(SingleValueInstruction, ValueBase, SILInstruction)

SINGLE_VALUE_INST(EndCOWMutationInst, end_cow_mutation,
SingleValueInstruction, None, DoesNotRelease)

SINGLE_VALUE_INST(DestroyNotEscapedClosureInst, destroy_not_escaped_closure,
SingleValueInstruction, MayHaveSideEffects, MayRelease)

Expand Down Expand Up @@ -908,7 +907,9 @@ NON_VALUE_INST(IncrementProfilerCounterInst, increment_profiler_counter,
NON_VALUE_INST(MarkDependenceAddrInst, mark_dependence_addr,
SILInstruction, None, DoesNotRelease)

NODE_RANGE(NonValueInstruction, UnreachableInst, MarkDependenceAddrInst)
NON_VALUE_INST(EndCOWMutationAddrInst, end_cow_mutation_addr,
SILInstruction, MayHaveSideEffects, DoesNotRelease)
NODE_RANGE(NonValueInstruction, UnreachableInst, EndCOWMutationAddrInst)

ABSTRACT_INST(MultipleValueInstruction, SILInstruction)
MULTIPLE_VALUE_INST(BeginApplyInst, begin_apply,
Expand Down
1 change: 1 addition & 0 deletions include/swift/SILOptimizer/OptimizerBridging.h
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ struct BridgedPassContext {
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedDomTree getDomTree() const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedPostDomTree getPostDomTree() const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedDeclObj getSwiftArrayDecl() const;
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedDeclObj getSwiftMutableSpanDecl() const;

// AST

Expand Down
5 changes: 5 additions & 0 deletions include/swift/SILOptimizer/OptimizerBridgingImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ BridgedDeclObj BridgedPassContext::getSwiftArrayDecl() const {
return {mod->getASTContext().getArrayDecl()};
}

BridgedDeclObj BridgedPassContext::getSwiftMutableSpanDecl() const {
swift::SILModule *mod = invocation->getPassManager()->getModule();
return {mod->getASTContext().getMutableSpanDecl()};
}

// AST

SWIFT_IMPORT_UNSAFE BRIDGED_INLINE
Expand Down
5 changes: 5 additions & 0 deletions lib/IRGen/IRGenSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1438,6 +1438,7 @@ class IRGenSILFunction :
void visitIsUniqueInst(IsUniqueInst *i);
void visitBeginCOWMutationInst(BeginCOWMutationInst *i);
void visitEndCOWMutationInst(EndCOWMutationInst *i);
void visitEndCOWMutationAddrInst(EndCOWMutationAddrInst *i);
void visitDestroyNotEscapedClosureInst(DestroyNotEscapedClosureInst *i);
void visitDeallocStackInst(DeallocStackInst *i);
void visitDeallocStackRefInst(DeallocStackRefInst *i);
Expand Down Expand Up @@ -6366,6 +6367,10 @@ void IRGenSILFunction::visitEndCOWMutationInst(EndCOWMutationInst *i) {
setLoweredExplosion(i, v);
}

void IRGenSILFunction::visitEndCOWMutationAddrInst(EndCOWMutationAddrInst *i) {
// end_cow_mutation_addr is purely for SIL.
}

void IRGenSILFunction::visitDestroyNotEscapedClosureInst(
swift::DestroyNotEscapedClosureInst *i) {
// The closure operand is allowed to be an optional closure.
Expand Down
1 change: 1 addition & 0 deletions lib/SIL/IR/OperandOwnership.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ OPERAND_OWNERSHIP(DestroyingConsume, DestroyNotEscapedClosure)
OPERAND_OWNERSHIP(DestroyingConsume, EndLifetime)
OPERAND_OWNERSHIP(DestroyingConsume, BeginCOWMutation)
OPERAND_OWNERSHIP(DestroyingConsume, EndCOWMutation)
OPERAND_OWNERSHIP(TrivialUse, EndCOWMutationAddr)
OPERAND_OWNERSHIP(DestroyingConsume, EndInitLetRef)
// The move_value instruction creates a distinct lifetime.
OPERAND_OWNERSHIP(DestroyingConsume, MoveValue)
Expand Down
3 changes: 3 additions & 0 deletions lib/SIL/IR/SILPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2763,6 +2763,9 @@ class SILPrinter : public SILInstructionVisitor<SILPrinter> {
*this << "[keep_unique] ";
*this << getIDAndType(ECMI->getOperand());
}
void visitEndCOWMutationAddrInst(EndCOWMutationAddrInst *ECMI) {
*this << getIDAndType(ECMI->getOperand());
}
void visitEndInitLetRefInst(EndInitLetRefInst *I) {
*this << getIDAndType(I->getOperand());
}
Expand Down
Loading