pub extern crate binaryen_sys;
#[cfg(test)]
extern crate rand;
#[cfg(test)]
extern crate wat;
pub use binaryen_sys as ffi;
use std::ffi::CString;
use std::os::raw::c_char;
use std::rc::Rc;
use std::str::FromStr;
use std::{ptr, slice};
pub mod tools;
#[derive(Default)]
pub struct CodegenConfig {
pub shrink_level: u32,
pub optimization_level: u32,
pub debug_info: bool,
}
fn is_valid_pass(pass: &str) -> bool {
ffi::passes::OptimizationPass::from_str(pass).is_ok()
}
struct InnerModule {
raw: ffi::BinaryenModuleRef,
}
impl Drop for InnerModule {
fn drop(&mut self) {
unsafe { ffi::BinaryenModuleDispose(self.raw) }
}
}
pub struct Module {
inner: Rc<InnerModule>,
}
impl Module {
fn new() -> Module {
unsafe {
let raw = ffi::BinaryenModuleCreate();
Module::from_raw(raw)
}
}
pub fn read(module: &[u8]) -> Result<Module, ()> {
unsafe {
let raw = ffi::BinaryenModuleSafeRead(module.as_ptr() as *const c_char, module.len());
if raw.is_null() {
return Err(());
}
Ok(Module::from_raw(raw))
}
}
pub unsafe fn from_raw(raw: ffi::BinaryenModuleRef) -> Module {
Module {
inner: Rc::new(InnerModule { raw }),
}
}
pub fn optimize(&mut self, codegen_config: &CodegenConfig) {
unsafe {
ffi::BinaryenModuleRunPassesWithSettings(
self.inner.raw,
std::ptr::null_mut(),
0 as u32,
codegen_config.shrink_level as i32,
codegen_config.optimization_level as i32,
codegen_config.debug_info as i32,
)
}
}
pub fn run_optimization_passes<B: AsRef<str>, I: IntoIterator<Item = B>>(
&mut self,
passes: I,
codegen_config: &CodegenConfig,
) -> Result<(), ()> {
let mut cstr_vec: Vec<_> = vec![];
for pass in passes {
if !is_valid_pass(pass.as_ref()) {
return Err(());
}
cstr_vec.push(CString::new(pass.as_ref()).unwrap());
}
let mut ptr_vec: Vec<_> = cstr_vec.iter().map(|pass| pass.as_ptr()).collect();
unsafe {
ffi::BinaryenModuleRunPassesWithSettings(
self.inner.raw,
ptr_vec.as_mut_ptr(),
ptr_vec.len() as u32,
codegen_config.shrink_level as i32,
codegen_config.optimization_level as i32,
codegen_config.debug_info as i32,
)
};
Ok(())
}
#[cfg(test)]
fn is_valid(&self) -> bool {
unsafe { ffi::BinaryenModuleSafeValidate(self.inner.raw) == 1 }
}
pub fn write(&self) -> Vec<u8> {
unsafe {
let write_result = ffi::BinaryenModuleAllocateAndWrite(self.inner.raw, ptr::null());
let binary_buf = if write_result.binaryBytes == 0 {
vec![]
} else {
slice::from_raw_parts(write_result.binary as *const u8, write_result.binaryBytes)
.to_vec()
};
ffi::BinaryenShimDisposeBinaryenModuleAllocateAndWriteResult(write_result);
binary_buf
}
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! wat2wasm {
($x:expr) => {
wat::parse_str($x).unwrap()
};
}
#[test]
fn module_reading() {
let invalid_module = b"\0asm\0\0\0\0";
let valid_module = b"\0asm\x01\0\0\0";
assert!(Module::read(invalid_module).is_err());
assert!(Module::read(valid_module).is_ok());
}
#[test]
fn test_optimization_passes() {
const CODE: &'static str = r#"
(module
(table 1 1 funcref)
(type $return_i32 (func (result i32)))
(func $test (; 0 ;) (result i32)
(call_indirect (type $return_i32)
(unreachable)
)
)
)
"#;
let mut module = Module::read(&wat2wasm!(CODE)).unwrap();
assert!(module.is_valid());
module
.run_optimization_passes(&["vacuum", "untee"], &CodegenConfig::default())
.expect("passes succeeded");
assert!(module.is_valid());
}
#[test]
fn test_invalid_optimization_passes() {
let mut module = Module::new();
assert!(module
.run_optimization_passes(&["invalid"], &CodegenConfig::default())
.is_err());
}
#[test]
fn optimization_pass_list() {
let pass_list = [
"alignment-lowering",
"asyncify",
"avoid-reinterprets",
"dae",
"dae-optimizing",
"coalesce-locals",
"coalesce-locals-learning",
"code-pushing",
"code-folding",
"const-hoisting",
"cfp",
"dce",
"dealign",
"denan",
"directize",
"dfo",
"dwarfdump",
"duplicate-import-elimination",
"duplicate-function-elimination",
"emit-target-features",
"extract-function",
"extract-function-index",
"flatten",
"fpcast-emu",
"func-metrics",
"generate-dyncalls",
"generate-i64-dyncalls",
"generate-stack-ir",
"global-refining",
"gto",
"gsi",
"type-refining",
"heap2local",
"inline-main",
"inlining",
"inlining-optimizing",
"intrinsic-lowering",
"legalize-js-interface",
"legalize-js-interface-minimally",
"local-cse",
"local-subtyping",
"log-execution",
"i64-to-i32-lowering",
"instrument-locals",
"instrument-memory",
"licm",
"limit-segments",
"memory64-lowering",
"memory-packing",
"merge-blocks",
"merge-similar-functions",
"merge-locals",
"metrics",
"minify-imports",
"minify-imports-and-exports",
"minify-imports-and-exports-and-modules",
"mod-asyncify-always-and-only-unwind",
"mod-asyncify-never-unwind",
"nm",
"name-types",
"once-reduction",
"optimize-added-constants",
"optimize-added-constants-propagate",
"optimize-instructions",
"optimize-stack-ir",
"pick-load-signs",
"poppify",
"post-emscripten",
"optimize-for-js",
"precompute",
"precompute-propagate",
"print",
"print-minified",
"print-features",
"print-full",
"print-call-graph",
"print-function-map",
"symbolmap",
"print-stack-ir",
"remove-non-js-ops",
"remove-imports",
"remove-memory",
"remove-unused-brs",
"remove-unused-module-elements",
"remove-unused-nonfunction-module-elements",
"remove-unused-names",
"reorder-functions",
"reorder-locals",
"rereloop",
"rse",
"roundtrip",
"safe-heap",
"set-globals",
"signature-pruning",
"signature-refining",
"simplify-globals",
"simplify-globals-optimizing",
"simplify-locals",
"simplify-locals-nonesting",
"simplify-locals-notee",
"simplify-locals-nostructure",
"simplify-locals-notee-nostructure",
"souperify",
"souperify-single-use",
"spill-pointers",
"stub-unsupported-js",
"ssa",
"ssa-nomerge",
"strip",
"stack-check",
"strip-debug",
"strip-dwarf",
"strip-producers",
"strip-target-features",
"trap-mode-clamp",
"trap-mode-js",
"untee",
"vacuum",
];
for pass in pass_list.iter() {
assert!(is_valid_pass(pass), "not a valid pass: {}", pass);
}
}
#[test]
fn test_smoke_optimize() {
let input: Vec<u8> = vec![
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x60, 0x00, 0x00,
0x03, 0x02, 0x01, 0x00, 0x07, 0x08, 0x01, 0x04, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00,
0x08, 0x01, 0x00, 0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b,
];
let expected: Vec<u8> = vec![
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x60, 0x00, 0x00,
0x03, 0x02, 0x01, 0x00, 0x07, 0x08, 0x01, 0x04, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00,
0x0a, 0x05, 0x01, 0x03, 0x00, 0x01, 0x0b,
];
let mut module = Module::read(&input).unwrap();
assert!(module.is_valid());
module.optimize(&CodegenConfig::default());
assert!(module.is_valid());
assert_eq!(module.write(), expected);
}
}