Skip to content

Commit 48db890

Browse files
committed
SIL: be more tolerant when a user is deleted during use-list iteration.
Check if an operand's instruction has been deleted in `UseList.next()`. This allows to delete an instruction, which has two uses of a value, during use-list iteration.
1 parent 67f312c commit 48db890

File tree

6 files changed

+96
-3
lines changed

6 files changed

+96
-3
lines changed

SwiftCompilerSources/Sources/Optimizer/TestPasses/TestInstructionIteration.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ private func handle(instruction: Instruction, _ context: FunctionPassContext) {
6262
deleteAllInstructions(ofType: BranchInst.self, in: instruction.parentBlock, context)
6363
case "split_block":
6464
_ = context.splitBlock(before: instruction)
65+
case "print_uses":
66+
for use in sl.uses {
67+
print("use: \(use)")
68+
}
69+
case "delete_first_user":
70+
deleteUser(of: sl, at: 0, context)
71+
case "delete_second_user":
72+
deleteUser(of: sl, at: 1, context)
6573
default:
6674
break
6775
}
@@ -75,3 +83,13 @@ private func deleteAllInstructions<InstType: Instruction>(ofType: InstType.Type,
7583
}
7684
}
7785
}
86+
87+
private func deleteUser(of value: Value, at deleteIndex: Int, _ context: FunctionPassContext) {
88+
for (idx, use) in value.uses.enumerated() {
89+
if idx == deleteIndex {
90+
context.erase(instruction: use.instruction)
91+
} else {
92+
print("use: \(use)")
93+
}
94+
}
95+
}

SwiftCompilerSources/Sources/SIL/Operand.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,16 @@ public struct UseList : CollectionLikeSequence {
105105
var currentOpPtr: OptionalBridgedOperand
106106

107107
public mutating func next() -> Operand? {
108-
if let op = currentOpPtr.operand {
109-
currentOpPtr = op.getNextUse()
108+
if let bridgedOp = currentOpPtr.operand {
109+
var op = bridgedOp
110+
// Skip operands of deleted instructions.
111+
while op.isDeleted() {
112+
guard let nextOp = op.getNextUse().operand else {
113+
return nil
114+
}
115+
op = nextOp
116+
}
117+
currentOpPtr = op.getNextUse();
110118
return Operand(bridged: op)
111119
}
112120
return nil

include/swift/SIL/SILBridging.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ struct BridgedOperand {
384384
BRIDGED_INLINE bool isTypeDependent() const;
385385
BRIDGED_INLINE bool isLifetimeEnding() const;
386386
BRIDGED_INLINE bool canAcceptOwnership(BridgedValue::Ownership ownership) const;
387+
BRIDGED_INLINE bool isDeleted() const;
387388
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE OptionalBridgedOperand getNextUse() const;
388389
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedValue getValue() const;
389390
SWIFT_IMPORT_UNSAFE BRIDGED_INLINE BridgedInstruction getUser() const;

include/swift/SIL/SILBridgingImpl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,10 @@ bool BridgedOperand::canAcceptOwnership(BridgedValue::Ownership ownership) const
616616
return op->canAcceptKind(BridgedValue::unbridge(ownership));
617617
}
618618

619+
bool BridgedOperand::isDeleted() const {
620+
return op->getUser() == nullptr;
621+
}
622+
619623
OptionalBridgedOperand BridgedOperand::getNextUse() const {
620624
return {op->getNextUse()};
621625
}

include/swift/SIL/SILValue.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,7 @@ class Operand {
10521052
Operand **Back = nullptr;
10531053

10541054
/// The owner of this operand.
1055+
/// If null, the Owner was deleted (but not freed, yet).
10551056
/// FIXME: this could be space-compressed.
10561057
SILInstruction *Owner;
10571058

@@ -1104,9 +1105,12 @@ class Operand {
11041105
void drop() {
11051106
removeFromCurrent();
11061107
TheValue = SILValue();
1107-
NextUse = nullptr;
11081108
Back = nullptr;
11091109
Owner = nullptr;
1110+
// Note: we are _not_ clearing the `NextUse` pointer to be able to delete
1111+
// users while iterating over the use list.
1112+
// In such a case, the iterator can detect that the Owner is null and skip
1113+
// to the next (non-deleted) use by following the non-null `NextUse` pointer.
11101114
}
11111115

11121116
~Operand() {

test/SIL/instruction_iteration.sil

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,61 @@ bb0:
235235
return %0 : $()
236236
}
237237

238+
sil @use1 : $@convention(thin) (Builtin.RawPointer) -> ()
239+
sil @use2 : $@convention(thin) (Builtin.RawPointer, Builtin.RawPointer) -> ()
240+
241+
// LOG-LABEL: Test instruction iteration in print_uses:
242+
// LOG-NEXT: bb0:
243+
// LOG-NEXT: %0 = string_literal utf8 "print_uses"
244+
// LOG-NEXT: use: operand #2 of %4 = apply %3(%0, %0)
245+
// LOG-NEXT: use: operand #1 of %4 = apply %3(%0, %0)
246+
// LOG-NEXT: use: operand #1 of %2 = apply %1(%0)
247+
// LOG-NEXT: // function_ref use1
248+
// LOG: End function print_uses:
249+
sil @print_uses : $@convention(thin) () -> () {
250+
bb0:
251+
%0 = string_literal utf8 "print_uses"
252+
%1 = function_ref @use1 : $@convention(thin) (Builtin.RawPointer) -> ()
253+
%2 = apply %1(%0) : $@convention(thin) (Builtin.RawPointer) -> ()
254+
%3 = function_ref @use2 : $@convention(thin) (Builtin.RawPointer, Builtin.RawPointer) -> ()
255+
%4 = apply %3(%0, %0) : $@convention(thin) (Builtin.RawPointer, Builtin.RawPointer) -> ()
256+
%5 = tuple ()
257+
return %5
258+
}
259+
260+
// LOG-LABEL: Test instruction iteration in delete_first_user:
261+
// LOG-NEXT: bb0:
262+
// LOG-NEXT: %0 = string_literal utf8 "delete_first_user"
263+
// LOG-NEXT: use: operand #1 of %2 = apply %1(%0)
264+
// LOG-NEXT: // function_ref use1
265+
// LOG: End function delete_first_user:
266+
sil @delete_first_user : $@convention(thin) () -> () {
267+
bb0:
268+
%0 = string_literal utf8 "delete_first_user"
269+
%1 = function_ref @use1 : $@convention(thin) (Builtin.RawPointer) -> ()
270+
%2 = apply %1(%0) : $@convention(thin) (Builtin.RawPointer) -> ()
271+
%3 = function_ref @use2 : $@convention(thin) (Builtin.RawPointer, Builtin.RawPointer) -> ()
272+
%4 = apply %3(%0, %0) : $@convention(thin) (Builtin.RawPointer, Builtin.RawPointer) -> ()
273+
%5 = tuple ()
274+
return %5
275+
}
276+
277+
// LOG-LABEL: Test instruction iteration in delete_second_user:
278+
// LOG-NEXT: bb0:
279+
// LOG-NEXT: %0 = string_literal utf8 "delete_second_user"
280+
// LOG-NEXT: use: operand #1 of %5 = apply %1(%0)
281+
// LOG-NEXT: use: operand #1 of %2 = apply %1(%0)
282+
// LOG-NEXT: // function_ref use1
283+
// LOG: End function delete_second_user:
284+
sil @delete_second_user : $@convention(thin) () -> () {
285+
bb0:
286+
%0 = string_literal utf8 "delete_second_user"
287+
%1 = function_ref @use1 : $@convention(thin) (Builtin.RawPointer) -> ()
288+
%2 = apply %1(%0) : $@convention(thin) (Builtin.RawPointer) -> ()
289+
%3 = function_ref @use2 : $@convention(thin) (Builtin.RawPointer, Builtin.RawPointer) -> ()
290+
%4 = apply %3(%0, %0) : $@convention(thin) (Builtin.RawPointer, Builtin.RawPointer) -> ()
291+
%5 = apply %1(%0) : $@convention(thin) (Builtin.RawPointer) -> ()
292+
%6 = tuple ()
293+
return %6
294+
}
295+

0 commit comments

Comments
 (0)