`shouldEmitPartialMutationErrorForType` asserted that a nominal type
reaching its cross-module-use branch had formal access scope
public-or-package from the calling function's DeclContext:
assert(nominal
->getFormalAccessScope(fn->getDeclContext(),
/*treatUsableFromInlineAsPublic=*/true)
.isPublicOrPackage());
That check was correct when added (in `da968dbd58d`, "Ban exported
partial consumption"), but breaks whenever a public type from another
module is imported with restricted access. Two cases trip it:
- `internal import Library` (the assertion fires with no feature flags
at all)
- `-enable-upcoming-feature InternalImportsByDefault`, which is the
originally-reported manifestation
In both cases a partial consume of a `@frozen public ~Copyable` field
hits the assertion. The downstream logic
(`hasExplicitFixedLayoutAnnotation` → allow/reject) doesn't depend on
the scope value — the assertion is purely informational. Replace the
over-strict check with a comment explaining the invariant (the type is
visible from `fn`'s DC; access scope may be `Internal` when the client's
import is internal).
Adds
`test/SILOptimizer/moveonly_addresschecker_diagnostics_partial_consume_internal_imports.swift`
which uses a plain `internal import Library` (no feature flags) and
covers both the `@frozen` (compiles cleanly) and non-`@frozen`
(diagnosed as `cannot partially consume ... of non-frozen type ...
imported from 'Library'`) cases. Verified by reverting the relaxation:
the test triggers exactly the original assertion at
MoveOnlyAddressCheckerUtils.cpp:1829.
Address results from borrow accessors should be treated as endpoint uses
in the move-only checker. The result address has its own mark_unresolved_non_copyable_value
that gets checked separately.
**Explanation**: Fix a `MoveOnlyChecker` assertion crash on noncopyable
`let`s that have two stacked `mark_unresolved_non_copyable_value`
instructions on the same `alloc_stack` — one gating the init store,
another gating reads.
**Scope**: Defensive fix in the move-only checker. Converts a
user-visible assertion crash into a clean compile. No effect on code
that wasn't already hitting the assertion.
**Risk**: Very low. Single narrow condition added to an existing method;
existing behavior preserved for all other patterns. No SIL output
changes for any test that was previously compiling.
**Testing**: New `.sil` regression test with two variants covering both
admitted outer-mark check kinds, plus existing SILGen and SILOptimizer
tests.
`MoveOnlyAddressCheckerPImpl::addressBeginsInitialized` decides whether
a marked address arrives already-initialized; if not, the use-walk needs
to find an init `store`, and `finishedInitializationOfDefs` later
asserts at least one def was recorded. None of the existing special
cases recognized a `MarkUnresolvedNonCopyableValueInst` whose operand is
itself a `MarkUnresolvedNonCopyableValueInst`. SILGen emits stacked
marks when an outer mark (`[consumable_and_assignable]` or
`[initable_but_not_consumable]`) gates an init store and an inner
`[no_consume_or_assign]` mark layered on top gates the binding's reads.
The inner mark fell through every branch, returned `false`, no
`initializeDef` was ever called, and `finishedInitializationOfDefs`
asserted `(isInitialized())`. This PR adds a branch that treats the
inner mark as beginning-initialized when its operand is an outer mark
whose check kind permits init (`ConsumableAndAssignable` or
`InitableButNotConsumable`); other check kinds fall through to existing
heuristics unchanged.
- **Explanation**: Defensive prep for an upcoming SILGen change. Teaches
`CopiedLoadBorrowEliminationVisitor` to recognize a noncopyable
`load_borrow` → on-stack `partial_apply` → `convert_function` →
`destroy_value` chain, instead of hitting `llvm_unreachable("We should
never hit this")`
- **Scope**: Narrow change to one case of one visitor in the move-only
checker. The crash isn't reachable from vanilla Swift source on current
`main` — no code path produces the triggering SIL shape today. Lands
ahead of a SILGen change that will route noncopyable `let` captures into
non-escaping closures through on-stack `partial_apply`, at which point a
`@MainActor`-isolated noescape async closure body introduces the
intervening `convert_function` (stripping `@Sendable`) that this fix
accommodates.
- **Risk**: Very low. The edit is scoped to the
`ForwardingConsume`/`DestroyingConsume` arm of a single `switch` in
`MoveOnlyAddressCheckerUtils.cpp::CopiedLoadBorrowEliminationVisitor::visitUse`.
The original invariant (only `destroy_value` users accepted) is
preserved by an explicit user-type check, but broadened to include
`convert_function` chains back to the on-stack `partial_apply`. Both
`llvm_unreachable`s still fire for anything outside the expected shape.
The broadened path only fires on a SIL shape that current SILGen doesn't
produce, so in principle no existing test should exercise it — this is a
defensive-preparation change whose behavioral effect is required for an
upcoming SILGen PR.
- **Testing**: New `.sil` regression test at
`test/SILOptimizer/moveonly_addresschecker_convert_function_onstack.sil`
with two variants (single `convert_function` between PA and destroy;
chained pair). Verified by reverting the fix — the
single-`convert_function` variant triggers exactly the
`llvm_unreachable` it's designed to catch.
`CopiedLoadBorrowEliminationVisitor` walks forward from a noncopyable
`load_borrow`, tracking uses via `OperandOwnership`. An on-stack
`partial_apply` sees its `load_borrow` operand as
`OperandOwnership::Borrow`; the `Borrow` case then walks the PA's
results. Those PA-result uses can have
`OperandOwnership::ForwardingConsume`/`DestroyingConsume` (on-stack PA
has `OwnershipKind::Owned` in OSSA, and `convert_function` forwards that
ownership). The existing special case only recognized a direct
`destroy_value` of the PA and fired `llvm_unreachable` on any
intermediary.
The fix keeps the original syntactic invariant — the user must be a
`destroy_value` or a `convert_function` — and broadens the operand check
to walk backward through a `convert_function` chain to find the
underlying `PartialApplyInst`. If it's on-stack, the consuming use
closes the borrow scope rather than consuming the captured noncopyable.
When the use is itself a `convert_function` (forwarding consume rather
than terminating), its uses are pushed onto the worklist so the
downstream `destroy_value`/`apply` is visited with the right context.
Mirrors the existing look-through-`convert_function` treatment in the
forwarding traversal earlier in this file. Deliberately does not
generalize to other forwarding owners (e.g., `move_value`,
`mark_dependence`, `convert_escape_to_noescape`) since they are not
produced by SILGen between an on-stack PA and its destroy today.
We cannot use spare bits or other overlapping storage layout tricks with fundamentally
address-only enums, and we can take advantage of this to do borrowing switches or other
in-place projections without copying the value. However, for resilient enums, the
implementation may use spare bit packing, but the type must be handled address-only
outside of its defining module, and we didn't have a way to express that with
borrowing switch. Optimization passes have also been running into problems with the
complexity that we were using `unchecked_take_enum_data_addr` sometimes as a pure
operation. This patch splits the instruction into three:
- `unchecked_inplace_enum_data_addr` represents a nondestructive in-place enum
projection. It is only allowed for enums whose projection operation is
nondestructive.
- `unchecked_take_enum_data_addr` represents a destructive enum projection,
invalidating the enum and leaving the payload to be further consumed.
This matches the current instruction's semantics.
- `unchecked_borrow_enum_data_addr` represents a borrowing enum projection.
The instruction takes a second operand for "scratch" space, which the
enum representation may be copied into in order to avoid invalidating the
enum value, so the result is dependent on the lifetime of both the
original enum and the scratch buffer. This allows for borrowing switches
over resilient enums.
`unchecked_borrow_enum_data_addr` is implemented by taking advantage of the
"address-only enums can't do spare bit optimization" property at runtime.
We inspect the operand type's bitwise-borrowability from its metadata. If
the type is bitwise-borrowable, then we are allowed to bitwise-copy the
enum to the scratch space and apply the projection to the scratch space,
preserving the original value. If the type is not bitwise-borrowable, then
we cannot use spare bit optimization in its layout, so we apply the
projection in-place.
Fixes rdar://174952822.
Enable walking into `TypeOffsetSizePair`s from an existential into an
archetype. And set the access kind on `open_existential_addr`
instructions which are the sources of rewritten `copy_addr`s to mutable.
rdar://141279635
A trivial store is allowed to occur on an existing live value, and should not
trigger an attempt to destroy the original value completely. Fixes rdar://147791932.
i```
swift\lib\SILOptimizer\Mandatory\MoveOnlyAddressCheckerUtils.cpp(2981): warning C5030: attribute [[clang::fallthrough]] is not recognized
```
Replace the use of `clang::fallthrough` with `LLVM_FALLTHROUGH` which
properly uses the C++ standard spelling (`[[fallthrough]]`) depending on
the compiler version.
This is necessary to fix a recent OSSA bug that breaks common occurrences on
mark_dependence [nonescaping]. Rather than reverting that change above, we make
forward progress toward implicit borrows scopes, as was the original intention.
In the near future, all InteriorPointer instructions will create an implicit
borrow scope. This means we have the option of not emitting extraneous
begin/end_borrow instructions around intructions like ref_element_addr,
open_existential, and project_box. After that, we can also migrate
GuaranteedForwarding instructions like tuple_extract and struct_extract.
Unfortunately, importing them as is results in ambiguous call sites.
E.g., std::vector::push_back has overloads for lvalue reference and
rvalue reference and we have no way to distinguish them at the call site
in Swift. To overcome this issue, functions with rvalue reference
parameters are imported with 'consuming:' argument labels.
Note that, in general, move only types and consuming is not properly
supported in Swift yet. We do not invoke the dtor for the moved-from
objects. This is a preexisting problem that can be observed with move
only types before this PR, so the fix will be done in a separate PR.
Fortunately, for most types, the moved-from objects do not require
additional cleanups.
rdar://125816354
A begin_apply token may be used by operands that do not end the coroutine:
mark_dependence.
We need an API that gives us only the coroutine-ending uses. This blocks
~Escapable accessors.
end_borrow is considered coroutine-ending even though it does not actually
terminate the coroutine.
We cannot simply ask isLifetimeEnding, because end_apply and abort_apply do not
end any lifetime.
While the FIXME to derive whether an address begins initialized requires
auditing all sites where the instruction is emitted to begin with,
making this a predicate that depends only on the instruction can be done
now.
The changes to allow for partial consumption unintentionally also allowed for
`self` to be consumed as a whole during `deinit`, which we don't yet want to
allow because it could lead to accidental "resurrection" and/or accidental
infinite recursion if the consuming method lets `deinit` be implicitly run
again. This makes it an error again. The experimental feature
`ConsumeSelfInDeinit` will allow it for test coverage or experimentation
purposes. rdar://132761460
This corresponds to the parameter-passing convention of the Itanium C++
ABI, in which the argument is passed indirectly and possibly modified,
but not destroyed, by the callee.
@in_cxx is handled the same way as @in in callers and @in_guaranteed in
callees. OwnershipModelEliminator emits the call to destroy_addr that is
needed to destroy the argument in the caller.
rdar://122707697
- While an opaque borrow access occurs to part of a value, the entire scope of
the access needs to be treated as a liveness range, so add the `EndAccess`es
to the liveness range.
- The SIL verifier may crash the compiler on SILGen-generated code when the
developer's source contains consume-during-borrow code patterns. Allow
`load_borrow` instructions to be marked `[unchecked]`, which suppresses
verifier checks until the move checker runs and gets a chance to properly
diagnose these errors.
Fixes rdar://124360175.
Create two versions of the following functions:
isConsumedParameter
isGuaranteedParameter
SILParameterInfo::isConsumed
SILParameterInfo::isGuaranteed
SILArgumentConvention::isOwnedConvention
SILArgumentConvention::isGuaranteedConvention
These changes will be needed when we add a new convention for
non-trivial C++ types as the functions will return different answers
depending on whether they are called for the caller or the callee. This
commit doesn't change any functionality.
This condition can occur in practice if, while doing the walk back to find the liveness
reason for a consume-without-reinitialization of an `inout` binding through conditional
control flow, we visit a block that reinitializes the binding before any branch that
leaves the binding uninitialized. Fixes rdar://123604613.
Although I don't plan to bring over new assertions wholesale
into the current qualification branch, it's entirely possible
that various minor changes in main will use the new assertions;
having this basic support in the release branch will simplify that.
(This is why I'm adding the includes as a separate pass from
rewriting the individual assertions)