diff --git a/Cargo.toml b/Cargo.toml index 2b37e54..c5b07aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,12 +10,13 @@ env_logger = "0.4" lazy_static = "0.2" clap = "2.24" glob = "0.2" +byteorder = "1" [lib] [[bin]] -name = "wasm-opt" -path = "opt/src/main.rs" +name = "wasm-prune" +path = "prune/src/main.rs" [[bin]] name = "wasm-ext" @@ -31,4 +32,8 @@ path = "pack/src/main.rs" [[bin]] name = "wasm-build" -path = "build/src/main.rs" \ No newline at end of file +path = "build/src/main.rs" + +[[bin]] +name = "nondeterminism-check" +path = "nondeterminism_check/src/main.rs" diff --git a/README.md b/README.md index 636d48a..5d6c575 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ Easiest way to use is to install via `cargo install`: cargo install --git https://github.com/paritytech/wasm-utils wasm-build ``` -## Symbols optimizer (wasm-opt) +## Symbols pruning (wasm-prune) ``` -cargo run --release --bin wasm-opt -- +cargo run --release --bin wasm-prune -- ``` This will optimize WASM symbols tree to leave only those elements that are used by contract `_call` function entry. diff --git a/build/Cargo.toml b/build/Cargo.toml index 9ac7979..69643a8 100644 --- a/build/Cargo.toml +++ b/build/Cargo.toml @@ -7,7 +7,7 @@ authors = ["NikVolf "] glob = "0.2" wasm-utils = { path = "../" } clap = "2.24" -parity-wasm = "0.12" +parity-wasm = "0.14" [[bin]] -name = "wasm-build" \ No newline at end of file +name = "wasm-build" diff --git a/build/src/main.rs b/build/src/main.rs index 9cf3a1e..71b23b9 100644 --- a/build/src/main.rs +++ b/build/src/main.rs @@ -76,6 +76,14 @@ fn main() { .arg(Arg::with_name("skip_alloc") .help("Skip allocator externalizer step producing final wasm") .long("skip-externalize")) + .arg(Arg::with_name("runtime_type") + .help("Injects RUNTIME_TYPE global export") + .takes_value(true) + .long("runtime-type")) + .arg(Arg::with_name("runtime_version") + .help("Injects RUNTIME_VERSION global export") + .takes_value(true) + .long("runtime-version")) .get_matches(); let target_dir = matches.value_of("target").expect("is required; qed"); @@ -98,5 +106,15 @@ fn main() { wasm_utils::optimize(&mut module, vec!["_call", "setTempRet0"]).expect("Optimizer to finish without errors"); } + if let Some(runtime_type) = matches.value_of("runtime_type") { + let runtime_type: &[u8] = runtime_type.as_bytes(); + if runtime_type.len() != 4 { + panic!("--runtime-type should be equal to 4 bytes"); + } + let runtime_version: u32 = matches.value_of("runtime_version").unwrap_or("1").parse() + .expect("--runtime-version should be a positive integer"); + module = wasm_utils::inject_runtime_type(module, &runtime_type, runtime_version); + } + parity_wasm::serialize_to_file(&path, module).unwrap(); -} \ No newline at end of file +} diff --git a/nondeterminism_check/.gitignore b/nondeterminism_check/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/nondeterminism_check/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/nondeterminism_check/Cargo.toml b/nondeterminism_check/Cargo.toml new file mode 100644 index 0000000..e5ccfc9 --- /dev/null +++ b/nondeterminism_check/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "nondeterminism_check" +version = "0.1.0" +authors = ["NikVolf "] + +[dependencies] +parity-wasm = "0.14" +wasm-utils = { path = "../" } +clap = "2.24" diff --git a/nondeterminism_check/src/main.rs b/nondeterminism_check/src/main.rs new file mode 100644 index 0000000..fca40af --- /dev/null +++ b/nondeterminism_check/src/main.rs @@ -0,0 +1,26 @@ +extern crate parity_wasm; +extern crate wasm_utils; + +use std::env; + + +fn main() { + + wasm_utils::init_log(); + + let args = env::args().collect::>(); + if args.len() != 2 { + println!("Usage: {} input_file.wasm", args[0]); + return; + } + + // Loading module + let module = parity_wasm::deserialize_file(&args[1]).expect("Module deserialization to succeed"); + + if wasm_utils::is_deterministic(&module) { + println!("Module is deterministic"); + } else { + println!("Module is not deterministic"); + } + +} diff --git a/opt/.gitignore b/prune/.gitignore similarity index 100% rename from opt/.gitignore rename to prune/.gitignore diff --git a/opt/Cargo.toml b/prune/Cargo.toml similarity index 100% rename from opt/Cargo.toml rename to prune/Cargo.toml diff --git a/opt/src/main.rs b/prune/src/main.rs similarity index 100% rename from opt/src/main.rs rename to prune/src/main.rs diff --git a/src/lib.rs b/src/lib.rs index ea075b2..12d0a9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ extern crate parity_wasm; extern crate env_logger; +extern crate byteorder; #[macro_use] extern crate log; #[macro_use] extern crate lazy_static; @@ -11,9 +12,13 @@ mod symbols; mod logger; mod ext; mod pack; +mod nondeterminism_check; +mod runtime_type; pub use optimizer::{optimize, Error as OptimizerError}; pub use gas::inject_gas_counter; pub use logger::init_log; pub use ext::externalize; -pub use pack::pack_instance; \ No newline at end of file +pub use pack::pack_instance; +pub use nondeterminism_check::is_deterministic; +pub use runtime_type::inject_runtime_type; diff --git a/src/nondeterminism_check.rs b/src/nondeterminism_check.rs new file mode 100644 index 0000000..eae6f1a --- /dev/null +++ b/src/nondeterminism_check.rs @@ -0,0 +1,138 @@ +use parity_wasm::{elements}; +use parity_wasm::elements::{ Section, Opcode }; +use parity_wasm::elements::Opcode::*; + +fn have_nondeterministic_opcodes (opcodes: &[Opcode]) -> bool { + for opcode in opcodes { + match *opcode { + F32Abs | + F32Neg | + F32Ceil | + F32Floor | + F32Trunc | + F32Nearest | + F32Sqrt | + F32Add | + F32Sub | + F32Mul | + F32Div | + F32Min | + F32Max | + F32Copysign | + F64Abs | + F64Neg | + F64Ceil | + F64Floor | + F64Trunc | + F64Nearest | + F64Sqrt | + F64Add | + F64Sub | + F64Mul | + F64Div | + F64Min | + F64Max | + F64Copysign | + I32TruncSF32 | + I32TruncUF32 | + I32TruncSF64 | + I32TruncUF64 | + I64TruncSF32 | + I64TruncUF32 | + I64TruncSF64 | + I64TruncUF64 | + F32ConvertSI32 | + F32ConvertUI32 | + F32ConvertSI64 | + F32ConvertUI64 | + F32DemoteF64 | + F64ConvertSI32 | + F64ConvertUI32 | + F64ConvertSI64 | + F64ConvertUI64 | + F64PromoteF32 | + I32ReinterpretF32 | + I64ReinterpretF64 | + F32ReinterpretI32 | + F64ReinterpretI64 | + F32Eq | + F32Ne | + F32Lt | + F32Gt | + F32Le | + F32Ge | + F64Eq | + F64Ne | + F64Lt | + F64Gt | + F64Le | + F64Ge + => return true, + _ => continue + } + } + false +} + + + + +pub fn is_deterministic(module: &elements::Module) -> bool { + for section in module.sections() { + match *section { + Section::Code(ref cs) => { + for body in cs.bodies() { + if have_nondeterministic_opcodes(body.code().elements()) { + return false; + } + } + }, + _ => continue + } + } + true +} + +#[cfg(test)] +mod tests { + use parity_wasm::{builder, elements}; + use super::*; + + #[test] + fn nondeterminism_found() { + let module = builder::module() + .function().signature().return_type().f32().build() + .body() + .with_opcodes(elements::Opcodes::new( + vec![ + elements::Opcode::F32Const(1), // unrelated to this test matter + elements::Opcode::F32Const(1), // unrelated to this test matter + elements::Opcode::F32Add, + elements::Opcode::End + ] + )) + .build() + .build() + .build(); + assert_eq!(false, is_deterministic(&module)); + } + + #[test] + fn nondeterminism_not() { + let module = builder::module() + .function().signature().return_type().i32().build() + .body() + .with_opcodes(elements::Opcodes::new( + vec![ + elements::Opcode::I32Const(1), + elements::Opcode::I32Const(1), + elements::Opcode::I32Add, + elements::Opcode::End + ] + )) + .build() + .build() + .build(); + assert_eq!(true, is_deterministic(&module)); + } +} diff --git a/src/runtime_type.rs b/src/runtime_type.rs new file mode 100644 index 0000000..24443ef --- /dev/null +++ b/src/runtime_type.rs @@ -0,0 +1,43 @@ +use parity_wasm::{elements, builder}; +use self::elements::{ Module, GlobalEntry, External, ExportEntry, GlobalType, ValueType, InitExpr, Opcode, Internal }; +use byteorder::{ LittleEndian, ByteOrder }; + +pub fn inject_runtime_type(module: Module, runtime_type: &[u8], runtime_version: u32) -> Module { + let runtime_type: u32 = LittleEndian::read_u32(&runtime_type); + let globals_count: u32 = match module.global_section() { + Some(ref section) => section.entries().len() as u32, + None => 0 + }; + let imported_globals_count: u32 = match module.import_section() { + Some(ref section) => section.entries().iter().filter(|e| match *e.external() { + External::Global(ref _a) => true, + _ => false + }).count() as u32, + None => 0 + }; + let total_globals_count: u32 = globals_count + imported_globals_count; + + builder::from_module(module) + .with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Opcode::I32Const(runtime_type as i32), Opcode::End]))) + .with_export(ExportEntry::new("RUNTIME_TYPE".into(), Internal::Global(total_globals_count))) + .with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Opcode::I32Const(runtime_version as i32), Opcode::End]))) + .with_export(ExportEntry::new("RUNTIME_VERSION".into(), Internal::Global(total_globals_count + 1))) + .build() +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn it_injects() { + let mut module = builder::module() + .with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Opcode::I32Const(42 as i32)]))) + .build(); + module = inject_runtime_type(module, b"emcc", 1); + let global_section = module.global_section().expect("Global section expected"); + assert_eq!(3, global_section.entries().len()); + let export_section = module.export_section().expect("Export section expected"); + assert!(export_section.entries().iter().find(|e| e.field() == "RUNTIME_TYPE" ).is_some()); + assert!(export_section.entries().iter().find(|e| e.field() == "RUNTIME_VERSION" ).is_some()); + } +}