Skip to content

Commit ee1d76e

Browse files
authored
[opt] Re-work broadenSingleElementStores to use projections. (#32318)
* Fixes loadable edge case for address-only types. * Re-work, generalize, and simplify by using projections. * Support `-enable-cxx-interop` in sil-opt.
1 parent f3e389c commit ee1d76e

File tree

7 files changed

+113
-45
lines changed

7 files changed

+113
-45
lines changed

include/swift/SIL/Projection.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ class Projection {
462462
static NullablePtr<SingleValueInstruction>
463463
createAggFromFirstLevelProjections(SILBuilder &B, SILLocation Loc,
464464
SILType BaseType,
465-
llvm::SmallVectorImpl<SILValue> &Values);
465+
ArrayRef<SILValue> Values);
466466

467467
void print(raw_ostream &os, SILType baseType) const;
468468
private:

lib/SIL/Utils/Projection.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -803,10 +803,9 @@ Projection::operator<(const Projection &Other) const {
803803
}
804804

805805
NullablePtr<SingleValueInstruction>
806-
Projection::
807-
createAggFromFirstLevelProjections(SILBuilder &B, SILLocation Loc,
808-
SILType BaseType,
809-
llvm::SmallVectorImpl<SILValue> &Values) {
806+
Projection::createAggFromFirstLevelProjections(
807+
SILBuilder &B, SILLocation Loc, SILType BaseType,
808+
ArrayRef<SILValue> Values) {
810809
if (BaseType.getStructOrBoundGenericStruct()) {
811810
return B.createStruct(Loc, BaseType, Values);
812811
}

lib/SILOptimizer/Utils/CanonicalizeInstruction.cpp

Lines changed: 37 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -315,68 +315,65 @@ splitAggregateLoad(LoadInst *loadInst, CanonicalizeInstruction &pass) {
315315
// (store (struct_element_addr %base) object)
316316
// ->
317317
// (store %base (struct object))
318+
//
319+
// TODO: supporting enums here would be very easy. The main thing is adding
320+
// support in `createAggFromFirstLevelProjections`.
321+
// Note: we will not be able to support tuples because we cannot have a
322+
// single-element tuple.
318323
static SILBasicBlock::iterator
319324
broadenSingleElementStores(StoreInst *storeInst,
320325
CanonicalizeInstruction &pass) {
321326
// Keep track of the next iterator after any newly added or to-be-deleted
322327
// instructions. This must be valid regardless of whether the pass immediately
323328
// deletes the instructions or simply records them for later deletion.
324329
auto nextII = std::next(storeInst->getIterator());
325-
326-
// Bail if the store's destination is not a struct_element_addr.
327-
auto *sea = dyn_cast<StructElementAddrInst>(storeInst->getDest());
328-
if (!sea)
329-
return nextII;
330-
331330
auto *f = storeInst->getFunction();
332331

333-
// Continue up the struct_element_addr chain, as long as each struct only has
334-
// a single property, creating StoreInsts along the way.
335-
SILBuilderWithScope builder(storeInst);
336-
337-
SILValue result = storeInst->getSrc();
338-
SILValue baseAddr = sea->getOperand();
339-
SILValue storeAddr;
340-
while (true) {
332+
ProjectionPath projections(storeInst->getDest()->getType());
333+
SILValue op = storeInst->getDest();
334+
while (isa<StructElementAddrInst>(op)) {
335+
auto *inst = cast<SingleValueInstruction>(op);
336+
SILValue baseAddr = inst->getOperand(0);
341337
SILType baseAddrType = baseAddr->getType();
342-
343-
// If our aggregate has unreferenced storage then we can never prove if it
344-
// actually has a single field.
345-
if (baseAddrType.aggregateHasUnreferenceableStorage())
346-
break;
347-
348338
auto *decl = baseAddrType.getStructOrBoundGenericStruct();
349339
assert(
350340
!decl->isResilient(f->getModule().getSwiftModule(),
351341
f->getResilienceExpansion()) &&
352342
"This code assumes resilient structs can not have fragile fields. If "
353343
"this assert is hit, this has been changed. Please update this code.");
354344

355-
// NOTE: If this is ever changed to support enums, we must check for address
356-
// only types here. For structs we do not have to check since a single
357-
// element struct with a loadable element can never be address only. We
358-
// additionally do not have to worry about our input value being address
359-
// only since we are storing into it.
360-
if (decl->getStoredProperties().size() != 1)
345+
// Bail if the store's destination is not a struct_element_addr or if the
346+
// store's destination (%base) is not a loadable type. If %base is not a
347+
// loadable type, we can't create it as a struct later on.
348+
// If our aggregate has unreferenced storage then we can never prove if it
349+
// actually has a single field.
350+
if (!baseAddrType.isLoadable(*f) ||
351+
baseAddrType.aggregateHasUnreferenceableStorage() ||
352+
decl->getStoredProperties().size() != 1)
361353
break;
362354

363-
// Update the store location now that we know it is safe.
364-
storeAddr = baseAddr;
355+
projections.push_back(Projection(inst));
356+
op = baseAddr;
357+
}
365358

366-
// Otherwise, create the struct.
367-
result = builder.createStruct(storeInst->getLoc(),
368-
baseAddrType.getObjectType(), result);
359+
// If we couldn't create a projection, bail.
360+
if (projections.empty())
361+
return nextII;
369362

370-
// See if we have another struct_element_addr we can strip off. If we don't
371-
// then this as much as we can promote.
372-
sea = dyn_cast<StructElementAddrInst>(sea->getOperand());
373-
if (!sea)
374-
break;
375-
baseAddr = sea->getOperand();
363+
// Now work our way back up. At this point we know all operations we are going
364+
// to do succeed (cast<SingleValueInst>, createAggFromFirstLevelProjections,
365+
// etc.) so we can omit null checks. We should not bail at this point (we
366+
// could create a double consume, or worse).
367+
SILBuilderWithScope builder(storeInst);
368+
SILValue result = storeInst->getSrc();
369+
SILValue storeAddr = storeInst->getDest();
370+
for (Projection proj : llvm::reverse(projections)) {
371+
storeAddr = cast<SingleValueInstruction>(storeAddr)->getOperand(0);
372+
result = proj.createAggFromFirstLevelProjections(
373+
builder, storeInst->getLoc(),
374+
storeAddr->getType().getObjectType(), {result})
375+
.get();
376376
}
377-
// If we failed to create any structs, bail.
378-
if (result == storeInst->getSrc())
379-
return nextII;
380377

381378
// Store the new struct-wrapped value into the final base address.
382379
builder.createStore(storeInst->getLoc(), result, storeAddr,

test/SILOptimizer/Inputs/foo.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#ifndef VALIDATION_TEST_SILOPTIMIZER_FOO_H
2+
#define VALIDATION_TEST_SILOPTIMIZER_FOO_H
3+
4+
struct Foo {
5+
int x;
6+
~Foo() {}
7+
};
8+
9+
struct Loadable {
10+
int x;
11+
};
12+
13+
struct Bar {
14+
Loadable y;
15+
~Bar() {}
16+
};
17+
18+
#endif // VALIDATION_TEST_SILOPTIMIZER_FOO_H
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module Foo {
2+
header "foo.h"
3+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// RUN: %target-sil-opt -silgen-cleanup %s -I %S/Inputs -enable-sil-verify-all -enable-cxx-interop | %FileCheck %s
2+
3+
sil_stage canonical
4+
5+
import Builtin
6+
import Swift
7+
import SwiftShims
8+
import Foo
9+
10+
// Make sure we don't try to create a struct here. Foo is not loadable, even
11+
// though it's only property is.
12+
// CHECK-LABEL: @test_foo
13+
// CHECK: bb0
14+
// CHECK-NEXT: [[E:%.*]] = struct_element_addr
15+
// CHECK-NEXT: store %1 to [trivial] [[E]]
16+
// CHECK-NEXT: tuple
17+
// CHECK-NEXT: return
18+
// CHECK-LABEL: end sil function 'test_foo'
19+
sil shared [transparent] [serializable] [ossa] @test_foo : $@convention(method) (Int32, @thin Foo.Type) -> @out Foo {
20+
bb0(%0 : $*Foo, %1 : $Int32, %2 : $@thin Foo.Type):
21+
%3 = struct_element_addr %0 : $*Foo, #Foo.x
22+
store %1 to [trivial] %3 : $*Int32
23+
%5 = tuple ()
24+
return %5 : $()
25+
}
26+
27+
// Make sure we create a struct for the first (loadable) type but not the second
28+
// type (Bar).
29+
// CHECK-LABEL: @test_bar
30+
// CHECK: bb0
31+
// CHECK-NEXT: [[E:%.*]] = struct_element_addr
32+
// CHECK-NEXT: [[AGG:%.*]] = struct $Loadable (%1 : $Int32)
33+
// CHECK-NEXT: store [[AGG]] to [trivial] [[E]]
34+
// CHECK-NEXT: tuple
35+
// CHECK-NEXT: return
36+
// CHECK-LABEL: end sil function 'test_bar'
37+
sil shared [transparent] [serializable] [ossa] @test_bar : $@convention(method) (Int32, @thin Bar.Type) -> @out Bar {
38+
bb0(%0 : $*Bar, %1 : $Int32, %2 : $@thin Bar.Type):
39+
%3 = struct_element_addr %0 : $*Bar, #Bar.y
40+
%3a = struct_element_addr %3 : $*Loadable, #Loadable.x
41+
store %1 to [trivial] %3a : $*Int32
42+
%5 = tuple ()
43+
return %5 : $()
44+
}

tools/sil-opt/SILOpt.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,11 @@ static cl::opt<std::string> RemarksFormat(
270270
cl::desc("The format used for serializing remarks (default: YAML)"),
271271
cl::value_desc("format"), cl::init("yaml"));
272272

273+
static llvm::cl::opt<bool>
274+
EnableCxxInterop("enable-cxx-interop",
275+
llvm::cl::desc("Enable C++ interop."),
276+
llvm::cl::init(false));
277+
273278
static void runCommandLineSelectedPasses(SILModule *Module,
274279
irgen::IRGenModule *IRGenMod) {
275280
auto &opts = Module->getOptions();
@@ -348,6 +353,8 @@ int main(int argc, char **argv) {
348353
Invocation.getLangOptions().EnableExperimentalDifferentiableProgramming =
349354
EnableExperimentalDifferentiableProgramming;
350355

356+
Invocation.getLangOptions().EnableCXXInterop = EnableCxxInterop;
357+
351358
Invocation.getDiagnosticOptions().VerifyMode =
352359
VerifyMode ? DiagnosticOptions::Verify : DiagnosticOptions::NoVerify;
353360

0 commit comments

Comments
 (0)