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}