Skip to content

Commit f4176b9

Browse files
committed
[SIL-opaque] Code review suggestions
Mostly documentation and typos.
1 parent b187ba0 commit f4176b9

File tree

4 files changed

+101
-73
lines changed

4 files changed

+101
-73
lines changed

docs/SIL.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2193,6 +2193,26 @@ parts::
21932193
return %1 : $Klass
21942194
}
21952195

2196+
Forwarding Address-Only Values
2197+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2198+
2199+
Address-only values are potentially unmovable when borrowed. This
2200+
means that they cannot be forwarded with guaranteed ownership unless
2201+
the forwarded value has the same representation as in the original
2202+
value and can reuse the same storage. Non-destructive projection is
2203+
allowed, such as `struct_extract`. Aggregation, such as `struct`, and
2204+
destructive disaggregation, such as `switch_enum` is not allowed. This
2205+
is an invariant for OSSA with opaque SIL values for these reasons:
2206+
2207+
1. To avoid implicit semantic copies. For move-only values, this allows
2208+
complete diagnostics. And in general, it makes it impossible for SIL
2209+
passes to "accidentally" create copies.
2210+
2211+
2. To reuse borrowed storage. This allows the optimizer to share the same
2212+
storage for multiple exclusive reads of the same variable, avoiding
2213+
copies. It may also be necessary to support native Swift atomics, which
2214+
will be unmovable-when-borrowed.
2215+
21962216
Borrowed Object based Safe Interior Pointers
21972217
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21982218

lib/SIL/Verifier/SILVerifier.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,13 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
10081008
auto *TI = predBB->getTerminator();
10091009
if (F.hasOwnership()) {
10101010
require(isa<BranchInst>(TI), "All phi inputs must be branch operands.");
1011+
1012+
// Address-only values are potentially unmovable when borrowed. See also
1013+
// checkOwnershipForwardingInst. A phi implies a move of its arguments
1014+
// because they can't necessarilly all reuse the same storage.
1015+
require((!arg->getType().isAddressOnly(F)
1016+
|| arg->getOwnershipKind() != OwnershipKind::Guaranteed),
1017+
"Guaranteed address-only phi not allowed--implies a copy");
10111018
} else {
10121019
// FIXME: when critical edges are removed and cond_br arguments are
10131020
// disallowed, only allow BranchInst.
@@ -1269,10 +1276,11 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
12691276
checkOwnershipForwardingTermInst(term);
12701277
}
12711278

1272-
// Address-only values are potentially move-only, and unmovable if they are
1273-
// borrowed. Ensure that guaranteed address-only values are forwarded with
1274-
// the same representation. Non-destructive projection is
1275-
// allowed. Aggregation and destructive disaggregation is not allowed.
1279+
// Address-only values are potentially unmovable when borrowed. Ensure that
1280+
// guaranteed address-only values are forwarded with the same
1281+
// representation. Non-destructive projection is allowed. Aggregation and
1282+
// destructive disaggregation is not allowed. See SIL.rst, Forwarding
1283+
// Addres-Only Values.
12761284
if (ownership == OwnershipKind::Guaranteed
12771285
&& OwnershipForwardingMixin::isAddressOnly(i)) {
12781286
require(OwnershipForwardingMixin::hasSameRepresentation(i),

lib/SILOptimizer/Mandatory/AddressLowering.cpp

Lines changed: 42 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,23 @@
1414
/// memory locations such as a stack locations. This is mandatory for IRGen.
1515
///
1616
/// Lowering to LLVM IR requires each SILValue's type to be a valid "SIL storage
17-
/// type". Opaque SILValues have address-only types. Address-only values require
18-
/// indirect storage in LLVM, so their SIL storage type must be an address type.
17+
/// type". Opaque SILValues have address-only types. These require indirect
18+
/// storage in LLVM, so their SIL storage type must be an address type.
1919
///
20-
/// This pass should not introduce any semantic copies. Guaranteed values always
21-
/// reuse the borrowed value's storage. This means that we SIL cannot allow
22-
/// guaranteed opaque uses unless they are projections of the definition. In
23-
/// particular, borrowed structs, tuples, and enums of address-only types are
24-
/// not allowed.
20+
/// This pass never creates copies except to replace explicit value copies
21+
/// (copy_value, load [copy], store). For move-only values, this allows complete
22+
/// diagnostics. And in general, it makes it impossible for SIL passes to
23+
/// "accidentally" create copies.
2524
///
26-
/// When owned values are consumed by phis, multiple storage locations are
27-
/// required to avoid interfering with other phi operands. However, the value
28-
/// never needs to be live in multiple storage locations a once. When the value
29-
/// is consumed by a phi, either it's own storage is coalesced with the phi
30-
/// storage (they have the same address), or the value is bitwise moved into the
31-
/// phi's storage.
25+
/// This pass inserts moves (copy_addr [take] [initialize]) of owned values to
26+
/// - compose aggregates
27+
/// - resolve phi interference
28+
///
29+
/// For guarantee values, this pass inserts neither copies nor moves. Opaque
30+
/// values are potentially unmovable when borrowed. This means that guaranteed
31+
/// address-only aggregates and phis are prohibited. This SIL invariant is
32+
/// enforced by SILVerifier::checkOwnershipForwardingInst() and
33+
/// SILVerifier::visitSILPhiArgument().
3234
///
3335
/// ## Step #1: Map opaque values
3436
///
@@ -58,7 +60,8 @@
5860
/// during rewriting.
5961
///
6062
/// After allocating storage for all non-phi opaque values, phi storage is
61-
/// allocated. This is handled by a PhiStorageOptimizer that checks for
63+
/// allocated. (Phi values are block arguments in which phi's arguments are
64+
/// branch operands). This is handled by a PhiStorageOptimizer that checks for
6265
/// interference among the phi operands and reuses storage allocated to other
6366
/// values.
6467
///
@@ -169,7 +172,7 @@ cleanupAfterCall(FullApplySite apply,
169172
// Calls are currently SILValues, but when the result type is a tuple, the call
170173
// value does not represent a real value with storage. This is a bad situation
171174
// for address lowering because there's no way to tell from any given value
172-
// whether its legal to assign storage to that value. As a result, the
175+
// whether it's legal to assign storage to that value. As a result, the
173176
// implementation of call lowering doesn't fall out naturally from the algorithm
174177
// that lowers values to storage.
175178
//===----------------------------------------------------------------------===//
@@ -218,7 +221,7 @@ visitCallResults(FullApplySite apply,
218221

219222
/// Return true if the given value is either a "fake" tuple that represents all
220223
/// of a call's results or an empty tuple of no results. This may return true
221-
/// for either tuple_inst or a block argument.
224+
/// for either an apply instruction or a block argument.
222225
static bool isPseudoCallResult(SILValue value) {
223226
if (auto *apply = dyn_cast<ApplyInst>(value))
224227
return ApplySite(apply).getSubstCalleeConv().getNumDirectSILResults() > 1;
@@ -255,7 +258,7 @@ static bool isPseudoReturnValue(SILValue value) {
255258
/// the tuple is a pseudo-return value, return the indirect function argument
256259
/// for the corresponding result after lowering.
257260
///
258-
/// bb0(%loweredIndirectResult : $*T, ...)
261+
/// bb0(..., %loweredIndirectResult : $*T, ...)
259262
/// ....
260263
/// %tuple = tuple(..., %operand, ...)
261264
/// return %tuple
@@ -268,16 +271,12 @@ static bool isPseudoReturnValue(SILValue value) {
268271
/// (see insertIndirectReturnArgs()).
269272
static SILValue getTupleStorageValue(Operand *operand) {
270273
auto *tuple = cast<TupleInst>(operand->getUser());
271-
Operand *singleUse = tuple->getSingleUse();
272-
if (!singleUse || !isa<ReturnInst>(singleUse->getUser()))
273-
return tuple;
274-
275-
SILFunction *function = tuple->getFunction();
276-
if (function->getConventions().getNumDirectSILResults() < 2)
274+
if (!isPseudoReturnValue(tuple))
277275
return tuple;
278276

279277
unsigned resultIdx = tuple->getElementIndex(operand);
280278

279+
auto *function = tuple->getFunction();
281280
auto loweredFnConv = getLoweredFnConv(function);
282281
assert(loweredFnConv.getResults().size() == tuple->getElements().size());
283282

@@ -286,24 +285,22 @@ static SILValue getTupleStorageValue(Operand *operand) {
286285
if (loweredFnConv.isSILIndirect(result))
287286
++indirectResultIdx;
288287
}
289-
// Cannot call F->getIndirectSILResults here because that API uses the
288+
// Cannot call function->getIndirectSILResults here because that API uses the
290289
// function conventions before address lowering.
291290
return function->getArguments()[indirectResultIdx];
292291
}
293292

294293
/// Return the value representing storage for a single return value.
295294
///
296-
/// bb0(%loweredIndirectResult : $*T, ...) // function entry
295+
/// bb0(..., %loweredIndirectResult : $*T, ...) // function entry
297296
/// return %oper
298297
///
299298
/// For %oper, return %loweredIndirectResult
300299
static SILValue getSingleReturnAddress(Operand *operand) {
301300
assert(!isPseudoReturnValue(operand->get()));
302301

303302
auto *function = operand->getParentFunction();
304-
auto loweredFnConv = getLoweredFnConv(function);
305-
assert(loweredFnConv.getNumIndirectSILResults() == 1);
306-
(void)loweredFnConv;
303+
assert(getLoweredFnConv(function).getNumIndirectSILResults() == 1);
307304

308305
// Cannot call getIndirectSILResults here because that API uses the
309306
// function conventions before address lowering.
@@ -331,17 +328,15 @@ static bool isStoreCopy(SILValue value) {
331328
return isa<StoreInst>(user) || isa<AssignInst>(user);
332329
}
333330

334-
ValueStorage &ValueStorageMap::insertValue(SILValue value) {
331+
void ValueStorageMap::insertValue(SILValue value, SILValue storageAddress) {
335332
assert(!stableStorage && "cannot grow stable storage map");
336333

337334
auto hashResult =
338335
valueHashMap.insert(std::make_pair(value, valueVector.size()));
339336
(void)hashResult;
340337
assert(hashResult.second && "SILValue already mapped");
341338

342-
valueVector.emplace_back(value, ValueStorage());
343-
344-
return valueVector.back().storage;
339+
valueVector.emplace_back(value, ValueStorage(storageAddress));
345340
}
346341

347342
void ValueStorageMap::replaceValue(SILValue oldValue, SILValue newValue) {
@@ -409,7 +404,7 @@ struct AddressLoweringState {
409404
SmallBlotSetVector<FullApplySite, 16> indirectApplies;
410405

411406
// All function-exiting terminators (return or throw instructions).
412-
SmallVector<SILInstruction *, 8> exitingInsts;
407+
SmallVector<TermInst *, 8> exitingInsts;
413408

414409
// Copies from a phi's operand storage to the phi storage. These logically
415410
// occur on the CFG edge. Keep track of them to resolve anti-dependencies.
@@ -462,7 +457,7 @@ struct AddressLoweringState {
462457
/// Before populating the ValueStorageMap, replace each value-typed argument to
463458
/// the current function with an address-typed argument by inserting a temporary
464459
/// load instruction.
465-
static void convertIndirectFunctionArgs(AddressLoweringState &pass) {
460+
static void convertDirectToIndirectFunctionArgs(AddressLoweringState &pass) {
466461
// Insert temporary argument loads at the top of the function.
467462
SILBuilder argBuilder =
468463
pass.getBuilder(pass.function->getEntryBlock()->begin());
@@ -490,9 +485,7 @@ static void convertIndirectFunctionArgs(AddressLoweringState &pass) {
490485
// Indirect calling convention may be used for loadable types. In that
491486
// case, generating the argument loads is sufficient.
492487
if (addrType.isAddressOnly(*pass.function)) {
493-
auto &storage = pass.valueStorageMap.insertValue(loadArg);
494-
storage.storageAddress = arg;
495-
storage.isRewritten = true;
488+
pass.valueStorageMap.insertValue(loadArg, arg);
496489
}
497490
}
498491
++argIdx;
@@ -520,10 +513,9 @@ static unsigned insertIndirectReturnArgs(AddressLoweringState &pass) {
520513
argIdx, bodyResultTy.getAddressType(), OwnershipKind::None, var);
521514
// Insert function results into valueStorageMap so that the caller storage
522515
// can be projected onto values inside the function as use projections.
523-
auto &storage = pass.valueStorageMap.insertValue(funcArg);
516+
//
524517
// This is the only case where a value defines its own storage.
525-
storage.storageAddress = funcArg;
526-
storage.isRewritten = true;
518+
pass.valueStorageMap.insertValue(funcArg, funcArg);
527519

528520
++argIdx;
529521
}
@@ -621,10 +613,11 @@ void OpaqueValueVisitor::visitValue(SILValue value) {
621613
pass.valueStorageMap.getStorage(value).storageAddress));
622614
return;
623615
}
624-
pass.valueStorageMap.insertValue(value);
616+
pass.valueStorageMap.insertValue(value, SILValue());
625617
}
626618

627-
// Canonicalize returned values.
619+
// Canonicalize returned values. For multiple direct results, the operand of the
620+
// return instruction must be a tuple with no other uses.
628621
//
629622
// Given $() -> @out (T, T):
630623
// %t = def : $(T, T)
@@ -688,7 +681,7 @@ void OpaqueValueVisitor::canonicalizeReturnValues() {
688681
/// function.
689682
static void prepareValueStorage(AddressLoweringState &pass) {
690683
// Fixup this function's argument types with temporary loads.
691-
convertIndirectFunctionArgs(pass);
684+
convertDirectToIndirectFunctionArgs(pass);
692685

693686
// Create a new function argument for each indirect result.
694687
insertIndirectReturnArgs(pass);
@@ -2012,7 +2005,7 @@ void ApplyRewriter::rewriteApply(ArrayRef<SILValue> newCallArgs) {
20122005

20132006
// Replace \p tryApply with a new try_apply using \p newCallArgs.
20142007
//
2015-
// If the old result was a single address-only value, then create and return a
2008+
// If the old result was a single opaque value, then create and return a
20162009
// fake load that takes its place in the storage map. Otherwise, return an
20172010
// invalid SILValue.
20182011
//
@@ -3056,8 +3049,8 @@ static void removeOpaquePhis(SILBasicBlock *bb, AddressLoweringState &pass) {
30563049
}
30573050
}
30583051

3059-
// Instructions that use an address-only value without producing one are already
3060-
// deleted. The rest of the address-only definitions are now removed bottom-up
3052+
// Instructions that use an opaque value without producing one are already
3053+
// deleted. The rest of the opaque definitions are now removed bottom-up
30613054
// by visiting valuestorageMap.
30623055
//
30633056
// Phis are removed here after all other instructions.
@@ -3145,12 +3138,12 @@ void AddressLowering::runOnFunction(SILFunction *function) {
31453138
// ## Step #1: Map opaque values
31463139
//
31473140
// First, rewrite this function's arguments and return values, then populate
3148-
// pass.valueStorageMap with an entry for each address-only value.
3141+
// pass.valueStorageMap with an entry for each opaque value.
31493142
prepareValueStorage(pass);
31503143

31513144
// ## Step #2: Allocate storage
31523145
//
3153-
// For each address-only value mapped in step #1, either create an
3146+
// For each opaque value mapped in step #1, either create an
31543147
// alloc_stack/dealloc_stack pair, or mark its ValueStorage entry as a
31553148
// def-projection out of its operand's def or a use projection into its
31563149
// composing use or into a phi (branch operand).
@@ -3162,7 +3155,7 @@ void AddressLowering::runOnFunction(SILFunction *function) {
31623155

31633156
// ## Step #3. Rewrite opaque values
31643157
//
3165-
// Rewrite all instructions that either define or use an address-only value.
3158+
// Rewrite all instructions that either define or use an opaque value.
31663159
// Creates new '_addr' variants of instructions, obtaining the storage
31673160
// address from the 'valueStorageMap'. This materializes projections in
31683161
// forward order, setting 'storageAddress' for each projection as it goes.

0 commit comments

Comments
 (0)