Skip to content

[move-only] Add support for move only empty structs and fix a SILGen issue around their initializers. #65130

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

Merged
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
5 changes: 5 additions & 0 deletions lib/SIL/Utils/FieldSensitivePrunedLiveness.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ TypeSubElementCount::TypeSubElementCount(SILType type, SILModule &mod,
numElements += TypeSubElementCount(
type.getFieldType(fieldDecl, mod, context), mod, context);
number = numElements;

// If we do not have any elements, just set our size to 1.
if (numElements == 0)
number = 1;

return;
}

Expand Down
17 changes: 16 additions & 1 deletion lib/SILGen/SILGenConstructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,22 @@ void SILGenFunction::emitValueConstructor(ConstructorDecl *ctor) {
if (!isDelegating) {
auto *typeDC = ctor->getDeclContext();
auto *nominal = typeDC->getSelfNominalTypeDecl();
emitMemberInitializers(ctor, selfDecl, nominal);

// If we have an empty move only struct, then we will not initialize it with
// any member initializers, breaking SIL. So in that case, just construct a
// SIL struct value and initialize the memory with that.
//
// DISCUSSION: This only happens with noncopyable types since the memory
// lifetime checker doesn't seem to process trivial locations. But empty
// move only structs are non-trivial, so we need to handle this here.
if (isa<StructDecl>(nominal) && nominal->isMoveOnly() &&
nominal->getStoredProperties().empty()) {
auto *si = B.createStruct(ctor, lowering.getLoweredType(), {});
B.emitStoreValueOperation(ctor, si, selfLV.getLValueAddress(),
StoreOwnershipQualifier::Init);
} else {
emitMemberInitializers(ctor, selfDecl, nominal);
}
}

emitProfilerIncrement(ctor->getTypecheckedBody());
Expand Down
14 changes: 14 additions & 0 deletions test/Interpreter/moveonly.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,17 @@ Tests.test("deinit not called in init when assigned") {
}
expectEqual(0, FD2.count)
}

Tests.test("empty struct") {
@_moveOnly
struct EmptyStruct {
func doSomething() {}
var value: Bool { false }
}

let e = EmptyStruct()
e.doSomething()
if e.value {
let _ = consume e
}
}
24 changes: 24 additions & 0 deletions test/SILGen/moveonly.swift
Original file line number Diff line number Diff line change
Expand Up @@ -779,3 +779,27 @@ func checkMarkMustCheckOnCaptured(x: __owned FD) {
func clodger<T>(_: () -> T) {}
clodger({ consumeVal(x) })
}

//////////////////
// Empty Struct //
//////////////////

@_moveOnly
struct EmptyStruct {
// Make sure we explicitly initialize empty struct as appropriate despite the
// fact we do not have any fields.
//
// CHECK-LABEL: sil hidden [ossa] @$s8moveonly11EmptyStructVACycfC : $@convention(method) (@thin EmptyStruct.Type) -> @owned EmptyStruct {
// CHECK: [[BOX:%.*]] = alloc_box ${ var EmptyStruct }, var, name "self"
// CHECK: [[MARKED_UNINIT:%.*]] = mark_uninitialized [rootself] [[BOX]]
// CHECK: [[PROJECT:%.*]] = project_box [[MARKED_UNINIT]]
// CHECK: [[STRUCT:%.*]] = struct $EmptyStruct ()
// CHECK: store [[STRUCT]] to [init] [[PROJECT]]
// CHECK: [[MV_CHECK:%.*]] = mark_must_check [assignable_but_not_consumable] [[PROJECT]]
// CHECK: [[LOADED_VALUE:%.*]] = load [copy] [[MV_CHECK]]
// CHECK: destroy_value [[MARKED_UNINIT]]
// CHECK: return [[LOADED_VALUE]]
// CHECK: } // end sil function '$s8moveonly11EmptyStructVACycfC'
init() {
}
}
309 changes: 309 additions & 0 deletions test/SILOptimizer/moveonly_addresschecker_diagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2482,3 +2482,312 @@ func yieldTest() {
}
}
}

///////////////////////
// Empty Struct Test //
///////////////////////

@_moveOnly
struct EmptyStruct {
var bool: Bool { false }
func doSomething() {}
mutating func doSomething2() {}
consuming func doSomething3() {}
}

func borrow(_ x: borrowing EmptyStruct) {}
func consume(_ x: consuming EmptyStruct) {}

func testEmptyStruct() {
func testArg1(_ x: consuming EmptyStruct) {
borrow(x)
}

func testArg2(_ x: consuming EmptyStruct) {
consume(x)
}

func testArg2a(_ x: consuming EmptyStruct) {
// expected-error @-1 {{'x' consumed more than once}}
consume(x) // expected-note {{consuming use here}}
consume(x) // expected-note {{consuming use here}}
}

func testArg2b(_ x: consuming EmptyStruct) {
// expected-error @-1 {{'x' used after consume}}
borrow(x)
consume(x) // expected-note {{consuming use here}}
borrow(x) // expected-note {{non-consuming use here}}
}

func testArg3(_ x: consuming EmptyStruct) {
let _ = x
}

func testArg3a(_ x: consuming EmptyStruct) {
// expected-error @-1 {{'x' consumed more than once}}
let _ = x // expected-note {{consuming use here}}
let _ = x // expected-note {{consuming use here}}
}

func testArg4(_ x: consuming EmptyStruct) {
_ = x
}

func testArg4a(_ x: consuming EmptyStruct) {
// expected-error @-1 {{'x' consumed more than once}}
_ = x // expected-note {{consuming use here}}
_ = x // expected-note {{consuming use here}}
}

func testArg4b(_ x: consuming EmptyStruct) {
// expected-error @-1 {{'x' consumed more than once}}
// expected-error @-2 {{'x' consumed more than once}}
_ = x // expected-note {{consuming use here}}
_ = x // expected-note {{consuming use here}}
// expected-note @-1 {{consuming use here}}
let _ = x // expected-note {{consuming use here}}
}

func testArg5(_ x: consuming EmptyStruct) {
let y = x
_ = y
}

func testArg6(_ x: consuming EmptyStruct) {
x.doSomething()
}

func testArg7(_ x: consuming EmptyStruct) {
x.doSomething3()
}

func testArg7a(_ x: consuming EmptyStruct) {
// expected-error @-1 {{'x' consumed more than once}}
x.doSomething3() // expected-note {{consuming use here}}
x.doSomething3() // expected-note {{consuming use here}}
}
}

////////////////////////////////////
// Struct Containing Empty Struct //
////////////////////////////////////

// Make sure that we handle a struct that recursively holds an empty struct
// correctly.
@_moveOnly
struct StructContainingEmptyStruct {
var x: EmptyStruct
}

func borrow(_ x: consuming StructContainingEmptyStruct) {}
func consume(_ x: consuming StructContainingEmptyStruct) {}

func testStructContainingEmptyStruct() {
func testArg1(_ x: consuming StructContainingEmptyStruct) {
borrow(x)
}

func testArg2(_ x: consuming StructContainingEmptyStruct) {
consume(x)
}

func testArg3(_ x: consuming StructContainingEmptyStruct) {
let _ = x
}

func testArg4(_ x: consuming StructContainingEmptyStruct) {
_ = x
}

func testArg5(_ x: consuming StructContainingEmptyStruct) {
let y = x
_ = y
}

func testArg6(_ x: consuming StructContainingEmptyStruct) {
x.x.doSomething()
}

func testArg7(_ x: consuming StructContainingEmptyStruct) {
x.x.doSomething3()
}

func testArg7a(_ x: consuming StructContainingEmptyStruct) {
// expected-error @-1 {{'x' consumed more than once}}
x.x.doSomething3() // expected-note {{consuming use here}}
x.x.doSomething3() // expected-note {{consuming use here}}
}
}

////////////////////////////////////
// Struct Containing Empty Struct //
////////////////////////////////////

// Make sure that we handle a struct that recursively holds an empty struct
// correctly.
@_moveOnly
struct StructContainingTwoEmptyStruct {
var x: EmptyStruct
var y: EmptyStruct
}

func borrow(_ x: consuming StructContainingTwoEmptyStruct) {}
func consume(_ x: consuming StructContainingTwoEmptyStruct) {}

func testStructContainingTwoEmptyStruct() {
func testArg1(_ x: consuming StructContainingTwoEmptyStruct) {
borrow(x)
}

func testArg2(_ x: consuming StructContainingTwoEmptyStruct) {
consume(x)
}

func testArg3(_ x: consuming StructContainingTwoEmptyStruct) {
let _ = x
}

func testArg4(_ x: consuming StructContainingTwoEmptyStruct) {
_ = x
}

func testArg5(_ x: consuming StructContainingTwoEmptyStruct) {
let y = x
_ = y
}

func testArg6(_ x: consuming StructContainingTwoEmptyStruct) {
x.x.doSomething()
}

func testArg7(_ x: consuming StructContainingTwoEmptyStruct) {
x.x.doSomething3()
}

func testArg8(_ x: consuming StructContainingTwoEmptyStruct) {
x.y.doSomething3()
}

func testArg9(_ x: consuming StructContainingTwoEmptyStruct) {
x.x.doSomething3()
x.y.doSomething3()
}

func testArg10(_ x: consuming StructContainingTwoEmptyStruct) {
// expected-error @-1 {{'x' consumed more than once}}
x.x.doSomething3() // expected-note {{consuming use here}}
x.y.doSomething3()
x.x.doSomething3() // expected-note {{consuming use here}}
}
}

//////////////////////////////////
// Enum Containing Empty Struct //
//////////////////////////////////

@_moveOnly
enum MyEnum2 {
case first(EmptyStruct)
case second(String)
}

@_moveOnly
enum MyEnum {
case first(EmptyStruct)
case second(String)
case third(MyEnum2)
}

func borrow(_ x: borrowing MyEnum) {}

func testMyEnum() {
func test1(_ x: consuming MyEnum) {
if case let .first(y) = x {
_ = y
}
}

func test1a(_ x: consuming MyEnum) { // expected-error {{'x' consumed more than once}}
if case let .first(y) = x { // expected-note {{consuming use here}}
_ = consume x // expected-note {{consuming use here}}
_ = y
}
}

func test1b(_ x: consuming MyEnum) { // expected-error {{'x' consumed more than once}}
if case let .first(y) = x { // expected-note {{consuming use here}}
_ = y
}
_ = consume x // expected-note {{consuming use here}}
}

func test2(_ x: consuming MyEnum) {
if case let .third(.first(y)) = x {
_ = y
}
}

func test2a(_ x: consuming MyEnum) { // expected-error {{'x' consumed more than once}}
if case let .third(.first(y)) = x { // expected-note {{consuming use here}}
_ = consume x // expected-note {{consuming use here}}
_ = y
}
}

func test2b(_ x: consuming MyEnum) { // expected-error {{'x' consumed more than once}}
if case let .third(.first(y)) = x { // expected-note {{consuming use here}}
_ = y
}
_ = consume x // expected-note {{consuming use here}}
}

func test2c(_ x: consuming MyEnum) { // expected-error {{'x' used after consume}}
if case let .third(.first(y)) = x { // expected-note {{consuming use here}}
_ = y
}
borrow(x) // expected-note {{non-consuming use here}}
}

func test3(_ x: consuming MyEnum) {
switch x {
case let .first(y):
_ = y
break
default:
break
}
}

func test3a(_ x: consuming MyEnum) { // expected-error {{'x' consumed more than once}}
switch x { // expected-note {{consuming use here}}
case let .first(y):
_ = y
break
default:
break
}
_ = consume x // expected-note {{consuming use here}}
}

func test4(_ x: consuming MyEnum) {
switch x {
case let .third(.first(y)):
_ = y
break
default:
break
}
}

func test4a(_ x: consuming MyEnum) { // expected-error {{'x' consumed more than once}}
switch x { // expected-note {{consuming use here}}
case let .third(.first(y)):
_ = y
break
default:
break
}
_ = consume x // expected-note {{consuming use here}}
}
}
Loading