Skip to main content

charon_lib/minirust/
intrinsics.rs

1//! Functions synthesized to implement intrinsics and start the program.
2
3use super::*;
4use itertools::Itertools;
5
6pub(super) struct FunctionBuilder {
7    locals: Vec<mini::Type>,
8    arg_count: usize,
9    blocks: Vec<Option<mini::BasicBlock>>,
10    calling_convention: mini::CallingConvention,
11}
12
13impl FunctionBuilder {
14    pub(super) fn new<T: mini::Target>(
15        ctx: &TranslateCtx<'_, T>,
16        span: Span,
17        signature: &FunSig,
18    ) -> Result<Self> {
19        let locals = std::iter::once(&signature.output)
20            .chain(&signature.inputs)
21            .map(|ty| ctx.ty(span, ty))
22            .try_collect()?;
23        Ok(Self {
24            locals,
25            arg_count: signature.inputs.len(),
26            blocks: Vec::new(),
27            calling_convention: ctx.calling_convention(span, &signature.abi)?,
28        })
29    }
30
31    pub(super) fn return_local(&self) -> mini::LocalName {
32        mini::LocalName(name(0))
33    }
34
35    pub(super) fn argument(&self, index: usize) -> mini::LocalName {
36        assert!(index < self.arg_count);
37        mini::LocalName(name((index + 1) as u32))
38    }
39
40    pub(super) fn add_local(&mut self, ty: mini::Type) -> mini::LocalName {
41        let name = mini::LocalName(name(self.locals.len() as u32));
42        self.locals.push(ty);
43        name
44    }
45
46    pub(super) fn declare_block(&mut self) -> mini::BbName {
47        let name = mini::BbName(name(self.blocks.len() as u32));
48        self.blocks.push(None);
49        name
50    }
51
52    pub(super) fn set_block(&mut self, name: mini::BbName, block: mini::BasicBlock) {
53        assert!(
54            self.blocks[name.0.get_internal() as usize]
55                .replace(block)
56                .is_none(),
57            "MiniRust block was defined twice"
58        );
59    }
60
61    pub(super) fn finish(self) -> mini::Function {
62        // Start the function by retagging the function arguments.
63        let arg_retags = (0..self.arg_count)
64            .map(|index| mini::Statement::Validate {
65                place: mini::PlaceExpr::Local(self.argument(index)),
66                fn_entry: true,
67            })
68            .collect_vec();
69        let mut blocks = self
70            .blocks
71            .into_iter()
72            .map(|block| block.expect("MiniRust block was declared but never defined"))
73            .collect_vec();
74        let first_block = &mut blocks[0];
75        first_block.statements = arg_retags
76            .into_iter()
77            .chain(first_block.statements.iter())
78            .collect();
79
80        let mut function = mb::function(mb::Ret::Yes, self.arg_count, &self.locals, &blocks);
81        function.calling_convention = self.calling_convention;
82        function
83    }
84}
85
86pub(super) enum UnwindSource {
87    ExplicitPayload,
88    OpaquePayload,
89}
90
91impl<T: mini::Target> TranslateCtx<'_, T> {
92    /// Lowers this function to an intrinsic if we recognize it as such. Otherwise return `None`.
93    pub(super) fn lower_intrinsic(&self, fdecl: &FunDecl) -> Result<Option<mini::Function>> {
94        let span = fdecl.item_meta.span;
95        let function = if let [PathElem::Ident(krate, _), PathElem::Ident(item, _)] =
96            fdecl.item_meta.name.as_slice_uninstantiated()
97            && krate == "intrinsics"
98        {
99            // Recognize the intrinsics used by MiniRust's `minimize` test suite.
100            let intrinsic_op = match item.as_str() {
101                "print" => mini::IntrinsicOp::PrintStdout,
102                "eprint" => mini::IntrinsicOp::PrintStderr,
103                "exit" => mini::IntrinsicOp::Exit,
104                "allocate" => mini::IntrinsicOp::Allocate,
105                "deallocate" => mini::IntrinsicOp::Deallocate,
106                "spawn" => mini::IntrinsicOp::Spawn,
107                "join" => mini::IntrinsicOp::Join,
108                "create_lock" => mini::IntrinsicOp::Lock(mini::IntrinsicLockOp::Create),
109                "acquire" => mini::IntrinsicOp::Lock(mini::IntrinsicLockOp::Acquire),
110                "release" => mini::IntrinsicOp::Lock(mini::IntrinsicLockOp::Release),
111                "atomic_store" => mini::IntrinsicOp::AtomicStore,
112                "atomic_load" => mini::IntrinsicOp::AtomicLoad,
113                "compare_exchange" => mini::IntrinsicOp::AtomicCompareExchange,
114                "atomic_fetch_add" => mini::IntrinsicOp::AtomicFetchAndOp(mini::IntBinOp::Add),
115                "atomic_fetch_sub" => mini::IntrinsicOp::AtomicFetchAndOp(mini::IntBinOp::Sub),
116                _ => raise!(span, "unknown MiniRust test intrinsic `{item}`"),
117            };
118            self.make_intrinsic_function(span, fdecl, intrinsic_op)?
119        } else if let Some(lang_item) = &fdecl.item_meta.lang_item {
120            match lang_item {
121                LangItem::PanicFmt | LangItem::PanicImpl => {
122                    self.make_unwind_function(span, fdecl, UnwindSource::OpaquePayload)?
123                }
124                _ => return Ok(None),
125            }
126        } else {
127            match &fdecl.body {
128                Body::Extern(name) => match name.as_str() {
129                    "minirust_print" => {
130                        self.make_intrinsic_function(span, fdecl, mini::IntrinsicOp::PrintStdout)?
131                    }
132                    "minirust_start_unwind" => {
133                        self.make_unwind_function(span, fdecl, UnwindSource::ExplicitPayload)?
134                    }
135                    _ => return Ok(None),
136                },
137                Body::Intrinsic { name, .. } => match name.as_str() {
138                    "abort" => {
139                        self.make_intrinsic_function(span, fdecl, mini::IntrinsicOp::Abort)?
140                    }
141                    "assume" => {
142                        self.make_intrinsic_function(span, fdecl, mini::IntrinsicOp::Assume)?
143                    }
144                    "raw_eq" => {
145                        self.make_intrinsic_function(span, fdecl, mini::IntrinsicOp::RawEq)?
146                    }
147                    "catch_unwind" => self.make_catch_unwind_function(span, fdecl)?,
148                    "size_of_val" => self.make_layout_of_val_function(span, fdecl, false)?,
149                    "align_of_val" => self.make_layout_of_val_function(span, fdecl, true)?,
150                    "cold_path" => self.make_cold_path_function(span, fdecl)?,
151                    "arith_offset" => self.make_arith_offset_function(span, fdecl)?,
152                    "ptr_offset_from" => self.make_ptr_offset_from_function(span, fdecl, false)?,
153                    "ptr_offset_from_unsigned" => {
154                        self.make_ptr_offset_from_function(span, fdecl, true)?
155                    }
156                    _ => return Ok(None),
157                },
158                _ => return Ok(None),
159            }
160        };
161        Ok(Some(function))
162    }
163
164    /// Make a function that invokes the matching MiniRust intrinsic.
165    fn make_intrinsic_function(
166        &self,
167        span: Span,
168        fdecl: &FunDecl,
169        intrinsic: mini::IntrinsicOp,
170    ) -> Result<mini::Function> {
171        let signature = &fdecl.signature;
172        let mut builder = FunctionBuilder::new(self, span, signature)?;
173        let ret = builder.return_local();
174        let arguments: Vec<_> = (0..signature.inputs.len())
175            .map(|index| builder.argument(index))
176            .collect();
177        let start = builder.declare_block();
178        let return_block = builder.declare_block();
179        builder.set_block(
180            start,
181            mb::block(
182                &[],
183                mini::Terminator::Intrinsic {
184                    intrinsic,
185                    arguments: arguments
186                        .iter()
187                        .map(|argument| mb::load(mini::PlaceExpr::Local(*argument)))
188                        .collect(),
189                    ret: mini::PlaceExpr::Local(ret),
190                    next_block: Some(return_block),
191                },
192                mini::BbKind::Regular,
193            ),
194        );
195        builder.set_block(
196            return_block,
197            mb::block(&[], mini::Terminator::Return, mini::BbKind::Regular),
198        );
199        Ok(builder.finish())
200    }
201
202    /// Implement `core::intrinsics::catch_unwind` in MiniRust.
203    fn make_catch_unwind_function(&self, span: Span, fdecl: &FunDecl) -> Result<mini::Function> {
204        let signature = &fdecl.signature;
205        check!(
206            span,
207            signature.inputs.len() == 3
208                && matches!(signature.inputs[0].kind(), TyKind::FnPtr(_))
209                && matches!(signature.inputs[1].kind(), TyKind::RawPtr(..))
210                && matches!(signature.inputs[2].kind(), TyKind::FnPtr(_))
211                && signature.output.is_bool(),
212            "unexpected signature for `core::intrinsics::catch_unwind`"
213        );
214
215        let mut builder = FunctionBuilder::new(self, span, signature)?;
216        let ret = builder.return_local();
217        let try_fn = builder.argument(0);
218        let data = builder.argument(1);
219        let catch_fn = builder.argument(2);
220        let call_ret = builder.add_local(mini::unit_ty());
221        let payload_ty = mini::Type::Ptr(mini::PtrType::Raw {
222            meta_kind: mini::PointerMetaKind::None,
223        });
224        let payload = builder.add_local(payload_ty);
225
226        let start = builder.declare_block();
227        let returned = builder.declare_block();
228        let get_payload = builder.declare_block();
229        let call_catch = builder.declare_block();
230        let stop_unwind = builder.declare_block();
231        let caught = builder.declare_block();
232        let load = |local| mb::load(mini::PlaceExpr::Local(local));
233        builder.set_block(
234            start,
235            mb::block(
236                &[
237                    mini::Statement::StorageLive(call_ret),
238                    mini::Statement::StorageLive(payload),
239                ],
240                mini::Terminator::Call {
241                    callee: load(try_fn),
242                    calling_convention: mini::CallingConvention::Rust,
243                    arguments: [mini::ArgumentExpr::ByValue(load(data))]
244                        .into_iter()
245                        .collect(),
246                    ret: mini::PlaceExpr::Local(call_ret),
247                    next_block: Some(returned),
248                    unwind_block: Some(get_payload),
249                },
250                mini::BbKind::Regular,
251            ),
252        );
253        builder.set_block(
254            returned,
255            mb::block(
256                &[mb::assign(
257                    mini::PlaceExpr::Local(ret),
258                    mb::const_bool(false),
259                )],
260                mini::Terminator::Return,
261                mini::BbKind::Regular,
262            ),
263        );
264        builder.set_block(
265            get_payload,
266            mb::block(
267                &[],
268                mini::Terminator::Intrinsic {
269                    intrinsic: mini::IntrinsicOp::GetUnwindPayload,
270                    arguments: Default::default(),
271                    ret: mini::PlaceExpr::Local(payload),
272                    next_block: Some(call_catch),
273                },
274                mini::BbKind::Catch,
275            ),
276        );
277        builder.set_block(
278            call_catch,
279            mb::block(
280                &[],
281                mini::Terminator::Call {
282                    callee: load(catch_fn),
283                    calling_convention: mini::CallingConvention::Rust,
284                    arguments: [
285                        mini::ArgumentExpr::ByValue(load(data)),
286                        mini::ArgumentExpr::ByValue(load(payload)),
287                    ]
288                    .into_iter()
289                    .collect(),
290                    ret: mini::PlaceExpr::Local(call_ret),
291                    next_block: Some(stop_unwind),
292                    // The intrinsic contract requires this function not to unwind.
293                    unwind_block: None,
294                },
295                mini::BbKind::Catch,
296            ),
297        );
298        builder.set_block(
299            stop_unwind,
300            mb::block(
301                &[],
302                mini::Terminator::StopUnwind(caught),
303                mini::BbKind::Catch,
304            ),
305        );
306        builder.set_block(
307            caught,
308            mb::block(
309                &[mb::assign(
310                    mini::PlaceExpr::Local(ret),
311                    mb::const_bool(true),
312                )],
313                mini::Terminator::Return,
314                mini::BbKind::Regular,
315            ),
316        );
317        Ok(builder.finish())
318    }
319
320    /// Function that starts unwinding.
321    fn make_unwind_function(
322        &self,
323        span: Span,
324        fdecl: &FunDecl,
325        source: UnwindSource,
326    ) -> Result<mini::Function> {
327        let mut builder = FunctionBuilder::new(self, span, &fdecl.signature)?;
328        let arg = builder.argument(0);
329        let start = builder.declare_block();
330        let unwind = builder.declare_block();
331        builder.set_block(
332            start,
333            mb::block(
334                &[],
335                mini::Terminator::StartUnwind {
336                    unwind_payload: match source {
337                        UnwindSource::ExplicitPayload => mb::load(mini::PlaceExpr::Local(arg)),
338                        UnwindSource::OpaquePayload => self.opaque_panic_payload(),
339                    },
340                    unwind_block: unwind,
341                },
342                mini::BbKind::Regular,
343            ),
344        );
345        builder.set_block(
346            unwind,
347            mb::block(&[], mini::Terminator::ResumeUnwind, mini::BbKind::Cleanup),
348        );
349        Ok(builder.finish())
350    }
351
352    /// Compute a DST's layout from the metadata carried by its pointer.
353    fn make_layout_of_val_function(
354        &self,
355        span: Span,
356        fdecl: &FunDecl,
357        alignment: bool,
358    ) -> Result<mini::Function> {
359        let signature = &fdecl.signature;
360        let [input] = signature.inputs.as_slice() else {
361            raise!(span, "unexpected signature for layout-of-value intrinsic")
362        };
363        let pointee = self.ty(span, input.builtin_deref(self.krate).unwrap())?;
364
365        let mut builder = FunctionBuilder::new(self, span, signature)?;
366        let ret = mini::PlaceExpr::Local(builder.return_local());
367        let arg = mini::PlaceExpr::Local(builder.argument(0));
368        let start = builder.declare_block();
369        let metadata = mb::get_metadata(mb::load(arg));
370        let value = if alignment {
371            mb::compute_align(pointee, metadata)
372        } else {
373            mb::compute_size(pointee, metadata)
374        };
375        builder.set_block(
376            start,
377            mb::block(
378                &[mb::assign(ret, value)],
379                mini::Terminator::Return,
380                mini::BbKind::Regular,
381            ),
382        );
383        Ok(builder.finish())
384    }
385
386    /// `cold_path` is only an optimization hint.
387    fn make_cold_path_function(&self, span: Span, fdecl: &FunDecl) -> Result<mini::Function> {
388        let signature = &fdecl.signature;
389        let mut builder = FunctionBuilder::new(self, span, signature)?;
390        let ret = mini::PlaceExpr::Local(builder.return_local());
391        let start = builder.declare_block();
392        builder.set_block(
393            start,
394            mb::block(
395                &[mb::assign(ret, mb::unit())],
396                mini::Terminator::Return,
397                mini::BbKind::Regular,
398            ),
399        );
400        Ok(builder.finish())
401    }
402
403    /// `arith_offset` wraps pointer arithmetic, unlike an in-bounds pointer offset.
404    fn make_arith_offset_function(&self, span: Span, fdecl: &FunDecl) -> Result<mini::Function> {
405        let signature = &fdecl.signature;
406        let [pointer, offset] = signature.inputs.as_slice() else {
407            raise!(span, "unexpected signature for `arith_offset`");
408        };
409        let pointee = pointer
410            .builtin_deref(self.krate)
411            .ok_or("expected a pointer")
412            .context(span)?;
413        let (size, _) = self.size_and_align(span, pointee)?;
414
415        let mut builder = FunctionBuilder::new(self, span, signature)?;
416        let ret = mini::PlaceExpr::Local(builder.return_local());
417        let pointer = mb::load(mini::PlaceExpr::Local(builder.argument(0)));
418        let offset_value = mb::load(mini::PlaceExpr::Local(builder.argument(1)));
419        let size = mini::ValueExpr::Constant(
420            mini::Constant::Int(mini::Int::from(size)),
421            self.ty(span, offset)?,
422        );
423        let value = mb::ptr_offset(
424            pointer,
425            mb::mul_unchecked(offset_value, size),
426            mb::InBounds::No,
427        );
428        let start = builder.declare_block();
429        builder.set_block(
430            start,
431            mb::block(
432                &[mb::assign(ret, value)],
433                mini::Terminator::Return,
434                mini::BbKind::Regular,
435            ),
436        );
437        Ok(builder.finish())
438    }
439
440    /// Compute a pointer distance in elements, checking divisibility and optional nonnegativity.
441    fn make_ptr_offset_from_function(
442        &self,
443        span: Span,
444        fdecl: &FunDecl,
445        unsigned: bool,
446    ) -> Result<mini::Function> {
447        let signature = &fdecl.signature;
448        let [pointer, _base] = signature.inputs.as_slice() else {
449            raise!(span, "unexpected signature for pointer distance intrinsic");
450        };
451        let pointee = pointer
452            .builtin_deref(self.krate)
453            .ok_or("expected a pointer")
454            .context(span)?;
455        let (size, _) = self.size_and_align(span, pointee)?;
456
457        let mut builder = FunctionBuilder::new(self, span, signature)?;
458        let ret = mini::PlaceExpr::Local(builder.return_local());
459        let pointer = mb::load(mini::PlaceExpr::Local(builder.argument(0)));
460        let base = mb::load(mini::PlaceExpr::Local(builder.argument(1)));
461        let distance = if unsigned {
462            mb::ptr_offset_from_nonneg(pointer, base, mb::InBounds::No)
463        } else {
464            mb::ptr_offset_from(pointer, base, mb::InBounds::No)
465        };
466        let distance = mb::div_exact(
467            distance,
468            mb::const_int_typed::<isize>(mini::Int::from(size)),
469        );
470        let value = if unsigned {
471            mb::int_cast::<usize>(distance)
472        } else {
473            distance
474        };
475        let start = builder.declare_block();
476        builder.set_block(
477            start,
478            mb::block(
479                &[mb::assign(ret, value)],
480                mini::Terminator::Return,
481                mini::BbKind::Regular,
482            ),
483        );
484        Ok(builder.finish())
485    }
486}