Skip to content

Commit 314d459

Browse files
Stub panics during MIR transformation (#4169)
Modifies the `RustcIntrinsicsPass` to stub out panic functions during body transformation at the MIR level. This avoids having to bring in (and do transformation passes on) the large amounts of code they use for string formatting. Our panic hook has been modified to then detect these stubs during codegen and still generate the same Goto code for them. The results of this change on the MIR bloat of `panic!()` and its callers is shown below: | test | lines of MIR (before) | lines of MIR (w/ panic stubbing) | | - | - | - | | `Option<usize>::unwrap()` | 292 | 217 | | `Option<usize>::expect()` | 8664 | 225 | | `Result<usize, usize>::unwrap()` | 20461 | 20374 | | `Result<usize, usize>::expect()` | 20463 | 20376 | | `panic!("hello!")` | 8498 | 41 | | `panic!("hello {x}!")` (x being a `&'static str`) | 8502 | 45 | The lack of significant improvement on the two `Result` functions is elaborated on in #4168 and will take additional consideration to fix. Resolves #2835 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.
1 parent 9fbf838 commit 314d459

File tree

10 files changed

+169
-20
lines changed

10 files changed

+169
-20
lines changed

kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,12 @@ struct Panic;
326326
impl GotocHook for Panic {
327327
fn hook_applies(&self, tcx: TyCtxt, instance: Instance) -> bool {
328328
let def_id = rustc_internal::internal(tcx, instance.def.def_id());
329-
Some(def_id) == tcx.lang_items().panic_fn()
329+
let kani_tool_attr = attributes::fn_marker(instance.def);
330+
331+
// we check the attributes to make sure this hook applies to
332+
// panic functions we've stubbed too
333+
kani_tool_attr.is_some_and(|kani| kani.contains("PanicStub"))
334+
|| Some(def_id) == tcx.lang_items().panic_fn()
330335
|| tcx.has_attr(def_id, rustc_span::sym::rustc_const_panic_str)
331336
|| Some(def_id) == tcx.lang_items().panic_fmt()
332337
|| Some(def_id) == tcx.lang_items().begin_panic_fn()

kani-compiler/src/kani_middle/kani_functions.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ pub enum KaniModel {
9999
SetSliceChunkPtrInitialized,
100100
#[strum(serialize = "SetSlicePtrInitializedModel")]
101101
SetSlicePtrInitialized,
102+
#[strum(serialize = "PanicStub")]
103+
PanicStub,
102104
#[strum(serialize = "SetStrPtrInitializedModel")]
103105
SetStrPtrInitialized,
104106
#[strum(serialize = "SizeOfDynObjectModel")]

kani-compiler/src/kani_middle/transform/rustc_intrinsics.rs

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,24 @@ impl TransformPass for RustcIntrinsicsPass {
5252
/// For every unsafe dereference or a transmute operation, we check all values are valid.
5353
fn transform(&mut self, tcx: TyCtxt, body: Body, instance: Instance) -> (bool, Body) {
5454
debug!(function=?instance.name(), "transform");
55+
5556
let mut new_body = MutableBody::from(body);
5657
let mut visitor =
57-
ReplaceIntrinsicCallVisitor::new(&self.models, new_body.locals().to_vec());
58+
ReplaceIntrinsicCallVisitor::new(&self.models, new_body.locals().to_vec(), tcx);
5859
visitor.visit_body(&mut new_body);
5960
let changed = self.replace_lowered_intrinsics(tcx, &mut new_body);
6061
(visitor.changed || changed, new_body.into())
6162
}
6263
}
6364

65+
fn is_panic_function(tcx: &TyCtxt, def_id: rustc_smir::stable_mir::DefId) -> bool {
66+
let def_id = rustc_internal::internal(*tcx, def_id);
67+
Some(def_id) == tcx.lang_items().panic_fn()
68+
|| tcx.has_attr(def_id, rustc_span::sym::rustc_const_panic_str)
69+
|| Some(def_id) == tcx.lang_items().panic_fmt()
70+
|| Some(def_id) == tcx.lang_items().begin_panic_fn()
71+
}
72+
6473
impl RustcIntrinsicsPass {
6574
pub fn new(queries: &QueryDb) -> Self {
6675
let models = queries
@@ -132,19 +141,24 @@ impl RustcIntrinsicsPass {
132141
}
133142
}
134143

135-
struct ReplaceIntrinsicCallVisitor<'a> {
144+
struct ReplaceIntrinsicCallVisitor<'a, 'tcx> {
136145
models: &'a HashMap<KaniModel, FnDef>,
137146
locals: Vec<LocalDecl>,
147+
tcx: TyCtxt<'tcx>,
138148
changed: bool,
139149
}
140150

141-
impl<'a> ReplaceIntrinsicCallVisitor<'a> {
142-
fn new(models: &'a HashMap<KaniModel, FnDef>, locals: Vec<LocalDecl>) -> Self {
143-
ReplaceIntrinsicCallVisitor { models, locals, changed: false }
151+
impl<'a, 'tcx> ReplaceIntrinsicCallVisitor<'a, 'tcx> {
152+
fn new(
153+
models: &'a HashMap<KaniModel, FnDef>,
154+
locals: Vec<LocalDecl>,
155+
tcx: TyCtxt<'tcx>,
156+
) -> Self {
157+
ReplaceIntrinsicCallVisitor { models, locals, changed: false, tcx }
144158
}
145159
}
146160

147-
impl MutMirVisitor for ReplaceIntrinsicCallVisitor<'_> {
161+
impl MutMirVisitor for ReplaceIntrinsicCallVisitor<'_, '_> {
148162
/// Replace the terminator for some rustc's intrinsics.
149163
///
150164
/// In some cases, we replace a function call to a rustc intrinsic by a call to the
@@ -159,22 +173,35 @@ impl MutMirVisitor for ReplaceIntrinsicCallVisitor<'_> {
159173
if let TerminatorKind::Call { func, .. } = &mut term.kind
160174
&& let TyKind::RigidTy(RigidTy::FnDef(def, args)) =
161175
func.ty(&self.locals).unwrap().kind()
162-
&& def.is_intrinsic()
163176
{
164-
let instance = Instance::resolve(def, &args).unwrap();
165-
let intrinsic = Intrinsic::from_instance(&instance);
166-
debug!(?intrinsic, "handle_terminator");
167-
let model = match intrinsic {
168-
Intrinsic::SizeOfVal => self.models[&KaniModel::SizeOfVal],
169-
Intrinsic::MinAlignOfVal => self.models[&KaniModel::AlignOfVal],
170-
Intrinsic::PtrOffsetFrom => self.models[&KaniModel::PtrOffsetFrom],
171-
Intrinsic::PtrOffsetFromUnsigned => self.models[&KaniModel::PtrOffsetFromUnsigned],
172-
// The rest is handled in codegen.
173-
_ => {
174-
return self.super_terminator(term);
177+
// Get the model we should use to replace this function call, if any.
178+
let replacement_model = if def.is_intrinsic() {
179+
let instance = Instance::resolve(def, &args).unwrap();
180+
let intrinsic = Intrinsic::from_instance(&instance);
181+
debug!(?intrinsic, "handle_terminator");
182+
match intrinsic {
183+
Intrinsic::SizeOfVal => self.models[&KaniModel::SizeOfVal],
184+
Intrinsic::MinAlignOfVal => self.models[&KaniModel::AlignOfVal],
185+
Intrinsic::PtrOffsetFrom => self.models[&KaniModel::PtrOffsetFrom],
186+
Intrinsic::PtrOffsetFromUnsigned => {
187+
self.models[&KaniModel::PtrOffsetFromUnsigned]
188+
}
189+
// The rest is handled in codegen.
190+
_ => {
191+
return self.super_terminator(term);
192+
}
175193
}
194+
} else if is_panic_function(&self.tcx, def.0) {
195+
// If we find a panic function, we replace it with our stub.
196+
self.models[&KaniModel::PanicStub]
197+
} else {
198+
return self.super_terminator(term);
176199
};
177-
let new_instance = Instance::resolve(model, &args).unwrap();
200+
201+
let new_instance = Instance::resolve(replacement_model, &args).unwrap();
202+
203+
// Construct the wrapper types needed to insert our resolved model [Instance]
204+
// back into the MIR as an operand.
178205
let literal = MirConst::try_new_zero_sized(new_instance.ty()).unwrap();
179206
let span = term.span;
180207
let new_func = ConstOperand { span, user_ty: None, const_: literal };

library/kani_core/src/models.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ macro_rules! generate_models {
3232
}
3333
}
3434

35+
#[kanitool::fn_marker = "PanicStub"]
36+
pub fn panic_stub(t: &str) -> ! {
37+
// Using an infinite loop here to have the function return the never (`!`) type.
38+
// We could also use `exit()` / `abort()` but both require depending on std::process.
39+
loop {}
40+
}
41+
3542
#[kanitool::fn_marker = "AlignOfValRawModel"]
3643
pub fn align_of_val_raw<T: ?Sized>(ptr: *const T) -> usize {
3744
if let Some(size) = kani::mem::checked_align_of_raw(ptr) {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Copyright Kani Contributors
2+
# SPDX-License-Identifier: Apache-2.0 OR MIT
3+
script: run.sh
4+
expected: expected
5+
exit_code: 0
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
--VERIFYING panic.rs--
2+
Manual Harness Summary: \
3+
Verification failed for - main \
4+
Complete - 0 successfully verified harnesses, 1 failures, 1 total.
5+
6+
--READING MIR for panic.rs--
7+
panic | fn kani::rustc_intrinsics::panic_stub(_1: &str) -> ! { \
8+
panic | let mut _0: !; \
9+
panic | debug t => _1; \
10+
panic | bb0: { \
11+
panic | goto -> bb1; \
12+
panic | } \
13+
panic | bb1: { \
14+
panic | goto -> bb1; \
15+
panic | } \
16+
panic | }
17+
18+
19+
--VERIFYING option.rs--
20+
Manual Harness Summary: \
21+
Verification failed for - main \
22+
Complete - 0 successfully verified harnesses, 1 failures, 1 total.
23+
24+
--READING MIR for option.rs--
25+
option | fn kani::rustc_intrinsics::panic_stub(_1: &str) -> ! { \
26+
option | let mut _0: !; \
27+
option | debug t => _1; \
28+
option | bb0: { \
29+
option | goto -> bb1; \
30+
option | } \
31+
option | bb1: { \
32+
option | goto -> bb1; \
33+
option | } \
34+
option | }
35+
36+
37+
--VERIFYING result.rs--
38+
Manual Harness Summary: \
39+
Verification failed for - main \
40+
Complete - 0 successfully verified harnesses, 1 failures, 1 total.
41+
42+
--READING MIR for result.rs--
43+
result | fn kani::rustc_intrinsics::panic_stub(_1: &str) -> ! { \
44+
result | let mut _0: !; \
45+
result | debug t => _1; \
46+
result | bb0: { \
47+
result | goto -> bb1; \
48+
result | } \
49+
result | bb1: { \
50+
result | goto -> bb1; \
51+
result | } \
52+
result | }
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Ensure the panic!() internal to `Option::unwrap()` is stubbed.
5+
#[kani::proof]
6+
fn main() {
7+
foo();
8+
}
9+
10+
fn foo() -> usize {
11+
let a: Option<usize> = kani::any();
12+
a.unwrap()
13+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Ensure that the panic!() macro itself gets stubbed.
5+
#[kani::proof]
6+
fn main() {
7+
panic!("hello!");
8+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Ensure the panic!() internal to `Result::unwrap()` is stubbed.
5+
#[kani::proof]
6+
fn main() {
7+
foo();
8+
}
9+
10+
fn foo() -> usize {
11+
let a: Result<usize, usize> = kani::any();
12+
a.unwrap()
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright Kani Contributors
2+
# SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
echo "--VERIFYING panic.rs--"
5+
RUSTFLAGS="--emit mir" kani panic.rs
6+
echo "--READING MIR for panic.rs--"
7+
cat panic__* | sed 's/^/ panic | /'
8+
9+
echo "--VERIFYING option.rs--"
10+
RUSTFLAGS="--emit mir" kani option.rs
11+
echo "--READING MIR for option.rs--"
12+
cat option__* | sed 's/^/ option | /'
13+
14+
echo "--VERIFYING result.rs--"
15+
RUSTFLAGS="--emit mir" kani result.rs
16+
echo "--READING MIR for result.rs--"
17+
cat result__* | sed 's/^/ result | /'

0 commit comments

Comments
 (0)