Skip to content

Commit 787fc6f

Browse files
keithwalexcrichton
andauthored
wasm-tools print: add option to print operand stack between instructions (#2251)
* wasm-tools print: add --print-operand-stack option This prints the operand stack types within function bodies, flagging newly pushed operands when color output is enabled. E.g.: ```wasm (module (type (;0;) (func)) (func (;0;) (type 0) i32.const 4 ;; [i32] i32.const 5 ;; [i32 i32] i32.add ;; [i32] drop ;; [] ) ) ``` * Remove unnecessary stack of validators * Refactor the implementation of printing the operand stack Minimize the `#[cfg]` in the "main code" by avoiding it entirely and instead switching between two modules at compile time. Preserve the property of not needing the validator when this feature is disabled, however. * Add an operand stack test for components * Add a test with an invalid operand stack --------- Co-authored-by: Alex Crichton <[email protected]>
1 parent 782efe9 commit 787fc6f

15 files changed

+788
-44
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ bitflags = { workspace = true, optional = true }
175175
rayon = { workspace = true, optional = true }
176176

177177
# Dependencies of `print`
178-
wasmprinter = { workspace = true, features = ['component-model'] }
178+
wasmprinter = { workspace = true, features = ['component-model', 'validate'] }
179179

180180
# Dependencies of `smith`
181181
arbitrary = { workspace = true, optional = true }

crates/wasmprinter/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ wasmparser = { workspace = true, features = ['std', 'simd'] }
2525
wat = { path = "../wat" }
2626

2727
[features]
28-
default = ['component-model']
28+
default = ['component-model', 'validate']
2929
component-model = ['wasmparser/component-model']
30+
validate = ['wasmparser/validate', 'wasmparser/features']

crates/wasmprinter/src/lib.rs

Lines changed: 108 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//! and debugging and such.
88
99
#![deny(missing_docs)]
10+
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
1011

1112
use anyhow::{Context, Result, anyhow, bail};
1213
use operator::{OpPrinter, OperatorSeparator, OperatorState, PrintOperator, PrintOperatorFolded};
@@ -25,6 +26,12 @@ const MAX_WASM_FUNCTION_SIZE: u32 = 128 * 1024;
2526

2627
#[cfg(feature = "component-model")]
2728
mod component;
29+
#[cfg(feature = "validate")]
30+
mod operand_stack;
31+
#[cfg(not(feature = "validate"))]
32+
mod operand_stack_disabled;
33+
#[cfg(not(feature = "validate"))]
34+
use operand_stack_disabled as operand_stack;
2835
mod operator;
2936
mod print;
3037

@@ -57,6 +64,7 @@ pub struct Config {
5764
name_unnamed: bool,
5865
fold_instructions: bool,
5966
indent_text: String,
67+
print_operand_stack: bool,
6068
}
6169

6270
impl Default for Config {
@@ -67,6 +75,7 @@ impl Default for Config {
6775
name_unnamed: false,
6876
fold_instructions: false,
6977
indent_text: " ".to_string(),
78+
print_operand_stack: false,
7079
}
7180
}
7281
}
@@ -246,6 +255,30 @@ impl Config {
246255
self
247256
}
248257

258+
/// Print the operand stack types within function bodies,
259+
/// flagging newly pushed operands when color output is enabled. E.g.:
260+
///
261+
/// ```wasm
262+
/// (module
263+
/// (type (;0;) (func))
264+
/// (func (;0;) (type 0)
265+
/// i32.const 4
266+
/// ;; [i32]
267+
/// i32.const 5
268+
/// ;; [i32 i32]
269+
/// i32.add
270+
/// ;; [i32]
271+
/// drop
272+
/// ;; []
273+
/// )
274+
/// )
275+
/// ```
276+
#[cfg(feature = "validate")]
277+
pub fn print_operand_stack(&mut self, enable: bool) -> &mut Self {
278+
self.print_operand_stack = enable;
279+
self
280+
}
281+
249282
/// Select the string to use when indenting.
250283
///
251284
/// The indent allowed here are arbitrary and unchecked. You should enter
@@ -415,6 +448,12 @@ impl Printer<'_, '_> {
415448
#[cfg(feature = "component-model")]
416449
let mut parsers = Vec::new();
417450

451+
let mut validator = if self.config.print_operand_stack {
452+
operand_stack::Validator::new()
453+
} else {
454+
None
455+
};
456+
418457
loop {
419458
let payload = match parser.parse(bytes, true)? {
420459
Chunk::NeedMoreData(_) => unreachable!(),
@@ -423,6 +462,15 @@ impl Printer<'_, '_> {
423462
payload
424463
}
425464
};
465+
if let Some(validator) = &mut validator {
466+
match validator.payload(&payload) {
467+
Ok(()) => {}
468+
Err(e) => {
469+
self.newline_unknown_pos()?;
470+
write!(self.result, ";; module or component is invalid: {e}")?;
471+
}
472+
}
473+
}
426474
match payload {
427475
Payload::Version { encoding, .. } => {
428476
if let Some(e) = expected {
@@ -605,7 +653,11 @@ impl Printer<'_, '_> {
605653
Self::ensure_module(&states)?;
606654
}
607655
Payload::CodeSectionEntry(body) => {
608-
self.print_code_section_entry(states.last_mut().unwrap(), &body)?;
656+
self.print_code_section_entry(
657+
states.last_mut().unwrap(),
658+
&body,
659+
validator.as_mut().and_then(|v| v.next_func()),
660+
)?;
609661
self.update_custom_section_place(&mut states, "after code");
610662
}
611663
Payload::DataCountSection { .. } => {
@@ -699,6 +751,7 @@ impl Printer<'_, '_> {
699751
}
700752
}
701753
parser = parsers.pop().unwrap();
754+
702755
continue;
703756
}
704757
}
@@ -1328,6 +1381,7 @@ impl Printer<'_, '_> {
13281381
&mut self,
13291382
state: &mut State,
13301383
body: &FunctionBody<'_>,
1384+
validator: Option<operand_stack::FuncValidator>,
13311385
) -> Result<()> {
13321386
self.newline(body.get_binary_reader().original_position())?;
13331387
self.start_group("func ")?;
@@ -1355,7 +1409,7 @@ impl Printer<'_, '_> {
13551409
if self.config.print_skeleton {
13561410
self.result.write_str(" ...")?;
13571411
} else {
1358-
self.print_func_body(state, func_idx, params, &body, &hints)?;
1412+
self.print_func_body(state, func_idx, params, &body, &hints, validator)?;
13591413
}
13601414

13611415
self.end_group()?;
@@ -1370,6 +1424,7 @@ impl Printer<'_, '_> {
13701424
params: u32,
13711425
body: &FunctionBody<'_>,
13721426
branch_hints: &[(usize, BranchHint)],
1427+
mut validator: Option<operand_stack::FuncValidator>,
13731428
) -> Result<()> {
13741429
let mut first = true;
13751430
let mut local_idx = 0;
@@ -1400,6 +1455,14 @@ impl Printer<'_, '_> {
14001455
}
14011456
locals.finish(self)?;
14021457

1458+
if let Some(f) = &mut validator {
1459+
if let Err(e) = f.read_locals(body.get_binary_reader()) {
1460+
validator = None;
1461+
self.newline_unknown_pos()?;
1462+
write!(self.result, ";; locals are invalid: {e}")?;
1463+
}
1464+
}
1465+
14031466
let nesting_start = self.nesting;
14041467
let fold_instructions = self.config.fold_instructions;
14051468
let mut operator_state = OperatorState::new(self, OperatorSeparator::Newline);
@@ -1408,11 +1471,22 @@ impl Printer<'_, '_> {
14081471
let mut folded_printer = PrintOperatorFolded::new(self, state, &mut operator_state);
14091472
folded_printer.set_offset(func_start);
14101473
folded_printer.begin_function(func_idx)?;
1411-
Self::print_operators(&mut reader, branch_hints, func_start, &mut folded_printer)?;
1412-
folded_printer.finalize()?;
1474+
Self::print_operators(
1475+
&mut reader,
1476+
branch_hints,
1477+
func_start,
1478+
&mut folded_printer,
1479+
validator,
1480+
)?;
14131481
} else {
14141482
let mut flat_printer = PrintOperator::new(self, state, &mut operator_state);
1415-
Self::print_operators(&mut reader, branch_hints, func_start, &mut flat_printer)?;
1483+
Self::print_operators(
1484+
&mut reader,
1485+
branch_hints,
1486+
func_start,
1487+
&mut flat_printer,
1488+
validator,
1489+
)?;
14161490
}
14171491

14181492
// If this was an invalid function body then the nesting may not
@@ -1433,12 +1507,24 @@ impl Printer<'_, '_> {
14331507
mut branch_hints: &[(usize, BranchHint)],
14341508
func_start: usize,
14351509
op_printer: &mut O,
1510+
mut validator: Option<operand_stack::FuncValidator>,
14361511
) -> Result<()> {
14371512
let mut ops = OperatorsReader::new(body.clone());
14381513
while !ops.eof() {
14391514
if ops.is_end_then_eof() {
1515+
let mut annotation = None;
1516+
if let Some(f) = &mut validator {
1517+
match f.visit_operator(&ops, true) {
1518+
Ok(()) => {}
1519+
Err(_) => {
1520+
annotation = Some(String::from("type mismatch at end of expression"))
1521+
}
1522+
}
1523+
}
1524+
14401525
ops.read()?; // final "end" opcode terminates instruction sequence
14411526
ops.finish()?;
1527+
op_printer.finalize(annotation.as_deref())?;
14421528
return Ok(());
14431529
}
14441530

@@ -1450,9 +1536,22 @@ impl Printer<'_, '_> {
14501536
op_printer.branch_hint(*hint_offset, hint.taken)?;
14511537
}
14521538
}
1453-
1539+
let mut annotation = None;
1540+
if let Some(f) = &mut validator {
1541+
let result = f
1542+
.visit_operator(&ops, false)
1543+
.map_err(anyhow::Error::from)
1544+
.and_then(|()| f.visualize_operand_stack(op_printer.use_color()));
1545+
match result {
1546+
Ok(s) => annotation = Some(s),
1547+
Err(_) => {
1548+
validator = None;
1549+
annotation = Some(String::from("(invalid)"));
1550+
}
1551+
}
1552+
}
14541553
op_printer.set_offset(ops.original_position());
1455-
op_printer.visit_operator(&mut ops)?;
1554+
op_printer.visit_operator(&mut ops, annotation.as_deref())?;
14561555
}
14571556
ops.finish()?; // for the error message
14581557
bail!("unexpected end of operators");
@@ -1726,12 +1825,10 @@ impl Printer<'_, '_> {
17261825
if fold {
17271826
let mut folded_printer = PrintOperatorFolded::new(self, state, &mut operator_state);
17281827
folded_printer.begin_const_expr();
1729-
Self::print_operators(&mut reader, &[], 0, &mut folded_printer)?;
1730-
folded_printer.finalize()?;
1828+
Self::print_operators(&mut reader, &[], 0, &mut folded_printer, None)?;
17311829
} else {
17321830
let mut op_printer = PrintOperator::new(self, state, &mut operator_state);
1733-
op_printer.suppress_label_comments();
1734-
Self::print_operators(&mut reader, &[], 0, &mut op_printer)?;
1831+
Self::print_operators(&mut reader, &[], 0, &mut op_printer, None)?;
17351832
}
17361833

17371834
Ok(())
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use crate::{Config, PrintTermcolor, Printer};
2+
use wasmparser::{
3+
BinaryReader, Frame, OperatorsReader, Payload, Result, ValType, ValidPayload,
4+
ValidatorResources, WasmFeatures,
5+
};
6+
7+
pub struct Validator {
8+
validator: wasmparser::Validator,
9+
last_function: Option<FuncValidator>,
10+
}
11+
12+
impl Validator {
13+
pub fn new() -> Option<Validator> {
14+
Some(Validator {
15+
validator: wasmparser::Validator::new_with_features(WasmFeatures::all()),
16+
last_function: None,
17+
})
18+
}
19+
20+
pub fn payload(&mut self, payload: &Payload<'_>) -> Result<()> {
21+
match self.validator.payload(payload)? {
22+
ValidPayload::Ok | ValidPayload::End(_) | ValidPayload::Parser(_) => Ok(()),
23+
ValidPayload::Func(validator, _) => {
24+
assert!(self.last_function.is_none());
25+
self.last_function = Some(FuncValidator {
26+
push_count: 0,
27+
validator: validator.into_validator(Default::default()),
28+
});
29+
Ok(())
30+
}
31+
}
32+
}
33+
34+
pub fn next_func(&mut self) -> Option<FuncValidator> {
35+
Some(self.last_function.take().unwrap())
36+
}
37+
}
38+
39+
pub struct FuncValidator {
40+
validator: wasmparser::FuncValidator<ValidatorResources>,
41+
push_count: u32,
42+
}
43+
44+
impl FuncValidator {
45+
pub fn read_locals(&mut self, mut reader: BinaryReader<'_>) -> Result<()> {
46+
self.validator.read_locals(&mut reader)
47+
}
48+
49+
pub fn visit_operator(&mut self, reader: &OperatorsReader<'_>, is_end: bool) -> Result<()> {
50+
let pos = reader.original_position();
51+
reader
52+
.clone()
53+
.visit_operator(&mut self.validator.visitor(pos))??;
54+
55+
if !is_end {
56+
let op = reader.clone().read()?;
57+
let arity = op.operator_arity(&self.validator.visitor(pos));
58+
if let Some((_pop_count, push_count)) = arity {
59+
self.push_count = push_count;
60+
}
61+
}
62+
Ok(())
63+
}
64+
65+
pub fn visualize_operand_stack(&self, use_color: bool) -> crate::Result<String> {
66+
let mut buf_color = PrintTermcolor(termcolor::Ansi::new(Vec::new()));
67+
let mut buf_nocolor = PrintTermcolor(termcolor::NoColor::new(Vec::new()));
68+
69+
let stack_printer = Printer {
70+
result: if use_color {
71+
&mut buf_color
72+
} else {
73+
&mut buf_nocolor
74+
},
75+
config: &Config::new(),
76+
nesting: 0,
77+
line: 0,
78+
group_lines: Vec::new(),
79+
code_section_hints: Vec::new(),
80+
};
81+
82+
if let Some(&Frame { height, .. }) = self.validator.get_control_frame(0) {
83+
stack_printer.result.start_comment()?;
84+
stack_printer.result.write_str("[")?;
85+
let max_height = self.validator.operand_stack_height() as usize;
86+
for depth in (height..max_height).rev() {
87+
if depth + 1 < max_height {
88+
stack_printer.result.write_str(" ")?;
89+
}
90+
if depth + 1 == height + self.push_count as usize {
91+
stack_printer.result.start_type()?;
92+
}
93+
match self.validator.get_operand_type(depth) {
94+
Some(Some(ty)) => {
95+
stack_printer.result.write_str(&ty_to_str(ty))?;
96+
}
97+
Some(None) => {
98+
stack_printer.result.write_str("(unknown)")?;
99+
}
100+
None => {
101+
stack_printer.result.write_str("(invalid)")?;
102+
}
103+
}
104+
}
105+
stack_printer.result.start_comment()?;
106+
stack_printer.result.write_str("]")?;
107+
stack_printer.result.reset_color()?;
108+
}
109+
110+
let ret = String::from_utf8(if use_color {
111+
buf_color.0.into_inner()
112+
} else {
113+
buf_nocolor.0.into_inner()
114+
})?;
115+
return Ok(ret);
116+
117+
fn ty_to_str(ty: ValType) -> String {
118+
match ty {
119+
ValType::I32 => String::from("i32"),
120+
ValType::I64 => String::from("i64"),
121+
ValType::F32 => String::from("f32"),
122+
ValType::F64 => String::from("f64"),
123+
ValType::V128 => String::from("v128"),
124+
ValType::Ref(r) => format!("{r}"),
125+
}
126+
}
127+
}
128+
}

0 commit comments

Comments
 (0)