Skip to content

Commit 9b78356

Browse files
committed
Fix @_rawLayout initialization to avoid spurious lifetime ends.
We can't really treat them as always-initialized because that makes move checking think that there's a value to destroy even on initialization, causing deinits to run on uninitialized memory. Remove my previous hack, and use a `zeroInitializer` to initialize the value state when emitting `init`, which is where we really need the bootstrapping-into-initialized behavior. rdar://113057256
1 parent 2eade1d commit 9b78356

9 files changed

+161
-13
lines changed

lib/IRGen/GenBuiltin.cpp

+12-3
Original file line numberDiff line numberDiff line change
@@ -1188,9 +1188,18 @@ void irgen::emitBuiltinCall(IRGenFunction &IGF, const BuiltinInfo &Builtin,
11881188
// Build a zero initializer of the result type.
11891189
auto valueTy = getLoweredTypeAndTypeInfo(IGF.IGM,
11901190
substitutions.getReplacementTypes()[0]);
1191-
auto schema = valueTy.second.getSchema();
1192-
for (auto &elt : schema) {
1193-
out.add(llvm::Constant::getNullValue(elt.getScalarType()));
1191+
1192+
if (args.size() > 0) {
1193+
// `memset` the memory addressed by the argument.
1194+
auto address = args.claimNext();
1195+
IGF.Builder.CreateMemSet(valueTy.second.getAddressForPointer(address),
1196+
llvm::ConstantInt::get(IGF.IGM.Int8Ty, 0),
1197+
valueTy.second.getSize(IGF, argTypes[0]));
1198+
} else {
1199+
auto schema = valueTy.second.getSchema();
1200+
for (auto &elt : schema) {
1201+
out.add(llvm::Constant::getNullValue(elt.getScalarType()));
1202+
}
11941203
}
11951204
return;
11961205
}

lib/SIL/IR/SILInstruction.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -976,6 +976,13 @@ MemoryBehavior SILInstruction::getMemoryBehavior() const {
976976
if (auto *BI = dyn_cast<BuiltinInst>(this)) {
977977
// Handle Swift builtin functions.
978978
const BuiltinInfo &BInfo = BI->getBuiltinInfo();
979+
if (BInfo.ID == BuiltinValueKind::ZeroInitializer) {
980+
// The address form of `zeroInitializer` writes to its argument to
981+
// initialize it. The value form has no side effects.
982+
return BI->getArguments().size() > 0
983+
? MemoryBehavior::MayWrite
984+
: MemoryBehavior::None;
985+
}
979986
if (BInfo.ID != BuiltinValueKind::None)
980987
return BInfo.isReadNone() ? MemoryBehavior::None
981988
: MemoryBehavior::MayHaveSideEffects;

lib/SIL/Utils/AddressWalker.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ AddressUseKind TransitiveAddressWalker::walk(SILValue projectedAddress) && {
185185
case BuiltinValueKind::GenericFRem:
186186
case BuiltinValueKind::GenericXor:
187187
case BuiltinValueKind::TaskRunInline:
188+
case BuiltinValueKind::ZeroInitializer:
188189
callVisitUse(op);
189190
continue;
190191
default:

lib/SIL/Utils/MemAccessUtils.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -2592,6 +2592,13 @@ static void visitBuiltinAddress(BuiltinInst *builtin,
25922592
// SIL address.
25932593
// visitor(&builtin->getAllOperands()[0]);
25942594
return;
2595+
2596+
// zeroInitializer with an address operand zeroes the address.
2597+
case BuiltinValueKind::ZeroInitializer:
2598+
if (builtin->getAllOperands().size() > 0) {
2599+
visitor(&builtin->getAllOperands()[0]);
2600+
}
2601+
return;
25952602

25962603
// Arrays: (T.Type, Builtin.RawPointer, Builtin.RawPointer,
25972604
// Builtin.Word)

lib/SILGen/SILGenConstructor.cpp

+14-3
Original file line numberDiff line numberDiff line change
@@ -722,9 +722,20 @@ void SILGenFunction::emitValueConstructor(ConstructorDecl *ctor) {
722722
// DISCUSSION: This only happens with noncopyable types since the memory
723723
// lifetime checker doesn't seem to process trivial locations. But empty
724724
// move only structs are non-trivial, so we need to handle this here.
725-
if (isa<StructDecl>(nominal) && nominal->isMoveOnly()
726-
&& !nominal->getAttrs().hasAttribute<RawLayoutAttr>()
727-
&& nominal->getStoredProperties().empty()) {
725+
if (nominal->getAttrs().hasAttribute<RawLayoutAttr>()) {
726+
// Raw memory is not directly decomposable, but we still want to mark
727+
// it as initialized. Use a zero initializer.
728+
auto &C = ctor->getASTContext();
729+
auto zeroInit = getBuiltinValueDecl(C, C.getIdentifier("zeroInitializer"));
730+
B.createBuiltin(ctor, zeroInit->getBaseIdentifier(),
731+
SILType::getEmptyTupleType(C),
732+
SubstitutionMap::get(zeroInit->getInnermostDeclContext()
733+
->getGenericSignatureOfContext(),
734+
{selfDecl->getType()},
735+
{}),
736+
selfLV.getLValueAddress());
737+
} else if (isa<StructDecl>(nominal) && nominal->isMoveOnly()
738+
&& nominal->getStoredProperties().empty()) {
728739
auto *si = B.createStruct(ctor, lowering.getLoweredType(), {});
729740
B.emitStoreValueOperation(ctor, si, selfLV.getLValueAddress(),
730741
StoreOwnershipQualifier::Init);

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

+1-7
Original file line numberDiff line numberDiff line change
@@ -952,13 +952,7 @@ void UseState::initializeLiveness(
952952
SILValue operand = address->getOperand();
953953
if (auto *c = dyn_cast<CopyableToMoveOnlyWrapperAddrInst>(operand))
954954
operand = c->getOperand();
955-
// If the type uses raw layout, its initialization is unmanaged, so consider
956-
// it always initialized.
957-
auto s = operand->getType().getStructOrBoundGenericStruct();
958-
if (s && s->getAttrs().hasAttribute<RawLayoutAttr>()) {
959-
recordInitUse(address, address, liveness.getTopLevelSpan());
960-
liveness.initializeDef(address, liveness.getTopLevelSpan());
961-
} else if (auto *fArg = dyn_cast<SILFunctionArgument>(operand)) {
955+
if (auto *fArg = dyn_cast<SILFunctionArgument>(operand)) {
962956
switch (fArg->getArgumentConvention()) {
963957
case swift::SILArgumentConvention::Indirect_In:
964958
case swift::SILArgumentConvention::Indirect_In_Guaranteed:

lib/SILOptimizer/Mandatory/MoveOnlyUtils.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,13 @@ bool noncopyable::memInstMustInitialize(Operand *memOper) {
215215
return qual == StoreOwnershipQualifier::Init ||
216216
qual == StoreOwnershipQualifier::Trivial;
217217
}
218+
case SILInstructionKind::BuiltinInst: {
219+
auto bi = cast<BuiltinInst>(memInst);
220+
if (bi->getBuiltinKind() == BuiltinValueKind::ZeroInitializer) {
221+
// `zeroInitializer` with an address operand zeroes out the address operand
222+
return true;
223+
}
224+
}
218225

219226
#define NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \
220227
case SILInstructionKind::Store##Name##Inst: \
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift -enable-experimental-feature RawLayout %s -o %t/a.out
3+
// RUN: %target-codesign %t/a.out
4+
// RUN: %target-run %t/a.out | %FileCheck %s
5+
// REQUIRES: executable_test
6+
7+
@_rawLayout(size: 16, alignment: 16)
8+
struct Foo: ~Copyable {
9+
init(_ value: Int) {
10+
print("Foo.init(\(value))")
11+
}
12+
13+
deinit {
14+
print("Foo.deinit")
15+
}
16+
17+
func bar() {
18+
print("Foo.bar()")
19+
}
20+
}
21+
22+
func test() {
23+
let foo = Foo(42)
24+
foo.bar()
25+
}
26+
27+
print("-- start")
28+
test()
29+
print("-- done")
30+
31+
// CHECK: -- start
32+
// CHECK-NEXT: Foo.init(42)
33+
// CHECK-NEXT: Foo.bar()
34+
// CHECK-NEXT: Foo.deinit
35+
// CHECK-NEXT: -- done
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// RUN: %target-swift-frontend -enable-experimental-feature BuiltinModule -enable-experimental-feature RawLayout -emit-sil %s | %FileCheck %s
2+
3+
import Builtin
4+
5+
@_silgen_name("init_lock")
6+
func init_lock(_: Builtin.RawPointer)
7+
8+
@_silgen_name("deinit_lock")
9+
func deinit_lock(_: Builtin.RawPointer)
10+
11+
@_rawLayout(size: 4, alignment: 4)
12+
struct Lock: ~Copyable {
13+
var _address: Builtin.RawPointer { return Builtin.addressOfBorrow(self) }
14+
15+
// CHECK-LABEL: // Lock.init()
16+
// CHECK-NEXT: sil{{.*}} @[[INIT:\$.*4LockV.*fC]] :
17+
init() {
18+
// CHECK-NOT: destroy_addr
19+
// CHECK: builtin "zeroInitializer"<Lock>
20+
// CHECK-NOT: destroy_addr
21+
// CHECK: [[F:%.*]] = function_ref @init_lock
22+
// CHECK: apply [[F]](
23+
// CHECK-NOT: destroy_addr
24+
// CHECK: } // end sil function '[[INIT]]'
25+
init_lock(_address)
26+
}
27+
28+
// CHECK-LABEL: // Lock.deinit
29+
// CHECK-NEXT: sil{{.*}} @[[DEINIT:\$.*4LockV.*fD]] :
30+
deinit {
31+
// CHECK-NOT: destroy_addr
32+
// CHECK: [[F:%.*]] = function_ref @deinit_lock
33+
// CHECK: apply [[F]](
34+
// CHECK-NOT: destroy_addr
35+
// CHECK: } // end sil function '[[DEINIT]]'
36+
deinit_lock(_address)
37+
}
38+
}
39+
40+
@_silgen_name("borrow_lock")
41+
func borrow_lock(_: borrowing Lock)
42+
43+
// CHECK-LABEL: sil{{.*}} @{{.*}}7useLock
44+
public func useLock() {
45+
// CHECK: [[L:%.*]] = alloc_stack
46+
// CHECK-NOT: destroy_addr [[L]] :
47+
// CHECK: [[F:%.*]] = function_ref @[[INIT]]
48+
// CHECK: apply [[F]]([[L]],
49+
var l = Lock()
50+
// CHECK-NOT: destroy_addr [[L]] :
51+
// CHECK: [[L_BORROW:%.*]] = begin_access [read] [static] [[L]] :
52+
// CHECK: [[F:%.*]] = function_ref @borrow_lock
53+
// CHECK: apply [[F]]([[L_BORROW]])
54+
// CHECK: end_access [[L_BORROW]]
55+
borrow_lock(l)
56+
57+
// CHECK: [[L2:%.*]] = alloc_stack
58+
// CHECK-NOT: destroy_addr [[L2]] :
59+
// CHECK: [[F:%.*]] = function_ref @[[INIT]]
60+
// CHECK: apply [[F]]([[L2]],
61+
// CHECK: [[L_INOUT:%.*]] = begin_access [modify] [static] [[L]] :
62+
// CHECK: destroy_addr [[L]] :
63+
// CHECK: copy_addr [take] [[L2]] to [init] [[L_INOUT]]
64+
// CHECK: end_access [[L_INOUT]]
65+
// CHECK-NOT: destroy_addr [[L2]] :
66+
// CHECK: dealloc_stack [[L2]]
67+
l = Lock()
68+
// CHECK-NOT: destroy_addr [[L]] :
69+
// CHECK: [[L_BORROW:%.*]] = begin_access [read] [static] [[L]] :
70+
// CHECK: [[F:%.*]] = function_ref @borrow_lock
71+
// CHECK: apply [[F]]([[L_BORROW]])
72+
// CHECK: end_access [[L_BORROW]]
73+
borrow_lock(l)
74+
75+
// CHECK: destroy_addr [[L]]
76+
// CHECK: dealloc_stack [[L]]
77+
}

0 commit comments

Comments
 (0)