#![deny(missing_docs)]
use anyhow::{anyhow, bail, Context, Result};
use operator::{OpPrinter, OperatorSeparator, OperatorState, PrintOperator, PrintOperatorFolded};
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::io;
use std::marker;
use std::mem;
use std::path::Path;
use wasmparser::*;
const MAX_LOCALS: u32 = 50000;
const MAX_NESTING_TO_PRINT: u32 = 50;
const MAX_WASM_FUNCTIONS: u32 = 1_000_000;
const MAX_WASM_FUNCTION_SIZE: u32 = 128 * 1024;
#[cfg(feature = "component-model")]
mod component;
mod operator;
mod print;
pub use self::print::*;
pub fn print_file(file: impl AsRef<Path>) -> Result<String> {
let file = file.as_ref();
let contents = std::fs::read(file).context(format!("failed to read `{}`", file.display()))?;
print_bytes(contents)
}
pub fn print_bytes(wasm: impl AsRef<[u8]>) -> Result<String> {
let mut dst = String::new();
Config::new().print(wasm.as_ref(), &mut PrintFmtWrite(&mut dst))?;
Ok(dst)
}
#[derive(Debug, Default)]
pub struct Config {
print_offsets: bool,
print_skeleton: bool,
name_unnamed: bool,
fold_instructions: bool,
}
struct Printer<'cfg, 'env> {
config: &'cfg Config,
result: &'cfg mut (dyn Print + 'env),
nesting: u32,
line: usize,
group_lines: Vec<usize>,
code_section_hints: Vec<(u32, Vec<(usize, BranchHint)>)>,
}
#[derive(Default)]
struct CoreState {
types: Vec<Option<SubType>>,
funcs: u32,
func_to_type: Vec<Option<u32>>,
memories: u32,
tags: u32,
tag_to_type: Vec<Option<u32>>,
globals: u32,
tables: u32,
#[cfg(feature = "component-model")]
modules: u32,
#[cfg(feature = "component-model")]
instances: u32,
func_names: NamingMap<u32, NameFunc>,
local_names: NamingMap<(u32, u32), NameLocal>,
label_names: NamingMap<(u32, u32), NameLabel>,
type_names: NamingMap<u32, NameType>,
field_names: NamingMap<(u32, u32), NameField>,
tag_names: NamingMap<u32, NameTag>,
table_names: NamingMap<u32, NameTable>,
memory_names: NamingMap<u32, NameMemory>,
global_names: NamingMap<u32, NameGlobal>,
element_names: NamingMap<u32, NameElem>,
data_names: NamingMap<u32, NameData>,
#[cfg(feature = "component-model")]
module_names: NamingMap<u32, NameModule>,
#[cfg(feature = "component-model")]
instance_names: NamingMap<u32, NameInstance>,
}
struct NamingMap<T, K> {
index_to_name: HashMap<T, Naming>,
_marker: marker::PhantomData<K>,
}
impl<T, K> Default for NamingMap<T, K> {
fn default() -> NamingMap<T, K> {
NamingMap {
index_to_name: HashMap::new(),
_marker: marker::PhantomData,
}
}
}
#[derive(Default)]
#[cfg(feature = "component-model")]
struct ComponentState {
types: u32,
funcs: u32,
instances: u32,
components: u32,
values: u32,
type_names: NamingMap<u32, NameType>,
func_names: NamingMap<u32, NameFunc>,
component_names: NamingMap<u32, NameComponent>,
instance_names: NamingMap<u32, NameInstance>,
value_names: NamingMap<u32, NameValue>,
}
struct State {
encoding: Encoding,
name: Option<Naming>,
core: CoreState,
#[cfg(feature = "component-model")]
component: ComponentState,
custom_section_place: Option<&'static str>,
}
impl State {
fn new(encoding: Encoding) -> Self {
Self {
encoding,
name: None,
core: CoreState::default(),
#[cfg(feature = "component-model")]
component: ComponentState::default(),
custom_section_place: None,
}
}
}
struct Naming {
name: String,
kind: NamingKind,
}
enum NamingKind {
DollarName,
DollarQuotedName,
SyntheticPrefix(String),
}
impl Config {
pub fn new() -> Self {
Self::default()
}
pub fn print_offsets(&mut self, print: bool) -> &mut Self {
self.print_offsets = print;
self
}
pub fn print_skeleton(&mut self, print: bool) -> &mut Self {
self.print_skeleton = print;
self
}
pub fn name_unnamed(&mut self, enable: bool) -> &mut Self {
self.name_unnamed = enable;
self
}
pub fn fold_instructions(&mut self, enable: bool) -> &mut Self {
self.fold_instructions = enable;
self
}
pub fn print(&self, wasm: &[u8], result: &mut impl Print) -> Result<()> {
Printer {
config: self,
result,
code_section_hints: Vec::new(),
group_lines: Vec::new(),
line: 0,
nesting: 0,
}
.print_contents(wasm)
}
pub fn offsets_and_lines<'a>(
&self,
wasm: &[u8],
storage: &'a mut String,
) -> Result<impl Iterator<Item = (Option<usize>, &'a str)> + 'a> {
struct TrackingPrint<'a> {
dst: &'a mut String,
lines: Vec<usize>,
line_offsets: Vec<Option<usize>>,
}
impl Print for TrackingPrint<'_> {
fn write_str(&mut self, s: &str) -> io::Result<()> {
self.dst.push_str(s);
Ok(())
}
fn start_line(&mut self, offset: Option<usize>) {
self.lines.push(self.dst.len());
self.line_offsets.push(offset);
}
}
let mut output = TrackingPrint {
dst: storage,
lines: Vec::new(),
line_offsets: Vec::new(),
};
self.print(wasm, &mut output)?;
let TrackingPrint {
dst,
lines,
line_offsets,
} = output;
let end = dst.len();
let dst = &dst[..];
let mut offsets = line_offsets.into_iter();
let mut lines = lines.into_iter().peekable();
Ok(std::iter::from_fn(move || {
let offset = offsets.next()?;
let i = lines.next()?;
let j = lines.peek().copied().unwrap_or(end);
let line = &dst[i..j];
Some((offset, line))
}))
}
}
impl Printer<'_, '_> {
fn read_names<'a>(
&mut self,
mut bytes: &'a [u8],
mut parser: Parser,
state: &mut State,
) -> Result<()> {
loop {
let payload = match parser.parse(bytes, true)? {
Chunk::NeedMoreData(_) => unreachable!(),
Chunk::Parsed { payload, consumed } => {
bytes = &bytes[consumed..];
payload
}
};
match payload {
Payload::CodeSectionStart { size, .. } => {
if size as usize > bytes.len() {
bail!("invalid code section size");
}
bytes = &bytes[size as usize..];
parser.skip_section();
}
#[cfg(feature = "component-model")]
Payload::ModuleSection {
unchecked_range: range,
..
}
| Payload::ComponentSection {
unchecked_range: range,
..
} => {
let offset = range.end - range.start;
if offset > bytes.len() {
bail!("invalid module or component section range");
}
bytes = &bytes[offset..];
}
Payload::CustomSection(c) => {
match c.as_known() {
KnownCustom::Name(reader) => {
drop(self.register_names(state, reader));
}
#[cfg(feature = "component-model")]
KnownCustom::ComponentName(reader) => {
drop(self.register_component_names(state, reader));
}
KnownCustom::BranchHints(reader) => {
drop(self.register_branch_hint_section(reader));
}
_ => {}
}
}
Payload::End(_) => break,
_ => {}
}
}
Ok(())
}
fn ensure_module(states: &[State]) -> Result<()> {
if !matches!(states.last().unwrap().encoding, Encoding::Module) {
bail!("a module section was encountered when parsing a component");
}
Ok(())
}
#[cfg(feature = "component-model")]
fn ensure_component(states: &[State]) -> Result<()> {
if !matches!(states.last().unwrap().encoding, Encoding::Component) {
bail!("a component section was encountered when parsing a module");
}
Ok(())
}
fn print_contents(&mut self, mut bytes: &[u8]) -> Result<()> {
self.result.start_line(Some(0));
let mut expected = None;
let mut states: Vec<State> = Vec::new();
let mut parser = Parser::new(0);
#[cfg(feature = "component-model")]
let mut parsers = Vec::new();
loop {
let payload = match parser.parse(bytes, true)? {
Chunk::NeedMoreData(_) => unreachable!(),
Chunk::Parsed { payload, consumed } => {
bytes = &bytes[consumed..];
payload
}
};
match payload {
Payload::Version { encoding, .. } => {
if let Some(e) = expected {
if encoding != e {
bail!("incorrect encoding for nested module or component");
}
expected = None;
}
assert!(states.last().map(|s| s.encoding) != Some(Encoding::Module));
match encoding {
Encoding::Module => {
states.push(State::new(Encoding::Module));
states.last_mut().unwrap().custom_section_place = Some("before first");
if states.len() > 1 {
self.start_group("core module")?;
} else {
self.start_group("module")?;
}
#[cfg(feature = "component-model")]
if states.len() > 1 {
let parent = &states[states.len() - 2];
self.result.write_str(" ")?;
self.print_name(&parent.core.module_names, parent.core.modules)?;
}
}
Encoding::Component => {
#[cfg(feature = "component-model")]
{
states.push(State::new(Encoding::Component));
self.start_group("component")?;
if states.len() > 1 {
let parent = &states[states.len() - 2];
self.result.write_str(" ")?;
self.print_name(
&parent.component.component_names,
parent.component.components,
)?;
}
}
#[cfg(not(feature = "component-model"))]
{
bail!(
"support for printing components disabled \
at compile-time"
);
}
}
}
let len = states.len();
let state = states.last_mut().unwrap();
self.read_names(bytes, parser.clone(), state)?;
if len == 1 {
if let Some(name) = state.name.as_ref() {
self.result.write_str(" ")?;
name.write(self)?;
}
}
}
Payload::CustomSection(c) => {
let printed =
self.result
.print_custom_section(c.name(), c.data_offset(), c.data())?;
if printed {
continue;
}
let state = states.last().unwrap();
let start = self.nesting;
let err = match self.print_custom_section(state, c.clone()) {
Ok(()) => continue,
Err(e) if !e.is::<BinaryReaderError>() => return Err(e),
Err(e) => e,
};
while self.nesting > start {
self.end_group()?;
}
let msg = format!("failed to parse custom section `{}`: {err}", c.name());
for line in msg.lines() {
self.newline(c.data_offset())?;
write!(self.result, ";; {line}")?;
}
self.newline(c.range().end)?;
}
Payload::TypeSection(s) => {
self.update_custom_section_place(&mut states, "after type");
self.print_types(states.last_mut().unwrap(), s)?;
}
Payload::ImportSection(s) => {
self.update_custom_section_place(&mut states, "after import");
Self::ensure_module(&states)?;
self.print_imports(states.last_mut().unwrap(), s)?
}
Payload::FunctionSection(reader) => {
self.update_custom_section_place(&mut states, "after func");
Self::ensure_module(&states)?;
if reader.count() > MAX_WASM_FUNCTIONS {
bail!(
"module contains {} functions which exceeds the limit of {}",
reader.count(),
MAX_WASM_FUNCTIONS
);
}
for ty in reader {
states.last_mut().unwrap().core.func_to_type.push(Some(ty?))
}
}
Payload::TableSection(s) => {
self.update_custom_section_place(&mut states, "after table");
Self::ensure_module(&states)?;
self.print_tables(states.last_mut().unwrap(), s)?
}
Payload::MemorySection(s) => {
self.update_custom_section_place(&mut states, "after memory");
Self::ensure_module(&states)?;
self.print_memories(states.last_mut().unwrap(), s)?
}
Payload::TagSection(s) => {
self.update_custom_section_place(&mut states, "after tag");
Self::ensure_module(&states)?;
self.print_tags(states.last_mut().unwrap(), s)?
}
Payload::GlobalSection(s) => {
self.update_custom_section_place(&mut states, "after global");
Self::ensure_module(&states)?;
self.print_globals(states.last_mut().unwrap(), s)?
}
Payload::ExportSection(s) => {
self.update_custom_section_place(&mut states, "after export");
Self::ensure_module(&states)?;
self.print_exports(states.last().unwrap(), s)?
}
Payload::StartSection { func, range } => {
self.update_custom_section_place(&mut states, "after start");
Self::ensure_module(&states)?;
self.newline(range.start)?;
self.start_group("start ")?;
self.print_idx(&states.last().unwrap().core.func_names, func)?;
self.end_group()?;
}
Payload::ElementSection(s) => {
self.update_custom_section_place(&mut states, "after element");
Self::ensure_module(&states)?;
self.print_elems(states.last_mut().unwrap(), s)?;
}
Payload::CodeSectionStart { .. } => {
self.update_custom_section_place(&mut states, "after code");
Self::ensure_module(&states)?;
}
Payload::CodeSectionEntry(body) => {
self.print_code_section_entry(states.last_mut().unwrap(), &body)?
}
Payload::DataCountSection { .. } => {
Self::ensure_module(&states)?;
}
Payload::DataSection(s) => {
self.update_custom_section_place(&mut states, "after data");
Self::ensure_module(&states)?;
self.print_data(states.last_mut().unwrap(), s)?;
}
#[cfg(feature = "component-model")]
Payload::ModuleSection {
parser: inner,
unchecked_range: range,
} => {
Self::ensure_component(&states)?;
expected = Some(Encoding::Module);
parsers.push(parser);
parser = inner;
self.newline(range.start)?;
}
#[cfg(feature = "component-model")]
Payload::InstanceSection(s) => {
Self::ensure_component(&states)?;
self.print_instances(states.last_mut().unwrap(), s)?;
}
#[cfg(feature = "component-model")]
Payload::CoreTypeSection(s) => self.print_core_types(&mut states, s)?,
#[cfg(feature = "component-model")]
Payload::ComponentSection {
parser: inner,
unchecked_range: range,
} => {
Self::ensure_component(&states)?;
expected = Some(Encoding::Component);
parsers.push(parser);
parser = inner;
self.newline(range.start)?;
}
#[cfg(feature = "component-model")]
Payload::ComponentInstanceSection(s) => {
Self::ensure_component(&states)?;
self.print_component_instances(states.last_mut().unwrap(), s)?;
}
#[cfg(feature = "component-model")]
Payload::ComponentAliasSection(s) => {
Self::ensure_component(&states)?;
self.print_component_aliases(&mut states, s)?;
}
#[cfg(feature = "component-model")]
Payload::ComponentTypeSection(s) => {
Self::ensure_component(&states)?;
self.print_component_types(&mut states, s)?;
}
#[cfg(feature = "component-model")]
Payload::ComponentCanonicalSection(s) => {
Self::ensure_component(&states)?;
self.print_canonical_functions(states.last_mut().unwrap(), s)?;
}
#[cfg(feature = "component-model")]
Payload::ComponentStartSection { start, range } => {
Self::ensure_component(&states)?;
self.print_component_start(states.last_mut().unwrap(), range.start, start)?;
}
#[cfg(feature = "component-model")]
Payload::ComponentImportSection(s) => {
Self::ensure_component(&states)?;
self.print_component_imports(states.last_mut().unwrap(), s)?;
}
#[cfg(feature = "component-model")]
Payload::ComponentExportSection(s) => {
Self::ensure_component(&states)?;
self.print_component_exports(states.last_mut().unwrap(), s)?;
}
Payload::End(offset) => {
self.end_group()?; #[cfg(feature = "component-model")]
{
let state = states.pop().unwrap();
if let Some(parent) = states.last_mut() {
match state.encoding {
Encoding::Module => {
parent.core.modules += 1;
}
Encoding::Component => {
parent.component.components += 1;
}
}
parser = parsers.pop().unwrap();
continue;
}
}
self.newline(offset)?;
if self.config.print_offsets {
self.result.newline()?;
}
break;
}
other => match other.as_section() {
Some((id, _)) => bail!("found unknown section `{}`", id),
None => bail!("found unknown payload"),
},
}
}
Ok(())
}
fn update_custom_section_place(&self, states: &mut Vec<State>, place: &'static str) {
if let Some(last) = states.last_mut() {
if let Some(prev) = &mut last.custom_section_place {
*prev = place;
}
}
}
fn start_group(&mut self, name: &str) -> Result<()> {
write!(self.result, "(")?;
self.result.start_keyword()?;
write!(self.result, "{name}")?;
self.result.reset_color()?;
self.nesting += 1;
self.group_lines.push(self.line);
Ok(())
}
fn end_group(&mut self) -> Result<()> {
self.nesting -= 1;
if let Some(line) = self.group_lines.pop() {
if line != self.line {
self.newline_unknown_pos()?;
}
}
self.result.write_str(")")?;
Ok(())
}
fn register_names(&mut self, state: &mut State, names: NameSectionReader<'_>) -> Result<()> {
fn indirect_name_map<K>(
into: &mut NamingMap<(u32, u32), K>,
names: IndirectNameMap<'_>,
name: &str,
) -> Result<()> {
for indirect in names {
let indirect = indirect?;
let mut used = match name {
"label" => None,
"local" | "field" => Some(HashSet::new()),
_ => unimplemented!("{name} is an unknown type of indirect names"),
};
for naming in indirect.names {
let naming = naming?;
into.index_to_name.insert(
(indirect.index, naming.index),
Naming::new(naming.name, naming.index, name, used.as_mut()),
);
}
}
Ok(())
}
for section in names {
match section? {
Name::Module { name, .. } => {
let name = Naming::new(name, 0, "module", None);
state.name = Some(name);
}
Name::Function(n) => name_map(&mut state.core.func_names, n, "func")?,
Name::Local(n) => indirect_name_map(&mut state.core.local_names, n, "local")?,
Name::Label(n) => indirect_name_map(&mut state.core.label_names, n, "label")?,
Name::Type(n) => name_map(&mut state.core.type_names, n, "type")?,
Name::Table(n) => name_map(&mut state.core.table_names, n, "table")?,
Name::Memory(n) => name_map(&mut state.core.memory_names, n, "memory")?,
Name::Global(n) => name_map(&mut state.core.global_names, n, "global")?,
Name::Element(n) => name_map(&mut state.core.element_names, n, "elem")?,
Name::Data(n) => name_map(&mut state.core.data_names, n, "data")?,
Name::Field(n) => indirect_name_map(&mut state.core.field_names, n, "field")?,
Name::Tag(n) => name_map(&mut state.core.tag_names, n, "tag")?,
Name::Unknown { .. } => (),
}
}
Ok(())
}
fn print_rec(
&mut self,
state: &mut State,
offset: Option<usize>,
rec: RecGroup,
is_component: bool,
) -> Result<()> {
if rec.is_explicit_rec_group() {
if is_component {
self.start_group("core rec")?;
} else {
self.start_group("rec")?;
}
for ty in rec.into_types() {
match offset {
Some(offset) => self.newline(offset + 2)?,
None => self.newline_unknown_pos()?,
}
self.print_type(state, ty, false)?;
}
self.end_group()?; } else {
assert_eq!(rec.types().len(), 1);
let ty = rec.into_types().next().unwrap();
self.print_type(state, ty, is_component)?;
}
Ok(())
}
fn print_type(&mut self, state: &mut State, ty: SubType, is_component: bool) -> Result<()> {
if is_component {
self.start_group("core type ")?;
} else {
self.start_group("type ")?;
}
let ty_idx = state.core.types.len() as u32;
self.print_name(&state.core.type_names, ty_idx)?;
self.result.write_str(" ")?;
self.print_sub(state, &ty, ty_idx)?;
self.end_group()?; state.core.types.push(Some(ty));
Ok(())
}
fn print_sub(&mut self, state: &State, ty: &SubType, ty_idx: u32) -> Result<u32> {
let r = if !ty.is_final || !ty.supertype_idx.is_none() {
self.start_group("sub")?;
self.print_sub_type(state, ty)?;
let r = self.print_composite(state, &ty.composite_type, ty_idx)?;
self.end_group()?; r
} else {
self.print_composite(state, &ty.composite_type, ty_idx)?
};
Ok(r)
}
fn print_composite(&mut self, state: &State, ty: &CompositeType, ty_idx: u32) -> Result<u32> {
if ty.shared {
self.start_group("shared")?;
self.result.write_str(" ")?;
}
let r = match &ty.inner {
CompositeInnerType::Func(ty) => {
self.start_group("func")?;
let r = self.print_func_type(state, ty, None)?;
self.end_group()?; r
}
CompositeInnerType::Array(ty) => {
self.start_group("array")?;
let r = self.print_array_type(state, ty)?;
self.end_group()?; r
}
CompositeInnerType::Struct(ty) => {
self.start_group("struct")?;
let r = self.print_struct_type(state, ty, ty_idx)?;
self.end_group()?; r
}
CompositeInnerType::Cont(ty) => {
self.start_group("cont")?;
let r = self.print_cont_type(state, ty)?;
self.end_group()?; r
}
};
if ty.shared {
self.end_group()?; }
Ok(r)
}
fn print_types(&mut self, state: &mut State, parser: TypeSectionReader<'_>) -> Result<()> {
for ty in parser.into_iter_with_offsets() {
let (offset, rec_group) = ty?;
self.newline(offset)?;
self.print_rec(state, Some(offset), rec_group, false)?;
}
Ok(())
}
fn print_core_functype_idx(
&mut self,
state: &State,
idx: u32,
names_for: Option<u32>,
) -> Result<Option<u32>> {
self.print_core_type_ref(state, idx)?;
match state.core.types.get(idx as usize) {
Some(Some(SubType {
composite_type:
CompositeType {
inner: CompositeInnerType::Func(ty),
shared: false,
},
..
})) => self.print_func_type(state, ty, names_for).map(Some),
Some(Some(_)) | Some(None) | None => Ok(None),
}
}
fn print_func_type(
&mut self,
state: &State,
ty: &FuncType,
names_for: Option<u32>,
) -> Result<u32> {
if !ty.params().is_empty() {
self.result.write_str(" ")?;
}
let mut params = NamedLocalPrinter::new("param");
for (i, param) in ty.params().iter().enumerate() {
params.start_local(names_for, i as u32, self, state)?;
self.print_valtype(state, *param)?;
params.end_local(self)?;
}
params.finish(self)?;
if !ty.results().is_empty() {
self.result.write_str(" ")?;
self.start_group("result")?;
for result in ty.results().iter() {
self.result.write_str(" ")?;
self.print_valtype(state, *result)?;
}
self.end_group()?;
}
Ok(ty.params().len() as u32)
}
fn print_field_type(
&mut self,
state: &State,
ty: &FieldType,
ty_field_idx: Option<(u32, u32)>,
) -> Result<u32> {
self.result.write_str(" ")?;
if let Some(idxs @ (_, field_idx)) = ty_field_idx {
match state.core.field_names.index_to_name.get(&idxs) {
Some(name) => {
name.write_identifier(self)?;
self.result.write_str(" ")?;
}
None if self.config.name_unnamed => write!(self.result, "$#field{field_idx} ")?,
None => {}
}
}
if ty.mutable {
self.result.write_str("(mut ")?;
}
self.print_storage_type(state, ty.element_type)?;
if ty.mutable {
self.result.write_str(")")?;
}
Ok(0)
}
fn print_array_type(&mut self, state: &State, ty: &ArrayType) -> Result<u32> {
self.print_field_type(state, &ty.0, None)
}
fn print_struct_type(&mut self, state: &State, ty: &StructType, ty_idx: u32) -> Result<u32> {
for (field_index, field) in ty.fields.iter().enumerate() {
self.result.write_str(" (field")?;
self.print_field_type(state, field, Some((ty_idx, field_index as u32)))?;
self.result.write_str(")")?;
}
Ok(0)
}
fn print_cont_type(&mut self, state: &State, ct: &ContType) -> Result<u32> {
self.result.write_str(" ")?;
self.print_idx(&state.core.type_names, ct.0.as_module_index().unwrap())?;
Ok(0)
}
fn print_sub_type(&mut self, state: &State, ty: &SubType) -> Result<u32> {
self.result.write_str(" ")?;
if ty.is_final {
self.result.write_str("final ")?;
}
if let Some(idx) = ty.supertype_idx {
self.print_idx(&state.core.type_names, idx.as_module_index().unwrap())?;
self.result.write_str(" ")?;
}
Ok(0)
}
fn print_storage_type(&mut self, state: &State, ty: StorageType) -> Result<()> {
match ty {
StorageType::I8 => self.result.write_str("i8")?,
StorageType::I16 => self.result.write_str("i16")?,
StorageType::Val(val_type) => self.print_valtype(state, val_type)?,
}
Ok(())
}
fn print_valtype(&mut self, state: &State, ty: ValType) -> Result<()> {
match ty {
ValType::I32 => self.print_type_keyword("i32")?,
ValType::I64 => self.print_type_keyword("i64")?,
ValType::F32 => self.print_type_keyword("f32")?,
ValType::F64 => self.print_type_keyword("f64")?,
ValType::V128 => self.print_type_keyword("v128")?,
ValType::Ref(rt) => self.print_reftype(state, rt)?,
}
Ok(())
}
fn print_reftype(&mut self, state: &State, ty: RefType) -> Result<()> {
if ty.is_nullable() {
match ty.as_non_null() {
RefType::FUNC => self.print_type_keyword("funcref")?,
RefType::EXTERN => self.print_type_keyword("externref")?,
RefType::I31 => self.print_type_keyword("i31ref")?,
RefType::ANY => self.print_type_keyword("anyref")?,
RefType::NONE => self.print_type_keyword("nullref")?,
RefType::NOEXTERN => self.print_type_keyword("nullexternref")?,
RefType::NOFUNC => self.print_type_keyword("nullfuncref")?,
RefType::EQ => self.print_type_keyword("eqref")?,
RefType::STRUCT => self.print_type_keyword("structref")?,
RefType::ARRAY => self.print_type_keyword("arrayref")?,
RefType::EXN => self.print_type_keyword("exnref")?,
RefType::NOEXN => self.print_type_keyword("nullexnref")?,
_ => {
self.start_group("ref")?;
self.result.write_str(" null ")?;
self.print_heaptype(state, ty.heap_type())?;
self.end_group()?;
}
}
} else {
self.start_group("ref ")?;
self.print_heaptype(state, ty.heap_type())?;
self.end_group()?;
}
Ok(())
}
fn print_heaptype(&mut self, state: &State, ty: HeapType) -> Result<()> {
match ty {
HeapType::Concrete(i) => {
self.print_idx(&state.core.type_names, i.as_module_index().unwrap())?;
}
HeapType::Abstract { shared, ty } => {
use AbstractHeapType::*;
if shared {
self.start_group("shared ")?;
}
match ty {
Func => self.print_type_keyword("func")?,
Extern => self.print_type_keyword("extern")?,
Any => self.print_type_keyword("any")?,
None => self.print_type_keyword("none")?,
NoExtern => self.print_type_keyword("noextern")?,
NoFunc => self.print_type_keyword("nofunc")?,
Eq => self.print_type_keyword("eq")?,
Struct => self.print_type_keyword("struct")?,
Array => self.print_type_keyword("array")?,
I31 => self.print_type_keyword("i31")?,
Exn => self.print_type_keyword("exn")?,
NoExn => self.print_type_keyword("noexn")?,
Cont => self.print_type_keyword("cont")?,
NoCont => self.print_type_keyword("nocont")?,
}
if shared {
self.end_group()?;
}
}
}
Ok(())
}
fn print_type_keyword(&mut self, keyword: &str) -> Result<()> {
self.result.start_type()?;
self.result.write_str(keyword)?;
self.result.reset_color()?;
Ok(())
}
fn print_imports(&mut self, state: &mut State, parser: ImportSectionReader<'_>) -> Result<()> {
for import in parser.into_iter_with_offsets() {
let (offset, import) = import?;
self.newline(offset)?;
self.print_import(state, &import, true)?;
match import.ty {
TypeRef::Func(idx) => {
debug_assert!(state.core.func_to_type.len() == state.core.funcs as usize);
state.core.funcs += 1;
state.core.func_to_type.push(Some(idx))
}
TypeRef::Table(_) => state.core.tables += 1,
TypeRef::Memory(_) => state.core.memories += 1,
TypeRef::Tag(TagType {
kind: _,
func_type_idx: idx,
}) => {
debug_assert!(state.core.tag_to_type.len() == state.core.tags as usize);
state.core.tags += 1;
state.core.tag_to_type.push(Some(idx))
}
TypeRef::Global(_) => state.core.globals += 1,
}
}
Ok(())
}
fn print_import(&mut self, state: &State, import: &Import<'_>, index: bool) -> Result<()> {
self.start_group("import ")?;
self.print_str(import.module)?;
self.result.write_str(" ")?;
self.print_str(import.name)?;
self.result.write_str(" ")?;
self.print_import_ty(state, &import.ty, index)?;
self.end_group()?;
Ok(())
}
fn print_import_ty(&mut self, state: &State, ty: &TypeRef, index: bool) -> Result<()> {
match ty {
TypeRef::Func(f) => {
self.start_group("func ")?;
if index {
self.print_name(&state.core.func_names, state.core.funcs)?;
self.result.write_str(" ")?;
}
self.print_core_type_ref(state, *f)?;
}
TypeRef::Table(f) => self.print_table_type(state, f, index)?,
TypeRef::Memory(f) => self.print_memory_type(state, f, index)?,
TypeRef::Tag(f) => self.print_tag_type(state, f, index)?,
TypeRef::Global(f) => self.print_global_type(state, f, index)?,
}
self.end_group()?;
Ok(())
}
fn print_table_type(&mut self, state: &State, ty: &TableType, index: bool) -> Result<()> {
self.start_group("table ")?;
if index {
self.print_name(&state.core.table_names, state.core.tables)?;
self.result.write_str(" ")?;
}
if ty.shared {
self.print_type_keyword("shared ")?;
}
if ty.table64 {
self.print_type_keyword("i64 ")?;
}
self.print_limits(ty.initial, ty.maximum)?;
self.result.write_str(" ")?;
self.print_reftype(state, ty.element_type)?;
Ok(())
}
fn print_memory_type(&mut self, state: &State, ty: &MemoryType, index: bool) -> Result<()> {
self.start_group("memory ")?;
if index {
self.print_name(&state.core.memory_names, state.core.memories)?;
self.result.write_str(" ")?;
}
if ty.memory64 {
self.print_type_keyword("i64 ")?;
}
self.print_limits(ty.initial, ty.maximum)?;
if ty.shared {
self.print_type_keyword(" shared")?;
}
if let Some(p) = ty.page_size_log2 {
let p = 1_u64
.checked_shl(p)
.ok_or_else(|| anyhow!("left shift overflow").context("invalid page size"))?;
self.result.write_str(" ")?;
self.start_group("pagesize ")?;
write!(self.result, "{p:#x}")?;
self.end_group()?;
}
Ok(())
}
fn print_tag_type(&mut self, state: &State, ty: &TagType, index: bool) -> Result<()> {
self.start_group("tag ")?;
if index {
self.print_name(&state.core.tag_names, state.core.tags)?;
self.result.write_str(" ")?;
}
self.print_core_functype_idx(state, ty.func_type_idx, None)?;
Ok(())
}
fn print_limits<T>(&mut self, initial: T, maximum: Option<T>) -> Result<()>
where
T: fmt::Display,
{
self.result.start_literal()?;
write!(self.result, "{}", initial)?;
if let Some(max) = maximum {
write!(self.result, " {}", max)?;
}
self.result.reset_color()?;
Ok(())
}
fn print_global_type(&mut self, state: &State, ty: &GlobalType, index: bool) -> Result<()> {
self.start_group("global ")?;
if index {
self.print_name(&state.core.global_names, state.core.globals)?;
self.result.write_str(" ")?;
}
if ty.shared || ty.mutable {
self.result.write_str("(")?;
if ty.shared {
self.print_type_keyword("shared ")?;
}
if ty.mutable {
self.print_type_keyword("mut ")?;
}
self.print_valtype(state, ty.content_type)?;
self.result.write_str(")")?;
} else {
self.print_valtype(state, ty.content_type)?;
}
Ok(())
}
fn print_tables(&mut self, state: &mut State, parser: TableSectionReader<'_>) -> Result<()> {
for table in parser.into_iter_with_offsets() {
let (offset, table) = table?;
self.newline(offset)?;
self.print_table_type(state, &table.ty, true)?;
match &table.init {
TableInit::RefNull => {}
TableInit::Expr(expr) => {
self.result.write_str(" ")?;
self.print_const_expr(state, expr, self.config.fold_instructions)?;
}
}
self.end_group()?;
state.core.tables += 1;
}
Ok(())
}
fn print_memories(&mut self, state: &mut State, parser: MemorySectionReader<'_>) -> Result<()> {
for memory in parser.into_iter_with_offsets() {
let (offset, memory) = memory?;
self.newline(offset)?;
self.print_memory_type(state, &memory, true)?;
self.end_group()?;
state.core.memories += 1;
}
Ok(())
}
fn print_tags(&mut self, state: &mut State, parser: TagSectionReader<'_>) -> Result<()> {
for tag in parser.into_iter_with_offsets() {
let (offset, tag) = tag?;
self.newline(offset)?;
self.print_tag_type(state, &tag, true)?;
self.end_group()?;
debug_assert!(state.core.tag_to_type.len() == state.core.tags as usize);
state.core.tags += 1;
state.core.tag_to_type.push(Some(tag.func_type_idx));
}
Ok(())
}
fn print_globals(&mut self, state: &mut State, parser: GlobalSectionReader<'_>) -> Result<()> {
for global in parser.into_iter_with_offsets() {
let (offset, global) = global?;
self.newline(offset)?;
self.print_global_type(state, &global.ty, true)?;
self.result.write_str(" ")?;
self.print_const_expr(state, &global.init_expr, self.config.fold_instructions)?;
self.end_group()?;
state.core.globals += 1;
}
Ok(())
}
fn print_code_section_entry(
&mut self,
state: &mut State,
body: &FunctionBody<'_>,
) -> Result<()> {
let mut body = body.get_binary_reader();
let offset = body.original_position();
self.newline(offset)?;
self.start_group("func ")?;
let func_idx = state.core.funcs;
self.print_name(&state.core.func_names, func_idx)?;
self.result.write_str(" ")?;
let ty = match state.core.func_to_type.get(func_idx as usize) {
Some(Some(x)) => *x,
_ => panic!("invalid function type"),
};
let params = self
.print_core_functype_idx(state, ty, Some(func_idx))?
.unwrap_or(0);
let hints = match self.code_section_hints.last() {
Some((f, _)) if *f == func_idx => {
let (_, hints) = self.code_section_hints.pop().unwrap();
hints
}
_ => Vec::new(),
};
if self.config.print_skeleton {
self.result.write_str(" ...")?;
} else {
self.print_func_body(state, func_idx, params, &mut body, &hints)?;
}
self.end_group()?;
state.core.funcs += 1;
Ok(())
}
fn print_func_body(
&mut self,
state: &mut State,
func_idx: u32,
params: u32,
body: &mut BinaryReader<'_>,
branch_hints: &[(usize, BranchHint)],
) -> Result<()> {
let mut first = true;
let mut local_idx = 0;
let mut locals = NamedLocalPrinter::new("local");
let func_start = body.original_position();
for _ in 0..body.read_var_u32()? {
let offset = body.original_position();
let cnt = body.read_var_u32()?;
let ty = body.read()?;
if MAX_LOCALS
.checked_sub(local_idx)
.and_then(|s| s.checked_sub(cnt))
.is_none()
{
bail!("function exceeds the maximum number of locals that can be printed");
}
for _ in 0..cnt {
if first {
self.newline(offset)?;
first = false;
}
locals.start_local(Some(func_idx), params + local_idx, self, state)?;
self.print_valtype(state, ty)?;
locals.end_local(self)?;
local_idx += 1;
}
}
locals.finish(self)?;
let nesting_start = self.nesting;
let fold_instructions = self.config.fold_instructions;
let mut operator_state = OperatorState::new(self, OperatorSeparator::Newline);
if fold_instructions {
let mut folded_printer = PrintOperatorFolded::new(self, state, &mut operator_state);
folded_printer.set_offset(func_start);
folded_printer.begin_function(func_idx)?;
Self::print_operators(body, branch_hints, func_start, &mut folded_printer)?;
folded_printer.finalize()?;
} else {
let mut flat_printer = PrintOperator::new(self, state, &mut operator_state);
Self::print_operators(body, branch_hints, func_start, &mut flat_printer)?;
}
if self.nesting != nesting_start {
self.nesting = nesting_start;
self.newline(body.original_position())?;
}
Ok(())
}
fn print_operators<'a, O: OpPrinter>(
body: &mut BinaryReader<'a>,
mut branch_hints: &[(usize, BranchHint)],
func_start: usize,
op_printer: &mut O,
) -> Result<()> {
while !body.is_end_then_eof() {
if let Some(((hint_offset, hint), rest)) = branch_hints.split_first() {
if hint.func_offset == (body.original_position() - func_start) as u32 {
branch_hints = rest;
op_printer.branch_hint(*hint_offset, hint.taken)?;
}
}
op_printer.set_offset(body.original_position());
op_printer.visit_operator(body)?;
}
Ok(())
}
fn newline(&mut self, offset: usize) -> Result<()> {
self.print_newline(Some(offset))
}
fn newline_unknown_pos(&mut self) -> Result<()> {
self.print_newline(None)
}
fn print_newline(&mut self, offset: Option<usize>) -> Result<()> {
self.result.newline()?;
self.result.start_line(offset);
if self.config.print_offsets {
match offset {
Some(offset) => write!(self.result, "(;@{offset:<6x};)")?,
None => self.result.write_str(" ")?,
}
}
self.line += 1;
for _ in 0..self.nesting.min(MAX_NESTING_TO_PRINT) {
self.result.write_str(" ")?;
}
Ok(())
}
fn print_exports(&mut self, state: &State, data: ExportSectionReader) -> Result<()> {
for export in data.into_iter_with_offsets() {
let (offset, export) = export?;
self.newline(offset)?;
self.print_export(state, &export)?;
}
Ok(())
}
fn print_export(&mut self, state: &State, export: &Export) -> Result<()> {
self.start_group("export ")?;
self.print_str(export.name)?;
self.result.write_str(" ")?;
self.print_external_kind(state, export.kind, export.index)?;
self.end_group()?; Ok(())
}
fn print_external_kind(&mut self, state: &State, kind: ExternalKind, index: u32) -> Result<()> {
match kind {
ExternalKind::Func => {
self.start_group("func ")?;
self.print_idx(&state.core.func_names, index)?;
}
ExternalKind::Table => {
self.start_group("table ")?;
self.print_idx(&state.core.table_names, index)?;
}
ExternalKind::Global => {
self.start_group("global ")?;
self.print_idx(&state.core.global_names, index)?;
}
ExternalKind::Memory => {
self.start_group("memory ")?;
self.print_idx(&state.core.memory_names, index)?;
}
ExternalKind::Tag => {
self.start_group("tag ")?;
write!(self.result, "{index}")?;
}
}
self.end_group()?;
Ok(())
}
fn print_core_type_ref(&mut self, state: &State, idx: u32) -> Result<()> {
self.start_group("type ")?;
self.print_idx(&state.core.type_names, idx)?;
self.end_group()?;
Ok(())
}
fn print_idx<K>(&mut self, names: &NamingMap<u32, K>, idx: u32) -> Result<()>
where
K: NamingNamespace,
{
self._print_idx(&names.index_to_name, idx, K::desc())
}
fn _print_idx(&mut self, names: &HashMap<u32, Naming>, idx: u32, desc: &str) -> Result<()> {
self.result.start_name()?;
match names.get(&idx) {
Some(name) => name.write_identifier(self)?,
None if self.config.name_unnamed => write!(self.result, "$#{desc}{idx}")?,
None => write!(self.result, "{idx}")?,
}
self.result.reset_color()?;
Ok(())
}
fn print_local_idx(&mut self, state: &State, func: u32, idx: u32) -> Result<()> {
self.result.start_name()?;
match state.core.local_names.index_to_name.get(&(func, idx)) {
Some(name) => name.write_identifier(self)?,
None if self.config.name_unnamed => write!(self.result, "$#local{idx}")?,
None => write!(self.result, "{}", idx)?,
}
self.result.reset_color()?;
Ok(())
}
fn print_field_idx(&mut self, state: &State, ty: u32, idx: u32) -> Result<()> {
self.result.start_name()?;
match state.core.field_names.index_to_name.get(&(ty, idx)) {
Some(name) => name.write_identifier(self)?,
None if self.config.name_unnamed => write!(self.result, "$#field{idx}")?,
None => write!(self.result, "{}", idx)?,
}
self.result.reset_color()?;
Ok(())
}
fn print_name<K>(&mut self, names: &NamingMap<u32, K>, cur_idx: u32) -> Result<()>
where
K: NamingNamespace,
{
self._print_name(&names.index_to_name, cur_idx, K::desc())
}
fn _print_name(
&mut self,
names: &HashMap<u32, Naming>,
cur_idx: u32,
desc: &str,
) -> Result<()> {
self.result.start_name()?;
match names.get(&cur_idx) {
Some(name) => {
name.write(self)?;
self.result.write_str(" ")?;
}
None if self.config.name_unnamed => {
write!(self.result, "$#{desc}{cur_idx} ")?;
}
None => {}
}
write!(self.result, "(;{cur_idx};)")?;
self.result.reset_color()?;
Ok(())
}
fn print_elems(&mut self, state: &mut State, data: ElementSectionReader) -> Result<()> {
for (i, elem) in data.into_iter_with_offsets().enumerate() {
let (offset, mut elem) = elem?;
self.newline(offset)?;
self.start_group("elem ")?;
self.print_name(&state.core.element_names, i as u32)?;
match &mut elem.kind {
ElementKind::Passive => {}
ElementKind::Declared => self.result.write_str(" declare")?,
ElementKind::Active {
table_index,
offset_expr,
} => {
if let Some(table_index) = *table_index {
self.result.write_str(" ")?;
self.start_group("table ")?;
self.print_idx(&state.core.table_names, table_index)?;
self.end_group()?;
}
self.result.write_str(" ")?;
self.print_const_expr_sugar(state, offset_expr, "offset")?;
}
}
self.result.write_str(" ")?;
if self.config.print_skeleton {
self.result.write_str("...")?;
} else {
match elem.items {
ElementItems::Functions(reader) => {
self.result.write_str("func")?;
for idx in reader {
self.result.write_str(" ")?;
self.print_idx(&state.core.func_names, idx?)?
}
}
ElementItems::Expressions(ty, reader) => {
self.print_reftype(state, ty)?;
for expr in reader {
self.result.write_str(" ")?;
self.print_const_expr_sugar(state, &expr?, "item")?
}
}
}
}
self.end_group()?;
}
Ok(())
}
fn print_data(&mut self, state: &mut State, data: DataSectionReader) -> Result<()> {
for (i, data) in data.into_iter_with_offsets().enumerate() {
let (offset, data) = data?;
self.newline(offset)?;
self.start_group("data ")?;
self.print_name(&state.core.data_names, i as u32)?;
self.result.write_str(" ")?;
match &data.kind {
DataKind::Passive => {}
DataKind::Active {
memory_index,
offset_expr,
} => {
if *memory_index != 0 {
self.start_group("memory ")?;
self.print_idx(&state.core.memory_names, *memory_index)?;
self.end_group()?;
self.result.write_str(" ")?;
}
self.print_const_expr_sugar(state, offset_expr, "offset")?;
self.result.write_str(" ")?;
}
}
if self.config.print_skeleton {
self.result.write_str("...")?;
} else {
self.print_bytes(data.data)?;
}
self.end_group()?;
}
Ok(())
}
fn print_const_expr_sugar(
&mut self,
state: &mut State,
expr: &ConstExpr,
explicit: &str,
) -> Result<()> {
self.start_group("")?;
let mut reader = expr.get_operators_reader();
if reader.read().is_ok() && !reader.is_end_then_eof() {
write!(self.result, "{explicit} ")?;
self.print_const_expr(state, expr, self.config.fold_instructions)?;
} else {
self.print_const_expr(state, expr, false)?;
}
self.end_group()?;
Ok(())
}
fn print_const_expr(&mut self, state: &mut State, expr: &ConstExpr, fold: bool) -> Result<()> {
let mut reader = expr.get_binary_reader();
let mut operator_state = OperatorState::new(self, OperatorSeparator::NoneThenSpace);
if fold {
let mut folded_printer = PrintOperatorFolded::new(self, state, &mut operator_state);
folded_printer.begin_const_expr();
Self::print_operators(&mut reader, &[], 0, &mut folded_printer)?;
folded_printer.finalize()?;
} else {
let mut op_printer = PrintOperator::new(self, state, &mut operator_state);
Self::print_operators(&mut reader, &[], 0, &mut op_printer)?;
}
Ok(())
}
fn print_str(&mut self, name: &str) -> Result<()> {
self.result.start_literal()?;
self.result.write_str("\"")?;
self.print_str_contents(name)?;
self.result.write_str("\"")?;
self.result.reset_color()?;
Ok(())
}
fn print_str_contents(&mut self, name: &str) -> Result<()> {
for c in name.chars() {
let v = c as u32;
if (0x20..0x7f).contains(&v) && c != '"' && c != '\\' && v < 0xff {
write!(self.result, "{c}")?;
} else {
write!(self.result, "\\u{{{v:x}}}",)?;
}
}
Ok(())
}
fn print_bytes(&mut self, bytes: &[u8]) -> Result<()> {
self.result.start_literal()?;
self.result.write_str("\"")?;
for byte in bytes {
if *byte >= 0x20 && *byte < 0x7f && *byte != b'"' && *byte != b'\\' {
write!(self.result, "{}", *byte as char)?;
} else {
self.hex_byte(*byte)?;
}
}
self.result.write_str("\"")?;
self.result.reset_color()?;
Ok(())
}
fn hex_byte(&mut self, byte: u8) -> Result<()> {
write!(self.result, "\\{byte:02x}")?;
Ok(())
}
fn print_custom_section(
&mut self,
state: &State,
section: CustomSectionReader<'_>,
) -> Result<()> {
match section.as_known() {
KnownCustom::Producers(s) => {
self.newline(section.range().start)?;
self.print_producers_section(s)
}
KnownCustom::Dylink0(s) => {
self.newline(section.range().start)?;
self.print_dylink0_section(s)
}
KnownCustom::Name(_) | KnownCustom::BranchHints(_) => Ok(()),
#[cfg(feature = "component-model")]
KnownCustom::ComponentName(_) => Ok(()),
_ => self.print_raw_custom_section(state, section),
}
}
fn print_raw_custom_section(
&mut self,
state: &State,
section: CustomSectionReader<'_>,
) -> Result<()> {
self.newline(section.range().start)?;
self.start_group("@custom ")?;
self.print_str(section.name())?;
if let Some(place) = state.custom_section_place {
write!(self.result, " ({place})")?;
}
self.result.write_str(" ")?;
if self.config.print_skeleton {
self.result.write_str("...")?;
} else {
self.print_bytes(section.data())?;
}
self.end_group()?;
Ok(())
}
fn print_producers_section(&mut self, section: ProducersSectionReader<'_>) -> Result<()> {
self.start_group("@producers")?;
for field in section {
let field = field?;
for value in field.values.into_iter_with_offsets() {
let (offset, value) = value?;
self.newline(offset)?;
self.start_group(field.name)?;
self.result.write_str(" ")?;
self.print_str(value.name)?;
self.result.write_str(" ")?;
self.print_str(value.version)?;
self.end_group()?;
}
}
self.end_group()?;
Ok(())
}
fn print_dylink0_section(&mut self, mut section: Dylink0SectionReader<'_>) -> Result<()> {
self.start_group("@dylink.0")?;
loop {
let start = section.original_position();
let next = match section.next() {
Some(Ok(next)) => next,
Some(Err(e)) => return Err(e.into()),
None => break,
};
match next {
Dylink0Subsection::MemInfo(info) => {
self.newline(start)?;
self.start_group("mem-info")?;
if info.memory_size > 0 || info.memory_alignment > 0 {
write!(
self.result,
" (memory {} {})",
info.memory_size, info.memory_alignment
)?;
}
if info.table_size > 0 || info.table_alignment > 0 {
write!(
self.result,
" (table {} {})",
info.table_size, info.table_alignment
)?;
}
self.end_group()?;
}
Dylink0Subsection::Needed(needed) => {
self.newline(start)?;
self.start_group("needed")?;
for s in needed {
self.result.write_str(" ")?;
self.print_str(s)?;
}
self.end_group()?;
}
Dylink0Subsection::ExportInfo(info) => {
for info in info {
self.newline(start)?;
self.start_group("export-info ")?;
self.print_str(info.name)?;
self.print_dylink0_flags(info.flags)?;
self.end_group()?;
}
}
Dylink0Subsection::ImportInfo(info) => {
for info in info {
self.newline(start)?;
self.start_group("import-info ")?;
self.print_str(info.module)?;
self.result.write_str(" ")?;
self.print_str(info.field)?;
self.print_dylink0_flags(info.flags)?;
self.end_group()?;
}
}
Dylink0Subsection::Unknown { ty, .. } => {
bail!("don't know how to print dylink.0 subsection id {ty}");
}
}
}
self.end_group()?;
Ok(())
}
fn print_dylink0_flags(&mut self, mut flags: SymbolFlags) -> Result<()> {
macro_rules! print_flag {
($($name:ident = $text:tt)*) => ({$(
if flags.contains(SymbolFlags::$name) {
flags.remove(SymbolFlags::$name);
write!(self.result, concat!(" ", $text))?;
}
)*})
}
print_flag! {
BINDING_WEAK = "binding-weak"
BINDING_LOCAL = "binding-local"
VISIBILITY_HIDDEN = "visibility-hidden"
UNDEFINED = "undefined"
EXPORTED = "exported"
EXPLICIT_NAME = "explicit-name"
NO_STRIP = "no-strip"
TLS = "tls"
ABSOLUTE = "absolute"
}
if !flags.is_empty() {
write!(self.result, " {:#x}", flags)?;
}
Ok(())
}
fn register_branch_hint_section(&mut self, section: BranchHintSectionReader<'_>) -> Result<()> {
self.code_section_hints.clear();
for func in section {
let func = func?;
if self.code_section_hints.len() >= MAX_WASM_FUNCTIONS as usize {
bail!("found too many hints");
}
if func.hints.count() >= MAX_WASM_FUNCTION_SIZE {
bail!("found too many hints");
}
let hints = func
.hints
.into_iter_with_offsets()
.collect::<wasmparser::Result<Vec<_>>>()?;
self.code_section_hints.push((func.func, hints));
}
self.code_section_hints.reverse();
Ok(())
}
}
struct NamedLocalPrinter {
group_name: &'static str,
in_group: bool,
end_group_after_local: bool,
first: bool,
}
impl NamedLocalPrinter {
fn new(group_name: &'static str) -> NamedLocalPrinter {
NamedLocalPrinter {
group_name,
in_group: false,
end_group_after_local: false,
first: true,
}
}
fn start_local(
&mut self,
func: Option<u32>,
local: u32,
dst: &mut Printer,
state: &State,
) -> Result<()> {
let name = state
.core
.local_names
.index_to_name
.get(&(func.unwrap_or(u32::MAX), local));
if name.is_some() && self.in_group {
dst.end_group()?;
self.in_group = false;
}
if self.first {
self.first = false;
} else {
dst.result.write_str(" ")?;
}
if !self.in_group {
dst.start_group(self.group_name)?;
dst.result.write_str(" ")?;
self.in_group = true;
}
match name {
Some(name) => {
name.write(dst)?;
dst.result.write_str(" ")?;
self.end_group_after_local = true;
}
None if dst.config.name_unnamed && func.is_some() => {
write!(dst.result, "$#local{local} ")?;
self.end_group_after_local = true;
}
None => {
self.end_group_after_local = false;
}
}
Ok(())
}
fn end_local(&mut self, dst: &mut Printer) -> Result<()> {
if self.end_group_after_local {
dst.end_group()?;
self.end_group_after_local = false;
self.in_group = false;
}
Ok(())
}
fn finish(self, dst: &mut Printer) -> Result<()> {
if self.in_group {
dst.end_group()?;
}
Ok(())
}
}
macro_rules! print_float {
($name:ident $float:ident $uint:ident $sint:ident $exp_bits:tt) => {
fn $name(&mut self, mut bits: $uint) -> Result<()> {
let int_width = mem::size_of::<$uint>() * 8;
let exp_width = $exp_bits;
let mantissa_width = int_width - 1 - exp_width;
let bias = (1 << (exp_width - 1)) - 1;
let max_exp = (1 as $sint) << (exp_width - 1);
let min_exp = -max_exp + 1;
let f = $float::from_bits(bits);
if bits >> (int_width - 1) != 0 {
bits ^= 1 << (int_width - 1);
self.result.write_str("-")?;
}
if f.is_infinite() {
self.result.start_literal()?;
self.result.write_str("inf ")?;
self.result.start_comment()?;
write!(self.result, "(;={f};)")?;
self.result.reset_color()?;
return Ok(());
}
if f.is_nan() {
let payload = bits & ((1 << mantissa_width) - 1);
self.result.start_literal()?;
if payload == 1 << (mantissa_width - 1) {
self.result.write_str("nan ")?;
self.result.start_comment()?;
write!(self.result, "(;={f};)")?;
} else {
write!(self.result, "nan:{:#x} ", payload)?;
self.result.start_comment()?;
write!(self.result, "(;={f};)")?;
}
self.result.reset_color()?;
return Ok(());
}
let mut exponent = (((bits << 1) as $sint) >> (mantissa_width + 1)).wrapping_sub(bias);
exponent = (exponent << (int_width - exp_width)) >> (int_width - exp_width);
let mut fraction = bits & ((1 << mantissa_width) - 1);
self.result.start_literal()?;
self.result.write_str("0x")?;
if bits == 0 {
self.result.write_str("0p+0")?;
} else {
self.result.write_str("1")?;
if fraction > 0 {
fraction <<= (int_width - mantissa_width);
if exponent == min_exp {
let leading = fraction.leading_zeros();
if (leading as usize) < int_width - 1 {
fraction <<= leading + 1;
} else {
fraction = 0;
}
exponent -= leading as $sint;
}
self.result.write_str(".")?;
while fraction > 0 {
write!(self.result, "{:x}", fraction >> (int_width - 4))?;
fraction <<= 4;
}
}
write!(self.result, "p{:+}", exponent)?;
}
self.result.start_comment()?;
write!(self.result, " (;={};)", f)?;
self.result.reset_color()?;
Ok(())
}
};
}
impl Printer<'_, '_> {
print_float!(print_f32 f32 u32 i32 8);
print_float!(print_f64 f64 u64 i64 11);
}
impl Naming {
fn new<'a>(
name: &'a str,
index: u32,
group: &str,
used: Option<&mut HashSet<&'a str>>,
) -> Naming {
let mut kind = NamingKind::DollarName;
if name.chars().any(|c| !is_idchar(c)) {
kind = NamingKind::DollarQuotedName;
}
if name.is_empty()
|| name.starts_with('#')
|| used.map(|set| !set.insert(name)).unwrap_or(false)
{
kind = NamingKind::SyntheticPrefix(format!("#{group}{index}"));
}
return Naming {
kind,
name: name.to_string(),
};
fn is_idchar(c: char) -> bool {
matches!(
c,
'0'..='9'
| 'a'..='z'
| 'A'..='Z'
| '!'
| '#'
| '$'
| '%'
| '&'
| '\''
| '*'
| '+'
| '-'
| '.'
| '/'
| ':'
| '<'
| '='
| '>'
| '?'
| '@'
| '\\'
| '^'
| '_'
| '`'
| '|'
| '~'
)
}
}
fn write_identifier(&self, printer: &mut Printer<'_, '_>) -> Result<()> {
match &self.kind {
NamingKind::DollarName => {
printer.result.write_str("$")?;
printer.result.write_str(&self.name)?;
}
NamingKind::DollarQuotedName => {
printer.result.write_str("$\"")?;
printer.print_str_contents(&self.name)?;
printer.result.write_str("\"")?;
}
NamingKind::SyntheticPrefix(prefix) => {
printer.result.write_str("$\"")?;
printer.result.write_str(&prefix)?;
printer.result.write_str(" ")?;
printer.print_str_contents(&self.name)?;
printer.result.write_str("\"")?;
}
}
Ok(())
}
fn write(&self, dst: &mut Printer<'_, '_>) -> Result<()> {
self.write_identifier(dst)?;
match &self.kind {
NamingKind::DollarName | NamingKind::DollarQuotedName => {}
NamingKind::SyntheticPrefix(_) => {
dst.result.write_str(" ")?;
dst.start_group("@name \"")?;
dst.print_str_contents(&self.name)?;
dst.result.write_str("\"")?;
dst.end_group()?;
}
}
Ok(())
}
}
trait NamingNamespace {
fn desc() -> &'static str;
}
macro_rules! naming_namespaces {
($(struct $name:ident => $desc:tt)*) => ($(
struct $name;
impl NamingNamespace for $name {
fn desc() -> &'static str { $desc }
}
)*)
}
naming_namespaces! {
struct NameFunc => "func"
struct NameGlobal => "global"
struct NameMemory => "memory"
struct NameLocal => "local"
struct NameLabel => "label"
struct NameTable => "table"
struct NameType => "type"
struct NameField => "field"
struct NameData => "data"
struct NameElem => "elem"
struct NameTag => "tag"
}
#[cfg(feature = "component-model")]
naming_namespaces! {
struct NameModule => "module"
struct NameInstance => "instance"
struct NameValue => "value"
struct NameComponent => "component"
}
fn name_map<K>(into: &mut NamingMap<u32, K>, names: NameMap<'_>, name: &str) -> Result<()> {
let mut used = HashSet::new();
for naming in names {
let naming = naming?;
into.index_to_name.insert(
naming.index,
Naming::new(naming.name, naming.index, name, Some(&mut used)),
);
}
Ok(())
}