mir/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2//! # [MIR project][mir] bindings for Rust
3//!
4//! [mir]: https://github.com/vnmakarov/mir
5//!
6//! ## Quick start
7//!
8//! > You can also check [`tests/smoke.rs`][more-example] for more examples.
9//!
10//! [more-example]: https://github.com/oxalica/mir-rs/blob/main/tests/smoke.rs
11//!
12//! ### Construct a function and execute it via MIR interpreter
13//!
14//! ```
15//! # #[cfg(feature = "interp")] {
16//! use mir::{InsnBuilder, MirContext, Ty, Val};
17//!
18//! // Initialize a context.
19//! let ctx = MirContext::new();
20//!
21//! // Create a module, a function, append instructions, and finally sealing them.
22//! let m = ctx.enter_new_module(c"module_add");
23//! // Note that MIR requires all argument types to be I64.
24//! // extern "C" fn add(a: i64, b: i64) -> i64
25//! let f = m.enter_new_function(c"add", &[Ty::I64], &[(c"a", Ty::I64), (c"b", Ty::I64)]);
26//! let a = f.get_reg(c"a");
27//! let b = f.get_reg(c"b");
28//! let ret = f.new_local_reg(c"ret", Ty::I64);
29//! f.ins().add(ret, a, b);
30//! f.ins().ret(ret);
31//! let func = f.finish();
32//! let module = m.finish();
33//!
34//! // Load and link modules.
35//! ctx.load_module(module);
36//! ctx.link_modules_for_interpret();
37//!
38//! // Execute our functions.
39//! let mut ret = [Val::default()];
40//! unsafe { ctx.interpret_unchecked(func, &mut ret, &[Val::from(40i64), Val::from(2i64)]) };
41//! assert_eq!(ret[0].as_i64(), 42);
42//! # }
43//! ```
44//!
45//! ### Codegen a function to native code and execute it natively
46//!
47//! ```
48//! # #[cfg(feature = "gen")] {
49//! use mir::{InsnBuilder, MirContext, MirGenContext, Ty};
50//!
51//! // Initialize a context and codegen context.
52//! let ctx = MirGenContext::new(MirContext::new());
53//!
54//! // Same creation code.
55//! let m = ctx.enter_new_module(c"module_add");
56//! // ...
57//! # let f = m.enter_new_function(c"add", &[Ty::I64], &[(c"a", Ty::I64), (c"b", Ty::I64)]);
58//! # let a = f.get_reg(c"a");
59//! # let b = f.get_reg(c"b");
60//! # let ret = f.new_local_reg(c"ret", Ty::I64);
61//! # f.ins().add(ret, a, b);
62//! # f.ins().ret(ret);
63//! # let func = f.finish();
64//! let module = m.finish();
65//!
66//! // Set optimization level and/or other configurables.
67//! ctx.set_opt_level(3);
68//! // Load and link modules, for codegen.
69//! ctx.load_module(module);
70//! ctx.link_modules_for_codegen();
71//!
72//! // Codegen and get a pointer to generated function.
73//! let func_ptr = ctx.codegen_func(func);
74//! type AddFunc = extern "C" fn(a: i64, b: i64) -> i64;
75//! let func_ptr = unsafe { std::mem::transmute::<*mut _, AddFunc>(func_ptr) };
76//!
77//! // Call it!
78//! assert_eq!(func_ptr(40, 2), 42);
79//! # }
80//! ```
81//!
82//! ## Panics and errors
83//!
84//! Unfortunately MIR [treats all errors as fatal errors][mir-error-issue] and is likely to
85//! continue be like this.
86//! In mir-rs, we did a best-effort recovery to unwind in error callback inside C code via
87//! ["C-unwind" ABI][c-unwind]. This is always safe because all intermediate C frames are
88//! [Plain Old Frames][pof]. But it may leave the C states inconsistent, thus it is strongly
89//! recommended to drop the [`MirContext`] if any modification method panics.
90//!
91//! [c-unwind]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html
92//! [pof]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html#plain-old-frames
93//! [mir-error-issue]: https://github.com/vnmakarov/mir/issues/220
94//!
95//! ## Features
96//!
97//! - `default`: Implies `io`, `interp`, `gen`.
98//!
99//! - `io`: De/serialization of MIR memory representation into/from bytes.
100//!
101//! - `interp`: Enables MIR interpreter.
102//!
103//! - `gen`: MIR native code generator.
104//!
105//! - `gen-debug`: Debug logging in MIR native code generator. It implies `gen`.
106#![warn(missing_debug_implementations)]
107#![warn(missing_docs)]
108use std::cell::Cell;
109use std::ffi::{CStr, c_char, c_void};
110use std::marker::PhantomData;
111use std::ptr::{self, NonNull, null, null_mut};
112
113use mem_file::MemoryFile;
114use types::InsnBuilderBase;
115
116pub use mir_sys as ffi;
117pub use types::{
118    BssItemRef, DataItemRef, ExportItemRef, ExprDataItemRef, ForwardItemRef, FuncItemRef,
119    ImportItemRef, InsnBuilder, IntoOperand, IntoOutOperand, ItemRef, Label, LabelRefDataItemRef,
120    MemOp, Operand, ProtoItemRef, RefDataItemRef, Reg, Ty,
121};
122
123#[cfg(feature = "gen")]
124mod codegen;
125#[cfg(feature = "gen")]
126pub use codegen::MirGenContext;
127
128#[cfg(feature = "interp")]
129mod interp;
130#[cfg(feature = "interp")]
131pub use interp::Val;
132
133mod mem_file;
134mod types;
135
136/// The context for code generation, linking and interpreter.
137///
138/// Almost all MIR functionality requires an initialized context to work.
139/// The context is not thread-safe.
140#[derive(Debug)]
141pub struct MirContext {
142    ctx: NonNull<ffi::MIR_context>,
143    module: Cell<Option<NonNull<ffi::MIR_module>>>,
144    func_item: Cell<Option<NonNull<ffi::MIR_item>>>,
145}
146
147impl Default for MirContext {
148    fn default() -> Self {
149        Self::new()
150    }
151}
152
153unsafe extern "C-unwind" {
154    fn MIRRS_error_handler_trampoline(
155        error_type: ffi::MIR_error_type_t,
156        format: *const c_char,
157        ...
158    ) -> !;
159}
160
161#[unsafe(no_mangle)]
162unsafe extern "C-unwind" fn MIRRS_error_handler_rust(
163    error_type: ffi::MIR_error_type_t,
164    msg: *const u8,
165    len: usize,
166) -> ! {
167    let msg = String::from_utf8_lossy(unsafe { std::slice::from_raw_parts(msg, len) });
168    panic!("mir error {error_type}: {msg}");
169}
170
171#[cfg(feature = "io")]
172unsafe extern "C-unwind" fn write_byte_callback(data: *mut libc::c_void, byte: u8) -> libc::c_int {
173    let data = unsafe { &mut *data.cast::<Vec<_>>() };
174    data.push(byte);
175    1
176}
177
178#[cfg(feature = "io")]
179unsafe extern "C-unwind" fn read_byte_callback(data: *mut libc::c_void) -> libc::c_int {
180    let data = unsafe { &mut *data.cast::<&[u8]>() };
181    match data.split_first() {
182        Some((byte, rest)) => {
183            *data = rest;
184            (*byte).into()
185        }
186        None => libc::EOF,
187    }
188}
189
190type ImportResolver = dyn Fn(&CStr) -> *mut c_void;
191
192impl MirContext {
193    /// Initialize a new MIR context.
194    #[expect(clippy::missing_panics_doc, reason = "assertion")]
195    pub fn new() -> Self {
196        let ctx = ffi::MIR_init();
197        unsafe { ffi::MIR_set_error_func(ctx, Some(MIRRS_error_handler_trampoline)) };
198        Self {
199            ctx: NonNull::new(ctx).expect("context must not be NULL"),
200            module: Cell::new(None),
201            func_item: Cell::new(None),
202        }
203    }
204
205    /// Get the underlying pointer for FFI.
206    pub fn as_raw(&self) -> *mut ffi::MIR_context {
207        self.ctx.as_ptr()
208    }
209
210    /// Dump the content of the context in a textual representation for human consumption.
211    #[must_use]
212    pub fn dump(&self) -> String {
213        MemoryFile::with(|file| unsafe { ffi::MIR_output(self.as_raw(), file) }).1
214    }
215
216    /// Get the list of all modules in the context.
217    ///
218    /// This includes all modules created in this context, not necessarily loaded or linked.
219    pub fn get_modules(&self) -> Vec<MirModuleRef<'_>> {
220        let head = unsafe { (*ffi::MIR_get_module_list(self.as_raw())).head };
221        std::iter::successors(NonNull::new(head), |m| unsafe {
222            NonNull::new(m.as_ref().module_link.next)
223        })
224        .map(|module| unsafe { MirModuleRef::from_raw(module.as_ptr()) })
225        .collect()
226    }
227
228    /// Serialize the context (including all modules) into bytes.
229    ///
230    /// The serialization format is stable across executions, but may not be stable across
231    /// MIR versions.
232    #[cfg(feature = "io")]
233    pub fn serialize(&self) -> Vec<u8> {
234        let mut buf = Vec::new();
235        unsafe {
236            ffi::MIR_write_with_func(
237                self.as_raw(),
238                Some(write_byte_callback),
239                ptr::from_mut(&mut buf).cast(),
240            );
241        }
242        buf
243    }
244
245    /// Deserialize context or modules from bytes.
246    ///
247    /// It will create one or more modules in `bytes`. The deserialized modules will be as ifthey
248    /// are created manually, not loaded or linked yet.
249    ///
250    /// # Safety
251    /// `bytes` must be trusted and produced from previous serialization.
252    ///
253    /// # Panics
254    ///
255    /// Panic if there is any unfinished module.
256    /// Panic from C if the `bytes` cannot be parsed or contain errors.
257    #[cfg(feature = "io")]
258    pub unsafe fn deserialize(&self, bytes: &[u8]) {
259        assert!(
260            self.module.get().is_none(),
261            "must not have unfinished module on deserialization",
262        );
263
264        let mut bytes = bytes;
265        unsafe {
266            ffi::MIR_read_with_func(
267                self.as_raw(),
268                Some(read_byte_callback),
269                ptr::from_mut(&mut bytes).cast(),
270            );
271        }
272    }
273
274    /// Create a new module and enter into it.
275    ///
276    /// The MIR context is stateful. When entering a new module, it should be correctly finished
277    /// before creating another module. See [`MirModuleBuilder`] for more details.
278    ///
279    /// # Panics
280    ///
281    /// Panic if there is any unfinished module.
282    pub fn enter_new_module(&self, name: &CStr) -> MirModuleBuilder<'_> {
283        assert!(self.module.get().is_none(), "already inside a module");
284        let module = unsafe { ffi::MIR_new_module(self.as_raw(), name.as_ptr()) };
285        self.module
286            .set(Some(NonNull::new(module).expect("module must not be null")));
287        MirModuleBuilder { ctx: self }
288    }
289
290    /// Load an MIR module for linking.
291    pub fn load_module(&self, module: MirModuleRef<'_>) {
292        unsafe { ffi::MIR_load_module(self.as_raw(), module.module.as_ptr()) };
293    }
294
295    /// Load an external name pointing to `addr` for linking.
296    ///
297    /// # Example
298    /// ```
299    /// # fn link(ctx: &mir::MirContext) {
300    /// // let ctx: &MirContext;
301    /// unsafe { ctx.load_external(c"memset", libc::memset as _) };
302    /// # }
303    /// ```
304    ///
305    /// # Safety
306    ///
307    /// `addr` must be a valid function pointer with prototype expected by generated code.
308    pub unsafe fn load_external(&self, name: &CStr, addr: *mut c_void) {
309        unsafe { ffi::MIR_load_external(self.as_raw(), name.as_ptr(), addr) };
310    }
311
312    /// Link loaded modules and external names with given custom interface and resolver.
313    ///
314    /// # Safety
315    ///
316    /// `set_interface` should be one of `MIR_set_*_interface`.
317    /// `resolver` must return valid function pointers with prototype expected by generated code.
318    pub unsafe fn link_modules_raw(
319        &self,
320        set_interface: Option<
321            unsafe extern "C-unwind" fn(ctx: ffi::MIR_context_t, item: ffi::MIR_item_t),
322        >,
323        resolver: Option<&ImportResolver>,
324    ) {
325        unsafe extern "C-unwind" fn trampoline(
326            data: *mut c_void,
327            name: *const c_char,
328        ) -> *mut c_void {
329            let name = unsafe { CStr::from_ptr(name) };
330            unsafe { (*data.cast::<&ImportResolver>())(name) }
331        }
332
333        // NB. Keep `resolver` alive on stack by taking a reference.
334        let (resolver, arg) = match &resolver {
335            Some(resolver) => (
336                Some(trampoline as _),
337                // NB. This is a pointer to fat reference to dyn Fn.
338                ptr::from_ref(resolver).cast_mut().cast(),
339            ),
340            None => (None, null_mut()),
341        };
342        unsafe { ffi::MIR_link(self.as_raw(), set_interface, resolver, arg) }
343    }
344}
345
346impl Drop for MirContext {
347    fn drop(&mut self) {
348        if std::thread::panicking() {
349            // MIR_finish* may fail. Avoid double-panicking when something already goes wrong.
350            return;
351        }
352
353        if self.func_item.get().is_some() {
354            unsafe { ffi::MIR_finish_func(self.as_raw()) };
355        }
356        if self.module.get().is_some() {
357            unsafe { ffi::MIR_finish_module(self.as_raw()) };
358        }
359        unsafe { ffi::MIR_finish(self.as_raw()) };
360    }
361}
362
363/// A module reference.
364#[derive(Debug, Clone, Copy)]
365pub struct MirModuleRef<'ctx> {
366    module: NonNull<ffi::MIR_module>,
367    _marker: PhantomData<&'ctx MirContext>,
368}
369
370impl MirModuleRef<'_> {
371    unsafe fn from_raw(raw: *mut ffi::MIR_module) -> Self {
372        Self {
373            module: NonNull::new(raw).expect("module must not be null"),
374            _marker: PhantomData,
375        }
376    }
377
378    /// Get the underlying pointer for FFI.
379    #[must_use]
380    pub fn as_raw(&self) -> *mut ffi::MIR_module {
381        self.module.as_ptr()
382    }
383
384    /// Get the name of the module.
385    #[must_use]
386    pub fn name(&self) -> &CStr {
387        unsafe { CStr::from_ptr(self.module.as_ref().name) }
388    }
389
390    /// Get the list of all items inside the module.
391    #[must_use]
392    pub fn get_items(&self) -> Vec<ItemRef<'_>> {
393        let head = unsafe { self.module.as_ref().items.head };
394        std::iter::successors(NonNull::new(head), |item| unsafe {
395            NonNull::new(item.as_ref().item_link.next)
396        })
397        .map(|item| unsafe { ItemRef::from_raw(item.as_ptr()) })
398        .collect()
399    }
400
401    /// Dump the content of the context in a textual representation for human consumption.
402    #[must_use]
403    pub fn dump(&self, ctx: &MirContext) -> String {
404        MemoryFile::with(|file| unsafe {
405            ffi::MIR_output_module(ctx.as_raw(), file, self.as_raw());
406        })
407        .1
408    }
409
410    /// Serialize the module  into bytes.
411    ///
412    /// The serialization format is stable across executions, but may not be stable across
413    /// MIR versions.
414    #[cfg(feature = "io")]
415    pub fn serialize(&self, ctx: &MirContext) -> Vec<u8> {
416        let mut buf = Vec::new();
417        unsafe {
418            ffi::MIR_write_module_with_func(
419                ctx.as_raw(),
420                Some(write_byte_callback),
421                self.as_raw(),
422                ptr::from_mut(&mut buf).cast(),
423            );
424        }
425        buf
426    }
427}
428
429/// The currently building MIR module.
430///
431/// There can only be at most one unfinished module for each [`MirContext`].
432/// It is strongly advised to explicitly call [`MirModuleBuilder::finish`] to finish the current
433/// module for clarity and observe any error outside drop impl.
434/// [`MirModuleBuilder`] will automatically finish it on drop.
435#[derive(Debug)]
436#[must_use = "module builder should be correctly finished by finish()"]
437pub struct MirModuleBuilder<'ctx> {
438    ctx: &'ctx MirContext,
439}
440
441impl Drop for MirModuleBuilder<'_> {
442    fn drop(&mut self) {
443        self.ctx.module.take().expect("must be inside a module");
444        unsafe { ffi::MIR_finish_module(self.as_raw_ctx()) };
445    }
446}
447
448impl<'ctx> MirModuleBuilder<'ctx> {
449    /// Explicitly finish the module and return the module reference.
450    ///
451    /// # Panics
452    ///
453    /// Panic from C if the module content is malformed.
454    #[expect(clippy::must_use_candidate, reason = "can be ignored")]
455    pub fn finish(self) -> MirModuleRef<'ctx> {
456        let module = self.ctx.module.get().expect("must be inside a module");
457        drop(self);
458        unsafe { MirModuleRef::from_raw(module.as_ptr()) }
459    }
460
461    fn as_raw_ctx(&self) -> *mut ffi::MIR_context {
462        self.ctx.as_raw()
463    }
464
465    /// Add a new function prototype and return its reference.
466    ///
467    /// # Panics
468    ///
469    /// Panic from C on duplicated names or invalid signature.
470    #[must_use]
471    pub fn add_proto(&self, name: &CStr, rets: &[Ty], args: &[(&CStr, Ty)]) -> ProtoItemRef<'_> {
472        let c_args = args
473            .iter()
474            .map(|(name, ty)| ffi::MIR_var {
475                type_: ty.0,
476                name: name.as_ptr(),
477                // Unused.
478                size: 0,
479            })
480            .collect::<Vec<_>>();
481        let item = unsafe {
482            ItemRef::from_raw(ffi::MIR_new_proto_arr(
483                self.as_raw_ctx(),
484                name.as_ptr(),
485                rets.len(),
486                rets.as_ptr().cast::<ffi::MIR_type_t>().cast_mut(),
487                c_args.len(),
488                c_args.as_ptr().cast_mut(),
489            ))
490        };
491        ProtoItemRef(item)
492    }
493
494    /// Add a new import name and return its reference.
495    ///
496    /// # Panics
497    ///
498    /// Panic from C on duplicated names.
499    #[must_use]
500    pub fn add_import(&self, name: &CStr) -> ImportItemRef<'_> {
501        let item =
502            unsafe { ItemRef::from_raw(ffi::MIR_new_import(self.as_raw_ctx(), name.as_ptr())) };
503        ImportItemRef(item)
504    }
505
506    /// Add a new export name and return its reference.
507    ///
508    /// # Panics
509    ///
510    /// Panic from C on duplicated names.
511    #[expect(clippy::must_use_candidate, reason = "can be ignored")]
512    pub fn add_export(&self, name: &CStr) -> ExportItemRef<'_> {
513        let item =
514            unsafe { ItemRef::from_raw(ffi::MIR_new_export(self.as_raw_ctx(), name.as_ptr())) };
515        ExportItemRef(item)
516    }
517
518    /// Add a new forward declaration and return its reference.
519    ///
520    /// # Panics
521    ///
522    /// Panic from C on duplicated names.
523    #[expect(clippy::must_use_candidate, reason = "can be ignored")]
524    pub fn add_forward(&self, name: &CStr) -> ForwardItemRef<'_> {
525        let item =
526            unsafe { ItemRef::from_raw(ffi::MIR_new_forward(self.as_raw_ctx(), name.as_ptr())) };
527        ForwardItemRef(item)
528    }
529
530    /// Add a new forward declaration and return its reference.
531    ///
532    /// # Panics
533    ///
534    /// Panic from C on duplicated names.
535    #[must_use]
536    pub fn add_data<'a>(&self, name: impl Into<Option<&'a CStr>>, data: &[u8]) -> DataItemRef<'_> {
537        unsafe {
538            DataItemRef(ItemRef::from_raw(ffi::MIR_new_data(
539                self.as_raw_ctx(),
540                name.into().map_or(null(), CStr::as_ptr),
541                Ty::U8.0,
542                data.len(),
543                data.as_ptr().cast(),
544            )))
545        }
546    }
547
548    /// Add a new data referencing another item and return its reference.
549    ///
550    /// The address of `ref_item` after linking plus `disp` is used to initialize the data.
551    ///
552    /// # Panics
553    ///
554    /// Panic from C on duplicated names.
555    #[must_use]
556    pub fn add_ref_data<'a>(
557        &self,
558        name: impl Into<Option<&'a CStr>>,
559        ref_item: ItemRef<'_>,
560        disp: i64,
561    ) -> RefDataItemRef<'_> {
562        unsafe {
563            RefDataItemRef(ItemRef::from_raw(ffi::MIR_new_ref_data(
564                self.as_raw_ctx(),
565                name.into().map_or(null(), CStr::as_ptr),
566                ref_item.as_raw(),
567                disp,
568            )))
569        }
570    }
571
572    /// Add a new data initialized by a function and return its reference.
573    ///
574    /// - Not all MIR functions can be used for expression data. The expression function should
575    ///   have only one result, have no arguments, not use any call or any instruction with memory.
576    /// - The expression function is called during linking and its result is used to initialize the
577    ///   data.
578    ///
579    /// # Panics
580    ///
581    /// Panic from C on duplicated names or unsupported functions.
582    #[must_use]
583    pub fn add_expr_data<'a>(
584        &self,
585        name: impl Into<Option<&'a CStr>>,
586        expr_func: FuncItemRef<'_>,
587    ) -> ExprDataItemRef<'_> {
588        unsafe {
589            ExprDataItemRef(ItemRef::from_raw(ffi::MIR_new_expr_data(
590                self.as_raw_ctx(),
591                name.into().map_or(null(), CStr::as_ptr),
592                expr_func.as_raw(),
593            )))
594        }
595    }
596
597    /// Add a new data initialized by a label and return its reference.
598    ///
599    /// The addresses defined as `label[-base_label]+disp` where `base_label` can be `None`.
600    /// `lref` can refers for labels the same function (this is checked during module load) and
601    /// there is a warranty label addresses to be defined only at the beginning of the function
602    /// execution.
603    ///
604    /// # Panics
605    ///
606    /// Panic from C on duplicated names.
607    #[must_use]
608    pub fn add_label_ref_data(
609        &self,
610        name: &CStr,
611        label: Label<'_>,
612        base_label: Option<Label<'_>>,
613        disp: i64,
614    ) -> LabelRefDataItemRef<'_> {
615        unsafe {
616            LabelRefDataItemRef(ItemRef::from_raw(ffi::MIR_new_lref_data(
617                self.as_raw_ctx(),
618                name.as_ptr(),
619                label.0,
620                base_label.map_or(null_mut(), |lbl| lbl.0),
621                disp,
622            )))
623        }
624    }
625
626    /// Add a new writable memory segment and return its reference.
627    ///
628    /// # Panics
629    ///
630    /// Panic from C on duplicated names.
631    #[must_use]
632    pub fn add_bss<'a>(&self, name: impl Into<Option<&'a CStr>>, len: usize) -> BssItemRef<'_> {
633        unsafe {
634            BssItemRef(ItemRef::from_raw(ffi::MIR_new_bss(
635                self.as_raw_ctx(),
636                name.into().map_or(null(), CStr::as_ptr),
637                len,
638            )))
639        }
640    }
641
642    /// Add a new function definition and enter into it.
643    ///
644    /// The MIR context is stateful and the previous function must be finished before creating
645    /// another one.
646    /// See more details in [`MirFuncBuilder`].
647    ///
648    /// # Panics
649    ///
650    /// Panic if there is any unfinished function.
651    /// Panic from C on duplicated names or invalid signature.
652    #[must_use]
653    pub fn enter_new_function(
654        &'_ self,
655        name: &CStr,
656        rets: &[Ty],
657        args: &[(&CStr, Ty)],
658    ) -> MirFuncBuilder<'_, 'ctx> {
659        assert!(
660            self.ctx.func_item.get().is_none(),
661            "already inside a function"
662        );
663        let c_args = args
664            .iter()
665            .map(|(name, ty)| ffi::MIR_var {
666                type_: ty.0,
667                name: name.as_ptr(),
668                // Unused.
669                size: 0,
670            })
671            .collect::<Vec<_>>();
672        let func_item = unsafe {
673            ffi::MIR_new_func_arr(
674                self.as_raw_ctx(),
675                name.as_ptr(),
676                rets.len(),
677                rets.as_ptr().cast::<ffi::MIR_type_t>().cast_mut(),
678                c_args.len(),
679                c_args.as_ptr().cast_mut(),
680            )
681        };
682        self.ctx.func_item.set(Some(
683            NonNull::new(func_item).expect("item must not be null"),
684        ));
685        let func = unsafe { NonNull::new((*func_item).u.func).expect("function must not be null") };
686        MirFuncBuilder {
687            func,
688            ctx: self.ctx,
689            _marker: PhantomData,
690        }
691    }
692}
693
694/// The currently building MIR function.
695///
696/// There can only be at most one unfinished function for each [`MirContext`] inside one unfinished
697/// module. It is strongly advised to explicitly call [`MirFuncBuilder::finish`] to finish the
698/// current function for clarity and observe any error outside drop impl.
699/// [`MirFuncBuilder`] will automatically finish it on drop.
700#[derive(Debug)]
701pub struct MirFuncBuilder<'module, 'ctx> {
702    func: NonNull<ffi::MIR_func>,
703    ctx: &'ctx MirContext,
704    _marker: PhantomData<&'module MirModuleRef<'ctx>>,
705}
706
707impl Drop for MirFuncBuilder<'_, '_> {
708    fn drop(&mut self) {
709        self.ctx
710            .func_item
711            .take()
712            .expect("must be inside a function");
713        unsafe { ffi::MIR_finish_func(self.ctx.ctx.as_ptr()) };
714    }
715}
716
717impl<'ctx> MirFuncBuilder<'_, 'ctx> {
718    /// Explicitly finish the function and return the function reference.
719    ///
720    /// # Panics
721    ///
722    /// Panic from C if the function content is malformed.
723    #[expect(clippy::must_use_candidate, reason = "can be ignored")]
724    pub fn finish(self) -> FuncItemRef<'ctx> {
725        let func_item = self.ctx.func_item.get().expect("must be inside a function");
726        drop(self);
727        FuncItemRef(ItemRef(func_item, PhantomData))
728    }
729
730    /// Get the virtual register or given name.
731    ///
732    /// Function parameters are represented as pre-defined virtual registers of same names.
733    #[must_use]
734    pub fn get_reg(&self, name: &CStr) -> Reg {
735        let reg = unsafe { ffi::MIR_reg(self.ctx.ctx.as_ptr(), name.as_ptr(), self.func.as_ptr()) };
736        Reg(reg)
737    }
738
739    /// Create a new virtual register.
740    #[must_use]
741    pub fn new_local_reg(&self, name: &CStr, ty: Ty) -> Reg {
742        let reg = unsafe {
743            ffi::MIR_new_func_reg(
744                self.ctx.ctx.as_ptr(),
745                self.func.as_ptr(),
746                ty.0,
747                name.as_ptr(),
748            )
749        };
750        Reg(reg)
751    }
752
753    /// Create a new unbound label.
754    ///
755    /// The label must be inserted later via [`InsnBuilder::label`].
756    #[must_use]
757    pub fn new_label(&self) -> Label<'_> {
758        let insn = unsafe { ffi::MIR_new_label(self.ctx.ctx.as_ptr()) };
759        Label(insn, PhantomData)
760    }
761
762    /// Append a new instruction to the function.
763    pub fn ins(&self) -> FuncInstBuilder<'_, 'ctx> {
764        FuncInstBuilder {
765            ctx: self.ctx,
766            _marker: PhantomData,
767        }
768    }
769}
770
771/// The instruction appender to an function.
772///
773/// See [`InsnBuilder`] for all instructions.
774#[derive(Debug)]
775#[must_use = "FuncInstBuilder does nothing unless calling an method"]
776pub struct FuncInstBuilder<'func, 'ctx> {
777    ctx: &'ctx MirContext,
778    _marker: PhantomData<&'func MirFuncBuilder<'func, 'ctx>>,
779}
780
781unsafe impl<'func> InsnBuilderBase<'func> for FuncInstBuilder<'func, '_> {
782    fn get_raw_ctx(&self) -> ffi::MIR_context_t {
783        self.ctx.ctx.as_ptr()
784    }
785
786    unsafe fn insert(self, insn: ffi::MIR_insn_t) {
787        unsafe {
788            ffi::MIR_append_insn(
789                self.ctx.as_raw(),
790                self.ctx
791                    .func_item
792                    .get()
793                    .expect("must be inside a function")
794                    .as_ptr()
795                    .cast::<ffi::MIR_item>(),
796                insn,
797            );
798        }
799    }
800}