* Support CLI parameters for string encoding * Fix `--skip-validate` * Fix printing binary to stdout
214 lines
7.5 KiB
Rust
214 lines
7.5 KiB
Rust
use anyhow::{bail, Context, Result};
|
|
use clap::Parser;
|
|
use std::io::Write;
|
|
use std::path::PathBuf;
|
|
use wasmparser::{Payload, Validator, WasmFeatures};
|
|
use wasmtime_environ::component::*;
|
|
use wasmtime_environ::fact::Module;
|
|
|
|
/// A small helper utility to explore generated adapter modules from Wasmtime's
|
|
/// adapter fusion compiler.
|
|
///
|
|
/// This utility takes a `*.wat` file as input which is expected to be a valid
|
|
/// WebAssembly component. The component is parsed and any type definition for a
|
|
/// component function gets a generated adapter for it as if the caller/callee
|
|
/// used that type as the adapter.
|
|
///
|
|
/// For example with an input that looks like:
|
|
///
|
|
/// (component
|
|
/// (type (func (param u32) (result (list u8))))
|
|
/// )
|
|
///
|
|
/// This tool can be used to generate an adapter for that signature.
|
|
#[derive(Parser)]
|
|
struct Factc {
|
|
/// Whether or not debug code is inserted into the generated adapter.
|
|
#[clap(long)]
|
|
debug: bool,
|
|
|
|
/// Whether or not the lifting options (the callee of the exported adapter)
|
|
/// uses a 64-bit memory as opposed to a 32-bit memory.
|
|
#[clap(long)]
|
|
lift64: bool,
|
|
|
|
/// Whether or not the lowering options (the caller of the exported adapter)
|
|
/// uses a 64-bit memory as opposed to a 32-bit memory.
|
|
#[clap(long)]
|
|
lower64: bool,
|
|
|
|
/// Whether or not a call to a `post-return` configured function is enabled
|
|
/// or not.
|
|
#[clap(long)]
|
|
post_return: bool,
|
|
|
|
/// Whether or not to skip validation of the generated adapter module.
|
|
#[clap(long)]
|
|
skip_validate: bool,
|
|
|
|
/// Where to place the generated adapter module. Standard output is used if
|
|
/// this is not specified.
|
|
#[clap(short, long)]
|
|
output: Option<PathBuf>,
|
|
|
|
/// Output the text format for WebAssembly instead of the binary format.
|
|
#[clap(short, long)]
|
|
text: bool,
|
|
|
|
#[clap(long, parse(try_from_str = parse_string_encoding), default_value = "utf8")]
|
|
lift_str: StringEncoding,
|
|
|
|
#[clap(long, parse(try_from_str = parse_string_encoding), default_value = "utf8")]
|
|
lower_str: StringEncoding,
|
|
|
|
/// TODO
|
|
input: PathBuf,
|
|
}
|
|
|
|
fn parse_string_encoding(name: &str) -> anyhow::Result<StringEncoding> {
|
|
Ok(match name {
|
|
"utf8" => StringEncoding::Utf8,
|
|
"utf16" => StringEncoding::Utf16,
|
|
"compact-utf16" => StringEncoding::CompactUtf16,
|
|
other => anyhow::bail!("invalid string encoding: `{other}`"),
|
|
})
|
|
}
|
|
|
|
fn main() -> Result<()> {
|
|
Factc::parse().execute()
|
|
}
|
|
|
|
impl Factc {
|
|
fn execute(self) -> Result<()> {
|
|
env_logger::init();
|
|
|
|
let mut types = ComponentTypesBuilder::default();
|
|
|
|
// Manufactures a unique `CoreDef` so all function imports get unique
|
|
// function imports.
|
|
let mut next_def = 0;
|
|
let mut dummy_def = || {
|
|
next_def += 1;
|
|
CoreDef::Adapter(AdapterIndex::from_u32(next_def))
|
|
};
|
|
|
|
// Manufactures a `CoreExport` for a memory with the shape specified. Note
|
|
// that we can't import as many memories as functions so these are
|
|
// intentionally limited. Once a handful of memories are generated of each
|
|
// type then they start getting reused.
|
|
let mut next_memory = 0;
|
|
let mut memories32 = Vec::new();
|
|
let mut memories64 = Vec::new();
|
|
let mut dummy_memory = |memory64: bool| {
|
|
let dst = if memory64 {
|
|
&mut memories64
|
|
} else {
|
|
&mut memories32
|
|
};
|
|
let idx = if dst.len() < 5 {
|
|
next_memory += 1;
|
|
dst.push(next_memory - 1);
|
|
next_memory - 1
|
|
} else {
|
|
dst[0]
|
|
};
|
|
CoreExport {
|
|
instance: RuntimeInstanceIndex::from_u32(idx),
|
|
item: ExportItem::Name(String::new()),
|
|
}
|
|
};
|
|
|
|
let mut adapters = Vec::new();
|
|
let input = wat::parse_file(&self.input)?;
|
|
types.push_type_scope();
|
|
let mut validator = Validator::new_with_features(WasmFeatures {
|
|
component_model: true,
|
|
..Default::default()
|
|
});
|
|
for payload in wasmparser::Parser::new(0).parse_all(&input) {
|
|
let payload = payload?;
|
|
validator.payload(&payload)?;
|
|
let section = match payload {
|
|
Payload::ComponentTypeSection(s) => s,
|
|
_ => continue,
|
|
};
|
|
for ty in section {
|
|
let ty = types.intern_component_type(&ty?)?;
|
|
types.push_component_typedef(ty);
|
|
let ty = match ty {
|
|
TypeDef::ComponentFunc(ty) => ty,
|
|
_ => continue,
|
|
};
|
|
adapters.push(Adapter {
|
|
lift_ty: ty,
|
|
lower_ty: ty,
|
|
lower_options: AdapterOptions {
|
|
instance: RuntimeComponentInstanceIndex::from_u32(0),
|
|
string_encoding: self.lower_str,
|
|
memory64: self.lower64,
|
|
// Pessimistically assume that memory/realloc are going to be
|
|
// required for this trampoline and provide it. Avoids doing
|
|
// calculations to figure out whether they're necessary and
|
|
// simplifies the fuzzer here without reducing coverage within FACT
|
|
// itself.
|
|
memory: Some(dummy_memory(self.lower64)),
|
|
realloc: Some(dummy_def()),
|
|
// Lowering never allows `post-return`
|
|
post_return: None,
|
|
},
|
|
lift_options: AdapterOptions {
|
|
instance: RuntimeComponentInstanceIndex::from_u32(1),
|
|
string_encoding: self.lift_str,
|
|
memory64: self.lift64,
|
|
memory: Some(dummy_memory(self.lift64)),
|
|
realloc: Some(dummy_def()),
|
|
post_return: if self.post_return {
|
|
Some(dummy_def())
|
|
} else {
|
|
None
|
|
},
|
|
},
|
|
func: dummy_def(),
|
|
});
|
|
}
|
|
}
|
|
types.pop_type_scope();
|
|
|
|
let types = types.finish();
|
|
let mut fact_module = Module::new(&types, self.debug);
|
|
for (i, adapter) in adapters.iter().enumerate() {
|
|
fact_module.adapt(&format!("adapter{i}"), adapter);
|
|
}
|
|
let wasm = fact_module.encode();
|
|
|
|
let output = if self.text {
|
|
wasmprinter::print_bytes(&wasm)
|
|
.context("failed to convert binary wasm to text")?
|
|
.into_bytes()
|
|
} else if self.output.is_none() && atty::is(atty::Stream::Stdout) {
|
|
bail!("cannot print binary wasm output to a terminal unless `-t` flag is passed")
|
|
} else {
|
|
wasm.clone()
|
|
};
|
|
|
|
match &self.output {
|
|
Some(file) => std::fs::write(file, output).context("failed to write output file")?,
|
|
None => std::io::stdout()
|
|
.write_all(&output)
|
|
.context("failed to write to stdout")?,
|
|
}
|
|
|
|
if !self.skip_validate {
|
|
Validator::new_with_features(WasmFeatures {
|
|
multi_memory: true,
|
|
memory64: true,
|
|
..WasmFeatures::default()
|
|
})
|
|
.validate_all(&wasm)
|
|
.context("failed to validate generated module")?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|