1pub mod costs;
16
17#[doc(inline)]
18pub use costs::Costs;
19
20use std::collections::HashMap;
21
22pub type Error = anyhow::Error;
24
25#[derive(Default)]
26struct InfoItem {
27 min_cost: usize,
29 escape_velocity: usize,
31}
32
33struct Instrument<C> {
34 costs: C,
35 spend: walrus::FunctionId,
36 info: Info,
37 stack: Vec<(walrus::ir::InstrSeqId, InfoItem)>,
40}
41
42#[derive(Default)]
43struct Info {
44 items: HashMap<walrus::ir::InstrSeqId, InfoItem>,
45}
46
47#[facile::facade]
48impl InstrExt for walrus::ir::Instr {
49 fn jump_targets(&self) -> Vec<walrus::ir::InstrSeqId> {
50 use walrus::ir::Instr::*;
51 match self {
52 Br(walrus::ir::Br { block })
53 | BrIf(walrus::ir::BrIf { block })
54 | BrOnCast(walrus::ir::BrOnCast { block, .. })
55 | BrOnCastFail(walrus::ir::BrOnCastFail { block, .. })
56 | BrOnNull(walrus::ir::BrOnNull { block })
57 | BrOnNonNull(walrus::ir::BrOnNonNull { block }) => vec![*block],
58 BrTable(walrus::ir::BrTable { blocks, default }) => blocks
59 .iter()
60 .cloned()
61 .chain(std::iter::once(*default))
62 .collect(),
63 _ => vec![],
64 }
65 }
66
67 fn branches(&self) -> Vec<walrus::ir::InstrSeqId> {
68 use walrus::ir::Instr::*;
69
70 match self {
71 IfElse(walrus::ir::IfElse {
72 consequent,
73 alternative,
74 }) => vec![*consequent, *alternative],
75
76 Block(walrus::ir::Block { seq }) => vec![*seq],
77
78 _ => vec![],
79 }
80 }
81
82 fn entry_points(&self) -> Vec<walrus::ir::InstrSeqId> {
83 if let walrus::ir::Instr::Loop(walrus::ir::Loop { seq }) = self {
84 vec![*seq]
85 } else {
86 self.branches()
87 }
88 }
89}
90
91impl Info {
92 fn can_escape(&self, instr: &walrus::ir::Instr) -> bool {
93 !instr.jump_targets().is_empty()
94 || instr
95 .entry_points()
96 .iter()
97 .map(|seq_id| self.items.get(seq_id).unwrap().escape_velocity)
98 .max()
99 .unwrap_or_default()
100 > 1
101 }
102}
103
104struct BasicBlocks<'info, 'instrs> {
105 info: &'info Info,
106 instrs: &'instrs [(walrus::ir::Instr, walrus::InstrLocId)],
107}
108
109impl<'info, 'instrs> Iterator for BasicBlocks<'info, 'instrs> {
110 type Item = &'instrs [(walrus::ir::Instr, walrus::InstrLocId)];
111
112 fn next(&mut self) -> Option<Self::Item> {
113 if self.instrs.is_empty() {
114 None
115 } else {
116 let pos = self
117 .instrs
118 .iter()
119 .position(|(instr, _loc)| self.info.can_escape(instr))
120 .map_or(self.instrs.len(), |pos| pos + 1);
121 let ret = &self.instrs[..pos];
122 self.instrs = &self.instrs[pos..];
123 Some(ret)
124 }
125 }
126}
127
128impl<C: Costs> Instrument<C> {
129 fn new(costs: C, spend: walrus::FunctionId) -> Self {
130 Self {
131 costs,
132 spend,
133 info: Info::default(),
134 stack: vec![],
135 }
136 }
137
138 fn enter_seq(&mut self, id: walrus::ir::InstrSeqId) {
139 self.stack.push((id, InfoItem::default()))
140 }
141
142 fn exit_seq(&mut self) -> (walrus::ir::InstrSeqId, InfoItem) {
143 self.stack.pop().expect("enter should be paired with exit")
144 }
145
146 fn info(&self, seq: walrus::ir::InstrSeqId) -> &InfoItem {
147 self.info.items.get(&seq).unwrap()
148 }
149
150 fn relative_label(&self, seq: walrus::ir::InstrSeqId) -> usize {
151 self.stack
152 .iter()
153 .rev()
154 .position(|(s, _info)| *s == seq)
155 .expect("Wasm should be well-formed")
156 }
157
158 fn escape_velocity(&self, instr: &walrus::ir::Instr) -> usize {
159 if instr.is_return()
160 || instr.is_return_call_indirect()
161 || instr.is_return_call()
162 || instr.is_return_call_ref()
163 {
164 self.stack.len()
165 } else {
166 std::cmp::max(
167 instr
168 .entry_points()
169 .into_iter()
170 .map(|seq| self.info(seq).escape_velocity)
171 .max()
172 .unwrap_or_default()
173 .saturating_sub(1),
174 instr
175 .jump_targets()
176 .into_iter()
177 .map(|seq| self.relative_label(seq) + 1)
178 .max()
179 .unwrap_or_default(),
180 )
181 }
182 }
183
184 fn basic_blocks<'instr>(
185 &self,
186 seq: &'instr [(walrus::ir::Instr, walrus::InstrLocId)],
187 ) -> impl Iterator<Item = &'instr [(walrus::ir::Instr, walrus::InstrLocId)]> {
188 BasicBlocks {
189 info: &self.info,
190 instrs: seq,
191 }
192 }
193
194 fn extract_min_cost(&mut self, instr: &walrus::ir::Instr) -> usize {
195 let nested_cost = instr
196 .branches()
197 .iter()
198 .map(|seq| self.info(*seq).min_cost)
199 .min()
200 .unwrap_or_default();
201 for nest in instr.branches() {
202 self.info.items.get_mut(&nest).unwrap().min_cost -= nested_cost;
203 }
204 self.costs.instruction(instr) as usize + nested_cost
205 }
206
207 fn call_spend(&self, output: &mut Vec<(walrus::ir::Instr, walrus::InstrLocId)>, cost: usize) {
208 let cost = i64::try_from(cost).unwrap();
209 if cost > 0 {
210 output.push((
211 walrus::ir::Const {
212 value: walrus::ir::Value::I64(cost),
213 }
214 .into(),
215 walrus::InstrLocId::default(),
216 ));
217
218 output.push((
219 walrus::ir::Call { func: self.spend }.into(),
220 walrus::InstrLocId::default(),
221 ));
222 }
223 }
224}
225
226impl<'instr, C: Costs> walrus::ir::Visitor<'instr> for Instrument<C> {
228 fn start_instr_seq(&mut self, seq: &'instr walrus::ir::InstrSeq) {
229 self.enter_seq(seq.id());
230 }
231
232 fn end_instr_seq(&mut self, seq: &'instr walrus::ir::InstrSeq) {
233 let escape_velocity = seq
234 .iter()
235 .map(|(instr, _loc)| self.escape_velocity(instr))
236 .max()
237 .unwrap_or_default();
238
239 let mut min_cost = 0;
240 for (instr, _loc) in self
241 .basic_blocks(seq)
242 .take(1)
243 .flatten()
244 .collect::<Vec<_>>()
245 {
246 min_cost += self.extract_min_cost(instr);
247 }
248
249 let (id, mut info) = self.exit_seq();
250 debug_assert_eq!(id, seq.id());
251
252 info.escape_velocity = escape_velocity;
253 info.min_cost = min_cost;
254
255 self.info.items.insert(id, info);
256 }
257}
258
259impl<C: Costs> walrus::ir::VisitorMut for Instrument<C> {
260 fn end_instr_seq_mut(&mut self, seq: &mut walrus::ir::InstrSeq) {
261 let seq_min_cost = self.info(seq.id()).min_cost;
262 for (index, basic_block) in self
263 .basic_blocks(&std::mem::take(&mut seq.instrs))
264 .collect::<Vec<_>>()
265 .into_iter()
266 .enumerate()
267 {
268 let cost = if index == 0 {
269 seq_min_cost
270 } else {
271 basic_block
272 .iter()
273 .map(|(instr, _loc)| self.extract_min_cost(instr))
274 .sum()
275 };
276
277 self.call_spend(&mut seq.instrs, cost);
278 seq.instrs.extend_from_slice(basic_block);
279 }
280 }
281}
282
283pub fn instrument(
286 module: &[u8],
287 costs: impl Costs,
288 (spend_module, spend_name): (&str, &str),
289) -> Result<Vec<u8>, Error> {
290 let mut module = walrus::Module::from_buffer(module)?;
291 let spend_ty = module.types.add(&[walrus::ValType::I64], &[]);
292 let (spend, _) = module.add_import_func(spend_module, spend_name, spend_ty);
293
294 for (_id, func) in module.funcs.iter_local_mut() {
295 let mut visitor = Instrument::new(&costs, spend);
296 walrus::ir::dfs_in_order(&mut visitor, func, func.entry_block());
297 walrus::ir::dfs_pre_order_mut(&mut visitor, func, func.entry_block());
298 }
299
300 Ok(module.emit_wasm())
301}