Skip to main content

qcode_passes/
lib.rs

1//! Block-local cleanup passes over the qcode IR.
2//!
3//! These are the transforms that a *lifter* wants while it is still building a
4//! function: they are cheap, local, and safe to run on a partially discovered
5//! CFG. They deliberately need no alias analysis, no calling convention, and no
6//! knowledge of the target architecture — which is what lets them sit below
7//! `qcode_analysis` and be used on their own by [`qcode_vm`] and other
8//! consumers that only ever want the cleanup, never the decompiler.
9//!
10//! ## The context view
11//!
12//! A pass reads the module through a [`PassCtx`] and mutates a single
13//! [`FunctionBody`] borrowed `&mut` from the bodies registry. Splitting a
14//! [`Context`] into those two halves is what [`with_body_mut`] does. Because
15//! `PassCtx` holds only shared references it is `Copy`, so a caller hands the
16//! same view to every helper.
17//!
18//! `PassCtx` is the environment-free half of the richer view used by the full
19//! analysis pipeline: it carries the module's shared IR state and published
20//! interfaces, but no architecture configuration. Arch-aware passes live in
21//! `qcode_analysis` and take the richer view instead.
22//!
23//! [`qcode_vm`]: https://docs.rs/qcode_vm
24//!
25//! # Example
26//!
27//! A pure instruction nothing reads is removed; one whose result is used is
28//! kept.
29//!
30//! ```
31//! use qcode::{context::Context, qcode};
32//! use qcode_passes::remove_dead_insns;
33//!
34//! let mut ctx = Context::new();
35//! qcode!(
36//!     ctx,
37//!     "
38//!     fn f:
39//!     <entry>
40//!         %unused = i64 0x2 + 0x3;
41//!         %kept   = i64 0x4 + 0x5;
42//!         goto <exit @r=%kept>;
43//!     <exit @r:i64>
44//!         goto <0x1001>;
45//!     "
46//! );
47//!
48//! assert!(remove_dead_insns(&mut ctx, entry));
49//! // Running it again is a no-op: the pass is idempotent.
50//! assert!(!remove_dead_insns(&mut ctx, entry));
51//! ```
52
53use jstd::registry::Registry;
54use qcode::{
55    context::{Context, Shared},
56    value::{
57        BodyView, FunctionBody, FunctionId, function::FunctionInterface, util::body_mut::BodyMut,
58    },
59};
60
61pub mod cfg;
62pub mod dce;
63pub mod symbolize;
64mod terminator;
65
66pub use cfg::absorb_straight_line;
67pub use dce::{dead_insns, remove_dead_insns, remove_dead_insns_body};
68pub use symbolize::{resolve_addresses, resolve_strings};
69pub use terminator::replace_terminator_with_branch;
70
71/// The bodies-free module view a block-local pass reads through:
72/// `{shared, interfaces}`.
73///
74/// A pass structurally cannot reach another function's body through it, which
75/// is what makes holding one the proof that the shared state is frozen while a
76/// worker holds a disjoint `&mut` body. `Copy`, since it holds only shared
77/// references.
78#[derive(Clone, Copy)]
79pub struct PassCtx<'ctx, 'str> {
80    shared: &'ctx Shared<'str>,
81    interfaces: &'ctx Registry<FunctionId, FunctionInterface<'str>>,
82}
83
84impl<'ctx, 'str> PassCtx<'ctx, 'str> {
85    /// Build a view over `ctx`'s shared state and interfaces. The bodies are
86    /// **not** captured; prefer [`split`], which proves that with a
87    /// simultaneous `&mut` bodies borrow.
88    pub fn new(ctx: &'ctx Context<'str>) -> Self {
89        Self {
90            shared: &ctx.shared,
91            interfaces: &ctx.interfaces,
92        }
93    }
94
95    /// Assemble a view from already-borrowed parts. This is the seam richer
96    /// views (such as the analysis pipeline's) use to hand their own
97    /// `{shared, interfaces}` to the passes here.
98    pub fn from_parts(
99        shared: &'ctx Shared<'str>,
100        interfaces: &'ctx Registry<FunctionId, FunctionInterface<'str>>,
101    ) -> Self {
102        Self { shared, interfaces }
103    }
104
105    /// The module's shared IR state (interners, spaces, registers, name and
106    /// address maps, memory image, truths).
107    pub fn shr(&self) -> &'ctx Shared<'str> {
108        self.shared
109    }
110
111    /// The published interface of function `f`.
112    pub fn interface(&self, f: FunctionId) -> &'ctx FunctionInterface<'str> {
113        &self.interfaces[f]
114    }
115
116    /// The whole interface registry.
117    pub fn interfaces(&self) -> &'ctx Registry<FunctionId, FunctionInterface<'str>> {
118        self.interfaces
119    }
120
121    /// Build the static read view for a pass's borrowed body.
122    pub fn body_view<'body>(self, body: &'body FunctionBody<'str>) -> BodyView<'body, 'str>
123    where
124        'ctx: 'body,
125    {
126        BodyView::new(body, self.shared, self.interfaces)
127    }
128
129    /// Build the mutation host for a pass's exclusively borrowed body.
130    pub fn host<'body>(self, body: &'body mut FunctionBody<'str>) -> BodyMut<'body, 'str>
131    where
132        'ctx: 'body,
133    {
134        BodyMut::new(body, self.shared, self.interfaces)
135    }
136}
137
138/// Split a context into its mutable bodies registry and the read-only module
139/// view, so a worker can hold one body `&mut` while reading shared state.
140pub fn split<'a, 'str>(
141    ctx: &'a mut Context<'str>,
142) -> (
143    &'a mut Registry<FunctionId, FunctionBody<'str>>,
144    PassCtx<'a, 'str>,
145) {
146    (
147        &mut ctx.bodies,
148        PassCtx {
149            shared: &ctx.shared,
150            interfaces: &ctx.interfaces,
151        },
152    )
153}
154
155/// Run `f` against function `fid`'s body borrowed `&mut` out of `ctx`, with the
156/// matching read-only view.
157///
158/// This is the `&mut Context` entry point the block-local passes expose to
159/// callers that hold a whole context and neither a [`FunctionBody`] nor a view.
160pub fn with_body_mut<'str, R>(
161    ctx: &mut Context<'str>,
162    fid: FunctionId,
163    f: impl FnOnce(&mut FunctionBody<'str>, PassCtx<'_, 'str>) -> R,
164) -> R {
165    let (bodies, view) = split(ctx);
166    f(&mut bodies[fid], view)
167}