Skip to content

Commit 1512d06

Browse files
committed
Add support to intrinsics fallback body
Before this fix, the call to `body()` would crash, since `has_body()` would return true, but we would try to retrieve the body of an intrinsic which is not allowed. Instead, the `Instance::body()` function will now convert an Intrinsic into an Item before retrieving its body.
1 parent ea40fa2 commit 1512d06

File tree

3 files changed

+149
-34
lines changed

3 files changed

+149
-34
lines changed

compiler/rustc_smir/src/rustc_smir/builder.rs

+24-33
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use crate::rustc_smir::{Stable, Tables};
77
use rustc_middle::mir;
88
use rustc_middle::mir::visit::MutVisitor;
9-
use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt};
9+
use rustc_middle::ty::{self, TyCtxt};
1010

1111
/// Builds a monomorphic body for a given instance.
1212
pub struct BodyBuilder<'tcx> {
@@ -16,46 +16,41 @@ pub struct BodyBuilder<'tcx> {
1616

1717
impl<'tcx> BodyBuilder<'tcx> {
1818
pub fn new(tcx: TyCtxt<'tcx>, instance: ty::Instance<'tcx>) -> Self {
19+
let instance = match instance.def {
20+
// To get the fallback body of an intrinsic, we need to convert it to an item.
21+
ty::InstanceDef::Intrinsic(def_id) => ty::Instance::new(def_id, instance.args),
22+
_ => instance,
23+
};
1924
BodyBuilder { tcx, instance }
2025
}
2126

2227
/// Build a stable monomorphic body for a given instance based on the MIR body.
2328
///
24-
/// Note that we skip instantiation for static and constants. Trying to do so can cause ICE.
25-
///
26-
/// We do monomorphize non-generic functions to eval unevaluated constants.
29+
/// All constants are also evaluated.
2730
pub fn build(mut self, tables: &mut Tables<'tcx>) -> stable_mir::mir::Body {
28-
let mut body = self.tcx.instance_mir(self.instance.def).clone();
29-
if self.tcx.def_kind(self.instance.def_id()).is_fn_like() || !self.instance.args.is_empty()
31+
let body = tables.tcx.instance_mir(self.instance.def).clone();
32+
let mono_body = if self.tcx.def_kind(self.instance.def_id()).is_fn_like()
33+
|| !self.instance.args.is_empty()
3034
{
31-
self.visit_body(&mut body);
32-
}
33-
body.stable(tables)
34-
}
35-
36-
fn monomorphize<T>(&self, value: T) -> T
37-
where
38-
T: ty::TypeFoldable<TyCtxt<'tcx>>,
39-
{
40-
self.instance.instantiate_mir_and_normalize_erasing_regions(
41-
self.tcx,
42-
ty::ParamEnv::reveal_all(),
43-
ty::EarlyBinder::bind(value),
44-
)
35+
// This call will currently will ICE in some shims which are already monomorphic.
36+
let mut mono_body = self.instance.instantiate_mir_and_normalize_erasing_regions(
37+
tables.tcx,
38+
ty::ParamEnv::reveal_all(),
39+
ty::EarlyBinder::bind(body),
40+
);
41+
self.visit_body(&mut mono_body);
42+
mono_body
43+
} else {
44+
// Already monomorphic.
45+
body
46+
};
47+
mono_body.stable(tables)
4548
}
4649
}
4750

4851
impl<'tcx> MutVisitor<'tcx> for BodyBuilder<'tcx> {
49-
fn visit_ty_const(&mut self, ct: &mut ty::Const<'tcx>, _location: mir::Location) {
50-
*ct = self.monomorphize(*ct);
51-
}
52-
53-
fn visit_ty(&mut self, ty: &mut Ty<'tcx>, _: mir::visit::TyContext) {
54-
*ty = self.monomorphize(*ty);
55-
}
56-
5752
fn visit_constant(&mut self, constant: &mut mir::ConstOperand<'tcx>, location: mir::Location) {
58-
let const_ = self.monomorphize(constant.const_);
53+
let const_ = constant.const_;
5954
let val = match const_.eval(self.tcx, ty::ParamEnv::reveal_all(), constant.span) {
6055
Ok(v) => v,
6156
Err(mir::interpret::ErrorHandled::Reported(..)) => return,
@@ -68,10 +63,6 @@ impl<'tcx> MutVisitor<'tcx> for BodyBuilder<'tcx> {
6863
self.super_constant(constant, location);
6964
}
7065

71-
fn visit_args(&mut self, args: &mut GenericArgsRef<'tcx>, _: mir::Location) {
72-
*args = self.monomorphize(*args);
73-
}
74-
7566
fn tcx(&self) -> TyCtxt<'tcx> {
7667
self.tcx
7768
}

compiler/stable_mir/src/mir/mono.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,22 @@ impl Instance {
4141
with(|cx| cx.instance_args(self.def))
4242
}
4343

44-
/// Get the body of an Instance. The body will be eagerly monomorphized.
44+
/// Get the body of an Instance.
45+
///
46+
/// The body will be eagerly monomorphized and all constants will already be evaluated.
47+
///
48+
/// This method will return the intrinsic fallback body if one was defined.
4549
pub fn body(&self) -> Option<Body> {
4650
with(|context| context.instance_body(self.def))
4751
}
4852

4953
/// Check whether this instance has a body available.
5054
///
55+
/// For intrinsics with fallback body, this will return `true`. It is up to the user to decide
56+
/// whether to specialize the intrinsic or to use its fallback body.
57+
///
58+
/// For more information on fallback body, see <https://github.com/rust-lang/rust/issues/93145>.
59+
///
5160
/// This call is much cheaper than `instance.body().is_some()`, since it doesn't try to build
5261
/// the StableMIR body.
5362
pub fn has_body(&self) -> bool {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//@ run-pass
2+
//! Test information regarding intrinsics and ensure we can retrieve the fallback body if it exists.
3+
//!
4+
//! This tests relies on the intrinsics implementation, and requires one intrinsic with and one
5+
//! without a body. It doesn't matter which intrinsic is called here, and feel free to update that
6+
//! if needed.
7+
8+
//@ ignore-stage1
9+
//@ ignore-cross-compile
10+
//@ ignore-remote
11+
//@ ignore-windows-gnu mingw has troubles with linking https://github.com/rust-lang/rust/pull/116837
12+
13+
#![feature(rustc_private)]
14+
15+
extern crate rustc_hir;
16+
#[macro_use]
17+
extern crate rustc_smir;
18+
extern crate rustc_driver;
19+
extern crate rustc_interface;
20+
extern crate stable_mir;
21+
22+
use rustc_smir::rustc_internal;
23+
use stable_mir::mir::mono::{Instance, InstanceKind};
24+
use stable_mir::mir::visit::{Location, MirVisitor};
25+
use stable_mir::mir::{LocalDecl, Terminator, TerminatorKind};
26+
use stable_mir::ty::{RigidTy, TyKind};
27+
use std::collections::HashSet;
28+
use std::convert::TryFrom;
29+
use std::io::Write;
30+
use std::ops::ControlFlow;
31+
32+
/// This function tests that we can correctly get type information from binary operations.
33+
fn test_intrinsics() -> ControlFlow<()> {
34+
// Find items in the local crate.
35+
let main_def = stable_mir::all_local_items()[0];
36+
let main_instance = Instance::try_from(main_def).unwrap();
37+
let main_body = main_instance.body().unwrap();
38+
let mut visitor = CallsVisitor { locals: main_body.locals(), calls: Default::default() };
39+
visitor.visit_body(&main_body);
40+
41+
let calls = visitor.calls;
42+
assert_eq!(calls.len(), 2, "Expected 2 calls, but found: {calls:?}");
43+
for intrinsic in &calls {
44+
check_intrinsic(intrinsic)
45+
}
46+
47+
ControlFlow::Continue(())
48+
}
49+
50+
/// This check is unfortunately tight to the implementation of intrinsics.
51+
///
52+
/// We want to ensure that StableMIR can handle intrinsics with and without fallback body.
53+
///
54+
/// If by any chance this test breaks because you changed how an intrinsic is implemented, please
55+
/// update the test to invoke a different intrinsic.
56+
fn check_intrinsic(intrinsic: &Instance) {
57+
assert_eq!(intrinsic.kind, InstanceKind::Intrinsic);
58+
let name = intrinsic.intrinsic_name().unwrap();
59+
if intrinsic.has_body() {
60+
let Some(body) = intrinsic.body() else { unreachable!("Expected a body") };
61+
assert!(!body.blocks.is_empty());
62+
assert_eq!(&name, "likely");
63+
} else {
64+
assert!(intrinsic.body().is_none());
65+
assert_eq!(&name, "size_of_val");
66+
}
67+
}
68+
69+
struct CallsVisitor<'a> {
70+
locals: &'a [LocalDecl],
71+
calls: HashSet<Instance>,
72+
}
73+
74+
impl<'a> MirVisitor for CallsVisitor<'a> {
75+
fn visit_terminator(&mut self, term: &Terminator, _loc: Location) {
76+
match &term.kind {
77+
TerminatorKind::Call { func, .. } => {
78+
let TyKind::RigidTy(RigidTy::FnDef(def, args)) =
79+
func.ty(self.locals).unwrap().kind()
80+
else {
81+
return;
82+
};
83+
self.calls.insert(Instance::resolve(def, &args).unwrap());
84+
}
85+
_ => {}
86+
}
87+
}
88+
}
89+
90+
/// This test will generate and analyze a dummy crate using the stable mir.
91+
/// For that, it will first write the dummy crate into a file.
92+
/// Then it will create a `StableMir` using custom arguments and then
93+
/// it will run the compiler.
94+
fn main() {
95+
let path = "binop_input.rs";
96+
generate_input(&path).unwrap();
97+
let args = vec!["rustc".to_string(), "--crate-type=lib".to_string(), path.to_string()];
98+
run!(args, test_intrinsics).unwrap();
99+
}
100+
101+
fn generate_input(path: &str) -> std::io::Result<()> {
102+
let mut file = std::fs::File::create(path)?;
103+
write!(
104+
file,
105+
r#"
106+
#![feature(core_intrinsics)]
107+
use std::intrinsics::*;
108+
pub fn use_intrinsics(init: bool) -> bool {{
109+
let sz = unsafe {{ size_of_val("hi") }};
110+
likely(init && sz == 2)
111+
}}
112+
"#
113+
)?;
114+
Ok(())
115+
}

0 commit comments

Comments
 (0)