1use walrus::ir::*;
2use walrus::*;
3
4use crate::utils::*;
5use std::collections::HashSet;
6
7const METADATA_SIZE: i32 = 24;
8const DEFAULT_PAGE_LIMIT: i32 = 16 * 256; const LOG_ITEM_SIZE: i32 = 12;
10const MAX_ITEMS_PER_QUERY: i32 = 174758; struct InjectionPoint {
13 position: usize,
14 cost: i64,
15 kind: InjectionKind,
16}
17impl InjectionPoint {
18 fn new() -> Self {
19 InjectionPoint {
20 position: 0,
21 cost: 0,
22 kind: InjectionKind::Static,
23 }
24 }
25}
26
27struct Variables {
28 total_counter: GlobalId,
29 log_size: GlobalId,
30 page_size: GlobalId,
31 is_init: GlobalId,
32 is_entry: GlobalId,
33 dynamic_counter_func: FunctionId,
34 dynamic_counter64_func: FunctionId,
35 heap_base: GlobalId,
37}
38
39pub struct Config {
40 pub trace_only_funcs: Vec<String>,
41 pub start_address: Option<i64>,
42 pub page_limit: Option<i32>,
43 pub heap_trace: bool,
45 pub heap_pages: i32,
47 pub stub_wasi: bool,
48}
49impl Config {
50 pub fn is_preallocated(&self) -> bool {
51 self.start_address.is_some()
52 }
53 pub fn log_start_address(&self) -> i64 {
54 self.start_address.unwrap_or(0) + METADATA_SIZE as i64
55 }
56 pub fn metadata_start_address(&self) -> i64 {
57 self.start_address.unwrap_or(0)
58 }
59 pub fn page_limit(&self) -> i64 {
60 i64::from(
61 self.page_limit
62 .map(|x| x - 1)
63 .unwrap_or(DEFAULT_PAGE_LIMIT - 1),
64 ) }
66 pub fn heap_trace_size_limit(&self) -> i32 {
68 self.heap_pages * 65536 - METADATA_SIZE
69 }
70}
71
72pub fn instrument(m: &mut Module, config: Config) -> Result<(), String> {
75 if config.stub_wasi {
76 stub_wasi_imports(m);
77 }
78 let mut trace_only_ids = HashSet::new();
79 for name in config.trace_only_funcs.iter() {
80 let id = match m.funcs.by_name(name) {
81 Some(id) => id,
82 None => return Err(format!("func \"{name}\" not found")),
83 };
84 trace_only_ids.insert(id);
85 }
86 let is_partial_tracing = !trace_only_ids.is_empty();
87 let func_cost = FunctionCost::new(m);
88 let total_counter =
89 m.globals
90 .add_local(ValType::I64, true, false, ConstExpr::Value(Value::I64(0)));
91 let log_size = m
92 .globals
93 .add_local(ValType::I32, true, false, ConstExpr::Value(Value::I32(0)));
94 let page_size = m
95 .globals
96 .add_local(ValType::I32, true, false, ConstExpr::Value(Value::I32(0)));
97 let is_init = m
98 .globals
99 .add_local(ValType::I32, true, false, ConstExpr::Value(Value::I32(1)));
100 let is_entry = m
101 .globals
102 .add_local(ValType::I32, true, false, ConstExpr::Value(Value::I32(0)));
103 let heap_base = m
105 .globals
106 .add_local(ValType::I32, true, false, ConstExpr::Value(Value::I32(0)));
107 let opt_init = if is_partial_tracing {
108 Some(is_init)
109 } else {
110 None
111 };
112 let dynamic_counter_func = make_dynamic_counter(m, total_counter, &opt_init);
113 let dynamic_counter64_func = make_dynamic_counter64(m, total_counter, &opt_init);
114 let vars = Variables {
115 total_counter,
116 log_size,
117 is_init,
118 is_entry,
119 dynamic_counter_func,
120 dynamic_counter64_func,
121 page_size,
122 heap_base,
123 };
124
125 for (id, func) in m.funcs.iter_local_mut() {
126 if id != dynamic_counter_func && id != dynamic_counter64_func {
127 inject_metering(
128 func,
129 func.entry_block(),
130 &vars,
131 &func_cost,
132 is_partial_tracing,
133 );
134 }
135 }
136 let writer = if config.heap_trace {
137 make_heap_writer(m, &vars, &config)
138 } else {
139 make_stable_writer(m, &vars, &config)
140 };
141 let printer = make_printer(m, &vars, writer);
142 for (id, func) in m.funcs.iter_local_mut() {
143 if id != printer
144 && id != writer
145 && id != dynamic_counter_func
146 && id != dynamic_counter64_func
147 {
148 let is_partial_tracing = trace_only_ids.contains(&id);
149 inject_profiling_prints(&m.types, printer, id, func, is_partial_tracing, &vars);
150 }
151 }
152 if !is_partial_tracing {
153 if config.heap_trace {
155 let memory_id = get_memory_id(m);
157 let memory = m.memories.get_mut(memory_id);
158 let current_max = memory.maximum.unwrap_or(memory.initial);
159 memory.maximum = Some(current_max + config.heap_pages as u64);
160 inject_init_heap_trace(m, &vars, &config);
161 } else {
162 inject_init(m, vars.is_init);
163 }
164 }
165 if !config.heap_trace {
167 inject_pre_upgrade(m, &vars, &config);
168 inject_post_upgrade(m, &vars, &config);
169 }
170
171 inject_canister_methods(m, &vars);
172 let leb = make_leb128_encoder(m);
173 if config.heap_trace {
174 make_heap_getter(m, &vars, leb, &config);
175 } else {
176 make_stable_getter(m, &vars, leb, &config);
177 }
178 make_getter(m, &vars);
179 make_toggle_func(m, "__toggle_tracing", vars.is_init);
180 make_toggle_func(m, "__toggle_entry", vars.is_entry);
181 let name = make_name_section(m);
182 m.customs.add(name);
183 Ok(())
184}
185
186fn inject_metering(
187 func: &mut LocalFunction,
188 start: InstrSeqId,
189 vars: &Variables,
190 func_cost: &FunctionCost,
191 is_partial_tracing: bool,
192) {
193 use InjectionKind::*;
194 let mut stack = vec![start];
195 while let Some(seq_id) = stack.pop() {
196 let seq = func.block(seq_id);
197 let mut injection_points = vec![];
199 let mut curr = InjectionPoint::new();
200 if seq_id == start {
202 curr.cost += 1;
203 }
204 for (pos, (instr, _)) in seq.instrs.iter().enumerate() {
205 curr.position = pos;
206 match instr {
207 Instr::Block(Block { seq }) | Instr::Loop(Loop { seq }) => {
208 match func.block(*seq).ty {
209 InstrSeqType::Simple(Some(_)) => curr.cost += instr_cost(instr),
210 InstrSeqType::Simple(None) => (),
211 InstrSeqType::MultiValue(_) => unreachable!("Multivalue not supported"),
212 }
213 stack.push(*seq);
214 injection_points.push(curr);
215 curr = InjectionPoint::new();
216 }
217 Instr::IfElse(IfElse {
218 consequent,
219 alternative,
220 }) => {
221 curr.cost += instr_cost(instr);
222 stack.push(*consequent);
223 stack.push(*alternative);
224 injection_points.push(curr);
225 curr = InjectionPoint::new();
226 }
227 Instr::Br(_) | Instr::BrIf(_) | Instr::BrTable(_) => {
228 curr.cost += instr_cost(instr);
230 injection_points.push(curr);
231 curr = InjectionPoint::new();
232 }
233 Instr::Return(_) | Instr::Unreachable(_) => {
234 curr.cost += instr_cost(instr);
235 injection_points.push(curr);
236 curr = InjectionPoint::new();
237 }
238 Instr::Call(Call { func }) => {
239 curr.cost += instr_cost(instr);
240 match func_cost.get_cost(*func) {
241 Some((cost, InjectionKind::Static)) => curr.cost += cost,
242 Some((cost, kind @ InjectionKind::Dynamic))
243 | Some((cost, kind @ InjectionKind::Dynamic64)) => {
244 curr.cost += cost;
245 let dynamic = InjectionPoint {
246 position: pos,
247 cost: 0,
248 kind,
249 };
250 injection_points.push(dynamic);
251 }
252 None => {}
253 }
254 }
255 Instr::MemoryFill(_)
256 | Instr::MemoryCopy(_)
257 | Instr::MemoryInit(_)
258 | Instr::TableCopy(_)
259 | Instr::TableInit(_) => {
260 curr.cost += instr_cost(instr);
261 let dynamic = InjectionPoint {
262 position: pos,
263 cost: 0,
264 kind: InjectionKind::Dynamic,
265 };
266 injection_points.push(dynamic);
267 }
268 _ => {
269 curr.cost += instr_cost(instr);
270 }
271 }
272 }
273 injection_points.push(curr);
274 let injection_points = injection_points
276 .iter()
277 .filter(|point| point.cost > 0 || point.kind != Static);
278 let mut builder = func.builder_mut().instr_seq(seq_id);
279 let original = builder.instrs_mut();
280 let mut instrs = vec![];
281 let mut last_injection_position = 0;
282 for point in injection_points {
283 instrs.extend_from_slice(&original[last_injection_position..point.position]);
284 match point.kind {
287 Static => {
288 #[rustfmt::skip]
289 instrs.extend_from_slice(&[
290 (GlobalGet { global: vars.total_counter }.into(), Default::default()),
291 (Const { value: Value::I64(point.cost) }.into(), Default::default()),
292 ]);
293 if is_partial_tracing {
294 #[rustfmt::skip]
295 instrs.extend_from_slice(&[
296 (GlobalGet { global: vars.is_init }.into(), Default::default()),
297 (Const { value: Value::I32(1) }.into(), Default::default()),
298 (Binop { op: BinaryOp::I32Xor }.into(), Default::default()),
299 (Unop { op: UnaryOp::I64ExtendUI32 }.into(), Default::default()),
300 (Binop { op: BinaryOp::I64Mul }.into(), Default::default()),
301 ]);
302 }
303 #[rustfmt::skip]
304 instrs.extend_from_slice(&[
305 (Binop { op: BinaryOp::I64Add }.into(), Default::default()),
306 (GlobalSet { global: vars.total_counter }.into(), Default::default()),
307 ]);
308 }
309 Dynamic => {
310 #[rustfmt::skip]
312 instrs.push((Call { func: vars.dynamic_counter_func }.into(), Default::default()));
313 }
314 Dynamic64 => {
315 #[rustfmt::skip]
316 instrs.push((Call { func: vars.dynamic_counter64_func }.into(), Default::default()));
317 }
318 };
319 last_injection_position = point.position;
320 }
321 instrs.extend_from_slice(&original[last_injection_position..]);
322 *original = instrs;
323 }
324}
325
326fn inject_profiling_prints(
327 types: &ModuleTypes,
328 printer: FunctionId,
329 id: FunctionId,
330 func: &mut LocalFunction,
331 is_partial_tracing: bool,
332 vars: &Variables,
333) {
334 let start_id = func.entry_block();
337 let original_block = func.block_mut(start_id);
338 let start_instrs = original_block.instrs.split_off(0);
339 let start_ty = match original_block.ty {
340 InstrSeqType::MultiValue(id) => {
341 let valtypes = types.results(id);
342 InstrSeqType::Simple(match valtypes.len() {
343 0 => None,
344 1 => Some(valtypes[0]),
345 _ => unreachable!("Multivalue return not supported"),
346 })
347 }
348 InstrSeqType::Simple(_) => unreachable!(),
350 };
351 let mut inner_start = func.builder_mut().dangling_instr_seq(start_ty);
352 *(inner_start.instrs_mut()) = start_instrs;
353 let inner_start_id = inner_start.id();
354 let mut start_builder = func.builder_mut().func_body();
355 if is_partial_tracing {
356 start_builder.i32_const(0).global_set(vars.is_init);
357 }
358 start_builder
359 .i32_const(id.index() as i32)
360 .call(printer)
361 .instr(Block {
362 seq: inner_start_id,
363 })
364 .i32_const(-(id.index() as i32))
366 .call(printer);
367 if is_partial_tracing {
369 start_builder.i32_const(1).global_set(vars.is_init);
370 }
371 let mut stack = vec![inner_start_id];
372 while let Some(seq_id) = stack.pop() {
373 let mut builder = func.builder_mut().instr_seq(seq_id);
374 let original = builder.instrs_mut();
375 let mut instrs = vec![];
376 for (instr, loc) in original.iter() {
377 match instr {
378 Instr::Block(Block { seq }) | Instr::Loop(Loop { seq }) => {
379 stack.push(*seq);
380 instrs.push((instr.clone(), *loc));
381 }
382 Instr::IfElse(IfElse {
383 consequent,
384 alternative,
385 }) => {
386 stack.push(*alternative);
387 stack.push(*consequent);
388 instrs.push((instr.clone(), *loc));
389 }
390 Instr::Return(_) => {
391 instrs.push((
392 Instr::Br(Br {
393 block: inner_start_id,
394 }),
395 *loc,
396 ));
397 }
398 Instr::Br(Br { block }) if *block == start_id => {
400 instrs.push((
401 Instr::Br(Br {
402 block: inner_start_id,
403 }),
404 *loc,
405 ));
406 }
407 Instr::BrIf(BrIf { block }) if *block == start_id => {
408 instrs.push((
409 Instr::BrIf(BrIf {
410 block: inner_start_id,
411 }),
412 *loc,
413 ));
414 }
415 Instr::BrTable(BrTable { blocks, default }) => {
416 let mut blocks = blocks.clone();
417 for i in 0..blocks.len() {
418 if let Some(id) = blocks.get_mut(i) {
419 if *id == start_id {
420 *id = inner_start_id
421 };
422 }
423 }
424 let default = if *default == start_id {
425 inner_start_id
426 } else {
427 *default
428 };
429 instrs.push((Instr::BrTable(BrTable { blocks, default }), *loc));
430 }
431 _ => instrs.push((instr.clone(), *loc)),
432 }
433 }
434 *original = instrs;
435 }
436}
437
438fn make_dynamic_counter(
439 m: &mut Module,
440 total_counter: GlobalId,
441 opt_init: &Option<GlobalId>,
442) -> FunctionId {
443 let mut builder = FunctionBuilder::new(&mut m.types, &[ValType::I32], &[ValType::I32]);
444 let size = m.locals.add(ValType::I32);
445 let mut seq = builder.func_body();
446 seq.local_get(size);
447 if let Some(is_init) = opt_init {
448 seq.global_get(*is_init)
449 .i32_const(1)
450 .binop(BinaryOp::I32Xor)
451 .binop(BinaryOp::I32Mul);
452 }
453 seq.unop(UnaryOp::I64ExtendUI32)
454 .global_get(total_counter)
455 .binop(BinaryOp::I64Add)
456 .global_set(total_counter)
457 .local_get(size);
458 builder.finish(vec![size], &mut m.funcs)
459}
460fn make_dynamic_counter64(
461 m: &mut Module,
462 total_counter: GlobalId,
463 opt_init: &Option<GlobalId>,
464) -> FunctionId {
465 let mut builder = FunctionBuilder::new(&mut m.types, &[ValType::I64], &[ValType::I64]);
466 let size = m.locals.add(ValType::I64);
467 let mut seq = builder.func_body();
468 seq.local_get(size);
469 if let Some(is_init) = opt_init {
470 seq.global_get(*is_init)
471 .i32_const(1)
472 .binop(BinaryOp::I32Xor)
473 .unop(UnaryOp::I64ExtendUI32)
474 .binop(BinaryOp::I64Mul);
475 }
476 seq.global_get(total_counter)
477 .binop(BinaryOp::I64Add)
478 .global_set(total_counter)
479 .local_get(size);
480 builder.finish(vec![size], &mut m.funcs)
481}
482fn make_stable_writer(m: &mut Module, vars: &Variables, config: &Config) -> FunctionId {
483 let writer = get_ic_func_id(m, "stable64_write");
484 let grow = get_ic_func_id(m, "stable64_grow");
485 let mut builder = FunctionBuilder::new(
486 &mut m.types,
487 &[ValType::I64, ValType::I64, ValType::I64],
488 &[],
489 );
490 let start_address = config.log_start_address();
491 let size_limit = config.page_limit() * 65536;
492 let is_preallocated = config.is_preallocated();
493 let offset = m.locals.add(ValType::I64);
494 let src = m.locals.add(ValType::I64);
495 let size = m.locals.add(ValType::I64);
496 builder
497 .func_body()
498 .local_get(offset)
499 .local_get(size)
500 .binop(BinaryOp::I64Add);
501 if is_preallocated {
502 builder.func_body().i64_const(size_limit);
503 } else {
504 builder
505 .func_body()
506 .global_get(vars.page_size)
507 .i32_const(65536)
508 .binop(BinaryOp::I32Mul)
509 .i32_const(METADATA_SIZE)
510 .binop(BinaryOp::I32Sub)
511 .unop(UnaryOp::I64ExtendSI32);
513 }
514 builder
515 .func_body()
516 .binop(BinaryOp::I64GtS)
517 .if_else(
518 None,
519 |then| {
520 if is_preallocated {
521 then.return_();
522 } else {
523 then.global_get(vars.page_size)
525 .i32_const(DEFAULT_PAGE_LIMIT)
526 .binop(BinaryOp::I32GtS) .if_else(
528 None,
529 |then| {
530 then.return_();
531 },
532 |else_| {
533 else_
534 .i64_const(1)
535 .call(grow)
536 .drop()
537 .global_get(vars.page_size)
538 .i32_const(1)
539 .binop(BinaryOp::I32Add)
540 .global_set(vars.page_size);
541 },
542 );
543 }
544 },
545 |_| {},
546 )
547 .i64_const(start_address)
548 .local_get(offset)
549 .binop(BinaryOp::I64Add)
550 .local_get(src)
551 .local_get(size)
552 .call(writer)
553 .global_get(vars.log_size)
554 .i32_const(1)
555 .binop(BinaryOp::I32Add)
556 .global_set(vars.log_size);
557 builder.finish(vec![offset, src, size], &mut m.funcs)
558}
559
560fn make_printer(m: &mut Module, vars: &Variables, writer: FunctionId) -> FunctionId {
561 let memory = get_memory_id(m);
562 let mut builder = FunctionBuilder::new(&mut m.types, &[ValType::I32], &[]);
563 let func_id = m.locals.add(ValType::I32);
564 let a = m.locals.add(ValType::I32);
565 let b = m.locals.add(ValType::I64);
566 builder.func_body().global_get(vars.is_init).if_else(
567 None,
568 |then| {
569 then.return_();
570 },
571 |else_| {
572 #[rustfmt::skip]
573 else_
574 .i32_const(0)
576 .load(memory, LoadKind::I32 { atomic: false }, MemArg { offset: 0, align: 4})
577 .local_set(a)
578 .i32_const(4)
579 .load(memory, LoadKind::I64 { atomic: false }, MemArg { offset: 0, align: 8})
580 .local_set(b)
581 .i32_const(0)
583 .local_get(func_id)
584 .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4 })
585 .i32_const(4)
586 .global_get(vars.total_counter)
587 .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 })
588 .global_get(vars.log_size)
589 .unop(UnaryOp::I64ExtendUI32)
590 .i64_const(LOG_ITEM_SIZE as i64)
591 .binop(BinaryOp::I64Mul)
592 .i64_const(0)
593 .i64_const(LOG_ITEM_SIZE as i64)
594 .call(writer)
595 .i32_const(0)
597 .local_get(a)
598 .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4 })
599 .i32_const(4)
600 .local_get(b)
601 .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 });
602 },
603 );
604 builder.finish(vec![func_id], &mut m.funcs)
605}
606fn inject_canister_methods(m: &mut Module, vars: &Variables) {
620 let methods: Vec<_> = m
621 .exports
622 .iter()
623 .filter_map(|e| match e.item {
624 ExportItem::Function(id)
625 if e.name != "canister_update __motoko_async_helper"
626 && (e.name.starts_with("canister_update")
627 || e.name.starts_with("canister_query")
628 || e.name.starts_with("canister_composite_query")
629 || e.name.starts_with("canister_heartbeat")
630 || e.name == "canister_pre_upgrade") =>
634 {
635 Some(id)
636 }
637 _ => None,
638 })
639 .collect();
640 for id in methods.iter() {
641 let mut builder = get_builder(m, *id);
642 #[rustfmt::skip]
643 inject_top(
644 &mut builder,
645 vec![
646 GlobalGet { global: vars.is_entry }.into(),
648 GlobalGet { global: vars.log_size }.into(),
649 Binop { op: BinaryOp::I32Mul }.into(),
650 GlobalSet { global: vars.log_size }.into(),
651 ],
652 );
653 }
654}
655fn inject_init(m: &mut Module, is_init: GlobalId) {
656 let mut builder = get_or_create_export_func(m, "canister_init");
657 builder.i32_const(0).global_set(is_init);
661}
662fn inject_pre_upgrade(m: &mut Module, vars: &Variables, config: &Config) {
663 let writer = get_ic_func_id(m, "stable64_write");
664 let memory = get_memory_id(m);
665 let a = m.locals.add(ValType::I64);
666 let b = m.locals.add(ValType::I64);
667 let c = m.locals.add(ValType::I64);
668 let mut builder = get_or_create_export_func(m, "canister_pre_upgrade");
669 #[rustfmt::skip]
670 builder
671 .i32_const(0)
673 .load(memory, LoadKind::I64 { atomic: false }, MemArg { offset: 0, align: 8})
674 .local_set(a)
675 .i32_const(8)
676 .load(memory, LoadKind::I64 { atomic: false }, MemArg { offset: 0, align: 8})
677 .local_set(b)
678 .i32_const(16)
679 .load(memory, LoadKind::I64 { atomic: false }, MemArg { offset: 0, align: 8})
680 .local_set(c)
681 .i32_const(0)
683 .global_get(vars.total_counter)
684 .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 })
685 .i32_const(8)
686 .global_get(vars.log_size)
687 .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4 })
688 .i32_const(12)
689 .global_get(vars.page_size)
690 .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4 })
691 .i32_const(16)
692 .global_get(vars.is_init)
693 .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4 })
694 .i32_const(20)
695 .global_get(vars.is_entry)
696 .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4 })
697 .i64_const(config.metadata_start_address())
698 .i64_const(0)
699 .i64_const(METADATA_SIZE as i64)
700 .call(writer)
701 .i32_const(0)
703 .local_get(a)
704 .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 })
705 .i32_const(8)
706 .local_get(b)
707 .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 })
708 .i32_const(16)
709 .local_get(c)
710 .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 });
711}
712fn inject_post_upgrade(m: &mut Module, vars: &Variables, config: &Config) {
713 let reader = get_ic_func_id(m, "stable64_read");
714 let memory = get_memory_id(m);
715 let a = m.locals.add(ValType::I64);
716 let b = m.locals.add(ValType::I64);
717 let c = m.locals.add(ValType::I64);
718 let mut builder = get_or_create_export_func(m, "canister_post_upgrade");
719 #[rustfmt::skip]
720 inject_top(&mut builder, vec![
721 Const { value: Value::I32(0) }.into(),
723 Load { memory, kind: LoadKind::I64 { atomic: false }, arg: MemArg { offset: 0, align: 8 } }.into(),
724 LocalSet { local: a }.into(),
725 Const { value: Value::I32(8) }.into(),
726 Load { memory, kind: LoadKind::I64 { atomic: false }, arg: MemArg { offset: 0, align: 8 } }.into(),
727 LocalSet { local: b }.into(),
728 Const { value: Value::I32(16) }.into(),
729 Load { memory, kind: LoadKind::I64 { atomic: false }, arg: MemArg { offset: 0, align: 8 } }.into(),
730 LocalSet { local: c }.into(),
731 Const { value: Value::I64(0) }.into(),
733 Const { value: Value::I64(config.metadata_start_address()) }.into(),
734 Const { value: Value::I64(METADATA_SIZE as i64) }.into(),
735 Call { func: reader }.into(),
736 Const { value: Value::I32(0) }.into(),
737 Load { memory, kind: LoadKind::I64 { atomic: false }, arg: MemArg { offset: 0, align: 8 } }.into(),
738 GlobalSet { global: vars.total_counter }.into(),
739 Const { value: Value::I32(8) }.into(),
740 Load { memory, kind: LoadKind::I32 { atomic: false }, arg: MemArg { offset: 0, align: 4 } }.into(),
741 GlobalSet { global: vars.log_size }.into(),
742 Const { value: Value::I32(12) }.into(),
743 Load { memory, kind: LoadKind::I32 { atomic: false }, arg: MemArg { offset: 0, align: 4 } }.into(),
744 GlobalSet { global: vars.page_size }.into(),
745 Const { value: Value::I32(16) }.into(),
746 Load { memory, kind: LoadKind::I32 { atomic: false }, arg: MemArg { offset: 0, align: 4 } }.into(),
747 GlobalSet { global: vars.is_init }.into(),
748 Const { value: Value::I32(20) }.into(),
749 Load { memory, kind: LoadKind::I32 { atomic: false }, arg: MemArg { offset: 0, align: 4 } }.into(),
750 GlobalSet { global: vars.is_entry }.into(),
751 Const { value: Value::I32(0) }.into(),
753 LocalGet { local: a }.into(),
754 Store { memory, kind: StoreKind::I64 { atomic: false }, arg: MemArg { offset: 0, align: 8 } }.into(),
755 Const { value: Value::I32(8) }.into(),
756 LocalGet { local: b }.into(),
757 Store { memory, kind: StoreKind::I64 { atomic: false }, arg: MemArg { offset: 0, align: 8 } }.into(),
758 Const { value: Value::I32(16) }.into(),
759 LocalGet { local: c }.into(),
760 Store { memory, kind: StoreKind::I64 { atomic: false }, arg: MemArg { offset: 0, align: 8 } }.into(),
761 ]);
762}
763
764fn make_stable_getter(m: &mut Module, vars: &Variables, leb: FunctionId, config: &Config) {
765 let memory = get_memory_id(m);
766 let arg_size = get_ic_func_id(m, "msg_arg_data_size");
767 let arg_copy = get_ic_func_id(m, "msg_arg_data_copy");
768 let reply_data = get_ic_func_id(m, "msg_reply_data_append");
769 let reply = get_ic_func_id(m, "msg_reply");
770 let trap = get_ic_func_id(m, "trap");
771 let reader = get_ic_func_id(m, "stable64_read");
772 let idx = m.locals.add(ValType::I32);
773 let len = m.locals.add(ValType::I32);
774 let next_idx = m.locals.add(ValType::I32);
775 let mut builder = FunctionBuilder::new(&mut m.types, &[], &[]);
776 builder.name("__get_profiling".to_string());
777 #[rustfmt::skip]
778 builder.func_body()
779 .memory_size(memory)
781 .i32_const(32)
782 .binop(BinaryOp::I32LtU)
783 .if_else(
784 None,
785 |then| {
786 then
787 .i32_const(32)
788 .memory_grow(memory)
789 .drop();
790 },
791 |_| {}
792 )
793 .call(arg_size)
795 .i32_const(11)
796 .binop(BinaryOp::I32Ne)
797 .if_else(
798 None,
799 |then| {
800 then.i32_const(0)
801 .i32_const(0)
802 .call(trap);
803 },
804 |_| {},
805 )
806 .i32_const(0)
807 .i32_const(7)
808 .i32_const(4)
809 .call(arg_copy)
810 .i32_const(0)
811 .load(memory, LoadKind::I32 { atomic: false }, MemArg { offset: 0, align: 4})
812 .local_set(idx)
813 .i32_const(0)
815 .i64_const(0x6c016d034c444944) .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 })
817 .i32_const(8)
818 .i64_const(0x02756e7401750002) .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 })
820 .i32_const(16)
821 .i32_const(0x0200) .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4})
823 .i32_const(0)
824 .i32_const(18)
825 .call(reply_data)
826 .global_get(vars.log_size)
828 .local_get(idx)
829 .binop(BinaryOp::I32Sub)
830 .local_tee(len)
831 .i32_const(MAX_ITEMS_PER_QUERY)
832 .binop(BinaryOp::I32GtU)
833 .if_else(
834 None,
835 |then| {
836 then.i32_const(MAX_ITEMS_PER_QUERY)
837 .local_set(len)
838 .local_get(idx)
839 .i32_const(MAX_ITEMS_PER_QUERY)
840 .binop(BinaryOp::I32Add)
841 .local_set(next_idx);
842 },
843 |else_| {
844 else_.i32_const(0)
845 .local_set(next_idx);
846 },
847 )
848 .local_get(len)
849 .call(leb)
850 .i32_const(0)
851 .i32_const(5)
852 .call(reply_data)
853 .i64_const(0)
855 .i64_const(config.log_start_address())
856 .local_get(idx)
857 .unop(UnaryOp::I64ExtendUI32)
858 .i64_const(LOG_ITEM_SIZE as i64)
859 .binop(BinaryOp::I64Mul)
860 .binop(BinaryOp::I64Add)
861 .local_get(len)
862 .unop(UnaryOp::I64ExtendUI32)
863 .i64_const(LOG_ITEM_SIZE as i64)
864 .binop(BinaryOp::I64Mul)
865 .call(reader)
866 .i32_const(0)
867 .local_get(len)
868 .i32_const(LOG_ITEM_SIZE)
869 .binop(BinaryOp::I32Mul)
870 .call(reply_data)
871 .local_get(next_idx)
873 .unop(UnaryOp::I32Eqz)
874 .if_else(
875 None,
876 |then| {
877 then.i32_const(0)
878 .i32_const(0)
879 .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4})
880 .i32_const(0)
881 .i32_const(1)
882 .call(reply_data);
883 },
884 |else_| {
885 else_.i32_const(0)
886 .i32_const(1)
887 .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 1})
888 .i32_const(1)
889 .local_get(next_idx)
890 .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4})
891 .i32_const(0)
892 .i32_const(5)
893 .call(reply_data);
894 },
895 )
896 .call(reply);
897 let getter = builder.finish(vec![], &mut m.funcs);
898 m.exports.add("canister_query __get_profiling", getter);
899}
900fn make_leb128_encoder(m: &mut Module) -> FunctionId {
902 let memory = get_memory_id(m);
903 let mut builder = FunctionBuilder::new(&mut m.types, &[ValType::I32], &[]);
904 let value = m.locals.add(ValType::I32);
905 let mut instrs = builder.func_body();
906 for i in 0..5 {
907 instrs
908 .i32_const(i)
909 .local_get(value)
910 .i32_const(0x7f)
911 .binop(BinaryOp::I32And);
912 if i < 4 {
913 instrs.i32_const(0x80).binop(BinaryOp::I32Or);
914 }
915 #[rustfmt::skip]
916 instrs
917 .store(memory, StoreKind::I32_8 { atomic: false }, MemArg { offset: 0, align: 1 })
918 .local_get(value)
919 .i32_const(7)
920 .binop(BinaryOp::I32ShrU)
921 .local_set(value);
922 }
923 builder.finish(vec![value], &mut m.funcs)
924}
925fn make_name_section(m: &Module) -> RawCustomSection {
926 use candid::Encode;
927 let name: Vec<_> = m
928 .funcs
929 .iter()
930 .filter_map(|f| {
931 if matches!(f.kind, FunctionKind::Local(_)) {
932 use rustc_demangle::demangle;
933 let name = f.name.as_ref()?;
934 let demangled = format!("{:#}", demangle(name));
935 Some((f.id().index() as u16, demangled))
936 } else {
937 None
938 }
939 })
940 .collect();
941 let data = Encode!(&name).unwrap();
942 RawCustomSection {
943 name: "icp:public name".to_string(),
944 data,
945 }
946}
947
948fn make_getter(m: &mut Module, vars: &Variables) {
949 let memory = get_memory_id(m);
950 let reply_data = get_ic_func_id(m, "msg_reply_data_append");
951 let reply = get_ic_func_id(m, "msg_reply");
952 let mut getter = FunctionBuilder::new(&mut m.types, &[], &[]);
953 getter.name("__get_cycles".to_string());
954 #[rustfmt::skip]
955 getter
956 .func_body()
957 .i32_const(0)
959 .i64_const(0x007401004c444944) .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 })
961 .i32_const(7)
962 .global_get(vars.total_counter)
963 .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 })
964 .i32_const(0)
965 .i32_const(15)
966 .call(reply_data)
967 .call(reply);
968 let getter = getter.finish(vec![], &mut m.funcs);
969 m.exports.add("canister_query __get_cycles", getter);
970}
971fn make_toggle_func(m: &mut Module, name: &str, var: GlobalId) {
972 let memory = get_memory_id(m);
973 let reply_data = get_ic_func_id(m, "msg_reply_data_append");
974 let reply = get_ic_func_id(m, "msg_reply");
975 let tmp = m.locals.add(ValType::I64);
976 let mut builder = FunctionBuilder::new(&mut m.types, &[], &[]);
977 builder.name(name.to_string());
978 #[rustfmt::skip]
979 builder
980 .func_body()
981 .global_get(var)
982 .i32_const(1)
983 .binop(BinaryOp::I32Xor)
984 .global_set(var)
985 .i32_const(0)
986 .load(memory, LoadKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 })
987 .local_set(tmp)
988 .i32_const(0)
989 .i64_const(0x4c444944) .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 })
991 .i32_const(0)
992 .i32_const(6)
993 .call(reply_data)
994 .i32_const(0)
995 .local_get(tmp)
996 .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 })
997 .call(reply);
998 let id = builder.finish(vec![], &mut m.funcs);
999 m.exports.add(&format!("canister_update {name}"), id);
1000}
1001
1002fn inject_init_heap_trace(m: &mut Module, vars: &Variables, config: &Config) {
1009 let memory = get_memory_id(m);
1010 let mut builder = get_or_create_export_func(m, "canister_init");
1011 #[rustfmt::skip]
1012 builder
1013 .memory_size(memory)
1015 .i32_const(65536)
1016 .binop(BinaryOp::I32Mul)
1017 .global_set(vars.heap_base)
1018 .i32_const(config.heap_pages)
1020 .memory_grow(memory)
1021 .drop()
1022 .i32_const(0)
1024 .global_set(vars.is_init);
1025}
1026
1027fn make_heap_writer(m: &mut Module, vars: &Variables, config: &Config) -> FunctionId {
1029 let memory = get_memory_id(m);
1030 let size_limit = config.heap_trace_size_limit();
1031 let mut builder = FunctionBuilder::new(
1032 &mut m.types,
1033 &[ValType::I64, ValType::I64, ValType::I64],
1034 &[],
1035 );
1036 let offset = m.locals.add(ValType::I64);
1037 let src = m.locals.add(ValType::I64);
1038 let size = m.locals.add(ValType::I64);
1039 let dest_addr = m.locals.add(ValType::I32);
1040 let tmp_i32 = m.locals.add(ValType::I32);
1041 let tmp_i64 = m.locals.add(ValType::I64);
1042
1043 #[rustfmt::skip]
1045 builder
1046 .func_body()
1047 .local_get(offset)
1048 .local_get(size)
1049 .binop(BinaryOp::I64Add)
1050 .i64_const(size_limit as i64)
1051 .binop(BinaryOp::I64GtS)
1052 .if_else(
1053 None,
1054 |then| {
1055 then.return_();
1057 },
1058 |_| {},
1059 )
1060 .global_get(vars.heap_base)
1062 .i32_const(METADATA_SIZE)
1063 .binop(BinaryOp::I32Add)
1064 .local_get(offset)
1065 .unop(UnaryOp::I32WrapI64)
1066 .binop(BinaryOp::I32Add)
1067 .local_set(dest_addr)
1068 .i32_const(0)
1070 .load(memory, LoadKind::I32 { atomic: false }, MemArg { offset: 0, align: 4 })
1071 .local_set(tmp_i32)
1072 .local_get(dest_addr)
1073 .local_get(tmp_i32)
1074 .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4 })
1075 .i32_const(4)
1077 .load(memory, LoadKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 })
1078 .local_set(tmp_i64)
1079 .local_get(dest_addr)
1080 .i32_const(4)
1081 .binop(BinaryOp::I32Add)
1082 .local_get(tmp_i64)
1083 .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 })
1084 .global_get(vars.log_size)
1086 .i32_const(1)
1087 .binop(BinaryOp::I32Add)
1088 .global_set(vars.log_size);
1089
1090 builder.finish(vec![offset, src, size], &mut m.funcs)
1091}
1092
1093fn make_heap_getter(m: &mut Module, vars: &Variables, leb: FunctionId, _config: &Config) {
1095 let memory = get_memory_id(m);
1096 let arg_size = get_ic_func_id(m, "msg_arg_data_size");
1097 let arg_copy = get_ic_func_id(m, "msg_arg_data_copy");
1098 let reply_data = get_ic_func_id(m, "msg_reply_data_append");
1099 let reply = get_ic_func_id(m, "msg_reply");
1100 let trap = get_ic_func_id(m, "trap");
1101 let idx = m.locals.add(ValType::I32);
1102 let len = m.locals.add(ValType::I32);
1103 let next_idx = m.locals.add(ValType::I32);
1104 let log_addr = m.locals.add(ValType::I32);
1105 let mut builder = FunctionBuilder::new(&mut m.types, &[], &[]);
1106 builder.name("__get_profiling".to_string());
1107 #[rustfmt::skip]
1108 builder.func_body()
1109 .call(arg_size)
1111 .i32_const(11)
1112 .binop(BinaryOp::I32Ne)
1113 .if_else(
1114 None,
1115 |then| {
1116 then.i32_const(0)
1117 .i32_const(0)
1118 .call(trap);
1119 },
1120 |_| {},
1121 )
1122 .i32_const(0)
1123 .i32_const(7)
1124 .i32_const(4)
1125 .call(arg_copy)
1126 .i32_const(0)
1127 .load(memory, LoadKind::I32 { atomic: false }, MemArg { offset: 0, align: 4})
1128 .local_set(idx)
1129 .i32_const(0)
1131 .i64_const(0x6c016d034c444944) .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 })
1133 .i32_const(8)
1134 .i64_const(0x02756e7401750002) .store(memory, StoreKind::I64 { atomic: false }, MemArg { offset: 0, align: 8 })
1136 .i32_const(16)
1137 .i32_const(0x0200) .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4})
1139 .i32_const(0)
1140 .i32_const(18)
1141 .call(reply_data)
1142 .global_get(vars.log_size)
1144 .local_get(idx)
1145 .binop(BinaryOp::I32Sub)
1146 .local_tee(len)
1147 .i32_const(MAX_ITEMS_PER_QUERY)
1148 .binop(BinaryOp::I32GtU)
1149 .if_else(
1150 None,
1151 |then| {
1152 then.i32_const(MAX_ITEMS_PER_QUERY)
1153 .local_set(len)
1154 .local_get(idx)
1155 .i32_const(MAX_ITEMS_PER_QUERY)
1156 .binop(BinaryOp::I32Add)
1157 .local_set(next_idx);
1158 },
1159 |else_| {
1160 else_.i32_const(0)
1161 .local_set(next_idx);
1162 },
1163 )
1164 .local_get(len)
1165 .call(leb)
1166 .i32_const(0)
1167 .i32_const(5)
1168 .call(reply_data)
1169 .global_get(vars.heap_base)
1171 .i32_const(METADATA_SIZE)
1172 .binop(BinaryOp::I32Add)
1173 .local_get(idx)
1174 .i32_const(LOG_ITEM_SIZE)
1175 .binop(BinaryOp::I32Mul)
1176 .binop(BinaryOp::I32Add)
1177 .local_set(log_addr)
1178 .local_get(log_addr)
1180 .local_get(len)
1181 .i32_const(LOG_ITEM_SIZE)
1182 .binop(BinaryOp::I32Mul)
1183 .call(reply_data)
1184 .local_get(next_idx)
1186 .unop(UnaryOp::I32Eqz)
1187 .if_else(
1188 None,
1189 |then| {
1190 then.i32_const(0)
1191 .i32_const(0)
1192 .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4})
1193 .i32_const(0)
1194 .i32_const(1)
1195 .call(reply_data);
1196 },
1197 |else_| {
1198 else_.i32_const(0)
1199 .i32_const(1)
1200 .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 1})
1201 .i32_const(1)
1202 .local_get(next_idx)
1203 .store(memory, StoreKind::I32 { atomic: false }, MemArg { offset: 0, align: 4})
1204 .i32_const(0)
1205 .i32_const(5)
1206 .call(reply_data);
1207 },
1208 )
1209 .call(reply);
1210 let getter = builder.finish(vec![], &mut m.funcs);
1211 m.exports.add("canister_query __get_profiling", getter);
1212}
1213
1214fn stub_wasi_imports(m: &mut Module) {
1220 use walrus::FunctionBuilder;
1221
1222 let wasi_imports: Vec<_> = m
1224 .imports
1225 .iter()
1226 .filter(|i| i.module == "wasi_snapshot_preview1")
1227 .filter_map(|i| {
1228 if let ImportKind::Function(func_id) = i.kind {
1229 Some((i.id(), i.name.clone(), func_id))
1230 } else {
1231 None
1232 }
1233 })
1234 .collect();
1235
1236 let memory = m.memories.iter().next().map(|mem| mem.id());
1237
1238 for (import_id, name, old_func_id) in wasi_imports {
1239 let func = m.funcs.get(old_func_id);
1241 let ty_id = func.ty();
1242 let ty = m.types.get(ty_id);
1243 let params: Vec<_> = ty.params().to_vec();
1244 let results: Vec<_> = ty.results().to_vec();
1245
1246 let mut builder = FunctionBuilder::new(&mut m.types, ¶ms, &results);
1248 builder.name(format!("__wasi_{name}_stub"));
1249
1250 let param_locals: Vec<_> = params.iter().map(|t| m.locals.add(*t)).collect();
1252
1253 match name.as_str() {
1254 "fd_write" => {
1255 if let Some(mem) = memory {
1258 if param_locals.len() >= 4 {
1259 builder
1260 .func_body()
1261 .local_get(param_locals[3]) .i32_const(0)
1263 .store(
1264 mem,
1265 StoreKind::I32 { atomic: false },
1266 MemArg {
1267 offset: 0,
1268 align: 4,
1269 },
1270 )
1271 .i32_const(0);
1272 } else {
1273 builder.func_body().i32_const(0);
1274 }
1275 } else {
1276 builder.func_body().i32_const(0);
1277 }
1278 }
1279 "fd_read" => {
1280 if let Some(mem) = memory {
1283 if param_locals.len() >= 4 {
1284 builder
1285 .func_body()
1286 .local_get(param_locals[3]) .i32_const(0)
1288 .store(
1289 mem,
1290 StoreKind::I32 { atomic: false },
1291 MemArg {
1292 offset: 0,
1293 align: 4,
1294 },
1295 )
1296 .i32_const(0);
1297 } else {
1298 builder.func_body().i32_const(0);
1299 }
1300 } else {
1301 builder.func_body().i32_const(0);
1302 }
1303 }
1304 "fd_seek" => {
1305 if let Some(mem) = memory {
1308 if param_locals.len() >= 4 {
1309 builder
1310 .func_body()
1311 .local_get(param_locals[3]) .i64_const(0)
1313 .store(
1314 mem,
1315 StoreKind::I64 { atomic: false },
1316 MemArg {
1317 offset: 0,
1318 align: 8,
1319 },
1320 )
1321 .i32_const(0);
1322 } else {
1323 builder.func_body().i32_const(0);
1324 }
1325 } else {
1326 builder.func_body().i32_const(0);
1327 }
1328 }
1329 "fd_close" => {
1330 builder.func_body().i32_const(0);
1333 }
1334 "environ_sizes_get" => {
1335 if let Some(mem) = memory {
1338 if param_locals.len() >= 2 {
1339 builder
1340 .func_body()
1341 .local_get(param_locals[0]) .i32_const(0)
1343 .store(
1344 mem,
1345 StoreKind::I32 { atomic: false },
1346 MemArg {
1347 offset: 0,
1348 align: 4,
1349 },
1350 )
1351 .local_get(param_locals[1]) .i32_const(0)
1353 .store(
1354 mem,
1355 StoreKind::I32 { atomic: false },
1356 MemArg {
1357 offset: 0,
1358 align: 4,
1359 },
1360 )
1361 .i32_const(0);
1362 } else {
1363 builder.func_body().i32_const(0);
1364 }
1365 } else {
1366 builder.func_body().i32_const(0);
1367 }
1368 }
1369 "environ_get" => {
1370 builder.func_body().i32_const(0);
1373 }
1374 "proc_exit" => {
1375 builder.func_body().unreachable();
1378 }
1379 _ => {
1380 for result in &results {
1382 match result {
1383 ValType::I32 => {
1384 builder.func_body().i32_const(0);
1385 }
1386 ValType::I64 => {
1387 builder.func_body().i64_const(0);
1388 }
1389 ValType::F32 => {
1390 builder.func_body().f32_const(0.0);
1391 }
1392 ValType::F64 => {
1393 builder.func_body().f64_const(0.0);
1394 }
1395 _ => {}
1396 }
1397 }
1398 }
1399 }
1400
1401 let stub_func_id = builder.finish(param_locals, &mut m.funcs);
1402
1403 for (_, func) in m.funcs.iter_local_mut() {
1405 replace_calls_in_func(func, old_func_id, stub_func_id);
1406 }
1407
1408 m.imports.delete(import_id);
1410 }
1411}
1412
1413fn replace_calls_in_func(func: &mut LocalFunction, old_id: FunctionId, new_id: FunctionId) {
1414 let mut stack = vec![func.entry_block()];
1415 while let Some(seq_id) = stack.pop() {
1416 let mut builder = func.builder_mut().instr_seq(seq_id);
1417 for (instr, _) in builder.instrs_mut().iter_mut() {
1418 match instr {
1419 Instr::Call(Call { func }) if *func == old_id => {
1420 *func = new_id;
1421 }
1422 Instr::Block(Block { seq }) | Instr::Loop(Loop { seq }) => {
1423 stack.push(*seq);
1424 }
1425 Instr::IfElse(IfElse {
1426 consequent,
1427 alternative,
1428 }) => {
1429 stack.push(*consequent);
1430 stack.push(*alternative);
1431 }
1432 _ => {}
1433 }
1434 }
1435 }
1436}