Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[opt] Re-work broadenSingleElementStores to use projections. #32318

Merged
merged 1 commit into from
Jun 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/swift/SIL/Projection.h
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ class Projection {
static NullablePtr<SingleValueInstruction>
createAggFromFirstLevelProjections(SILBuilder &B, SILLocation Loc,
SILType BaseType,
llvm::SmallVectorImpl<SILValue> &Values);
ArrayRef<SILValue> Values);

void print(raw_ostream &os, SILType baseType) const;
private:
Expand Down
7 changes: 3 additions & 4 deletions lib/SIL/Utils/Projection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -803,10 +803,9 @@ Projection::operator<(const Projection &Other) const {
}

NullablePtr<SingleValueInstruction>
Projection::
createAggFromFirstLevelProjections(SILBuilder &B, SILLocation Loc,
SILType BaseType,
llvm::SmallVectorImpl<SILValue> &Values) {
Projection::createAggFromFirstLevelProjections(
SILBuilder &B, SILLocation Loc, SILType BaseType,
ArrayRef<SILValue> Values) {
if (BaseType.getStructOrBoundGenericStruct()) {
return B.createStruct(Loc, BaseType, Values);
}
Expand Down
77 changes: 37 additions & 40 deletions lib/SILOptimizer/Utils/CanonicalizeInstruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -315,68 +315,65 @@ splitAggregateLoad(LoadInst *loadInst, CanonicalizeInstruction &pass) {
// (store (struct_element_addr %base) object)
// ->
// (store %base (struct object))
//
// TODO: supporting enums here would be very easy. The main thing is adding
// support in `createAggFromFirstLevelProjections`.
// Note: we will not be able to support tuples because we cannot have a
// single-element tuple.
static SILBasicBlock::iterator
broadenSingleElementStores(StoreInst *storeInst,
CanonicalizeInstruction &pass) {
// Keep track of the next iterator after any newly added or to-be-deleted
// instructions. This must be valid regardless of whether the pass immediately
// deletes the instructions or simply records them for later deletion.
auto nextII = std::next(storeInst->getIterator());

// Bail if the store's destination is not a struct_element_addr.
auto *sea = dyn_cast<StructElementAddrInst>(storeInst->getDest());
if (!sea)
return nextII;

auto *f = storeInst->getFunction();

// Continue up the struct_element_addr chain, as long as each struct only has
// a single property, creating StoreInsts along the way.
SILBuilderWithScope builder(storeInst);

SILValue result = storeInst->getSrc();
SILValue baseAddr = sea->getOperand();
SILValue storeAddr;
while (true) {
ProjectionPath projections(storeInst->getDest()->getType());
SILValue op = storeInst->getDest();
while (isa<StructElementAddrInst>(op)) {
auto *inst = cast<SingleValueInstruction>(op);
SILValue baseAddr = inst->getOperand(0);
SILType baseAddrType = baseAddr->getType();

// If our aggregate has unreferenced storage then we can never prove if it
// actually has a single field.
if (baseAddrType.aggregateHasUnreferenceableStorage())
break;

auto *decl = baseAddrType.getStructOrBoundGenericStruct();
assert(
!decl->isResilient(f->getModule().getSwiftModule(),
f->getResilienceExpansion()) &&
"This code assumes resilient structs can not have fragile fields. If "
"this assert is hit, this has been changed. Please update this code.");

// NOTE: If this is ever changed to support enums, we must check for address
// only types here. For structs we do not have to check since a single
// element struct with a loadable element can never be address only. We
// additionally do not have to worry about our input value being address
// only since we are storing into it.
if (decl->getStoredProperties().size() != 1)
// Bail if the store's destination is not a struct_element_addr or if the
// store's destination (%base) is not a loadable type. If %base is not a
// loadable type, we can't create it as a struct later on.
// If our aggregate has unreferenced storage then we can never prove if it
// actually has a single field.
if (!baseAddrType.isLoadable(*f) ||
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@atrick what do you think about supporting opaque types here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What cases are you trying to handle by doing that? Can you find out if it's possible to have
baseAddrType.isLoadable(*f) == true
AND
baseAddrType.aggregateHasUnreferenceableStorage() == false
??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean baseAddrType.isLoadable(*f) && baseAddrType.aggregateHasUnreferenceableStorage() == true? If not, I think that's most loadable types.

I think if we have a trivial struct with a bitfield that would do it: struct Foo { int x : 3; };. That struct will be loadable (even though, maybe it shouldn't be) but will also have un-referenceable storage (x).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, so what case are you trying to handle? A struct with a single opaque property? So that "store" would need to actually be a copy_addr?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@atrick my understanding is that has unreferencable storage is for things from c/c++ that have memory/other things that are not exposed to swift. To me it is sort of like an early, hacky resilience-esque sort of thing.

Copy link
Contributor Author

@zoecarver zoecarver Jun 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what case are you trying to handle?

I'm not trying to handle any particular case. Currently, we will bail on opaque types. I just wanted to make sure that's the correct behavior.

To me it is sort of like an early, hacky resilience-esque sort of thing.

I think this is true, yes. But most non-loadable types are non-loadable for ABI reasons, or to make sure calling conventions work with the C++ definitions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isLoadable seems like a sensible bailout considering we're dealing with storing values at its address

baseAddrType.aggregateHasUnreferenceableStorage() ||
decl->getStoredProperties().size() != 1)
break;

// Update the store location now that we know it is safe.
storeAddr = baseAddr;
projections.push_back(Projection(inst));
op = baseAddr;
}

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

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

// Store the new struct-wrapped value into the final base address.
builder.createStore(storeInst->getLoc(), result, storeAddr,
Expand Down
18 changes: 18 additions & 0 deletions test/SILOptimizer/Inputs/foo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef VALIDATION_TEST_SILOPTIMIZER_FOO_H
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zoecarver Can you change the name of this file to something more meaningful? I didn't notice the name when I was reviewing.

#define VALIDATION_TEST_SILOPTIMIZER_FOO_H

struct Foo {
int x;
~Foo() {}
};

struct Loadable {
int x;
};

struct Bar {
Loadable y;
~Bar() {}
};

#endif // VALIDATION_TEST_SILOPTIMIZER_FOO_H
3 changes: 3 additions & 0 deletions test/SILOptimizer/Inputs/module.modulemap
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Foo {
header "foo.h"
}
44 changes: 44 additions & 0 deletions test/SILOptimizer/dont_broaden_cxx_address_only.sil
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// RUN: %target-sil-opt -silgen-cleanup %s -I %S/Inputs -enable-sil-verify-all -enable-cxx-interop | %FileCheck %s

sil_stage canonical

import Builtin
import Swift
import SwiftShims
import Foo

// Make sure we don't try to create a struct here. Foo is not loadable, even
// though it's only property is.
// CHECK-LABEL: @test_foo
// CHECK: bb0
// CHECK-NEXT: [[E:%.*]] = struct_element_addr
// CHECK-NEXT: store %1 to [trivial] [[E]]
// CHECK-NEXT: tuple
// CHECK-NEXT: return
// CHECK-LABEL: end sil function 'test_foo'
sil shared [transparent] [serializable] [ossa] @test_foo : $@convention(method) (Int32, @thin Foo.Type) -> @out Foo {
bb0(%0 : $*Foo, %1 : $Int32, %2 : $@thin Foo.Type):
%3 = struct_element_addr %0 : $*Foo, #Foo.x
store %1 to [trivial] %3 : $*Int32
%5 = tuple ()
return %5 : $()
}

// Make sure we create a struct for the first (loadable) type but not the second
// type (Bar).
// CHECK-LABEL: @test_bar
// CHECK: bb0
// CHECK-NEXT: [[E:%.*]] = struct_element_addr
// CHECK-NEXT: [[AGG:%.*]] = struct $Loadable (%1 : $Int32)
// CHECK-NEXT: store [[AGG]] to [trivial] [[E]]
// CHECK-NEXT: tuple
// CHECK-NEXT: return
// CHECK-LABEL: end sil function 'test_bar'
sil shared [transparent] [serializable] [ossa] @test_bar : $@convention(method) (Int32, @thin Bar.Type) -> @out Bar {
bb0(%0 : $*Bar, %1 : $Int32, %2 : $@thin Bar.Type):
%3 = struct_element_addr %0 : $*Bar, #Bar.y
%3a = struct_element_addr %3 : $*Loadable, #Loadable.x
store %1 to [trivial] %3a : $*Int32
%5 = tuple ()
return %5 : $()
}
7 changes: 7 additions & 0 deletions tools/sil-opt/SILOpt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,11 @@ static cl::opt<std::string> RemarksFormat(
cl::desc("The format used for serializing remarks (default: YAML)"),
cl::value_desc("format"), cl::init("yaml"));

static llvm::cl::opt<bool>
EnableCxxInterop("enable-cxx-interop",
llvm::cl::desc("Enable C++ interop."),
llvm::cl::init(false));

static void runCommandLineSelectedPasses(SILModule *Module,
irgen::IRGenModule *IRGenMod) {
auto &opts = Module->getOptions();
Expand Down Expand Up @@ -348,6 +353,8 @@ int main(int argc, char **argv) {
Invocation.getLangOptions().EnableExperimentalDifferentiableProgramming =
EnableExperimentalDifferentiableProgramming;

Invocation.getLangOptions().EnableCXXInterop = EnableCxxInterop;

Invocation.getDiagnosticOptions().VerifyMode =
VerifyMode ? DiagnosticOptions::Verify : DiagnosticOptions::NoVerify;

Expand Down