Skip to content

Commit b1dba25

Browse files
committed
Introduce OperandOwnership to classify OSSA uses.
Migrating to this classification was made easy by the recent rewrite of the OSSA constraint model. It's also consistent with instruction-level abstractions for working with different kinds of OperandOwnership that are being designed. This classification vastly simplifies OSSA passes that rewrite OSSA live ranges, making it straightforward to reason about completeness and correctness. It will allow a simple utility to canonicalize OSSA live ranges on-the-fly. This avoids the need for OSSA-based utilities and passes to hard-code SIL opcodes. This will allow several of those unmaintainable pieces of code to be replaced with a trivial OperandOwnership check. It's extremely important for SIL maintainers to see a list of all SIL opcodes associated with a simple OSSA classification and set of well-specified rules for each opcode class, without needing to guess or reverse-engineer the meaning from the implementation. This classification does that while eliminating a pile of unreadable macros. This classification system is the model that CopyPropagation was initially designed to use. Now, rather than relying on a separate pass, a simple, lightweight utility will canonicalize OSSA live ranges. The major problem with writing optimizations based on OperandOwnership is that some operations don't follow structural OSSA requirements, such as project_box and unchecked_ownership_conversion. Those are classified as PointerEscape which prevents the compiler from reasoning about, or rewriting the OSSA live range. Functional Changes: As a side effect, this corrects many operand constraints that should in fact require trivial operand values.
1 parent fb4583f commit b1dba25

File tree

8 files changed

+797
-818
lines changed

8 files changed

+797
-818
lines changed

include/swift/SIL/ApplySite.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ class ApplySite {
337337
}
338338

339339
/// Return the SILArgumentConvention for the given applied argument operand.
340-
SILArgumentConvention getArgumentConvention(Operand &oper) const {
340+
SILArgumentConvention getArgumentConvention(const Operand &oper) const {
341341
unsigned calleeArgIdx =
342342
getCalleeArgIndexOfFirstAppliedArg() + getAppliedArgIndex(oper);
343343
return getSubstCalleeConv().getSILArgumentConvention(calleeArgIdx);
@@ -615,7 +615,8 @@ class FullApplySite : public ApplySite {
615615
/// Returns true if \p op is an operand that passes an indirect
616616
/// result argument to the apply site.
617617
bool isIndirectResultOperand(const Operand &op) const {
618-
return getCalleeArgIndex(op) < getNumIndirectSILResults();
618+
return isArgumentOperand(op)
619+
&& (getCalleeArgIndex(op) < getNumIndirectSILResults());
619620
}
620621

621622
static FullApplySite getFromOpaqueValue(void *p) { return FullApplySite(p); }

include/swift/SIL/OwnershipUtils.h

Lines changed: 38 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -32,29 +32,47 @@ class DeadEndBlocks;
3232
/// Returns true if v is an address or trivial.
3333
bool isValueAddressOrTrivial(SILValue v);
3434

35-
/// Is this an operand that can forward both owned and guaranteed ownership into
36-
/// one of the operand's owner instruction's result.
37-
bool isOwnershipForwardingUse(Operand *op);
38-
39-
/// Is this an operand that can forward guaranteed ownership into one of the
40-
/// operand's owner instruction's result.
41-
bool isGuaranteedForwardingUse(Operand *op);
42-
43-
/// Is this an operand that can forward owned ownership into one of the
44-
/// operand's owner instruction's result.
45-
bool isOwnedForwardingUse(Operand *use);
35+
/// Is the opcode that produces \p value capable of forwarding guaranteed
36+
/// values?
37+
///
38+
/// This may be true even if the current instance of the instruction is not a
39+
/// ForwardingBorrow. If true, then the operation may be trivially rewritten
40+
/// with Guaranteed ownership.
41+
bool canOpcodeForwardGuaranteedValues(SILValue value);
4642

47-
/// Is this a value that is the result of an instruction that forwards
48-
/// guaranteed ownership from one of its operands.
49-
bool isGuaranteedForwardingValue(SILValue value);
43+
/// Is the opcode that consumes \p use capable of forwarding guaranteed values?
44+
///
45+
/// This may be true even if \p use is not a ForwardingBorrow. If true, then the
46+
/// operation may be trivially rewritten with Guaranteed ownership.
47+
bool canOpcodeForwardGuaranteedValues(Operand *use);
48+
49+
// This is the use-def equivalent of use->getOperandOwnership() ==
50+
// OperandOwnership::ForwardingBorrow.
51+
inline bool isForwardingBorrow(SILValue value) {
52+
assert(value.getOwnershipKind() == OwnershipKind::Guaranteed);
53+
return canOpcodeForwardGuaranteedValues(value);
54+
}
55+
56+
/// Is the opcode that produces \p value capable of forwarding owned values?
57+
///
58+
/// This may be true even if the current instance of the instruction is not a
59+
/// ForwardingConsume. If true, then the operation may be trivially rewritten
60+
/// with Owned ownership.
61+
bool canOpcodeForwardOwnedValues(SILValue value);
5062

51-
/// Is this value the result of an instruction that 'forward's owned ownership,
52-
/// but may not be able to forward guaranteed ownership.
63+
/// Is this opcode that consumes \p use capable of forwarding owned values?
5364
///
54-
/// This will be either a multiple value instruction resuilt, a single value
55-
/// instruction that forwards or an argument that forwards the ownership from a
56-
/// previous terminator.
57-
bool isOwnedForwardingValue(SILValue value);
65+
/// This may be true even if the current instance of the instruction is not a
66+
/// ForwardingConsume. If true, then the operation may be trivially rewritten
67+
/// with Owned ownership.
68+
bool canOpcodeForwardOwnedValues(Operand *use);
69+
70+
// This is the use-def equivalent of use->getOperandOwnership() ==
71+
// OperandOwnership::ForwardingConsume.
72+
inline bool isForwardingConsume(SILValue value) {
73+
assert(value.getOwnershipKind() == OwnershipKind::Owned);
74+
return canOpcodeForwardOwnedValues(value);
75+
}
5876

5977
class ForwardingOperand {
6078
Operand *use;
@@ -225,25 +243,6 @@ struct BorrowingOperand {
225243
llvm_unreachable("Covered switch isn't covered?!");
226244
}
227245

228-
/// Is this a borrow scope operand that can open new borrow scopes
229-
/// for owned values.
230-
bool canAcceptOwnedValues() const {
231-
switch (kind) {
232-
// begin_borrow can take any parameter
233-
case BorrowingOperandKind::BeginBorrow:
234-
// Yield can implicit borrow owned values.
235-
case BorrowingOperandKind::Yield:
236-
// FullApplySites can implicit borrow owned values.
237-
case BorrowingOperandKind::BeginApply:
238-
case BorrowingOperandKind::Apply:
239-
case BorrowingOperandKind::TryApply:
240-
return true;
241-
case BorrowingOperandKind::Branch:
242-
return false;
243-
}
244-
llvm_unreachable("Covered switch isn't covered?!");
245-
}
246-
247246
/// Is the result of this instruction also a borrow introducer?
248247
///
249248
/// TODO: This needs a better name.

include/swift/SIL/SILValue.h

Lines changed: 144 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ struct OwnershipKind {
226226

227227
llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const OwnershipKind &kind);
228228

229+
enum class OperandOwnership;
230+
229231
/// A value representing the specific ownership semantics that a SILValue may
230232
/// have.
231233
struct ValueOwnershipKind {
@@ -286,6 +288,8 @@ struct ValueOwnershipKind {
286288
llvm_unreachable("covered switch");
287289
}
288290

291+
OperandOwnership getForwardingOperandOwnership() const;
292+
289293
/// Returns true if \p Other can be merged successfully with this, implying
290294
/// that the two ownership kinds are "compatibile".
291295
///
@@ -597,12 +601,6 @@ class OwnershipConstraint {
597601
return lifetimeConstraint;
598602
}
599603

600-
/// Return a constraint that is appropriate for an operand that can accept a
601-
/// value with any ownership kind without ending said value's lifetime.
602-
static OwnershipConstraint any() {
603-
return {OwnershipKind::Any, UseLifetimeConstraint::NonLifetimeEnding};
604-
}
605-
606604
bool satisfiedBy(const Operand *use) const;
607605

608606
bool satisfiesConstraint(ValueOwnershipKind testKind) const {
@@ -618,6 +616,124 @@ class OwnershipConstraint {
618616
llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
619617
OwnershipConstraint constraint);
620618

619+
/// Categorize all uses in terms of their ownership effect.
620+
///
621+
/// Used to verify completeness of the ownership use model and exhaustively
622+
/// switch over any category of ownership use. Implies ownership constraints and
623+
/// lifetime constraints.
624+
enum class OperandOwnership {
625+
/// Uses of ownership None. These uses are incompatible with values that have
626+
/// ownership but are otherwise not verified.
627+
None,
628+
629+
/// MARK: Uses of any ownership values:
630+
631+
/// Point-in-time use. Uses the value instantaneously.
632+
/// (copy_value, single-instruction apply with @guaranteed argument)
633+
InstantaneousUse,
634+
// FIXME: The PointerEscape category should be eliminated. All pointer escapes
635+
// should be InteriorPointer, guarded by a borrow scope.
636+
PointerEscape,
637+
/// Bitwise escape. Escapes the nontrivial contents of the value.
638+
/// OSSA does not enforce the lifetime of the escaping bits.
639+
/// The programmer must explicitly force lifetime extension.
640+
/// (ref_to_unowned, unchecked_trivial_bitcast)
641+
BitwiseEscape,
642+
643+
/// MARK: Uses of Unowned values:
644+
645+
/// Forwarding instruction with an Unowned result must have Unowned operands.
646+
ForwardingUnowned,
647+
648+
/// MARK: Uses of Owned values:
649+
650+
/// Borrow. Propagates the owned value within a scope, without consuming it.
651+
/// (begin_borrow, begin_apply with @guaranteed argument)
652+
Borrow,
653+
/// Destroying Consume. Destroys the owned value immediately.
654+
/// (store, destroy, @owned destructure).
655+
DestroyingConsume,
656+
/// Forwarding Consume. Consumes the owned value indirectly via a move.
657+
/// (br, destructure, tuple, struct, cast, switch).
658+
ForwardingConsume,
659+
660+
/// MARK: Uses of Guaranteed values:
661+
662+
/// Nested Borrow. Propagates the guaranteed value within a nested borrow
663+
/// scope, without ending the outer borrow scope, following stack discipline.
664+
/// (begin_borrow, begin_apply with @guaranteed).
665+
NestedBorrow,
666+
/// Interior Pointer. Propagates an address into the guaranteed value within
667+
/// the base's borrow scope. (ref_element_addr, open_existential_box)
668+
InteriorPointer,
669+
/// Forwarded Borrow. Propagates the guaranteed value within the base's
670+
/// borrow scope.
671+
/// (tuple_extract, struct_extract, cast, switch)
672+
ForwardingBorrow,
673+
/// End Borrow. End the borrow scope opened directly by the operand.
674+
/// The operand must be a begin_borrow, begin_apply, or function argument.
675+
/// (end_borrow, end_apply)
676+
EndBorrow,
677+
// Reborrow. Ends the borrow scope opened directly by the operand and begins
678+
// one or multiple disjoint borrow scopes. If a forwarded value is reborrowed,
679+
// then its base must also be reborrowed at the same point.
680+
// (br, FIXME: should also include destructure, tuple, struct)
681+
Reborrow
682+
};
683+
684+
llvm::raw_ostream &operator<<(llvm::raw_ostream &os, OperandOwnership operandOwnership);
685+
686+
/// Return the OwnershipConstraint for a OperandOwnership.
687+
///
688+
/// Defined inline so the switch is eliminated for constant OperandOwnership.
689+
inline OwnershipConstraint
690+
getOwnershipConstraint(OperandOwnership operandOwnership) {
691+
switch (operandOwnership) {
692+
case OperandOwnership::None:
693+
return {OwnershipKind::None, UseLifetimeConstraint::NonLifetimeEnding};
694+
case OperandOwnership::InstantaneousUse:
695+
case OperandOwnership::PointerEscape:
696+
case OperandOwnership::BitwiseEscape:
697+
return {OwnershipKind::Any, UseLifetimeConstraint::NonLifetimeEnding};
698+
case OperandOwnership::ForwardingUnowned:
699+
return {OwnershipKind::Unowned, UseLifetimeConstraint::NonLifetimeEnding};
700+
case OperandOwnership::Borrow:
701+
return {OwnershipKind::Owned, UseLifetimeConstraint::NonLifetimeEnding};
702+
case OperandOwnership::DestroyingConsume:
703+
case OperandOwnership::ForwardingConsume:
704+
return {OwnershipKind::Owned, UseLifetimeConstraint::LifetimeEnding};
705+
case OperandOwnership::NestedBorrow:
706+
case OperandOwnership::InteriorPointer:
707+
case OperandOwnership::ForwardingBorrow:
708+
return {OwnershipKind::Guaranteed,
709+
UseLifetimeConstraint::NonLifetimeEnding};
710+
case OperandOwnership::EndBorrow:
711+
case OperandOwnership::Reborrow:
712+
return {OwnershipKind::Guaranteed, UseLifetimeConstraint::LifetimeEnding};
713+
}
714+
}
715+
716+
// Forwarding instructions have a dynamic ownership kind. Their forwarded
717+
// operand constraint depends on that dynamic result ownership. If the result is
718+
// owned, then the instruction moves owned operand to its result, ending its
719+
// lifetime. If the result is guaranteed value, then the instruction propagates
720+
// the lifetime of its borrows operand through its result.
721+
inline OperandOwnership
722+
ValueOwnershipKind::getForwardingOperandOwnership() const {
723+
switch (value) {
724+
case OwnershipKind::Any:
725+
llvm_unreachable("invalid value ownership");
726+
case OwnershipKind::None:
727+
return OperandOwnership::None;
728+
case OwnershipKind::Unowned:
729+
return OperandOwnership::ForwardingUnowned;
730+
case OwnershipKind::Guaranteed:
731+
return OperandOwnership::ForwardingBorrow;
732+
case OwnershipKind::Owned:
733+
return OperandOwnership::ForwardingConsume;
734+
}
735+
}
736+
621737
/// A formal SIL reference to a value, suitable for use as a stored
622738
/// operand.
623739
class Operand {
@@ -695,12 +811,22 @@ class Operand {
695811
/// Return which operand this is in the operand list of the using instruction.
696812
unsigned getOperandNumber() const;
697813

814+
/// Return the use ownership of this operand. Returns none if the operand is a
815+
/// type dependent operand.
816+
///
817+
/// NOTE: This is implemented in OperandOwnership.cpp.
818+
Optional<OperandOwnership> getOperandOwnership() const;
819+
698820
/// Return the ownership constraint that restricts what types of values this
699821
/// Operand can contain. Returns none if the operand is a type dependent
700822
/// operand.
701-
///
702-
/// NOTE: This is implemented in OperandOwnership.cpp.
703-
Optional<OwnershipConstraint> getOwnershipConstraint() const;
823+
Optional<OwnershipConstraint> getOwnershipConstraint() const {
824+
auto operandOwnership = getOperandOwnership();
825+
if (!operandOwnership) {
826+
return None;
827+
}
828+
return swift::getOwnershipConstraint(operandOwnership.getValue());
829+
}
704830

705831
/// Returns true if changing the operand to use a value with the given
706832
/// ownership kind would not cause the operand to violate the operand's
@@ -711,24 +837,28 @@ class Operand {
711837
/// operand constraint.
712838
bool satisfiesConstraints() const;
713839

714-
/// Returns true if this operand acts as a use that consumes its associated
715-
/// value.
840+
/// Returns true if this operand acts as a use that ends the lifetime its
841+
/// associated value, either by consuming the owned value or ending the
842+
/// guaranteed scope.
716843
bool isLifetimeEnding() const;
717844

718845
SILBasicBlock *getParentBlock() const;
719846
SILFunction *getParentFunction() const;
720847

721848
private:
722849
void removeFromCurrent() {
723-
if (!Back) return;
850+
if (!Back)
851+
return;
724852
*Back = NextUse;
725-
if (NextUse) NextUse->Back = Back;
853+
if (NextUse)
854+
NextUse->Back = Back;
726855
}
727856

728857
void insertIntoCurrent() {
729858
Back = &TheValue->FirstUse;
730859
NextUse = TheValue->FirstUse;
731-
if (NextUse) NextUse->Back = &NextUse;
860+
if (NextUse)
861+
NextUse->Back = &NextUse;
732862
TheValue->FirstUse = this;
733863
}
734864

0 commit comments

Comments
 (0)