1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//! # opt_lang
//!
//! SSA optimizations for [`ir_lang`] 2.0: the IR-to-IR stage of the `-lang`
//! family's native compiler path, between the front end's lowering and
//! instruction selection. It makes a program smaller and faster without
//! changing what it computes, traps with, raises, or writes to memory.
//!
//! ## Quick start
//!
//! One call optimizes every function of a module:
//!
//! ```
//! use ir_lang::{Linkage, Module, Overflow, Signature, Type};
//!
//! // fn f(x: i64) -> i64 { let a = 2 + 3; let b = x * a; let c = x * a; b + c }
//! let mut m = Module::new("demo");
//! let f = m.declare_function("f", &Signature::new(&[Type::I64], &[Type::I64]), Linkage::Export)?;
//! let mut b = m.build(f)?;
//! let x = b.param(0)?;
//! let (two, three) = (b.iconst(Type::I64, 2)?, b.iconst(Type::I64, 3)?);
//! let a = b.add(two, three, Overflow::Wrap)?;
//! let p = b.mul(x, a, Overflow::Wrap)?;
//! let q = b.mul(x, a, Overflow::Wrap)?;
//! let r = b.add(p, q, Overflow::Wrap)?;
//! b.ret(&[r])?;
//!
//! let stats = opt_lang::optimize(&mut m)?;
//! assert_eq!((stats.insts_before(), stats.insts_after()), (6, 3));
//! let text = m.display_function(f).to_string();
//! assert!(text.contains("v1: i64 = const 5"), "{text}");
//! assert!(text.contains("v2: i64 = mul<overflow=wrap> v0, v1"), "{text}");
//! assert!(text.contains("v3: i64 = add<overflow=wrap> v2, v2"), "{text}");
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! ## The passes
//!
//! | Pass | What it does |
//! |---|---|
//! | [`PassKind::SimplifyCfg`] | folds constant and trivial branches, removes unreachable blocks, threads jumps through empty blocks, merges straight-line blocks |
//! | [`PassKind::Sccp`] | sparse conditional constant propagation over block parameters, `switch`, and `check` edges, folding with the exact OPS policies |
//! | [`PassKind::CopyProp`] | replaces block parameters that always receive one value, and removes them |
//! | [`PassKind::Gvn`] | dominator-based value numbering of pure operations |
//! | [`PassKind::Licm`] | hoists pure loop-invariant operations into preheaders |
//! | [`PassKind::Dce`] | removes dead instructions and dead block parameters, keeping every side effect |
//!
//! [`optimize`] runs [`DEFAULT_PIPELINE`] to a fixpoint. [`Optimizer`] chooses
//! the passes, the number of sweeps, the budget, and validation; [`run_pass`]
//! runs one pass on a [`Builder`](ir_lang::Builder) for callers with their own
//! pipeline.
//!
//! ## Guarantees
//!
//! - **Semantics are preserved**: return values, traps and their codes, `check`
//! errors and their codes, exceptions, and memory effects (stores, atomics,
//! volatile accesses, calls, and their order) are unchanged. Constant folding
//! follows `specs/OPS.md` exactly, and an operation whose policy would raise at
//! run time is never folded into a value. Checked by differential tests that
//! run random programs before and after optimization in the ir-lang reference
//! interpreter.
//! - **The IR stays valid**: every pass leaves the function valid under
//! [`ir_lang::Module::validate`], including the safepoint rules for GC
//! references. The validator runs after every pass in debug builds and tests.
//! - **Bounded work**: every pass is iterative (no recursion over the input) and
//! draws from a [`Budget`]; a function whose budget runs out is left valid and
//! partly optimized.
//!
//! What is not done yet: no memory optimizations (loads are not value-numbered
//! or forwarded, stores are not removed), no inlining, no SROA, no strength
//! reduction, and `fma` is not constant-folded. See `dev/ROADMAP.md`.
//!
//! ## Features
//!
//! - `std` (default): the standard library; without it the crate is `#![no_std]`
//! and needs only `alloc`. Optimization results are identical either way
//! (folding implements IEEE rounding and square root itself).
extern crate alloc;
extern crate std;
pub use Budget;
pub use OptError;
pub use ;
pub use ;
/// Optimizes every function of `module` with the default pipeline (Tier 1).
///
/// The same as `Optimizer::new().run(module)`: [`DEFAULT_PIPELINE`] run to a
/// fixpoint (at most [`DEFAULT_MAX_ITERATIONS`] sweeps), with the default
/// budget, validation between passes in debug builds, and changed functions
/// compacted.
///
/// # Errors
///
/// [`OptError::InvalidInput`] if a function does not validate; the other
/// variants only for a defect in a pass.
///
/// # Examples
///
/// ```
/// use ir_lang::{Linkage, Module, Signature, Type};
///
/// let mut m = Module::new("m");
/// let f = m.declare_function("f", &Signature::new(&[], &[Type::I32]), Linkage::Export)?;
/// let mut b = m.build(f)?;
/// let unused = b.iconst(Type::I32, 1)?;
/// let v = b.iconst(Type::I32, 2)?;
/// b.ret(&[v])?;
/// # let _ = unused;
/// let stats = opt_lang::optimize(&mut m)?;
/// assert_eq!(stats.insts_after(), 1);
/// m.validate()?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
/// Runs the README's examples as doctests, so its claims stay true.
;
/// Runs the API reference's examples as doctests.
;