This is phase-1 of switching from llvm::Optional to std::optional in the
next rebranch. llvm::Optional was removed from upstream LLVM, so we need
to migrate off rather soon. On Darwin, std::optional, and llvm::Optional
have the same layout, so we don't need to be as concerned about ABI
beyond the name mangling. `llvm::Optional` is only returned from one
function in
```
getStandardTypeSubst(StringRef TypeName,
bool allowConcurrencyManglings);
```
It's the return value, so it should not impact the mangling of the
function, and the layout is the same as `std::optional`, so it should be
mostly okay. This function doesn't appear to have users, and the ABI was
already broken 2 years ago for concurrency and no one seemed to notice
so this should be "okay".
I'm doing the migration incrementally so that folks working on main can
cherry-pick back to the release/5.9 branch. Once 5.9 is done and locked
away, then we can go through and finish the replacement. Since `None`
and `Optional` show up in contexts where they are not `llvm::None` and
`llvm::Optional`, I'm preparing the work now by going through and
removing the namespace unwrapping and making the `llvm` namespace
explicit. This should make it fairly mechanical to go through and
replace llvm::Optional with std::optional, and llvm::None with
std::nullopt. It's also a change that can be brought onto the
release/5.9 with minimal impact. This should be an NFC change.
"reborrow" flag on the SILArgument avoids transitive walk over the phi operandsi
to determine if it is a reborrow in multiple utilities.
SIL transforms must keep the flag up-to-date by calling SILArgument::setReborrow.
SILVerifier checks to ensure the flag is not invalidated.
Currently "escaping" is not used anywhere.
- SILPackType carries whether the elements are stored directly
in the pack, which we're not currently using in the lowering,
but it's probably something we'll want in the final ABI.
Having this also makes it clear that we're doing the right
thing with substitution and element lowering. I also toyed
with making this a scalar type, which made it necessary in
various places, although eventually I pulled back to the
design where we always use packs as addresses.
- Pack boundaries are a core ABI concept, so the lowering has
to wrap parameter pack expansions up as packs. There are huge
unimplemented holes here where the abstraction pattern will
need to tell us how many elements to gather into the pack,
but a naive approach is good enough to get things off the
ground.
- Pack conventions are related to the existing parameter and
result conventions, but they're different on enough grounds
that they deserve to be separated.
Previously, only @owned function arguments were treated as lexical.
Here, all function arguments which report themselves to be lexical (i.e.
based on their type and annotation) are regarded as lexical.
This allows code to handle terminator results similar to any other
instruction result. Data flow should generally handle terminator
results like any other instruction that may forward operand ownership
to its results. The fact that it is represented as a block argument is
an implementation detail that gets in the way of conceptual
simplicity.
`getValue` -> `value`
`getValueOr` -> `value_or`
`hasValue` -> `has_value`
`map` -> `transform`
The old API will be deprecated in the rebranch.
To avoid merge conflicts, use the new API already in the main branch.
rdar://102362022
This lets us write optimizer unit tests and selectively debug the
optimizer in general. We'll be able trace analyses and control
optimization selectively for certain values.
Adding a trace flag to debug_value is the easiest way to start using
it experimentally and develop the rest of the infrastructure. If this
takes off, then we can consider a new `trace_value`
instruction. For now, reusing debug_value is the least intrusive way to
start writing liveness unit tests.
Previously, every owned argument was considered lexical. That included
owned arguments with eager move lifetimes. Here, only owned arguments
whose lifetimes are lexical (i.e. not those whose lifetimes are eager
move) are considered lexical.
Andy some time ago already created the new API but didn't go through and update
the old occurences. I did that in this PR and then deprecated the old API. The
tree is clean, so I could just remove it, but I decided to be nicer to
downstream people by deprecating it first.
The destroys of owned arguments must not be hoisted over deinit barriers
to respect the semantics of lexical lifetimes. Here, owned SILValues
which are SILFunctionArguments are made to be lexical. Code which
hoists destroys will check this field and not hoist destroys.
Allowed more RAUWing to be done with lexical values; specifically,
allowed the replacement of a non-lexical value with a lexical value if
the replacing value's lifetime wouldn't be extended as a result.
Updated tests to get owned values via applies rather than having them
passed as arguments.
It just trampolines to calling ValueBase::dump(). The reason I added this is
sometimes one wants to do this in the debugger and confuses if one has a
ValueBase or a SILValue causing lldb to be unhappy. By mirroring this API, one
can do the same operation on either type and just move on with ones life.
I also wrapped this in LLVM_ATTRIBUTE_DEPRECATED with a msg saying only for use
in the debugger so people do not call dump() in the codebase by mistake.
Add OwnershipForwardingTermInst::createResult(SILBasicBlock, SILType)
Add SwitchEnumInst::createDefaultResult()
Add SwitchEnumInst::createOptionalSomeResult(), which handles most
compiler-generated switches.
Basic API for creating terminator results with consistent
ownership. This allows enabling OSSA verification on terminator
results. It fixes current issues, but is also a prerequisite for OSSA
simplify-cfg.
For switch_enum, this ensures that the default argument consistently
either forwards the original value, or handles the payload of the
unique case (the unique payload was already being inferred for
ownership, but the block argument was inconsistent with that fact).
switch_enum and checked_cast_br specify their forwarding
ownership. This can differ from their operand ownership.
For example:
%e = enum $Optional<AnyObject>, #Optional.none!enumelt
switch_enum %e : $Optional<AnyObject>,
case #Optional.some!enumelt: bb2...
bb2(%arg : @owned T):
Independent forwarding ownership is only supported with terminators in this
change, but in the near term it will be used for all forwarding
operations to support implicit borrow scopes.
This removes the ambiguity when casting from a SingleValueInstruction to SILNode, which makes the code simpler. E.g. the "isRepresentativeSILNode" logic is not needed anymore.
Also, it reduces the size of the most used instruction class - SingleValueInstruction - by one pointer.
Conceptually, SILInstruction is still a SILNode. But implementation-wise SILNode is not a base class of SILInstruction anymore.
Only the two sub-classes of SILInstruction - SingleValueInstruction and NonSingleValueInstruction - inherit from SILNode. SingleValueInstruction's SILNode is embedded into a ValueBase and its relative offset in the class is the same as in NonSingleValueInstruction (see SILNodeOffsetChecker).
This makes it possible to cast from a SILInstruction to a SILNode without knowing which SILInstruction sub-class it is.
Casting to SILNode cannot be done implicitly, but only with an LLVM `cast` or with SILInstruction::asSILNode(). But this is a rare case anyway.
This removes the ambiguity when casting from a SingleValueInstruction to SILNode, which makes the code simpler. E.g. the "isRepresentativeSILNode" logic is not needed anymore.
Also, it reduces the size of the most used instruction class - SingleValueInstruction - by one pointer.
Conceptually, SILInstruction is still a SILNode. But implementation-wise SILNode is not a base class of SILInstruction anymore.
Only the two sub-classes of SILInstruction - SingleValueInstruction and NonSingleValueInstruction - inherit from SILNode. SingleValueInstruction's SILNode is embedded into a ValueBase and its relative offset in the class is the same as in NonSingleValueInstruction (see SILNodeOffsetChecker).
This makes it possible to cast from a SILInstruction to a SILNode without knowing which SILInstruction sub-class it is.
Casting to SILNode cannot be done implicitly, but only with an LLVM `cast` or with SILInstruction::asSILNode(). But this is a rare case anyway.
Sometimes when you are working with SILValues, you need a "next" insertion
point. This creates a problem with the SILValue API since even though one can
get an instruction or an insertion point for a value, to find the appropriate
next instruction one needs to pierce through the API and see if one has a
SILArgument or SILInstruction breaking the whole point of abstraction. The
specific problem here is that a SILArgument's "next instruction" is the first
element of the block (that is ValueBase::getDefiningInsertionPoint()) and
SILInstruction's "next instruction" is
std::next(ValueBase::getDefiningInsertionPoint()). This new
API (ValueBase::getNextInstruction()) handles this case for the compiler writer
and eliminates unnecessary code contortions.
I also did a little cleanup where I moved a doxygen comment from a near by a
const_casting trampoline method to the method that the trampoline called (see
getDefiningInsertionPoint()).
Now that OperandOwnership determines the operand constraints, it
doesn't make sense to distinguish between Borrow and NestedBorrow at
this level. We want these uses to automatically convert between the
nested/non-nested state as the operand's ownership changes. The use
does not need to impose any constraint on the ownership of the
incoming value.
For algorithms that need to distinguish nested borrows, it's still
trivial to do so.
A NonUse operand does not use the value itself, so it ignores
ownership and does not require liveness. This is for operands that
represent dependence on a type but are not actually passed the value
of that type (e.g. they may refer an open_existential). This could be
used for other dependence-only operands in the future.
A TrivialUse operand has undefined ownership semantics aside from
requiring liveness. Therefore it is only legal to pass the use a value
with ownership None (a trivial value). Contrast this with things like
InstantaneousUse or BitwiseEscape, which just don't care about
ownership (i.e. they have no ownership semantics.
All of the explicitly listed operations in this category require
trivially typed operands. So the meaning is obvious to anyone
adding SIL operations and updating OperandOwnership.cpp, without
needing to decifer the value ownership kinds.
Clarify which uses are allowed to take Unowned values. Add enforcement
to ensure that Unowned values are not passed to other uses.
Operations that can take unowned are:
- copy_value
- apply/return @unowned argument
- aggregates (struct, tuple, destructure, phi)
- forwarding operations that are arbitrary type casts
Unowned values are currently borrowed within ObjC deinitializers
materialized by the Swift compiler. This will be banned as soon as
SILGen is fixed.
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.
This makes it easier to understand conceptually why a ValueOwnershipKind with
Any ownership is invalid and also allowed me to explicitly document the lattice
that relates ownership constraints/value ownership kinds.
This verifier validates that while a load_borrow's value is live (that is until
it is invalidated by its end_borrow), the load_borrow's address source is never
written to.
The reason why this verifier is especially important now is that I am adding
many optimizations that convert `load [copy]` -> `load_borrow`. If that
optimization messes up, we break this invariant [in fact, an optimization I am
working on right now violated the invariant =--(]. So by adding this verifier I
am checking that semantic arc opts doesn't break it as well as eliminating any
other such bugs from the compiler (in the future).
Specifically, I split it into 3 initial categories: IR, Utils, Verifier. I just
did this quickly, we can always split it more later if we want.
I followed the model that we use in SILOptimizer: ./lib/SIL/CMakeLists.txt vends
a macro (sil_register_sources) to the sub-folders that register the sources of
the subdirectory with a global state variable that ./lib/SIL/CMakeLists.txt
defines. Then after including those subdirs, the parent cmake declares the SIL
library. So the output is the same, but we have the flexibility of having
subdirectories to categorize source files.