mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Optimizer: let the InstructionDeleter respect deinit-barriers by default
The InstructionDeleter can remove instructions including their destroys and then insert compensating destroys at a new place. This is effectively destroy-hoisting which doesn't respect deinit-barriers. Therefore it's not done for lexical lifetimes. However, since https://github.com/swiftlang/swift/pull/85334, the optimizer should treat _all_ lifetimes as fixed and not only lexical lifetimes. This change adds a `assumeFixedLifetimes` flag to InstructionDeleter which is on by default. Only mandatory passes (like OSLogOptimization) should turn this off.
This commit is contained in:
@@ -119,11 +119,15 @@ class InstructionDeleter {
|
||||
/// Callbacks used when adding/deleting instructions.
|
||||
InstModCallbacks callbacks;
|
||||
|
||||
public:
|
||||
InstructionDeleter() : deadInstructions() {}
|
||||
bool assumeFixedLifetimes = true;
|
||||
|
||||
InstructionDeleter(InstModCallbacks &&callbacks)
|
||||
: deadInstructions(), callbacks(std::move(callbacks)) {}
|
||||
public:
|
||||
InstructionDeleter(bool assumeFixedLifetimes = true)
|
||||
: deadInstructions(), assumeFixedLifetimes(assumeFixedLifetimes) {}
|
||||
|
||||
InstructionDeleter(InstModCallbacks &&callbacks, bool assumeFixedLifetimes = true)
|
||||
: deadInstructions(), callbacks(std::move(callbacks)),
|
||||
assumeFixedLifetimes(assumeFixedLifetimes) {}
|
||||
|
||||
InstModCallbacks &getCallbacks() { return callbacks; }
|
||||
|
||||
|
||||
@@ -1206,7 +1206,7 @@ static bool tryEliminateOSLogMessage(SingleValueInstruction *oslogMessage) {
|
||||
}
|
||||
(void)deletedInstructions.insert(deadInst);
|
||||
});
|
||||
InstructionDeleter deleter(std::move(callbacks));
|
||||
InstructionDeleter deleter(std::move(callbacks), /*assumeFixedLifetimes=*/ false);
|
||||
|
||||
unsigned startIndex = 0;
|
||||
while (startIndex < worklist.size()) {
|
||||
@@ -1433,7 +1433,7 @@ suppressGlobalStringTablePointerError(SingleValueInstruction *oslogMessage) {
|
||||
|
||||
// Replace the globalStringTablePointer builtins by a string_literal
|
||||
// instruction for an empty string and clean up dead code.
|
||||
InstructionDeleter deleter;
|
||||
InstructionDeleter deleter(/*assumeFixedLifetimes=*/ false);
|
||||
for (BuiltinInst *bi : globalStringTablePointerInsts) {
|
||||
SILBuilderWithScope builder(bi);
|
||||
StringLiteralInst *stringLiteral = builder.createStringLiteral(
|
||||
|
||||
@@ -50,7 +50,8 @@ static bool hasOnlyIncidentalUses(SILInstruction *inst,
|
||||
///
|
||||
/// TODO: Handle partial_apply [stack] which has a dealloc_stack user.
|
||||
static bool isScopeAffectingInstructionDead(SILInstruction *inst,
|
||||
bool fixLifetime) {
|
||||
bool fixLifetime,
|
||||
bool assumeFixedLifetimes) {
|
||||
SILFunction *fun = inst->getFunction();
|
||||
assert(fun && "Instruction has no function.");
|
||||
// Only support ownership SIL for scoped instructions.
|
||||
@@ -84,7 +85,7 @@ static bool isScopeAffectingInstructionDead(SILInstruction *inst,
|
||||
}
|
||||
|
||||
// If result was lexical, lifetime shortening maybe observed, return.
|
||||
if (result->isLexical()) {
|
||||
if (result->isLexical() || assumeFixedLifetimes) {
|
||||
auto resultTy = result->getType().getAs<SILFunctionType>();
|
||||
// Allow deleted dead lexical values when they are trivial no escape types.
|
||||
if (!resultTy || !resultTy->isTrivialNoEscape()) {
|
||||
@@ -186,7 +187,7 @@ static bool isScopeAffectingInstructionDead(SILInstruction *inst,
|
||||
bool InstructionDeleter::trackIfDead(SILInstruction *inst) {
|
||||
bool fixLifetime = inst->getFunction()->hasOwnership();
|
||||
if (isInstructionTriviallyDead(inst)
|
||||
|| isScopeAffectingInstructionDead(inst, fixLifetime)) {
|
||||
|| isScopeAffectingInstructionDead(inst, fixLifetime, assumeFixedLifetimes)) {
|
||||
assert(!isIncidentalUse(inst)
|
||||
|| canTriviallyDeleteOSSAEndScopeInst(inst) &&
|
||||
"Incidental uses cannot be removed in isolation. "
|
||||
@@ -333,7 +334,7 @@ bool InstructionDeleter::deleteIfDead(SILInstruction *inst) {
|
||||
|
||||
bool InstructionDeleter::deleteIfDead(SILInstruction *inst, bool fixLifetime) {
|
||||
if (isInstructionTriviallyDead(inst)
|
||||
|| isScopeAffectingInstructionDead(inst, fixLifetime)) {
|
||||
|| isScopeAffectingInstructionDead(inst, fixLifetime, assumeFixedLifetimes)) {
|
||||
getCallbacks().notifyWillBeDeleted(inst);
|
||||
deleteWithUses(inst, fixLifetime);
|
||||
return true;
|
||||
@@ -349,7 +350,7 @@ namespace swift::test {
|
||||
static FunctionTest DeleterDeleteIfDeadTest(
|
||||
"deleter_delete_if_dead", [](auto &function, auto &arguments, auto &test) {
|
||||
auto *inst = arguments.takeInstruction();
|
||||
InstructionDeleter deleter;
|
||||
InstructionDeleter deleter(/*assumeFixedLifetimes=*/ false);
|
||||
llvm::outs() << "Deleting-if-dead " << *inst;
|
||||
auto deleted = deleter.deleteIfDead(inst);
|
||||
llvm::outs() << "deleteIfDead returned " << deleted << "\n";
|
||||
|
||||
@@ -2934,6 +2934,7 @@ bb0:
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @test_store_borrow_1_copy_addr : {{.*}} {
|
||||
// CHECK: alloc_stack
|
||||
// CHECK: [[ADDR:%[^,]+]] = alloc_stack
|
||||
// CHECK: apply undef<T>([[ADDR]])
|
||||
// CHECK: [[COPY:%[^,]+]] = alloc_stack
|
||||
|
||||
@@ -909,9 +909,9 @@ sil [ossa] @canonicalize_source_of_redundant_move_value : $@convention(thin) ()
|
||||
return %retval : $()
|
||||
}
|
||||
|
||||
// Verify that a dead copy_value is deleted.
|
||||
// TODO: why is the copy not deleted here?
|
||||
// CHECK-LABEL: sil [ossa] @delete_dead_reborrow_copy : {{.*}} {
|
||||
// CHECK-NOT: copy_value
|
||||
// xCHECK-NOT: copy_value
|
||||
// CHECK-LABEL: } // end sil function 'delete_dead_reborrow_copy'
|
||||
sil [ossa] @delete_dead_reborrow_copy : $@convention(thin) (@owned X) -> () {
|
||||
bb0(%instance : @owned $X):
|
||||
|
||||
@@ -168,7 +168,8 @@ bb0(%0 : @guaranteed $NativeObjectPair):
|
||||
// CHECK-LABEL: sil [ossa] @testBorrowOuterUse : {{.*}} {
|
||||
// CHECK: bb0:
|
||||
// CHECK: [[INSTANCE:%.*]] = apply
|
||||
// CHECK-NOT: begin_borrow
|
||||
// CHECK: begin_borrow
|
||||
// CHECK-NEXT: end_borrow
|
||||
// CHECK-NOT: copy
|
||||
// CHECK: apply %{{.*}}([[INSTANCE]]) : $@convention(thin) (@owned C) -> ()
|
||||
// CHECK-NOT: destroy
|
||||
@@ -191,7 +192,8 @@ bb0:
|
||||
//
|
||||
// CHECK-LABEL: sil [ossa] @testMultiBlockBorrow : $@convention(thin) (@guaranteed C) -> () {
|
||||
// CHECK: bb0(%0 : @guaranteed $C):
|
||||
// CHECK-NOT: borrow
|
||||
// CHECK: borrow
|
||||
// CHECK-NEXT: end_borrow
|
||||
// CHECK-NOT: copy
|
||||
// CHECK: cond_br undef, bb1, bb2
|
||||
// CHECK: bb1:
|
||||
@@ -485,14 +487,17 @@ bb3:
|
||||
// CHECK-LABEL: sil [ossa] @testNestedBorrowInsideAndOutsideUse : $@convention(thin) () -> () {
|
||||
// CHECK: [[ALLOC:%.*]] = alloc_ref $C
|
||||
// CHECK: [[B1:%.*]] = begin_borrow [[ALLOC]] : $C
|
||||
// CHECK-NOT: borrow
|
||||
// CHECK-NEXT: [[B2:%.*]] = begin_borrow [[ALLOC]] : $C
|
||||
// CHECK-NOT: copy_value
|
||||
// CHECK: bb1:
|
||||
// CHECK-NEXT: end_borrow [[B2]] : $C
|
||||
// CHECK-NEXT: end_borrow [[B1]] : $C
|
||||
// CHECK-NEXT: destroy_value [[ALLOC]] : $C
|
||||
// CHECK-NEXT: br bb3
|
||||
// CHECK: bb2:
|
||||
// CHECK-NEXT: end_borrow %1 : $C
|
||||
// CHECK-NEXT: destroy_value %0 : $C
|
||||
// CHECK-NEXT: end_borrow [[B1]] : $C
|
||||
// CHECK-NEXT: end_borrow [[B2]] : $C
|
||||
// CHECK-NEXT: destroy_value [[ALLOC]] : $C
|
||||
// CHECK-NEXT: br bb3
|
||||
// CHECK: bb3:
|
||||
// CHECK-NOT: destroy
|
||||
@@ -655,11 +660,12 @@ bb3(%borrowphi : @guaranteed $C):
|
||||
// The inner copy's lifetime will be canonicalized, removing
|
||||
// outercopy.
|
||||
//
|
||||
// TODO: why can't the first copy_value not be removed?
|
||||
// CHECK-LABEL: sil [ossa] @testDeadCopyAfterReborrow : $@convention(thin) () -> () {
|
||||
// CHECK: [[ALLOC:%.*]] = alloc_ref $C
|
||||
// CHECK: bb3([[BORROWPHI:%.*]] : @reborrow $C):
|
||||
// CHECK: [[R:%.*]] = borrowed [[BORROWPHI]] : $C from (%0 : $C)
|
||||
// CHECK-NOT: copy_value
|
||||
// xCHECK-NOT: copy_value
|
||||
// CHECK: end_borrow [[R]] : $C
|
||||
// CHECK-NOT: copy_value
|
||||
// CHECK: destroy_value [[ALLOC]] : $C
|
||||
@@ -696,13 +702,14 @@ bb3(%borrowphi : @guaranteed $C):
|
||||
// end borrowphi
|
||||
// outer copy -- removed when borrowphi's copy is canonicalized
|
||||
//
|
||||
// TODO: why can't the first copy_value not be removed?
|
||||
// CHECK-LABEL: sil [ossa] @testNestedReborrowOutsideUse : $@convention(thin) () -> () {
|
||||
// CHECK: [[ALLOC:%.*]] = alloc_ref $C
|
||||
// CHECK: bb3([[BORROWPHI:%.*]] : @reborrow $C):
|
||||
// CHECK: [[R:%.*]] = borrowed [[BORROWPHI]] : $C from (%0 : $C)
|
||||
// CHECK-NOT: copy
|
||||
// xCHECK-NOT: copy
|
||||
// CHECK: end_borrow [[R]]
|
||||
// CHECK-NEXT: destroy_value [[ALLOC]] : $C
|
||||
// CHECK: destroy_value [[ALLOC]] : $C
|
||||
// CHECK-LABEL: } // end sil function 'testNestedReborrowOutsideUse'
|
||||
sil [ossa] @testNestedReborrowOutsideUse : $@convention(thin) () -> () {
|
||||
bb0:
|
||||
@@ -883,7 +890,8 @@ bb0:
|
||||
// CHECK-LABEL: sil [ossa] @testForwardBorrow3 : {{.*}} {
|
||||
// CHECK: bb0:
|
||||
// CHECK: [[INSTANCE:%.*]] = apply
|
||||
// CHECK-NOT: borrow
|
||||
// CHECK: begin_borrow
|
||||
// CHECK-NEXT: end_borrow
|
||||
// CHECK-NOT: copy
|
||||
// CHECK: [[DSOUT1:%.*]] = destructure_struct [[INSTANCE]] : $Wrapper
|
||||
// CHECK-NEXT: ([[DSOUT2:%.*]], %{{.*}}) = destructure_struct [[DSOUT1]] : $HasObjectAndInt
|
||||
@@ -911,17 +919,18 @@ bb0:
|
||||
// but one has no outer uses.
|
||||
// Need to create two new destroys in this case.
|
||||
//
|
||||
// TODO: why can't the copy_value not be removed?
|
||||
//
|
||||
// CHECK-LABEL: sil [ossa] @testForwardBorrow4 : {{.*}} {
|
||||
// CHECK: bb0:
|
||||
// CHECK: [[INSTANCE:%.*]] = apply
|
||||
// CHECK-NEXT: ([[HASOBJECTS_0:%[^,]+]], [[HASOBJECTS_1:%[^,]+]]) = destructure_struct [[INSTANCE]] : $MultiWrapper
|
||||
// CHECK-NEXT: destroy_value [[HASOBJECTS_1]] : $HasObjects
|
||||
// CHECK-NEXT: ([[VAL:%.*]], [[OBJECT_1:%[^,]+]]) = destructure_struct [[HASOBJECTS_0]] : $HasObjects
|
||||
// CHECK-NEXT: destroy_value [[OBJECT_1]] : $C
|
||||
// CHECK-NOT: borrow
|
||||
// CHECK: apply %{{.*}}([[VAL]]) : $@convention(thin) (@owned C) -> ()
|
||||
// CHECK-NOT: destroy
|
||||
// xCHECK-NEXT: ([[HASOBJECTS_0:%[^,]+]], [[HASOBJECTS_1:%[^,]+]]) = destructure_struct [[INSTANCE]] : $MultiWrapper
|
||||
// xCHECK-NEXT: destroy_value [[HASOBJECTS_1]] : $HasObjects
|
||||
// xCHECK-NEXT: ([[VAL:%.*]], [[OBJECT_1:%[^,]+]]) = destructure_struct [[HASOBJECTS_0]] : $HasObjects
|
||||
// xCHECK-NEXT: destroy_value [[OBJECT_1]] : $C
|
||||
// xCHECK-NOT: borrow
|
||||
// xCHECK: apply %{{.*}}([[VAL]]) : $@convention(thin) (@owned C) -> ()
|
||||
// xCHECK-NOT: destroy
|
||||
// CHECK-LABEL: } // end sil function 'testForwardBorrow4'
|
||||
sil [ossa] @testForwardBorrow4 : $@convention(thin) () -> () {
|
||||
bb0:
|
||||
@@ -953,6 +962,9 @@ bb0:
|
||||
// CHECK: bb0:
|
||||
// CHECK: [[INSTANCE:%.*]] = apply
|
||||
// CHECK-NEXT: [[COPY:%[^,]+]] = copy_value [[INSTANCE]] : $HasObjectAndInt
|
||||
// CHECK-NEXT: begin_borrow
|
||||
// CHECK-NEXT: destructure_struct
|
||||
// CHECK-NEXT: end_borrow
|
||||
// CHECK-NEXT: ([[OBJECT:%[^,]+]], {{%[^,]+}}) = destructure_struct [[COPY]] : $HasObjectAndInt
|
||||
// CHECK-NEXT: [[BORROW:%[^,]+]] = begin_borrow [[OBJECT]] : $C
|
||||
// CHECK-NEXT: [[TAIL_ADDR:%[^,]+]] = ref_tail_addr [[BORROW]] : $C, $Builtin.Int8
|
||||
@@ -1082,11 +1094,15 @@ bb0(%0 : @owned $HasObject):
|
||||
// CHECK-LABEL: sil [ossa] @testUselessBorrowString : {{.*}} {
|
||||
// CHECK: bb0:
|
||||
// CHECK: [[INSTANCE:%.*]] = apply
|
||||
// CHECK-NEXT: begin_borrow
|
||||
// CHECK-NEXT: end_borrow
|
||||
// CHECK-NEXT: [[DESTRUCTURE:%.*]] = destructure_struct [[INSTANCE]] : $String
|
||||
// CHECK-NEXT: [[UTF16:%.*]] = struct $String.UTF16View ([[DESTRUCTURE]] : $_StringGuts)
|
||||
// CHECK-NEXT: br bb1
|
||||
// CHECK: bb1:
|
||||
// CHECK-NEXT: [[COPY:%.*]] = copy_value [[UTF16]] : $String.UTF16View
|
||||
// CHECK-NEXT: begin_borrow
|
||||
// CHECK-NEXT: end_borrow
|
||||
// CHECK-NEXT: [[GUTS:%.*]] = destructure_struct [[COPY]] : $String.UTF16View
|
||||
// CHECK-NEXT: [[OBJ:%.*]] = destructure_struct [[GUTS]] : $_StringGuts
|
||||
// CHECK-NEXT: [[BORROW:%.*]] = begin_borrow [[OBJ]] : $_StringObject
|
||||
|
||||
@@ -419,6 +419,8 @@ bb0:
|
||||
// CHECK-LABEL: sil [ossa] @testBorrowCopy : {{.*}} {
|
||||
// CHECK-LABEL: bb0:
|
||||
// CHECK: [[INSTANCE:%[^,]+]] = apply
|
||||
// CHECK-NEXT: begin_borrow
|
||||
// CHECK-NEXT: end_borrow
|
||||
// CHECK-NEXT: destroy_value [[INSTANCE]] : $T
|
||||
// CHECK-NEXT: tuple ()
|
||||
// CHECK-NEXT: return
|
||||
|
||||
@@ -1411,34 +1411,3 @@ bb5(%16 : @reborrow $FakeOptional<Klass>, %17 : @reborrow $FakeOptional<Klass>):
|
||||
return %23
|
||||
}
|
||||
|
||||
struct String {
|
||||
var guts: Builtin.AnyObject
|
||||
}
|
||||
|
||||
sil [_semantics "string.makeUTF8"] [ossa] @makeString : $@convention(thin) (Builtin.RawPointer, Builtin.Word) -> @owned String
|
||||
|
||||
// CHECK-LABEL: sil [ossa] @uncompletedDeadStrings
|
||||
// CHECK-NOT: apply
|
||||
// CHECK-LABEL: } // end sil function 'uncompletedDeadStrings'
|
||||
sil [ossa] @uncompletedDeadStrings : $@convention(thin) () -> () {
|
||||
%first_ptr = string_literal utf8 "first"
|
||||
%first_len = integer_literal $Builtin.Word, 5
|
||||
%makeString = function_ref @makeString : $@convention(thin) (Builtin.RawPointer, Builtin.Word) -> @owned String
|
||||
%first = apply %makeString(%first_ptr, %first_len) : $@convention(thin) (Builtin.RawPointer, Builtin.Word) -> @owned String
|
||||
cond_br undef, nope, yep
|
||||
|
||||
nope:
|
||||
destroy_value %first
|
||||
%second_ptr = string_literal utf8 "second"
|
||||
%second_len = integer_literal $Builtin.Word, 6
|
||||
%second = apply %makeString(%second_ptr, %second_len) : $@convention(thin) (Builtin.RawPointer, Builtin.Word) -> @owned String
|
||||
br this(%second)
|
||||
|
||||
yep:
|
||||
br this(%first)
|
||||
|
||||
this(%string : @owned $String):
|
||||
destroy_value %string
|
||||
%retval = tuple ()
|
||||
return %retval
|
||||
}
|
||||
|
||||
@@ -445,12 +445,8 @@ bb0(%0 : @owned $MO):
|
||||
return %63 : $()
|
||||
}
|
||||
|
||||
// The InstructionDeleter will delete the `load [take]` and insert a
|
||||
// `destroy_addr`. Observe the creation of the new destroy_addr instruction
|
||||
// that occurs when deleting the `load [take]` and mark it live. Prevents a
|
||||
// leak.
|
||||
// CHECK-LABEL: sil [ossa] @keep_new_destroy_addr : {{.*}} {
|
||||
// CHECK: destroy_addr
|
||||
// CHECK: load [take]
|
||||
// CHECK-LABEL: } // end sil function 'keep_new_destroy_addr'
|
||||
sil [ossa] @keep_new_destroy_addr : $@convention(thin) () -> () {
|
||||
bb0:
|
||||
|
||||
@@ -34,7 +34,9 @@ bb0(%0 : @owned $FileDescriptor):
|
||||
}
|
||||
|
||||
// CHECK-LABEL: sil hidden [ossa] @fd_deinit2 :
|
||||
// CHECK: end_lifetime
|
||||
// CHECK: %1 = drop_deinit
|
||||
// CHECK-NEXT: %2 = destructure_struct %1
|
||||
// CHECK-NEXT: debug_value
|
||||
// CHECK-LABEL: } // end sil function 'fd_deinit2'
|
||||
sil hidden [ossa] @fd_deinit2 : $@convention(method) (@owned FileDescriptor) -> () {
|
||||
bb0(%0 : @owned $FileDescriptor):
|
||||
|
||||
@@ -165,7 +165,8 @@ bb0(%0 : @owned $Builtin.NativeObject, %1 : $*Builtin.NativeObject):
|
||||
// CHECK: bb0([[ARG0:%.*]] : @owned $Builtin.NativeObject, [[ARG1:%.*]] : $*Builtin.NativeObject):
|
||||
// CHECK-NOT: alloc_stack
|
||||
// CHECK: destroy_value [[ARG0]]
|
||||
// CHECK: destroy_addr [[ARG1]]
|
||||
// CHECK: [[L:%.*]] = load [take] [[ARG1]]
|
||||
// CHECK: destroy_value [[L]]
|
||||
// CHECK: } // end sil function 'simple_init_take'
|
||||
sil [ossa] @simple_init_take : $@convention(thin) (@owned Builtin.NativeObject, @in Builtin.NativeObject) -> () {
|
||||
bb0(%0 : @owned $Builtin.NativeObject, %1 : $*Builtin.NativeObject):
|
||||
|
||||
@@ -283,9 +283,9 @@ bb0(%0 : $*X3, %1 : @guaranteed $Builtin.NativeObject):
|
||||
//
|
||||
// CHECK-LABEL: sil [ossa] @testDestructureTupleNoCrash : $@convention(thin) (@owned (Builtin.NativeObject, Builtin.NativeObject)) -> () {
|
||||
// CHECKOPT: bb0(
|
||||
// CHECKOPT-NEXT: destroy_value
|
||||
// CHECKOPT-NEXT: tuple
|
||||
// CHECKOPT-NEXT: return
|
||||
// CHECKOPT-NEXT: (%1, %2) = destructure_tuple
|
||||
// CHECKOPT: destroy_value %2
|
||||
// CHECKOPT-NEXT: destroy_value %1
|
||||
// CHECK: } // end sil function 'testDestructureTupleNoCrash'
|
||||
sil [ossa] @testDestructureTupleNoCrash : $@convention(thin) (@owned (Builtin.NativeObject, Builtin.NativeObject)) -> () {
|
||||
bb0(%0 : @owned $(Builtin.NativeObject, Builtin.NativeObject)):
|
||||
|
||||
Reference in New Issue
Block a user