Skip to content

Commit 8a7b5f7

Browse files
committed
[sil-verifier] Make flow sensitive verification handle begin_apply token/allocation correctly.
Specifically: 1. We were tracking the stack allocation both in handleScopeInst and in the Stack. We should only track it in one of them. I also used this as an opportunity to make sure the code worked for non_nested code. 2. I made it so that we properly handle the end tracking part of the code so we handle the token/stack part of begin_apply correctly. I generalized the code so that we should handle non_nested stack allocations as well. I included tests that validated that we now handle this correctly.
1 parent c907650 commit 8a7b5f7

File tree

3 files changed

+87
-26
lines changed

3 files changed

+87
-26
lines changed

lib/SIL/Verifier/FlowSensitiveVerifier.cpp

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,13 @@ struct BBState {
210210
verificationFailure("operation has already been ended", &i, nullptr);
211211
}
212212
}
213+
214+
void handleScopeEndingUse(SILValue value, SILInstruction *origUser) {
215+
if (!ActiveOps.erase(value)) {
216+
verificationFailure("operation has already been ended", origUser,
217+
nullptr);
218+
}
219+
}
213220
};
214221

215222
struct DeadEndRegionState {
@@ -224,7 +231,7 @@ struct DeadEndRegionState {
224231
/// If this is a scope ending inst, return the result from the instruction
225232
/// that provides the scoped value whose lifetime must be ended by some other
226233
/// scope ending instruction.
227-
static SILValue isScopeInst(SILInstruction *i) {
234+
static SILValue getScopeEndingValueOfScopeInst(SILInstruction *i) {
228235
if (auto *bai = dyn_cast<BeginAccessInst>(i))
229236
return bai;
230237
if (auto *bai = dyn_cast<BeginApplyInst>(i))
@@ -325,15 +332,15 @@ void swift::silverifier::verifyFlowSensitiveRules(SILFunction *F) {
325332
// verify their joint post-dominance.
326333
if (i.isStackAllocationNested()) {
327334
state.Stack.push_back(i.getStackAllocation());
328-
329-
// Also track begin_apply as a scope instruction.
330-
if (auto *bai = dyn_cast<BeginApplyInst>(&i)) {
331-
state.handleScopeInst(bai->getStackAllocation());
332-
state.handleScopeInst(bai->getTokenResult());
333-
}
334335
} else {
335336
state.handleScopeInst(i.getStackAllocation());
336337
}
338+
339+
// Also track begin_apply's token as an ActiveOp so we can also verify
340+
// its joint dominance.
341+
if (auto *bai = dyn_cast<BeginApplyInst>(&i)) {
342+
state.handleScopeInst(bai->getTokenResult());
343+
}
337344
continue;
338345
}
339346

@@ -343,28 +350,48 @@ void swift::silverifier::verifyFlowSensitiveRules(SILFunction *F) {
343350
op = mvi->getOperand();
344351
}
345352

346-
auto beginInst = op->getDefiningInstruction();
347-
if (beginInst && (!beginInst->isAllocatingStack() ||
348-
!beginInst->isStackAllocationNested())) {
349-
state.handleScopeEndingInst(i);
350-
} else if (!state.Stack.empty() && op == state.Stack.back()) {
353+
auto *definingInst = op->getDefiningInstruction();
354+
355+
// First see if we have a begin_inst. In such a case, we need to handle
356+
// the token result first.
357+
if (auto *beginInst =
358+
llvm::dyn_cast_if_present<BeginApplyInst>(definingInst)) {
359+
// Check if our value is the token result... in such a case, we need
360+
// to handle scope ending inst and continue.
361+
if (op == beginInst->getTokenResult()) {
362+
state.handleScopeEndingUse(op, &i);
363+
continue;
364+
}
365+
}
366+
367+
// Ok, we have some sort of memory. Lets check if our definingInst is
368+
// unnested stack memory. In such a case, we want to just verify post
369+
// dominance and not validate that we are in stack order.
370+
if (definingInst && definingInst->isAllocatingStack() &&
371+
!definingInst->isStackAllocationNested()) {
372+
state.handleScopeEndingUse(op, &i);
373+
continue;
374+
}
375+
376+
// Ok, we have nested stack memory. Lets try to pop the stack. If we
377+
// fail due to an empty stack or a lack of a match, emit an error.
378+
if (!state.Stack.empty() && op == state.Stack.back()) {
351379
state.Stack.pop_back();
352-
if (llvm::isa_and_present<BeginApplyInst>(beginInst))
353-
state.handleScopeEndingInst(i);
354-
} else {
355-
verificationFailure(
356-
"deallocating allocation that is not the top of the stack", &i,
357-
[&](SILPrintContext &ctx) {
358-
state.printStack(ctx, "Current stack state");
359-
ctx.OS() << "Stack allocation:\n" << *op;
360-
// The deallocation is printed out as the focus of the
361-
// failure.
362-
});
380+
continue;
363381
}
382+
383+
verificationFailure(
384+
"deallocating allocation that is not the top of the stack", &i,
385+
[&](SILPrintContext &ctx) {
386+
state.printStack(ctx, "Current stack state");
387+
ctx.OS() << "Stack allocation:\n" << *op;
388+
// The deallocation is printed out as the focus of the
389+
// failure.
390+
});
364391
continue;
365392
}
366393

367-
if (auto scopeEndingValue = isScopeInst(&i)) {
394+
if (auto scopeEndingValue = getScopeEndingValueOfScopeInst(&i)) {
368395
state.handleScopeInst(scopeEndingValue);
369396
continue;
370397
}

test/SIL/verifier_failures.sil

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,37 @@ bb2:
104104
abort_apply %3
105105
unwind
106106
}
107+
108+
// We shouldn't error here. Make sure that we properly handle the dealloc_stack
109+
// user vs the end_apply user.
110+
sil [ossa] @test_begin_apply_1 : $@yield_once @convention(method) (@guaranteed Builtin.Int32) -> @yields Builtin.Int32 {
111+
bb0(%0 : $Builtin.Int32):
112+
(%3, %4, %5) = begin_apply undef(%0) : $@yield_once_2 @convention(method) (@guaranteed Builtin.Int32) -> @yields Builtin.Int32
113+
%6 = end_apply %4 as $()
114+
yield %3 : $Builtin.Int32, resume bb1, unwind bb2
115+
116+
bb1:
117+
dealloc_stack %5 : $*Builtin.SILToken
118+
%9 = tuple ()
119+
return %9 : $()
120+
121+
bb2:
122+
dealloc_stack %5 : $*Builtin.SILToken
123+
unwind
124+
}
125+
126+
sil [ossa] @test_begin_apply_2 : $@yield_once @convention(method) (@guaranteed Builtin.Int32) -> @yields Builtin.Int32 {
127+
bb0(%0 : $Builtin.Int32):
128+
(%3, %4, %5) = begin_apply undef(%0) : $@yield_once_2 @convention(method) (@guaranteed Builtin.Int32) -> @yields Builtin.Int32
129+
dealloc_stack %5 : $*Builtin.SILToken
130+
yield %3 : $Builtin.Int32, resume bb1, unwind bb2
131+
132+
bb1:
133+
%6 = end_apply %4 as $()
134+
%9 = tuple ()
135+
return %9 : $()
136+
137+
bb2:
138+
%7 = end_apply %4 as $()
139+
unwind
140+
}

test/SIL/verifier_loweredsil_failures.sil

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-sil-opt -sil-verify-all=false -enable-sil-verify-all=false -emit-sorted-sil -verify-continue-on-failure -o /dev/null %s 2>&1 | %FileCheck %s
1+
// RUN: %target-sil-opt -sil-verify-all=false -enable-sil-verify-all=false -emit-sorted-sil -verify-continue-on-failure -o /dev/null %s 2>&1 | %FileCheck '--implicit-check-not=Begin Error' %s
22

33
// REQUIRES: asserts
44

@@ -19,7 +19,7 @@ sil @alloc_pack_metadata_before_tuple : $@convention(thin) () -> () {
1919
}
2020

2121
// CHECK-LABEL: Begin Error in function dealloc_pack_metadata_with_bad_operand
22-
// CHECK: SIL verification failed: operation has already been ended
22+
// CHECK: SIL verification failed: deallocating allocation that is not the top of the stack
2323
// CHECK-LABEL: End Error in function dealloc_pack_metadata_with_bad_operand
2424
// CHECK-LABEL: Begin Error in function dealloc_pack_metadata_with_bad_operand
2525
// CHECK: SIL verification failed: return with stack allocs that haven't been deallocated

0 commit comments

Comments
 (0)