mirror of
https://github.com/pezkuwichain/wasm-instrument.git
synced 2026-04-21 23:47:57 +00:00
Remove everything not needed by substrate
Also rename to wasm-instrument
This commit is contained in:
+17
-1
@@ -6,6 +6,22 @@ tab_width=4
|
||||
end_of_line=lf
|
||||
charset=utf-8
|
||||
trim_trailing_whitespace=true
|
||||
max_line_length=120
|
||||
max_line_length=100
|
||||
insert_final_newline=true
|
||||
|
||||
[*.md]
|
||||
max_line_length=80
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
|
||||
[*.yml]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
tab_width=8
|
||||
end_of_line=lf
|
||||
|
||||
[*.sh]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
tab_width=8
|
||||
end_of_line=lf
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# For details about syntax, see:
|
||||
# https://help.github.com/en/articles/about-code-owners
|
||||
|
||||
/ @NikVolf @athei @pepyakin
|
||||
/ @athei @pepyakin
|
||||
|
||||
+10
-62
@@ -1,78 +1,26 @@
|
||||
[package]
|
||||
name = "pwasm-utils"
|
||||
version = "0.19.0"
|
||||
name = "wasm-instrument"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.56.1"
|
||||
authors = ["Nikolay Volf <nikvolf@gmail.com>", "Sergey Pepyakin <s.pepyakin@gmail.com>"]
|
||||
license = "MIT/Apache-2.0"
|
||||
readme = "README.md"
|
||||
description = "Collection of command-line utilities and corresponding Rust api for producing pwasm-compatible executables"
|
||||
keywords = ["wasm", "webassembly", "pwasm"]
|
||||
repository = "https://github.com/paritytech/wasm-utils"
|
||||
include = ["src/**/*", "LICENSE-*", "README.md", "cli/**/*"]
|
||||
|
||||
[[bin]]
|
||||
name = "wasm-prune"
|
||||
path = "cli/prune/main.rs"
|
||||
required-features = ["cli"]
|
||||
|
||||
[[bin]]
|
||||
name = "wasm-ext"
|
||||
path = "cli/ext/main.rs"
|
||||
required-features = ["cli"]
|
||||
|
||||
[[bin]]
|
||||
name = "wasm-gas"
|
||||
path = "cli/gas/main.rs"
|
||||
required-features = ["cli"]
|
||||
|
||||
[[bin]]
|
||||
name = "wasm-build"
|
||||
path = "cli/build/main.rs"
|
||||
required-features = ["cli"]
|
||||
|
||||
[[bin]]
|
||||
name = "wasm-stack-height"
|
||||
path = "cli/stack_height/main.rs"
|
||||
required-features = ["cli"]
|
||||
|
||||
[[bin]]
|
||||
name = "wasm-pack"
|
||||
path = "cli/pack/main.rs"
|
||||
required-features = ["cli"]
|
||||
|
||||
[[bin]]
|
||||
name = "wasm-check"
|
||||
path = "cli/check/main.rs"
|
||||
required-features = ["cli"]
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Instrument and transform wasm modules."
|
||||
keywords = ["wasm", "webassembly", "blockchain", "gas-metering", "parity"]
|
||||
categories = ["wasm", "no-std"]
|
||||
repository = "https://github.com/paritytech/wasm-instrument"
|
||||
include = ["src/**/*", "LICENSE-*", "README.md"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = { version = "1", default-features = false }
|
||||
log = { version = "0.4", default-features = false }
|
||||
parity-wasm = { version = "0.42", default-features = false }
|
||||
|
||||
# Dependencies only used by the binaries
|
||||
clap = { version = "2", optional = true }
|
||||
env_logger = { version = "0.9", optional = true }
|
||||
glob = { version = "0.3", optional = true }
|
||||
lazy_static = { version = "1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
binaryen = "0.12"
|
||||
diff = "0.1"
|
||||
indoc = "1"
|
||||
rand = "0.8"
|
||||
tempdir = "0.3"
|
||||
wabt = "0.10"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["parity-wasm/std", "log/std", "byteorder/std"]
|
||||
cli = [
|
||||
"std",
|
||||
"glob",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"lazy_static",
|
||||
]
|
||||
std = ["parity-wasm/std"]
|
||||
sign_ext = ["parity-wasm/sign_ext"]
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
Copyright (c) 2017 Nikolay Volf
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
|
||||
@@ -1,51 +1,33 @@
|
||||
# pwasm-utils
|
||||
# wasm-instrument
|
||||
|
||||
[](https://travis-ci.org/paritytech/wasm-utils)
|
||||
A Rust library containing a collection of wasm module instrumentations and transformations
|
||||
mainly useful for wasm based block chains and smart contracts.
|
||||
|
||||
A collection of WASM utilities used in pwasm-ethereum and substrate contract development.
|
||||
## Provided functionality
|
||||
|
||||
This repository contains the package `pwasm-utils` which consists of a library crate
|
||||
and a collection of cli binaries that make use of this library.
|
||||
This is a non exhaustive list of provided functionality. Please check out the documentation for details.
|
||||
|
||||
## Installation of cli tools
|
||||
```
|
||||
cargo install pwasm-utils --features cli
|
||||
```
|
||||
### Gas Metering
|
||||
|
||||
This will install the following binaries:
|
||||
* wasm-build
|
||||
* wasm-check
|
||||
* wasm-ext
|
||||
* wasm-gas
|
||||
* wasm-pack
|
||||
* wasm-prune
|
||||
* wasm-stack-height
|
||||
Add gas metering to your platform by injecting the necessary code directly into the wasm module. This allows having a uniform gas metering implementation across different execution engines (interpreters, JIT compilers).
|
||||
|
||||
## Symbols pruning (wasm-prune)
|
||||
### Stack Height Limiter
|
||||
|
||||
```
|
||||
wasm-prune <input_wasm_binary.wasm> <output_wasm_binary.wasm>
|
||||
```
|
||||
Neither the wasm standard nor any sufficiently complex execution engine specifies how many items on the wasm stack are supported before the execution aborts or malfunctions. Even the same execution engine on different operating systems or host architectures could support a different number of stack items and be well within its rights.
|
||||
|
||||
This will optimize WASM symbols tree to leave only those elements that are used by contract `call` function entry.
|
||||
This is the kind of indeterminism that can lead to consensus failures when used in a blockchain context.
|
||||
|
||||
## Gas counter (wasm-gas)
|
||||
To address this issue we can inject some code that meters the stack height at runtime and aborts the execution when it reaches a predefined limit. Choosing this limit suffciently small so that it is smaller than any what any reasonbly written execution engine would support solves the issue: All execution engines would reach the injected limit before hitting any implementation specific limitation.
|
||||
|
||||
For development purposes, a raw WASM contract can be injected with gas counters (the same way as it done in the `pwasm-ethereum/substrate` runtime when running contracts)
|
||||
## License
|
||||
|
||||
```
|
||||
wasm-gas <input_wasm_binary.wasm> <output_wasm_binary.wasm>
|
||||
```
|
||||
|
||||
# License
|
||||
|
||||
`wasm-utils` is primarily distributed under the terms of both the MIT
|
||||
license and the Apache License (Version 2.0), at your choice.
|
||||
`wasm-instrument` is distributed under the terms of both the MIT license and the
|
||||
Apache License (Version 2.0), at your choice.
|
||||
|
||||
See LICENSE-APACHE, and LICENSE-MIT for details.
|
||||
|
||||
## Contribution
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in `wasm-utils` by you, as defined in the Apache-2.0 license, shall be
|
||||
for inclusion in `wasm-instrument` by you, as defined in the Apache-2.0 license, shall be
|
||||
dual licensed as above, without any additional terms or conditions.
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
//! Experimental build tool for cargo
|
||||
|
||||
use pwasm_utils::{build, logger, BuildError, SourceTarget, TargetRuntime};
|
||||
|
||||
mod source;
|
||||
|
||||
use std::{fs, io, path::PathBuf};
|
||||
|
||||
use clap::{crate_version, App, Arg};
|
||||
use parity_wasm::elements;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Io(io::Error),
|
||||
FailedToCopy(String),
|
||||
Decoding(elements::Error, String),
|
||||
Encoding(elements::Error),
|
||||
Build(BuildError),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||
use self::Error::*;
|
||||
match self {
|
||||
Io(io) => write!(f, "Generic i/o error: {}", io),
|
||||
FailedToCopy(msg) => write!(f, "{}. Have you tried to run \"cargo build\"?", msg),
|
||||
Decoding(err, file) => write!(
|
||||
f,
|
||||
"Decoding error ({}). Must be a valid wasm file {}. Pointed wrong file?",
|
||||
err, file
|
||||
),
|
||||
Encoding(err) => write!(
|
||||
f,
|
||||
"Encoding error ({}). Almost impossible to happen, no free disk space?",
|
||||
err
|
||||
),
|
||||
Build(err) => write!(f, "Build error: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wasm_path(input: &source::SourceInput) -> String {
|
||||
let mut path = PathBuf::from(input.target_dir());
|
||||
path.push(format!("{}.wasm", input.final_name()));
|
||||
path.to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
pub fn process_output(input: &source::SourceInput) -> Result<(), Error> {
|
||||
let mut cargo_path = PathBuf::from(input.target_dir());
|
||||
let wasm_name = input.bin_name().to_string().replace("-", "_");
|
||||
cargo_path.push(match input.target() {
|
||||
SourceTarget::Emscripten => source::EMSCRIPTEN_TRIPLET,
|
||||
SourceTarget::Unknown => source::UNKNOWN_TRIPLET,
|
||||
});
|
||||
cargo_path.push("release");
|
||||
cargo_path.push(format!("{}.wasm", wasm_name));
|
||||
|
||||
let mut target_path = PathBuf::from(input.target_dir());
|
||||
target_path.push(format!("{}.wasm", input.final_name()));
|
||||
fs::copy(cargo_path.as_path(), target_path.as_path()).map_err(|io| {
|
||||
Error::FailedToCopy(format!(
|
||||
"Failed to copy '{}' to '{}': {}",
|
||||
cargo_path.display(),
|
||||
target_path.display(),
|
||||
io
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_main() -> Result<(), Error> {
|
||||
logger::init();
|
||||
|
||||
let matches = App::new("wasm-build")
|
||||
.version(crate_version!())
|
||||
.arg(Arg::with_name("target")
|
||||
.index(1)
|
||||
.required(true)
|
||||
.help("Cargo target directory"))
|
||||
.arg(Arg::with_name("wasm")
|
||||
.index(2)
|
||||
.required(true)
|
||||
.help("Wasm binary name"))
|
||||
.arg(Arg::with_name("target-runtime")
|
||||
.help("What runtime we are compiling to")
|
||||
.long("target-runtime")
|
||||
.takes_value(true)
|
||||
.default_value("pwasm")
|
||||
.possible_values(&["substrate", "pwasm"]))
|
||||
.arg(Arg::with_name("skip_optimization")
|
||||
.help("Skip symbol optimization step producing final wasm")
|
||||
.long("skip-optimization"))
|
||||
.arg(Arg::with_name("enforce_stack_adjustment")
|
||||
.help("Enforce stack size adjustment (used for old wasm32-unknown-unknown)")
|
||||
.long("enforce-stack-adjustment"))
|
||||
.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"))
|
||||
.arg(Arg::with_name("source_target")
|
||||
.help("Cargo target type kind ('wasm32-unknown-unknown' or 'wasm32-unknown-emscripten'")
|
||||
.takes_value(true)
|
||||
.long("target"))
|
||||
.arg(Arg::with_name("final_name")
|
||||
.help("Final wasm binary name")
|
||||
.takes_value(true)
|
||||
.long("final"))
|
||||
.arg(Arg::with_name("save_raw")
|
||||
.help("Save intermediate raw bytecode to path")
|
||||
.takes_value(true)
|
||||
.long("save-raw"))
|
||||
.arg(Arg::with_name("shrink_stack")
|
||||
.help("Shrinks the new stack size for wasm32-unknown-unknown")
|
||||
.takes_value(true)
|
||||
.long("shrink-stack"))
|
||||
.arg(Arg::with_name("public_api")
|
||||
.help("Preserves specific imports in the library")
|
||||
.takes_value(true)
|
||||
.long("public-api"))
|
||||
|
||||
.get_matches();
|
||||
|
||||
let target_dir = matches.value_of("target").expect("is required; qed");
|
||||
let wasm_binary = matches.value_of("wasm").expect("is required; qed");
|
||||
|
||||
let mut source_input = source::SourceInput::new(target_dir, wasm_binary);
|
||||
|
||||
let source_target_val = matches.value_of("source_target").unwrap_or(source::EMSCRIPTEN_TRIPLET);
|
||||
if source_target_val == source::UNKNOWN_TRIPLET {
|
||||
source_input = source_input.unknown()
|
||||
} else if source_target_val == source::EMSCRIPTEN_TRIPLET {
|
||||
source_input = source_input.emscripten()
|
||||
} else {
|
||||
eprintln!(
|
||||
"--target can be: '{}' or '{}'",
|
||||
source::EMSCRIPTEN_TRIPLET,
|
||||
source::UNKNOWN_TRIPLET
|
||||
);
|
||||
::std::process::exit(1);
|
||||
}
|
||||
|
||||
if let Some(final_name) = matches.value_of("final_name") {
|
||||
source_input = source_input.with_final(final_name);
|
||||
}
|
||||
|
||||
process_output(&source_input)?;
|
||||
|
||||
let path = wasm_path(&source_input);
|
||||
|
||||
let module =
|
||||
parity_wasm::deserialize_file(&path).map_err(|e| Error::Decoding(e, path.to_string()))?;
|
||||
|
||||
let runtime_type_version = if let (Some(runtime_type), Some(runtime_version)) =
|
||||
(matches.value_of("runtime_type"), matches.value_of("runtime_version"))
|
||||
{
|
||||
let mut ty: [u8; 4] = Default::default();
|
||||
let runtime_bytes = runtime_type.as_bytes();
|
||||
if runtime_bytes.len() != 4 {
|
||||
panic!("--runtime-type should be equal to 4 bytes");
|
||||
}
|
||||
ty.copy_from_slice(runtime_bytes);
|
||||
let version: u32 =
|
||||
runtime_version.parse().expect("--runtime-version should be a positive integer");
|
||||
Some((ty, version))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let public_api_entries: Vec<_> = matches
|
||||
.value_of("public_api")
|
||||
.map(|val| val.split(',').collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let target_runtime = match matches
|
||||
.value_of("target-runtime")
|
||||
.expect("target-runtime has a default value; qed")
|
||||
{
|
||||
"pwasm" => TargetRuntime::pwasm(),
|
||||
"substrate" => TargetRuntime::substrate(),
|
||||
_ => unreachable!("all possible values are enumerated in clap config; qed"),
|
||||
};
|
||||
|
||||
let (module, ctor_module) = build(
|
||||
module,
|
||||
source_input.target(),
|
||||
runtime_type_version,
|
||||
&public_api_entries,
|
||||
matches.is_present("enforce_stack_adjustment"),
|
||||
matches
|
||||
.value_of("shrink_stack")
|
||||
.unwrap_or("49152")
|
||||
.parse()
|
||||
.expect("New stack size is not valid u32"),
|
||||
matches.is_present("skip_optimization"),
|
||||
&target_runtime,
|
||||
)
|
||||
.map_err(Error::Build)?;
|
||||
|
||||
if let Some(save_raw_path) = matches.value_of("save_raw") {
|
||||
parity_wasm::serialize_to_file(save_raw_path, module.clone()).map_err(Error::Encoding)?;
|
||||
}
|
||||
|
||||
if let Some(ctor_module) = ctor_module {
|
||||
parity_wasm::serialize_to_file(&path, ctor_module).map_err(Error::Encoding)?;
|
||||
} else {
|
||||
parity_wasm::serialize_to_file(&path, module).map_err(Error::Encoding)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(e) = do_main() {
|
||||
eprintln!("{}", e);
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs;
|
||||
use tempdir::TempDir;
|
||||
|
||||
use super::{process_output, source::SourceInput};
|
||||
|
||||
#[test]
|
||||
fn processes_cargo_output() {
|
||||
let tmp_dir = TempDir::new("target").expect("temp dir failed");
|
||||
|
||||
let target_path = tmp_dir.path().join("wasm32-unknown-emscripten").join("release");
|
||||
fs::create_dir_all(target_path.clone()).expect("create dir failed");
|
||||
|
||||
{
|
||||
use std::io::Write;
|
||||
|
||||
let wasm_path = target_path.join("example_wasm.wasm");
|
||||
let mut f = fs::File::create(wasm_path).expect("create fail failed");
|
||||
f.write_all(b"\0asm").expect("write file failed");
|
||||
}
|
||||
|
||||
let path = tmp_dir.path().to_string_lossy();
|
||||
let input = SourceInput::new(&path, "example-wasm");
|
||||
|
||||
process_output(&input).expect("process output failed");
|
||||
|
||||
assert!(fs::metadata(tmp_dir.path().join("example-wasm.wasm"))
|
||||
.expect("metadata failed")
|
||||
.is_file())
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
//! Configuration of source binaries
|
||||
|
||||
pub const UNKNOWN_TRIPLET: &str = "wasm32-unknown-unknown";
|
||||
pub const EMSCRIPTEN_TRIPLET: &str = "wasm32-unknown-emscripten";
|
||||
|
||||
use pwasm_utils::SourceTarget;
|
||||
|
||||
/// Configuration of previous build step (cargo compilation)
|
||||
#[derive(Debug)]
|
||||
pub struct SourceInput<'a> {
|
||||
target_dir: &'a str,
|
||||
bin_name: &'a str,
|
||||
final_name: &'a str,
|
||||
target: SourceTarget,
|
||||
}
|
||||
|
||||
impl<'a> SourceInput<'a> {
|
||||
pub fn new<'b>(target_dir: &'b str, bin_name: &'b str) -> SourceInput<'b> {
|
||||
SourceInput { target_dir, bin_name, final_name: bin_name, target: SourceTarget::Emscripten }
|
||||
}
|
||||
|
||||
pub fn unknown(mut self) -> Self {
|
||||
self.target = SourceTarget::Unknown;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn emscripten(mut self) -> Self {
|
||||
self.target = SourceTarget::Emscripten;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_final(mut self, final_name: &'a str) -> Self {
|
||||
self.final_name = final_name;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn target_dir(&self) -> &str {
|
||||
self.target_dir
|
||||
}
|
||||
|
||||
pub fn bin_name(&self) -> &str {
|
||||
self.bin_name
|
||||
}
|
||||
|
||||
pub fn final_name(&self) -> &str {
|
||||
self.final_name
|
||||
}
|
||||
|
||||
pub fn target(&self) -> SourceTarget {
|
||||
self.target
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
use clap::{App, Arg};
|
||||
use parity_wasm::elements;
|
||||
use pwasm_utils::logger;
|
||||
|
||||
fn fail(msg: &str) -> ! {
|
||||
eprintln!("{}", msg);
|
||||
std::process::exit(1)
|
||||
}
|
||||
|
||||
const ALLOWED_IMPORTS: &[&str] = &[
|
||||
"ret",
|
||||
"storage_read",
|
||||
"storage_write",
|
||||
"balance",
|
||||
"sender",
|
||||
"origin",
|
||||
"fetch_input",
|
||||
"input_length",
|
||||
"ccall",
|
||||
"dcall",
|
||||
"scall",
|
||||
"create",
|
||||
"balance",
|
||||
"blockhash",
|
||||
"blocknumber",
|
||||
"coinbase",
|
||||
"timestamp",
|
||||
"difficulty",
|
||||
"gaslimit",
|
||||
"address",
|
||||
"value",
|
||||
"suicide",
|
||||
"panic",
|
||||
"elog",
|
||||
"abort",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
logger::init();
|
||||
|
||||
let matches = App::new("wasm-check")
|
||||
.arg(Arg::with_name("input").index(1).required(true).help("Input WASM file"))
|
||||
.get_matches();
|
||||
|
||||
let input = matches.value_of("input").expect("is required; qed");
|
||||
|
||||
let module =
|
||||
parity_wasm::deserialize_file(&input).expect("Input module deserialization failed");
|
||||
|
||||
for section in module.sections() {
|
||||
match section {
|
||||
elements::Section::Import(import_section) => {
|
||||
let mut has_imported_memory_properly_named = false;
|
||||
for entry in import_section.entries() {
|
||||
if entry.module() != "env" {
|
||||
fail("All imports should be from env");
|
||||
}
|
||||
match entry.external() {
|
||||
elements::External::Function(_) => {
|
||||
if !ALLOWED_IMPORTS.contains(&entry.field()) {
|
||||
fail(&format!(
|
||||
"'{}' is not supported by the runtime",
|
||||
entry.field()
|
||||
));
|
||||
}
|
||||
},
|
||||
elements::External::Memory(m) => {
|
||||
if entry.field() == "memory" {
|
||||
has_imported_memory_properly_named = true;
|
||||
}
|
||||
|
||||
let max = if let Some(max) = m.limits().maximum() {
|
||||
max
|
||||
} else {
|
||||
fail("There is a limit on memory in Parity runtime, and this program does not limit memory");
|
||||
};
|
||||
|
||||
if max > 16 {
|
||||
fail(&format!(
|
||||
"Parity runtime has 1Mb limit (16 pages) on max contract memory, this program speicifies {}",
|
||||
max
|
||||
));
|
||||
}
|
||||
},
|
||||
elements::External::Global(_) =>
|
||||
fail("Parity runtime does not provide any globals"),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
if !has_imported_memory_properly_named {
|
||||
fail("No imported memory from env::memory in the contract");
|
||||
}
|
||||
},
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
fn main() {
|
||||
pwasm_utils::logger::init();
|
||||
|
||||
let args = std::env::args().collect::<Vec<_>>();
|
||||
if args.len() != 3 {
|
||||
println!("Usage: {} input_file.wasm output_file.wasm", args[0]);
|
||||
return
|
||||
}
|
||||
|
||||
let module = pwasm_utils::externalize(
|
||||
parity_wasm::deserialize_file(&args[1]).expect("Module to deserialize ok"),
|
||||
vec!["_free", "_malloc", "_memcpy", "_memset", "_memmove"],
|
||||
);
|
||||
|
||||
parity_wasm::serialize_to_file(&args[2], module).expect("Module to serialize ok");
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
use pwasm_utils::{self as utils, logger};
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
logger::init();
|
||||
|
||||
let args = env::args().collect::<Vec<_>>();
|
||||
if args.len() != 3 {
|
||||
println!("Usage: {} input_file.wasm output_file.wasm", args[0]);
|
||||
return
|
||||
}
|
||||
|
||||
// Loading module
|
||||
let module =
|
||||
parity_wasm::deserialize_file(&args[1]).expect("Module deserialization to succeed");
|
||||
|
||||
let result = utils::inject_gas_counter(module, &utils::rules::Set::default(), "env")
|
||||
.expect("Failed to inject gas. Some forbidden opcodes?");
|
||||
|
||||
parity_wasm::serialize_to_file(&args[2], result).expect("Module serialization to succeed")
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
use clap::{App, Arg};
|
||||
use pwasm_utils::{self as utils, logger};
|
||||
|
||||
fn main() {
|
||||
logger::init();
|
||||
|
||||
let target_runtime = utils::TargetRuntime::pwasm();
|
||||
|
||||
let matches = App::new("wasm-pack")
|
||||
.arg(Arg::with_name("input").index(1).required(true).help("Input WASM file"))
|
||||
.arg(Arg::with_name("output").index(2).required(true).help("Output WASM file"))
|
||||
.get_matches();
|
||||
|
||||
let input = matches.value_of("input").expect("is required; qed");
|
||||
let output = matches.value_of("output").expect("is required; qed");
|
||||
|
||||
let module =
|
||||
parity_wasm::deserialize_file(&input).expect("Input module deserialization failed");
|
||||
let ctor_module = module.clone();
|
||||
let raw_module = parity_wasm::serialize(module).expect("Serialization failed");
|
||||
|
||||
// Invoke packer
|
||||
let mut result_module =
|
||||
utils::pack_instance(raw_module, ctor_module, &utils::TargetRuntime::pwasm())
|
||||
.expect("Packing failed");
|
||||
// Optimize constructor, since it does not need everything
|
||||
utils::optimize(&mut result_module, vec![target_runtime.symbols().call])
|
||||
.expect("Optimization failed");
|
||||
|
||||
parity_wasm::serialize_to_file(&output, result_module).expect("Serialization failed");
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
use clap::{App, Arg};
|
||||
use pwasm_utils::{self as utils, logger};
|
||||
|
||||
fn main() {
|
||||
logger::init();
|
||||
|
||||
let target_runtime = utils::TargetRuntime::pwasm();
|
||||
|
||||
let matches = App::new("wasm-prune")
|
||||
.arg(Arg::with_name("input").index(1).required(true).help("Input WASM file"))
|
||||
.arg(Arg::with_name("output").index(2).required(true).help("Output WASM file"))
|
||||
.arg(
|
||||
Arg::with_name("exports")
|
||||
.long("exports")
|
||||
.short("e")
|
||||
.takes_value(true)
|
||||
.value_name("functions")
|
||||
.help(&format!(
|
||||
"Comma-separated list of exported functions to keep. Default: '{}'",
|
||||
target_runtime.symbols().call
|
||||
)),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let exports = matches
|
||||
.value_of("exports")
|
||||
.unwrap_or(target_runtime.symbols().call)
|
||||
.split(',')
|
||||
.collect();
|
||||
|
||||
let input = matches.value_of("input").expect("is required; qed");
|
||||
let output = matches.value_of("output").expect("is required; qed");
|
||||
|
||||
let mut module = parity_wasm::deserialize_file(&input).unwrap();
|
||||
|
||||
// Invoke optimizer
|
||||
// Contract is supposed to have only these functions as public api
|
||||
// All other symbols not usable by this list is optimized away
|
||||
utils::optimize(&mut module, exports).expect("Optimizer failed");
|
||||
|
||||
parity_wasm::serialize_to_file(&output, module).expect("Serialization failed");
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
use pwasm_utils::{logger, stack_height};
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
logger::init();
|
||||
|
||||
let args = env::args().collect::<Vec<_>>();
|
||||
if args.len() != 3 {
|
||||
println!("Usage: {} input_file.wasm output_file.wasm", args[0]);
|
||||
return
|
||||
}
|
||||
|
||||
let input_file = &args[1];
|
||||
let output_file = &args[2];
|
||||
|
||||
// Loading module
|
||||
let module =
|
||||
parity_wasm::deserialize_file(&input_file).expect("Module deserialization to succeed");
|
||||
|
||||
let result =
|
||||
stack_height::inject_limiter(module, 1024).expect("Failed to inject stack height counter");
|
||||
|
||||
parity_wasm::serialize_to_file(&output_file, result).expect("Module serialization to succeed")
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let args = env::args().collect::<Vec<_>>();
|
||||
if args.len() != 3 {
|
||||
println!("Usage: {} input_file.wasm output_file.wasm", args[0]);
|
||||
return
|
||||
}
|
||||
|
||||
// Loading module
|
||||
let mut module = pwasm_utils::Module::from_elements(
|
||||
&parity_wasm::deserialize_file(&args[1]).expect("Module deserialization to succeed"),
|
||||
)
|
||||
.expect("Failed to parse parity-wasm format");
|
||||
|
||||
let mut delete_types = Vec::new();
|
||||
for type_ in module.types.iter() {
|
||||
if type_.link_count() == 0 {
|
||||
delete_types.push(type_.order().expect("type in list should have index"));
|
||||
}
|
||||
}
|
||||
module.types.delete(&delete_types[..]);
|
||||
|
||||
parity_wasm::serialize_to_file(
|
||||
&args[2],
|
||||
module.generate().expect("Failed to generate valid format"),
|
||||
)
|
||||
.expect("Module serialization to succeed")
|
||||
}
|
||||
@@ -7,6 +7,9 @@ imports_granularity = "Crate"
|
||||
reorder_imports = true
|
||||
# Consistency
|
||||
newline_style = "Unix"
|
||||
# Format comments
|
||||
comment_width = 100
|
||||
wrap_comments = true
|
||||
# Misc
|
||||
chain_width = 80
|
||||
spaces_around_ranges = false
|
||||
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
use super::{
|
||||
externalize_mem, inject_runtime_type, optimize, pack_instance, shrink_unknown_stack, std::fmt,
|
||||
ununderscore_funcs, OptimizerError, PackingError, TargetRuntime,
|
||||
};
|
||||
use parity_wasm::elements;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Encoding(elements::Error),
|
||||
Packing(PackingError),
|
||||
Optimizer,
|
||||
}
|
||||
|
||||
impl From<OptimizerError> for Error {
|
||||
fn from(_err: OptimizerError) -> Self {
|
||||
Error::Optimizer
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PackingError> for Error {
|
||||
fn from(err: PackingError) -> Self {
|
||||
Error::Packing(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum SourceTarget {
|
||||
Emscripten,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
use self::Error::*;
|
||||
match self {
|
||||
Encoding(err) => write!(f, "Encoding error ({})", err),
|
||||
Optimizer => write!(f, "Optimization error due to missing export section. Pointed wrong file?"),
|
||||
Packing(e) => write!(f, "Packing failed due to module structure error: {}. Sure used correct libraries for building contracts?", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_ctor(module: &elements::Module, target_runtime: &TargetRuntime) -> bool {
|
||||
if let Some(section) = module.export_section() {
|
||||
section.entries().iter().any(|e| target_runtime.symbols().create == e.field())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn build(
|
||||
mut module: elements::Module,
|
||||
source_target: SourceTarget,
|
||||
runtime_type_version: Option<([u8; 4], u32)>,
|
||||
public_api_entries: &[&str],
|
||||
enforce_stack_adjustment: bool,
|
||||
stack_size: u32,
|
||||
skip_optimization: bool,
|
||||
target_runtime: &TargetRuntime,
|
||||
) -> Result<(elements::Module, Option<elements::Module>), Error> {
|
||||
if let SourceTarget::Emscripten = source_target {
|
||||
module = ununderscore_funcs(module);
|
||||
}
|
||||
|
||||
if let SourceTarget::Unknown = source_target {
|
||||
// 49152 is 48kb!
|
||||
if enforce_stack_adjustment {
|
||||
assert!(stack_size <= 1024 * 1024);
|
||||
let (new_module, new_stack_top) =
|
||||
shrink_unknown_stack(module, 1024 * 1024 - stack_size);
|
||||
module = new_module;
|
||||
let mut stack_top_page = new_stack_top / 65536;
|
||||
if new_stack_top % 65536 > 0 {
|
||||
stack_top_page += 1
|
||||
};
|
||||
module = externalize_mem(module, Some(stack_top_page), 16);
|
||||
} else {
|
||||
module = externalize_mem(module, None, 16);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(runtime_type_version) = runtime_type_version {
|
||||
let (runtime_type, runtime_version) = runtime_type_version;
|
||||
module = inject_runtime_type(module, runtime_type, runtime_version);
|
||||
}
|
||||
|
||||
let mut ctor_module = module.clone();
|
||||
|
||||
let mut public_api_entries = public_api_entries.to_vec();
|
||||
public_api_entries.push(target_runtime.symbols().call);
|
||||
if !skip_optimization {
|
||||
optimize(&mut module, public_api_entries)?;
|
||||
}
|
||||
|
||||
if !has_ctor(&ctor_module, target_runtime) {
|
||||
return Ok((module, None))
|
||||
}
|
||||
|
||||
if !skip_optimization {
|
||||
let preserved_exports = match target_runtime {
|
||||
TargetRuntime::PWasm(_) => vec![target_runtime.symbols().create],
|
||||
TargetRuntime::Substrate(_) => {
|
||||
vec![target_runtime.symbols().call, target_runtime.symbols().create]
|
||||
},
|
||||
};
|
||||
optimize(&mut ctor_module, preserved_exports)?;
|
||||
}
|
||||
|
||||
if let TargetRuntime::PWasm(_) = target_runtime {
|
||||
ctor_module = pack_instance(
|
||||
parity_wasm::serialize(module.clone()).map_err(Error::Encoding)?,
|
||||
ctor_module.clone(),
|
||||
target_runtime,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok((module, Some(ctor_module)))
|
||||
}
|
||||
+23
-7
@@ -1,13 +1,12 @@
|
||||
use alloc::{format, vec::Vec};
|
||||
use parity_wasm::elements;
|
||||
|
||||
use crate::optimizer::{export_section, global_section};
|
||||
|
||||
/// Export all declared mutable globals.
|
||||
/// Export all declared mutable globals as `prefix_index`.
|
||||
///
|
||||
/// This will export all internal mutable globals under the name of
|
||||
/// concat(`prefix`, i) where i is the index inside the range of
|
||||
/// [0..<total number of internal mutable globals>].
|
||||
pub fn export_mutable_globals(module: &mut elements::Module, prefix: impl Into<String>) {
|
||||
/// concat(`prefix`, `"_"`, `i`) where i is the index inside the range of
|
||||
/// [0..total number of internal mutable globals].
|
||||
pub fn export_mutable_globals(module: &mut elements::Module, prefix: &str) {
|
||||
let exports = global_section(module)
|
||||
.map(|section| {
|
||||
section
|
||||
@@ -33,7 +32,6 @@ pub fn export_mutable_globals(module: &mut elements::Module, prefix: impl Into<S
|
||||
.push(elements::Section::Export(elements::ExportSection::default()));
|
||||
}
|
||||
|
||||
let prefix: String = prefix.into();
|
||||
for (symbol_index, export) in exports.into_iter().enumerate() {
|
||||
let new_entry = elements::ExportEntry::new(
|
||||
format!("{}_{}", prefix, symbol_index),
|
||||
@@ -48,6 +46,24 @@ pub fn export_mutable_globals(module: &mut elements::Module, prefix: impl Into<S
|
||||
}
|
||||
}
|
||||
|
||||
fn export_section(module: &mut elements::Module) -> Option<&mut elements::ExportSection> {
|
||||
for section in module.sections_mut() {
|
||||
if let elements::Section::Export(sect) = section {
|
||||
return Some(sect)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn global_section(module: &mut elements::Module) -> Option<&mut elements::GlobalSection> {
|
||||
for section in module.sections_mut() {
|
||||
if let elements::Section::Global(sect) = section {
|
||||
return Some(sect)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
||||
-210
@@ -1,210 +0,0 @@
|
||||
use crate::std::{borrow::ToOwned, string::String, vec::Vec};
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use parity_wasm::{builder, elements};
|
||||
|
||||
use crate::optimizer::{export_section, import_section};
|
||||
|
||||
type Insertion = (usize, u32, u32, String);
|
||||
|
||||
pub fn update_call_index(
|
||||
instructions: &mut elements::Instructions,
|
||||
original_imports: usize,
|
||||
inserts: &[Insertion],
|
||||
) {
|
||||
use parity_wasm::elements::Instruction::*;
|
||||
for instruction in instructions.elements_mut().iter_mut() {
|
||||
if let Call(call_index) = instruction {
|
||||
if let Some(pos) = inserts.iter().position(|x| x.1 == *call_index) {
|
||||
*call_index = (original_imports + pos) as u32;
|
||||
} else if *call_index as usize > original_imports {
|
||||
*call_index += inserts.len() as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn memory_section(module: &mut elements::Module) -> Option<&mut elements::MemorySection> {
|
||||
for section in module.sections_mut() {
|
||||
if let elements::Section::Memory(sect) = section {
|
||||
return Some(sect)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn externalize_mem(
|
||||
mut module: elements::Module,
|
||||
adjust_pages: Option<u32>,
|
||||
max_pages: u32,
|
||||
) -> elements::Module {
|
||||
let mut entry = memory_section(&mut module)
|
||||
.expect("Memory section to exist")
|
||||
.entries_mut()
|
||||
.pop()
|
||||
.expect("Own memory entry to exist in memory section");
|
||||
|
||||
if let Some(adjust_pages) = adjust_pages {
|
||||
assert!(adjust_pages <= max_pages);
|
||||
entry = elements::MemoryType::new(adjust_pages, Some(max_pages));
|
||||
}
|
||||
|
||||
if entry.limits().maximum().is_none() {
|
||||
entry = elements::MemoryType::new(entry.limits().initial(), Some(max_pages));
|
||||
}
|
||||
|
||||
let mut builder = builder::from_module(module);
|
||||
builder.push_import(elements::ImportEntry::new(
|
||||
"env".to_owned(),
|
||||
"memory".to_owned(),
|
||||
elements::External::Memory(entry),
|
||||
));
|
||||
|
||||
builder.build()
|
||||
}
|
||||
|
||||
fn foreach_public_func_name<F>(mut module: elements::Module, f: F) -> elements::Module
|
||||
where
|
||||
F: Fn(&mut String),
|
||||
{
|
||||
if let Some(section) = import_section(&mut module) {
|
||||
for entry in section.entries_mut() {
|
||||
if let elements::External::Function(_) = *entry.external() {
|
||||
f(entry.field_mut())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(section) = export_section(&mut module) {
|
||||
for entry in section.entries_mut() {
|
||||
if let elements::Internal::Function(_) = *entry.internal() {
|
||||
f(entry.field_mut())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module
|
||||
}
|
||||
|
||||
pub fn underscore_funcs(module: elements::Module) -> elements::Module {
|
||||
foreach_public_func_name(module, |n| n.insert(0, '_'))
|
||||
}
|
||||
|
||||
pub fn ununderscore_funcs(module: elements::Module) -> elements::Module {
|
||||
foreach_public_func_name(module, |n| {
|
||||
n.remove(0);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn shrink_unknown_stack(
|
||||
mut module: elements::Module,
|
||||
// for example, `shrink_amount = (1MB - 64KB)` will limit stack to 64KB
|
||||
shrink_amount: u32,
|
||||
) -> (elements::Module, u32) {
|
||||
let mut new_stack_top = 0;
|
||||
for section in module.sections_mut() {
|
||||
match section {
|
||||
elements::Section::Data(data_section) => {
|
||||
for data_segment in data_section.entries_mut() {
|
||||
if *data_segment
|
||||
.offset()
|
||||
.as_ref()
|
||||
.expect("parity-wasm is compiled without bulk-memory operations")
|
||||
.code() == [elements::Instruction::I32Const(4), elements::Instruction::End]
|
||||
{
|
||||
assert_eq!(data_segment.value().len(), 4);
|
||||
let current_val = LittleEndian::read_u32(data_segment.value());
|
||||
let new_val = current_val - shrink_amount;
|
||||
LittleEndian::write_u32(data_segment.value_mut(), new_val);
|
||||
new_stack_top = new_val;
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
(module, new_stack_top)
|
||||
}
|
||||
|
||||
pub fn externalize(module: elements::Module, replaced_funcs: Vec<&str>) -> elements::Module {
|
||||
// Save import functions number for later
|
||||
let import_funcs_total = module
|
||||
.import_section()
|
||||
.expect("Import section to exist")
|
||||
.entries()
|
||||
.iter()
|
||||
.filter(|e| matches!(e.external(), &elements::External::Function(_)))
|
||||
.count();
|
||||
|
||||
// First, we find functions indices that are to be rewired to externals
|
||||
// Triple is (function_index (callable), type_index, function_name)
|
||||
let mut replaces: Vec<Insertion> = replaced_funcs
|
||||
.into_iter()
|
||||
.filter_map(|f| {
|
||||
let export = module
|
||||
.export_section()
|
||||
.expect("Export section to exist")
|
||||
.entries()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|&(_, entry)| entry.field() == f)
|
||||
.expect("All functions of interest to exist");
|
||||
|
||||
if let elements::Internal::Function(func_idx) = *export.1.internal() {
|
||||
let type_ref =
|
||||
module.function_section().expect("Functions section to exist").entries()
|
||||
[func_idx as usize - import_funcs_total]
|
||||
.type_ref();
|
||||
|
||||
Some((export.0, func_idx, type_ref, export.1.field().to_owned()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
replaces.sort_by_key(|e| e.0);
|
||||
|
||||
// Second, we duplicate them as import definitions
|
||||
let mut mbuilder = builder::from_module(module);
|
||||
for (_, _, type_ref, field) in replaces.iter() {
|
||||
mbuilder.push_import(
|
||||
builder::import().module("env").field(field).external().func(*type_ref).build(),
|
||||
);
|
||||
}
|
||||
|
||||
// Back to mutable access
|
||||
let mut module = mbuilder.build();
|
||||
|
||||
// Third, rewire all calls to imported functions and update all other calls indices
|
||||
for section in module.sections_mut() {
|
||||
match section {
|
||||
elements::Section::Code(code_section) =>
|
||||
for func_body in code_section.bodies_mut() {
|
||||
update_call_index(func_body.code_mut(), import_funcs_total, &replaces);
|
||||
},
|
||||
elements::Section::Export(export_section) => {
|
||||
for export in export_section.entries_mut() {
|
||||
if let elements::Internal::Function(func_index) = export.internal_mut() {
|
||||
if *func_index >= import_funcs_total as u32 {
|
||||
*func_index += replaces.len() as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
elements::Section::Element(elements_section) => {
|
||||
for segment in elements_section.entries_mut() {
|
||||
// update all indirect call addresses initial values
|
||||
for func_index in segment.members_mut() {
|
||||
if *func_index >= import_funcs_total as u32 {
|
||||
*func_index += replaces.len() as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
module
|
||||
}
|
||||
@@ -1,28 +1,232 @@
|
||||
//! This module is used to instrument a Wasm module with gas metering code.
|
||||
//!
|
||||
//! The primary public interface is the `inject_gas_counter` function which transforms a given
|
||||
//! The primary public interface is the [`inject`] function which transforms a given
|
||||
//! module into one that charges gas for code to be executed. See function documentation for usage
|
||||
//! and details.
|
||||
|
||||
#[cfg(test)]
|
||||
mod validation;
|
||||
|
||||
use crate::std::{cmp::min, mem, vec::Vec};
|
||||
use alloc::{vec, vec::Vec};
|
||||
use core::{cmp::min, mem, num::NonZeroU32};
|
||||
use parity_wasm::{
|
||||
builder,
|
||||
elements::{self, Instruction, ValueType},
|
||||
};
|
||||
|
||||
use crate::rules::Rules;
|
||||
use parity_wasm::{builder, elements, elements::ValueType};
|
||||
/// An interface that describes instruction costs.
|
||||
pub trait Rules {
|
||||
/// Returns the cost for the passed `instruction`.
|
||||
///
|
||||
/// Returning `None` makes the gas instrumention end with an error. This is meant
|
||||
/// as a way to have a partial rule set where any instruction that is not specifed
|
||||
/// is considered as forbidden.
|
||||
fn instruction_cost(&self, instruction: &Instruction) -> Option<u32>;
|
||||
|
||||
pub fn update_call_index(instructions: &mut elements::Instructions, inserted_index: u32) {
|
||||
use parity_wasm::elements::Instruction::*;
|
||||
for instruction in instructions.elements_mut().iter_mut() {
|
||||
if let Call(call_index) = instruction {
|
||||
if *call_index >= inserted_index {
|
||||
*call_index += 1
|
||||
}
|
||||
/// Returns the costs for growing the memory using the `memory.grow` instruction.
|
||||
///
|
||||
/// Please note that these costs are in addition to the costs specified by `instruction_cost`
|
||||
/// for the `memory.grow` instruction. Those are meant as dynamic costs which take the
|
||||
/// amount of pages that the memory is grown by into consideration. This is not possible
|
||||
/// using `instruction_cost` because those costs depend on the stack and must be injected as
|
||||
/// code into the function calling `memory.grow`. Therefore returning anything but
|
||||
/// [`MemoryGrowCost::Free`] introduces some overhead to the `memory.grow` instruction.
|
||||
fn memory_grow_cost(&self) -> MemoryGrowCost;
|
||||
}
|
||||
|
||||
/// Dynamic costs for memory growth.
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum MemoryGrowCost {
|
||||
/// Skip per page charge.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This makes sense when the amount of pages that a module is allowed to use is limited
|
||||
/// to a rather small number by static validation. In that case it is viable to
|
||||
/// benchmark the costs of `memory.grow` as the worst case (growing to to the maximum
|
||||
/// number of pages).
|
||||
Free,
|
||||
/// Charge the specified amount for each page that the memory is grown by.
|
||||
Linear(NonZeroU32),
|
||||
}
|
||||
|
||||
impl MemoryGrowCost {
|
||||
/// True iff memory growths code needs to be injected.
|
||||
fn enabled(&self) -> bool {
|
||||
match self {
|
||||
Self::Free => false,
|
||||
Self::Linear(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that implements [`Rules`] so that every instruction costs the same.
|
||||
///
|
||||
/// This is a simplification that is mostly useful for development and testing.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// In a production environment it usually makes no sense to assign every instruction
|
||||
/// the same cost. A proper implemention of [`Rules`] should be prived that is probably
|
||||
/// created by benchmarking.
|
||||
pub struct ConstantCostRules {
|
||||
instruction_cost: u32,
|
||||
memory_grow_cost: u32,
|
||||
}
|
||||
|
||||
impl ConstantCostRules {
|
||||
/// Create a new [`ConstantCostRules`].
|
||||
///
|
||||
/// Uses `instruction_cost` for every instruction and `memory_grow_cost` to dynamically
|
||||
/// meter the memory growth instruction.
|
||||
pub fn new(instruction_cost: u32, memory_grow_cost: u32) -> Self {
|
||||
Self { instruction_cost, memory_grow_cost }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConstantCostRules {
|
||||
/// Uses instruction cost of `1` and disables memory growth instrumentation.
|
||||
fn default() -> Self {
|
||||
Self { instruction_cost: 1, memory_grow_cost: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Rules for ConstantCostRules {
|
||||
fn instruction_cost(&self, _: &Instruction) -> Option<u32> {
|
||||
Some(self.instruction_cost)
|
||||
}
|
||||
|
||||
fn memory_grow_cost(&self) -> MemoryGrowCost {
|
||||
NonZeroU32::new(self.memory_grow_cost).map_or(MemoryGrowCost::Free, MemoryGrowCost::Linear)
|
||||
}
|
||||
}
|
||||
|
||||
/// Transforms a given module into one that charges gas for code to be executed by proxy of an
|
||||
/// imported gas metering function.
|
||||
///
|
||||
/// The output module imports a function "gas" from the specified module with type signature
|
||||
/// [i32] -> []. The argument is the amount of gas required to continue execution. The external
|
||||
/// function is meant to keep track of the total amount of gas used and trap or otherwise halt
|
||||
/// execution of the runtime if the gas usage exceeds some allowed limit.
|
||||
///
|
||||
/// The body of each function is divided into metered blocks, and the calls to charge gas are
|
||||
/// inserted at the beginning of every such block of code. A metered block is defined so that,
|
||||
/// unless there is a trap, either all of the instructions are executed or none are. These are
|
||||
/// similar to basic blocks in a control flow graph, except that in some cases multiple basic
|
||||
/// blocks can be merged into a single metered block. This is the case if any path through the
|
||||
/// control flow graph containing one basic block also contains another.
|
||||
///
|
||||
/// Charging gas is at the beginning of each metered block ensures that 1) all instructions
|
||||
/// executed are already paid for, 2) instructions that will not be executed are not charged for
|
||||
/// unless execution traps, and 3) the number of calls to "gas" is minimized. The corollary is that
|
||||
/// modules instrumented with this metering code may charge gas for instructions not executed in
|
||||
/// the event of a trap.
|
||||
///
|
||||
/// Additionally, each `memory.grow` instruction found in the module is instrumented to first make
|
||||
/// a call to charge gas for the additional pages requested. This cannot be done as part of the
|
||||
/// block level gas charges as the gas cost is not static and depends on the stack argument to
|
||||
/// `memory.grow`.
|
||||
///
|
||||
/// The above transformations are performed for every function body defined in the module. This
|
||||
/// function also rewrites all function indices references by code, table elements, etc., since
|
||||
/// the addition of an imported functions changes the indices of module-defined functions.
|
||||
///
|
||||
/// This routine runs in time linear in the size of the input module.
|
||||
///
|
||||
/// The function fails if the module contains any operation forbidden by gas rule set, returning
|
||||
/// the original module as an Err.
|
||||
pub fn inject<R: Rules>(
|
||||
module: elements::Module,
|
||||
rules: &R,
|
||||
gas_module_name: &str,
|
||||
) -> Result<elements::Module, elements::Module> {
|
||||
// Injecting gas counting external
|
||||
let mut mbuilder = builder::from_module(module);
|
||||
let import_sig =
|
||||
mbuilder.push_signature(builder::signature().with_param(ValueType::I32).build_sig());
|
||||
|
||||
mbuilder.push_import(
|
||||
builder::import()
|
||||
.module(gas_module_name)
|
||||
.field("gas")
|
||||
.external()
|
||||
.func(import_sig)
|
||||
.build(),
|
||||
);
|
||||
|
||||
// back to plain module
|
||||
let mut module = mbuilder.build();
|
||||
|
||||
// calculate actual function index of the imported definition
|
||||
// (subtract all imports that are NOT functions)
|
||||
|
||||
let gas_func = module.import_count(elements::ImportCountType::Function) as u32 - 1;
|
||||
let total_func = module.functions_space() as u32;
|
||||
let mut need_grow_counter = false;
|
||||
let mut error = false;
|
||||
|
||||
// Updating calling addresses (all calls to function index >= `gas_func` should be incremented)
|
||||
for section in module.sections_mut() {
|
||||
match section {
|
||||
elements::Section::Code(code_section) =>
|
||||
for func_body in code_section.bodies_mut() {
|
||||
for instruction in func_body.code_mut().elements_mut().iter_mut() {
|
||||
if let Instruction::Call(call_index) = instruction {
|
||||
if *call_index >= gas_func {
|
||||
*call_index += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if inject_counter(func_body.code_mut(), rules, gas_func).is_err() {
|
||||
error = true;
|
||||
break
|
||||
}
|
||||
if rules.memory_grow_cost().enabled() &&
|
||||
inject_grow_counter(func_body.code_mut(), total_func) > 0
|
||||
{
|
||||
need_grow_counter = true;
|
||||
}
|
||||
},
|
||||
elements::Section::Export(export_section) => {
|
||||
for export in export_section.entries_mut() {
|
||||
if let elements::Internal::Function(func_index) = export.internal_mut() {
|
||||
if *func_index >= gas_func {
|
||||
*func_index += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
elements::Section::Element(elements_section) => {
|
||||
// Note that we do not need to check the element type referenced because in the
|
||||
// WebAssembly 1.0 spec, the only allowed element type is funcref.
|
||||
for segment in elements_section.entries_mut() {
|
||||
// update all indirect call addresses initial values
|
||||
for func_index in segment.members_mut() {
|
||||
if *func_index >= gas_func {
|
||||
*func_index += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
elements::Section::Start(start_idx) =>
|
||||
if *start_idx >= gas_func {
|
||||
*start_idx += 1
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
if error {
|
||||
return Err(module)
|
||||
}
|
||||
|
||||
if need_grow_counter {
|
||||
Ok(add_grow_counter(module, rules, gas_func))
|
||||
} else {
|
||||
Ok(module)
|
||||
}
|
||||
}
|
||||
|
||||
/// A control flow block is opened with the `block`, `loop`, and `if` instructions and is closed
|
||||
/// with `end`. Each block implicitly defines a new label. The control blocks form a stack during
|
||||
/// program execution.
|
||||
@@ -40,7 +244,6 @@ pub fn update_call_index(instructions: &mut elements::Instructions, inserted_ind
|
||||
/// ```
|
||||
///
|
||||
/// The start of the block is `i32.const 1`.
|
||||
///
|
||||
#[derive(Debug)]
|
||||
struct ControlBlock {
|
||||
/// The lowest control stack index corresponding to a forward jump targeted by a br, br_if, or
|
||||
@@ -65,7 +268,7 @@ struct ControlBlock {
|
||||
/// are constructed with the property that, in the absence of any traps, either all instructions in
|
||||
/// the block are executed or none are.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct MeteredBlock {
|
||||
struct MeteredBlock {
|
||||
/// Index of the first instruction (aka `Opcode`) in the block.
|
||||
start_pos: usize,
|
||||
/// Sum of costs of all instructions until end of the block.
|
||||
@@ -232,12 +435,11 @@ fn add_grow_counter<R: Rules>(
|
||||
rules: &R,
|
||||
gas_func: u32,
|
||||
) -> elements::Module {
|
||||
use crate::rules::MemoryGrowCost;
|
||||
use parity_wasm::elements::Instruction::*;
|
||||
|
||||
let cost = match rules.memory_grow_cost() {
|
||||
None => return module,
|
||||
Some(MemoryGrowCost::Linear(val)) => val.get(),
|
||||
MemoryGrowCost::Free => return module,
|
||||
MemoryGrowCost::Linear(val) => val.get(),
|
||||
};
|
||||
|
||||
let mut b = builder::from_module(module);
|
||||
@@ -253,7 +455,8 @@ fn add_grow_counter<R: Rules>(
|
||||
GetLocal(0),
|
||||
I32Const(cost as i32),
|
||||
I32Mul,
|
||||
// todo: there should be strong guarantee that it does not return anything on stack?
|
||||
// todo: there should be strong guarantee that it does not return anything on
|
||||
// stack?
|
||||
Call(gas_func),
|
||||
GrowMemory(0),
|
||||
End,
|
||||
@@ -265,7 +468,7 @@ fn add_grow_counter<R: Rules>(
|
||||
b.build()
|
||||
}
|
||||
|
||||
pub(crate) fn determine_metered_blocks<R: Rules>(
|
||||
fn determine_metered_blocks<R: Rules>(
|
||||
instructions: &elements::Instructions,
|
||||
rules: &R,
|
||||
) -> Result<Vec<MeteredBlock>, ()> {
|
||||
@@ -339,7 +542,7 @@ pub(crate) fn determine_metered_blocks<R: Rules>(
|
||||
Ok(counter.finalized_blocks)
|
||||
}
|
||||
|
||||
pub fn inject_counter<R: Rules>(
|
||||
fn inject_counter<R: Rules>(
|
||||
instructions: &mut elements::Instructions,
|
||||
rules: &R,
|
||||
gas_func: u32,
|
||||
@@ -393,133 +596,12 @@ fn insert_metering_calls(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Transforms a given module into one that charges gas for code to be executed by proxy of an
|
||||
/// imported gas metering function.
|
||||
///
|
||||
/// The output module imports a function "gas" from the specified module with type signature
|
||||
/// [i32] -> []. The argument is the amount of gas required to continue execution. The external
|
||||
/// function is meant to keep track of the total amount of gas used and trap or otherwise halt
|
||||
/// execution of the runtime if the gas usage exceeds some allowed limit.
|
||||
///
|
||||
/// The body of each function is divided into metered blocks, and the calls to charge gas are
|
||||
/// inserted at the beginning of every such block of code. A metered block is defined so that,
|
||||
/// unless there is a trap, either all of the instructions are executed or none are. These are
|
||||
/// similar to basic blocks in a control flow graph, except that in some cases multiple basic
|
||||
/// blocks can be merged into a single metered block. This is the case if any path through the
|
||||
/// control flow graph containing one basic block also contains another.
|
||||
///
|
||||
/// Charging gas is at the beginning of each metered block ensures that 1) all instructions
|
||||
/// executed are already paid for, 2) instructions that will not be executed are not charged for
|
||||
/// unless execution traps, and 3) the number of calls to "gas" is minimized. The corollary is that
|
||||
/// modules instrumented with this metering code may charge gas for instructions not executed in
|
||||
/// the event of a trap.
|
||||
///
|
||||
/// Additionally, each `memory.grow` instruction found in the module is instrumented to first make
|
||||
/// a call to charge gas for the additional pages requested. This cannot be done as part of the
|
||||
/// block level gas charges as the gas cost is not static and depends on the stack argument to
|
||||
/// `memory.grow`.
|
||||
///
|
||||
/// The above transformations are performed for every function body defined in the module. This
|
||||
/// function also rewrites all function indices references by code, table elements, etc., since
|
||||
/// the addition of an imported functions changes the indices of module-defined functions.
|
||||
///
|
||||
/// This routine runs in time linear in the size of the input module.
|
||||
///
|
||||
/// The function fails if the module contains any operation forbidden by gas rule set, returning
|
||||
/// the original module as an Err.
|
||||
pub fn inject_gas_counter<R: Rules>(
|
||||
module: elements::Module,
|
||||
rules: &R,
|
||||
gas_module_name: &str,
|
||||
) -> Result<elements::Module, elements::Module> {
|
||||
// Injecting gas counting external
|
||||
let mut mbuilder = builder::from_module(module);
|
||||
let import_sig =
|
||||
mbuilder.push_signature(builder::signature().with_param(ValueType::I32).build_sig());
|
||||
|
||||
mbuilder.push_import(
|
||||
builder::import()
|
||||
.module(gas_module_name)
|
||||
.field("gas")
|
||||
.external()
|
||||
.func(import_sig)
|
||||
.build(),
|
||||
);
|
||||
|
||||
// back to plain module
|
||||
let mut module = mbuilder.build();
|
||||
|
||||
// calculate actual function index of the imported definition
|
||||
// (subtract all imports that are NOT functions)
|
||||
|
||||
let gas_func = module.import_count(elements::ImportCountType::Function) as u32 - 1;
|
||||
let total_func = module.functions_space() as u32;
|
||||
let mut need_grow_counter = false;
|
||||
let mut error = false;
|
||||
|
||||
// Updating calling addresses (all calls to function index >= `gas_func` should be incremented)
|
||||
for section in module.sections_mut() {
|
||||
match section {
|
||||
elements::Section::Code(code_section) =>
|
||||
for func_body in code_section.bodies_mut() {
|
||||
update_call_index(func_body.code_mut(), gas_func);
|
||||
if inject_counter(func_body.code_mut(), rules, gas_func).is_err() {
|
||||
error = true;
|
||||
break
|
||||
}
|
||||
if rules.memory_grow_cost().is_some() &&
|
||||
inject_grow_counter(func_body.code_mut(), total_func) > 0
|
||||
{
|
||||
need_grow_counter = true;
|
||||
}
|
||||
},
|
||||
elements::Section::Export(export_section) => {
|
||||
for export in export_section.entries_mut() {
|
||||
if let elements::Internal::Function(func_index) = export.internal_mut() {
|
||||
if *func_index >= gas_func {
|
||||
*func_index += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
elements::Section::Element(elements_section) => {
|
||||
// Note that we do not need to check the element type referenced because in the
|
||||
// WebAssembly 1.0 spec, the only allowed element type is funcref.
|
||||
for segment in elements_section.entries_mut() {
|
||||
// update all indirect call addresses initial values
|
||||
for func_index in segment.members_mut() {
|
||||
if *func_index >= gas_func {
|
||||
*func_index += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
elements::Section::Start(start_idx) =>
|
||||
if *start_idx >= gas_func {
|
||||
*start_idx += 1
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
if error {
|
||||
return Err(module)
|
||||
}
|
||||
|
||||
if need_grow_counter {
|
||||
Ok(add_grow_counter(module, rules, gas_func))
|
||||
} else {
|
||||
Ok(module)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::rules;
|
||||
use parity_wasm::{builder, elements, elements::Instruction::*, serialize};
|
||||
|
||||
pub fn get_function_body(
|
||||
fn get_function_body(
|
||||
module: &elements::Module,
|
||||
index: usize,
|
||||
) -> Option<&[elements::Instruction]> {
|
||||
@@ -547,9 +629,7 @@ mod tests {
|
||||
.build()
|
||||
.build();
|
||||
|
||||
let injected_module =
|
||||
inject_gas_counter(module, &rules::Set::default().with_grow_cost(10000), "env")
|
||||
.unwrap();
|
||||
let injected_module = inject(module, &ConstantCostRules::new(1, 10_000), "env").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
get_function_body(&injected_module, 0).unwrap(),
|
||||
@@ -583,7 +663,7 @@ mod tests {
|
||||
.build()
|
||||
.build();
|
||||
|
||||
let injected_module = inject_gas_counter(module, &rules::Set::default(), "env").unwrap();
|
||||
let injected_module = inject(module, &ConstantCostRules::default(), "env").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
get_function_body(&injected_module, 0).unwrap(),
|
||||
@@ -634,7 +714,7 @@ mod tests {
|
||||
.build()
|
||||
.build();
|
||||
|
||||
let injected_module = inject_gas_counter(module, &rules::Set::default(), "env").unwrap();
|
||||
let injected_module = inject(module, &ConstantCostRules::default(), "env").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
get_function_body(&injected_module, 1).unwrap(),
|
||||
@@ -660,31 +740,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forbidden() {
|
||||
let module = builder::module()
|
||||
.global()
|
||||
.value_type()
|
||||
.i32()
|
||||
.build()
|
||||
.function()
|
||||
.signature()
|
||||
.param()
|
||||
.i32()
|
||||
.build()
|
||||
.body()
|
||||
.with_instructions(elements::Instructions::new(vec![F32Const(555555), End]))
|
||||
.build()
|
||||
.build()
|
||||
.build();
|
||||
|
||||
let rules = rules::Set::default().with_forbidden_floats();
|
||||
|
||||
if inject_gas_counter(module, &rules, "env").is_ok() {
|
||||
panic!("Should be error because of the forbidden operation")
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_wat(source: &str) -> elements::Module {
|
||||
let module_bytes = wabt::Wat2Wasm::new()
|
||||
.validate(false)
|
||||
@@ -700,9 +755,8 @@ mod tests {
|
||||
let input_module = parse_wat($input);
|
||||
let expected_module = parse_wat($expected);
|
||||
|
||||
let injected_module =
|
||||
inject_gas_counter(input_module, &rules::Set::default(), "env")
|
||||
.expect("inject_gas_counter call failed");
|
||||
let injected_module = inject(input_module, &ConstantCostRules::default(), "env")
|
||||
.expect("inject_gas_counter call failed");
|
||||
|
||||
let actual_func_body = get_function_body(&injected_module, 0)
|
||||
.expect("injected module must have a function body");
|
||||
@@ -8,17 +8,9 @@
|
||||
//! searching through all paths, which may take exponential time in the size of the function body in
|
||||
//! the worst case.
|
||||
|
||||
use super::MeteredBlock;
|
||||
use crate::{
|
||||
rules::{Rules, Set as RuleSet},
|
||||
std::vec::Vec,
|
||||
};
|
||||
use super::{ConstantCostRules, MeteredBlock, Rules};
|
||||
use parity_wasm::elements::{FuncBody, Instruction};
|
||||
|
||||
#[cfg(not(features = "std"))]
|
||||
use crate::std::collections::BTreeMap as Map;
|
||||
#[cfg(features = "std")]
|
||||
use crate::std::collections::HashMap as Map;
|
||||
use std::collections::BTreeMap as Map;
|
||||
|
||||
/// An ID for a node in a ControlFlowGraph.
|
||||
type NodeId = usize;
|
||||
@@ -130,7 +122,7 @@ impl ControlFrame {
|
||||
/// This assumes that the function body has been validated already, otherwise this may panic.
|
||||
fn build_control_flow_graph(
|
||||
body: &FuncBody,
|
||||
rules: &RuleSet,
|
||||
rules: &impl Rules,
|
||||
blocks: &[MeteredBlock],
|
||||
) -> Result<ControlFlowGraph, ()> {
|
||||
let mut graph = ControlFlowGraph::new();
|
||||
@@ -324,7 +316,7 @@ fn validate_graph_gas_costs(graph: &ControlFlowGraph) -> bool {
|
||||
/// This assumes that the function body has been validated already, otherwise this may panic.
|
||||
fn validate_metering_injections(
|
||||
body: &FuncBody,
|
||||
rules: &RuleSet,
|
||||
rules: &impl Rules,
|
||||
blocks: &[MeteredBlock],
|
||||
) -> Result<bool, ()> {
|
||||
let graph = build_control_flow_graph(body, rules, blocks)?;
|
||||
@@ -349,7 +341,7 @@ mod tests {
|
||||
.expect("failed to parse Wasm blob generated by translate_to_fuzz");
|
||||
|
||||
for func_body in module.code_section().iter().flat_map(|section| section.bodies()) {
|
||||
let rules = RuleSet::default();
|
||||
let rules = ConstantCostRules::default();
|
||||
|
||||
let metered_blocks = determine_metered_blocks(func_body.code(), &rules).unwrap();
|
||||
let success =
|
||||
-956
@@ -1,956 +0,0 @@
|
||||
//! Wasm binary graph format
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use super::ref_list::{EntryRef, RefList};
|
||||
use crate::std::{borrow::ToOwned, collections::BTreeMap, string::String, vec::Vec};
|
||||
use parity_wasm::elements;
|
||||
|
||||
/// Imported or declared variant of the same thing.
|
||||
///
|
||||
/// In WebAssembly, function/global/memory/table instances can either be
|
||||
/// imported or declared internally, forming united index space.
|
||||
#[derive(Debug)]
|
||||
pub enum ImportedOrDeclared<T = ()> {
|
||||
/// Variant for imported instances.
|
||||
Imported(String, String),
|
||||
/// Variant for instances declared internally in the module.
|
||||
Declared(T),
|
||||
}
|
||||
|
||||
impl<T> From<&elements::ImportEntry> for ImportedOrDeclared<T> {
|
||||
fn from(v: &elements::ImportEntry) -> Self {
|
||||
ImportedOrDeclared::Imported(v.module().to_owned(), v.field().to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for this module
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Inconsistent source representation
|
||||
InconsistentSource,
|
||||
/// Format error
|
||||
Format(elements::Error),
|
||||
/// Detached entry
|
||||
DetachedEntry,
|
||||
}
|
||||
|
||||
/// Function origin (imported or internal).
|
||||
pub type FuncOrigin = ImportedOrDeclared<FuncBody>;
|
||||
/// Global origin (imported or internal).
|
||||
pub type GlobalOrigin = ImportedOrDeclared<Vec<Instruction>>;
|
||||
/// Memory origin (imported or internal).
|
||||
pub type MemoryOrigin = ImportedOrDeclared;
|
||||
/// Table origin (imported or internal).
|
||||
pub type TableOrigin = ImportedOrDeclared;
|
||||
|
||||
/// Function body.
|
||||
///
|
||||
/// Function consist of declaration (signature, i.e. type reference)
|
||||
/// and the actual code. This part is the actual code.
|
||||
#[derive(Debug)]
|
||||
pub struct FuncBody {
|
||||
pub locals: Vec<elements::Local>,
|
||||
pub code: Vec<Instruction>,
|
||||
}
|
||||
|
||||
/// Function declaration.
|
||||
///
|
||||
/// As with other instances, functions can be either imported or declared
|
||||
/// within the module - `origin` field is handling this.
|
||||
#[derive(Debug)]
|
||||
pub struct Func {
|
||||
/// Function signature/type reference.
|
||||
pub type_ref: EntryRef<elements::Type>,
|
||||
/// Where this function comes from (imported or declared).
|
||||
pub origin: FuncOrigin,
|
||||
}
|
||||
|
||||
/// Global declaration.
|
||||
///
|
||||
/// As with other instances, globals can be either imported or declared
|
||||
/// within the module - `origin` field is handling this.
|
||||
#[derive(Debug)]
|
||||
pub struct Global {
|
||||
pub content: elements::ValueType,
|
||||
pub is_mut: bool,
|
||||
pub origin: GlobalOrigin,
|
||||
}
|
||||
|
||||
/// Instruction.
|
||||
///
|
||||
/// Some instructions don't reference any entities within the WebAssembly module,
|
||||
/// while others do. This enum is for tracking references when required.
|
||||
#[derive(Debug)]
|
||||
pub enum Instruction {
|
||||
/// WebAssembly instruction that does not reference any module entities.
|
||||
Plain(elements::Instruction),
|
||||
/// Call instruction which references the function.
|
||||
Call(EntryRef<Func>),
|
||||
/// Indirect call instruction which references function type (function signature).
|
||||
CallIndirect(EntryRef<elements::Type>, u8),
|
||||
/// get_global instruction which references the global.
|
||||
GetGlobal(EntryRef<Global>),
|
||||
/// set_global instruction which references the global.
|
||||
SetGlobal(EntryRef<Global>),
|
||||
}
|
||||
|
||||
/// Memory instance decriptor.
|
||||
///
|
||||
/// As with other similar instances, memory instances can be either imported
|
||||
/// or declared within the module - `origin` field is handling this.
|
||||
#[derive(Debug)]
|
||||
pub struct Memory {
|
||||
/// Declared limits of the table instance.
|
||||
pub limits: elements::ResizableLimits,
|
||||
/// Origin of the memory instance (internal or imported).
|
||||
pub origin: MemoryOrigin,
|
||||
}
|
||||
|
||||
/// Memory instance decriptor.
|
||||
///
|
||||
/// As with other similar instances, memory instances can be either imported
|
||||
/// or declared within the module - `origin` field is handling this.
|
||||
#[derive(Debug)]
|
||||
pub struct Table {
|
||||
/// Declared limits of the table instance.
|
||||
pub limits: elements::ResizableLimits,
|
||||
/// Origin of the table instance (internal or imported).
|
||||
pub origin: TableOrigin,
|
||||
}
|
||||
|
||||
/// Segment location.
|
||||
///
|
||||
/// Reserved for future use. Currenty only `Default` variant is supported.
|
||||
#[derive(Debug)]
|
||||
pub enum SegmentLocation {
|
||||
/// Not used currently.
|
||||
Passive,
|
||||
/// Default segment location with index `0`.
|
||||
Default(Vec<Instruction>),
|
||||
/// Not used currently.
|
||||
WithIndex(u32, Vec<Instruction>),
|
||||
}
|
||||
|
||||
/// Data segment of data section.
|
||||
#[derive(Debug)]
|
||||
pub struct DataSegment {
|
||||
/// Location of the segment in the linear memory.
|
||||
pub location: SegmentLocation,
|
||||
/// Raw value of the data segment.
|
||||
pub value: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Element segment of element section.
|
||||
#[derive(Debug)]
|
||||
pub struct ElementSegment {
|
||||
/// Location of the segment in the table space.
|
||||
pub location: SegmentLocation,
|
||||
/// Raw value (function indices) of the element segment.
|
||||
pub value: Vec<EntryRef<Func>>,
|
||||
}
|
||||
|
||||
/// Export entry reference.
|
||||
///
|
||||
/// Module can export function, global, table or memory instance
|
||||
/// under specific name (field).
|
||||
#[derive(Debug)]
|
||||
pub enum ExportLocal {
|
||||
/// Function reference.
|
||||
Func(EntryRef<Func>),
|
||||
/// Global reference.
|
||||
Global(EntryRef<Global>),
|
||||
/// Table reference.
|
||||
Table(EntryRef<Table>),
|
||||
/// Memory reference.
|
||||
Memory(EntryRef<Memory>),
|
||||
}
|
||||
|
||||
/// Export entry description.
|
||||
#[derive(Debug)]
|
||||
pub struct Export {
|
||||
/// Name (field) of the export entry.
|
||||
pub name: String,
|
||||
/// What entity is exported.
|
||||
pub local: ExportLocal,
|
||||
}
|
||||
|
||||
/// Module
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Module {
|
||||
/// Refence-tracking list of types.
|
||||
pub types: RefList<elements::Type>,
|
||||
/// Refence-tracking list of funcs.
|
||||
pub funcs: RefList<Func>,
|
||||
/// Refence-tracking list of memory instances.
|
||||
pub memory: RefList<Memory>,
|
||||
/// Refence-tracking list of table instances.
|
||||
pub tables: RefList<Table>,
|
||||
/// Refence-tracking list of globals.
|
||||
pub globals: RefList<Global>,
|
||||
/// Reference to start function.
|
||||
pub start: Option<EntryRef<Func>>,
|
||||
/// References to exported objects.
|
||||
pub exports: Vec<Export>,
|
||||
/// List of element segments.
|
||||
pub elements: Vec<ElementSegment>,
|
||||
/// List of data segments.
|
||||
pub data: Vec<DataSegment>,
|
||||
/// Other module functions that are not decoded or processed.
|
||||
pub other: BTreeMap<usize, elements::Section>,
|
||||
}
|
||||
|
||||
impl Module {
|
||||
fn map_instructions(&self, instructions: &[elements::Instruction]) -> Vec<Instruction> {
|
||||
use parity_wasm::elements::Instruction::*;
|
||||
instructions
|
||||
.iter()
|
||||
.map(|instruction| match instruction {
|
||||
Call(func_idx) => Instruction::Call(self.funcs.clone_ref(*func_idx as usize)),
|
||||
CallIndirect(type_idx, arg2) =>
|
||||
Instruction::CallIndirect(self.types.clone_ref(*type_idx as usize), *arg2),
|
||||
SetGlobal(global_idx) =>
|
||||
Instruction::SetGlobal(self.globals.clone_ref(*global_idx as usize)),
|
||||
GetGlobal(global_idx) =>
|
||||
Instruction::GetGlobal(self.globals.clone_ref(*global_idx as usize)),
|
||||
other_instruction => Instruction::Plain(other_instruction.clone()),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn generate_instructions(&self, instructions: &[Instruction]) -> Vec<elements::Instruction> {
|
||||
use parity_wasm::elements::Instruction::*;
|
||||
instructions
|
||||
.iter()
|
||||
.map(|instruction| match instruction {
|
||||
Instruction::Call(func_ref) =>
|
||||
Call(func_ref.order().expect("detached instruction!") as u32),
|
||||
Instruction::CallIndirect(type_ref, arg2) =>
|
||||
CallIndirect(type_ref.order().expect("detached instruction!") as u32, *arg2),
|
||||
Instruction::SetGlobal(global_ref) =>
|
||||
SetGlobal(global_ref.order().expect("detached instruction!") as u32),
|
||||
Instruction::GetGlobal(global_ref) =>
|
||||
GetGlobal(global_ref.order().expect("detached instruction!") as u32),
|
||||
Instruction::Plain(plain) => plain.clone(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Initialize module from parity-wasm `Module`.
|
||||
pub fn from_elements(module: &elements::Module) -> Result<Self, Error> {
|
||||
let mut res = Module::default();
|
||||
let mut imported_functions = 0;
|
||||
|
||||
for (idx, section) in module.sections().iter().enumerate() {
|
||||
match section {
|
||||
elements::Section::Type(type_section) => {
|
||||
res.types = RefList::from_slice(type_section.types());
|
||||
},
|
||||
elements::Section::Import(import_section) =>
|
||||
for entry in import_section.entries() {
|
||||
match *entry.external() {
|
||||
elements::External::Function(f) => {
|
||||
res.funcs.push(Func {
|
||||
type_ref: res
|
||||
.types
|
||||
.get(f as usize)
|
||||
.ok_or(Error::InconsistentSource)?
|
||||
.clone(),
|
||||
origin: entry.into(),
|
||||
});
|
||||
imported_functions += 1;
|
||||
},
|
||||
elements::External::Memory(m) => {
|
||||
res.memory
|
||||
.push(Memory { limits: *m.limits(), origin: entry.into() });
|
||||
},
|
||||
elements::External::Global(g) => {
|
||||
res.globals.push(Global {
|
||||
content: g.content_type(),
|
||||
is_mut: g.is_mutable(),
|
||||
origin: entry.into(),
|
||||
});
|
||||
},
|
||||
elements::External::Table(t) => {
|
||||
res.tables
|
||||
.push(Table { limits: *t.limits(), origin: entry.into() });
|
||||
},
|
||||
};
|
||||
},
|
||||
elements::Section::Function(function_section) => {
|
||||
for f in function_section.entries() {
|
||||
res.funcs.push(Func {
|
||||
type_ref: res
|
||||
.types
|
||||
.get(f.type_ref() as usize)
|
||||
.ok_or(Error::InconsistentSource)?
|
||||
.clone(),
|
||||
origin: ImportedOrDeclared::Declared(FuncBody {
|
||||
locals: Vec::new(),
|
||||
// code will be populated later
|
||||
code: Vec::new(),
|
||||
}),
|
||||
});
|
||||
}
|
||||
},
|
||||
elements::Section::Table(table_section) =>
|
||||
for t in table_section.entries() {
|
||||
res.tables.push(Table {
|
||||
limits: *t.limits(),
|
||||
origin: ImportedOrDeclared::Declared(()),
|
||||
});
|
||||
},
|
||||
elements::Section::Memory(table_section) =>
|
||||
for t in table_section.entries() {
|
||||
res.memory.push(Memory {
|
||||
limits: *t.limits(),
|
||||
origin: ImportedOrDeclared::Declared(()),
|
||||
});
|
||||
},
|
||||
elements::Section::Global(global_section) =>
|
||||
for g in global_section.entries() {
|
||||
let init_code = res.map_instructions(g.init_expr().code());
|
||||
res.globals.push(Global {
|
||||
content: g.global_type().content_type(),
|
||||
is_mut: g.global_type().is_mutable(),
|
||||
origin: ImportedOrDeclared::Declared(init_code),
|
||||
});
|
||||
},
|
||||
elements::Section::Export(export_section) =>
|
||||
for e in export_section.entries() {
|
||||
let local = match e.internal() {
|
||||
elements::Internal::Function(func_idx) =>
|
||||
ExportLocal::Func(res.funcs.clone_ref(*func_idx as usize)),
|
||||
elements::Internal::Global(global_idx) =>
|
||||
ExportLocal::Global(res.globals.clone_ref(*global_idx as usize)),
|
||||
elements::Internal::Memory(mem_idx) =>
|
||||
ExportLocal::Memory(res.memory.clone_ref(*mem_idx as usize)),
|
||||
elements::Internal::Table(table_idx) =>
|
||||
ExportLocal::Table(res.tables.clone_ref(*table_idx as usize)),
|
||||
};
|
||||
|
||||
res.exports.push(Export { local, name: e.field().to_owned() })
|
||||
},
|
||||
elements::Section::Start(start_func) => {
|
||||
res.start = Some(res.funcs.clone_ref(*start_func as usize));
|
||||
},
|
||||
elements::Section::Element(element_section) => {
|
||||
for element_segment in element_section.entries() {
|
||||
// let location = if element_segment.passive() {
|
||||
// SegmentLocation::Passive
|
||||
// } else if element_segment.index() == 0 {
|
||||
// SegmentLocation::Default(Vec::new())
|
||||
// } else {
|
||||
// SegmentLocation::WithIndex(element_segment.index(), Vec::new())
|
||||
// };
|
||||
|
||||
// TODO: update parity-wasm and uncomment the above instead
|
||||
let init_expr = element_segment
|
||||
.offset()
|
||||
.as_ref()
|
||||
.expect("parity-wasm is compiled without bulk-memory operations")
|
||||
.code();
|
||||
let location = SegmentLocation::Default(res.map_instructions(init_expr));
|
||||
|
||||
let funcs_map = element_segment
|
||||
.members()
|
||||
.iter()
|
||||
.map(|idx| res.funcs.clone_ref(*idx as usize))
|
||||
.collect::<Vec<EntryRef<Func>>>();
|
||||
|
||||
res.elements.push(ElementSegment { value: funcs_map, location });
|
||||
}
|
||||
},
|
||||
elements::Section::Code(code_section) => {
|
||||
for (idx, func_body) in code_section.bodies().iter().enumerate() {
|
||||
let code = res.map_instructions(func_body.code().elements());
|
||||
let mut func = res.funcs.get_ref(imported_functions + idx).write();
|
||||
match &mut func.origin {
|
||||
ImportedOrDeclared::Declared(body) => {
|
||||
body.code = code;
|
||||
body.locals = func_body.locals().to_vec();
|
||||
},
|
||||
_ => return Err(Error::InconsistentSource),
|
||||
}
|
||||
}
|
||||
},
|
||||
elements::Section::Data(data_section) => {
|
||||
for data_segment in data_section.entries() {
|
||||
// TODO: update parity-wasm and use the same logic as in
|
||||
// commented element segment branch
|
||||
let init_expr = data_segment
|
||||
.offset()
|
||||
.as_ref()
|
||||
.expect("parity-wasm is compiled without bulk-memory operations")
|
||||
.code();
|
||||
let location = SegmentLocation::Default(res.map_instructions(init_expr));
|
||||
|
||||
res.data
|
||||
.push(DataSegment { value: data_segment.value().to_vec(), location });
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
res.other.insert(idx, section.clone());
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Generate raw format representation.
|
||||
pub fn generate(&self) -> Result<elements::Module, Error> {
|
||||
use self::ImportedOrDeclared::*;
|
||||
|
||||
let mut idx = 0;
|
||||
let mut sections = Vec::new();
|
||||
|
||||
custom_round(&self.other, &mut idx, &mut sections);
|
||||
|
||||
if !self.types.is_empty() {
|
||||
// TYPE SECTION (1)
|
||||
let mut type_section = elements::TypeSection::default();
|
||||
{
|
||||
let types = type_section.types_mut();
|
||||
|
||||
for type_entry in self.types.iter() {
|
||||
types.push(type_entry.read().clone())
|
||||
}
|
||||
}
|
||||
sections.push(elements::Section::Type(type_section));
|
||||
idx += 1;
|
||||
|
||||
custom_round(&self.other, &mut idx, &mut sections);
|
||||
}
|
||||
|
||||
// IMPORT SECTION (2)
|
||||
let mut import_section = elements::ImportSection::default();
|
||||
|
||||
let add = {
|
||||
let imports = import_section.entries_mut();
|
||||
for func in self.funcs.iter() {
|
||||
match &func.read().origin {
|
||||
Imported(module, field) => imports.push(elements::ImportEntry::new(
|
||||
module.to_owned(),
|
||||
field.to_owned(),
|
||||
elements::External::Function(
|
||||
func.read().type_ref.order().ok_or(Error::DetachedEntry)? as u32,
|
||||
),
|
||||
)),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
for global in self.globals.iter() {
|
||||
match &global.read().origin {
|
||||
Imported(module, field) => imports.push(elements::ImportEntry::new(
|
||||
module.to_owned(),
|
||||
field.to_owned(),
|
||||
elements::External::Global(elements::GlobalType::new(
|
||||
global.read().content,
|
||||
global.read().is_mut,
|
||||
)),
|
||||
)),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
for memory in self.memory.iter() {
|
||||
match &memory.read().origin {
|
||||
Imported(module, field) => imports.push(elements::ImportEntry::new(
|
||||
module.to_owned(),
|
||||
field.to_owned(),
|
||||
elements::External::Memory(elements::MemoryType::new(
|
||||
memory.read().limits.initial(),
|
||||
memory.read().limits.maximum(),
|
||||
)),
|
||||
)),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
for table in self.tables.iter() {
|
||||
match &table.read().origin {
|
||||
Imported(module, field) => imports.push(elements::ImportEntry::new(
|
||||
module.to_owned(),
|
||||
field.to_owned(),
|
||||
elements::External::Table(elements::TableType::new(
|
||||
table.read().limits.initial(),
|
||||
table.read().limits.maximum(),
|
||||
)),
|
||||
)),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
!imports.is_empty()
|
||||
};
|
||||
|
||||
if add {
|
||||
sections.push(elements::Section::Import(import_section));
|
||||
idx += 1;
|
||||
custom_round(&self.other, &mut idx, &mut sections);
|
||||
}
|
||||
|
||||
if !self.funcs.is_empty() {
|
||||
// FUNC SECTION (3)
|
||||
let mut func_section = elements::FunctionSection::default();
|
||||
{
|
||||
let funcs = func_section.entries_mut();
|
||||
|
||||
for func in self.funcs.iter() {
|
||||
match func.read().origin {
|
||||
Declared(_) => {
|
||||
funcs.push(elements::Func::new(
|
||||
func.read().type_ref.order().ok_or(Error::DetachedEntry)? as u32,
|
||||
));
|
||||
},
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
sections.push(elements::Section::Function(func_section));
|
||||
idx += 1;
|
||||
|
||||
custom_round(&self.other, &mut idx, &mut sections);
|
||||
}
|
||||
|
||||
if !self.tables.is_empty() {
|
||||
// TABLE SECTION (4)
|
||||
let mut table_section = elements::TableSection::default();
|
||||
{
|
||||
let tables = table_section.entries_mut();
|
||||
|
||||
for table in self.tables.iter() {
|
||||
match table.read().origin {
|
||||
Declared(_) => {
|
||||
tables.push(elements::TableType::new(
|
||||
table.read().limits.initial(),
|
||||
table.read().limits.maximum(),
|
||||
));
|
||||
},
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
sections.push(elements::Section::Table(table_section));
|
||||
idx += 1;
|
||||
|
||||
custom_round(&self.other, &mut idx, &mut sections);
|
||||
}
|
||||
|
||||
if !self.memory.is_empty() {
|
||||
// MEMORY SECTION (5)
|
||||
let mut memory_section = elements::MemorySection::default();
|
||||
{
|
||||
let memories = memory_section.entries_mut();
|
||||
|
||||
for memory in self.memory.iter() {
|
||||
match memory.read().origin {
|
||||
Declared(_) => {
|
||||
memories.push(elements::MemoryType::new(
|
||||
memory.read().limits.initial(),
|
||||
memory.read().limits.maximum(),
|
||||
));
|
||||
},
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
sections.push(elements::Section::Memory(memory_section));
|
||||
idx += 1;
|
||||
|
||||
custom_round(&self.other, &mut idx, &mut sections);
|
||||
}
|
||||
|
||||
if !self.globals.is_empty() {
|
||||
// GLOBAL SECTION (6)
|
||||
let mut global_section = elements::GlobalSection::default();
|
||||
{
|
||||
let globals = global_section.entries_mut();
|
||||
|
||||
for global in self.globals.iter() {
|
||||
match &global.read().origin {
|
||||
Declared(init_code) => {
|
||||
globals.push(elements::GlobalEntry::new(
|
||||
elements::GlobalType::new(
|
||||
global.read().content,
|
||||
global.read().is_mut,
|
||||
),
|
||||
elements::InitExpr::new(self.generate_instructions(&init_code[..])),
|
||||
));
|
||||
},
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
sections.push(elements::Section::Global(global_section));
|
||||
idx += 1;
|
||||
|
||||
custom_round(&self.other, &mut idx, &mut sections);
|
||||
}
|
||||
|
||||
if !self.exports.is_empty() {
|
||||
// EXPORT SECTION (7)
|
||||
let mut export_section = elements::ExportSection::default();
|
||||
{
|
||||
let exports = export_section.entries_mut();
|
||||
|
||||
for export in self.exports.iter() {
|
||||
let internal = match &export.local {
|
||||
ExportLocal::Func(func_ref) => elements::Internal::Function(
|
||||
func_ref.order().ok_or(Error::DetachedEntry)? as u32,
|
||||
),
|
||||
ExportLocal::Global(global_ref) => elements::Internal::Global(
|
||||
global_ref.order().ok_or(Error::DetachedEntry)? as u32,
|
||||
),
|
||||
ExportLocal::Table(table_ref) => elements::Internal::Table(
|
||||
table_ref.order().ok_or(Error::DetachedEntry)? as u32,
|
||||
),
|
||||
ExportLocal::Memory(memory_ref) => elements::Internal::Memory(
|
||||
memory_ref.order().ok_or(Error::DetachedEntry)? as u32,
|
||||
),
|
||||
};
|
||||
|
||||
exports.push(elements::ExportEntry::new(export.name.to_owned(), internal));
|
||||
}
|
||||
}
|
||||
sections.push(elements::Section::Export(export_section));
|
||||
idx += 1;
|
||||
|
||||
custom_round(&self.other, &mut idx, &mut sections);
|
||||
}
|
||||
|
||||
if let Some(func_ref) = &self.start {
|
||||
// START SECTION (8)
|
||||
sections.push(elements::Section::Start(
|
||||
func_ref.order().ok_or(Error::DetachedEntry)? as u32
|
||||
));
|
||||
}
|
||||
|
||||
if !self.elements.is_empty() {
|
||||
// START SECTION (9)
|
||||
let mut element_section = elements::ElementSection::default();
|
||||
{
|
||||
let element_segments = element_section.entries_mut();
|
||||
|
||||
for element in self.elements.iter() {
|
||||
match &element.location {
|
||||
SegmentLocation::Default(offset_expr) => {
|
||||
let mut elements_map = Vec::new();
|
||||
for f in element.value.iter() {
|
||||
elements_map.push(f.order().ok_or(Error::DetachedEntry)? as u32);
|
||||
}
|
||||
|
||||
element_segments.push(elements::ElementSegment::new(
|
||||
0,
|
||||
Some(elements::InitExpr::new(
|
||||
self.generate_instructions(&offset_expr[..]),
|
||||
)),
|
||||
elements_map,
|
||||
));
|
||||
},
|
||||
_ => unreachable!("Other segment location types are never added"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sections.push(elements::Section::Element(element_section));
|
||||
idx += 1;
|
||||
|
||||
custom_round(&self.other, &mut idx, &mut sections);
|
||||
}
|
||||
|
||||
if !self.funcs.is_empty() {
|
||||
// CODE SECTION (10)
|
||||
let mut code_section = elements::CodeSection::default();
|
||||
{
|
||||
let funcs = code_section.bodies_mut();
|
||||
|
||||
for func in self.funcs.iter() {
|
||||
match &func.read().origin {
|
||||
Declared(body) => {
|
||||
funcs.push(elements::FuncBody::new(
|
||||
body.locals.clone(),
|
||||
elements::Instructions::new(
|
||||
self.generate_instructions(&body.code[..]),
|
||||
),
|
||||
));
|
||||
},
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
sections.push(elements::Section::Code(code_section));
|
||||
idx += 1;
|
||||
|
||||
custom_round(&self.other, &mut idx, &mut sections);
|
||||
}
|
||||
|
||||
if !self.data.is_empty() {
|
||||
// DATA SECTION (11)
|
||||
let mut data_section = elements::DataSection::default();
|
||||
{
|
||||
let data_segments = data_section.entries_mut();
|
||||
|
||||
for data_entry in self.data.iter() {
|
||||
match &data_entry.location {
|
||||
SegmentLocation::Default(offset_expr) => {
|
||||
data_segments.push(elements::DataSegment::new(
|
||||
0,
|
||||
Some(elements::InitExpr::new(
|
||||
self.generate_instructions(&offset_expr[..]),
|
||||
)),
|
||||
data_entry.value.clone(),
|
||||
));
|
||||
},
|
||||
_ => unreachable!("Other segment location types are never added"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sections.push(elements::Section::Data(data_section));
|
||||
idx += 1;
|
||||
|
||||
custom_round(&self.other, &mut idx, &mut sections);
|
||||
}
|
||||
|
||||
Ok(elements::Module::new(sections))
|
||||
}
|
||||
}
|
||||
|
||||
fn custom_round(
|
||||
map: &BTreeMap<usize, elements::Section>,
|
||||
idx: &mut usize,
|
||||
sections: &mut Vec<elements::Section>,
|
||||
) {
|
||||
while let Some(other_section) = map.get(idx) {
|
||||
sections.push(other_section.clone());
|
||||
*idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// New module from parity-wasm `Module`
|
||||
pub fn parse(wasm: &[u8]) -> Result<Module, Error> {
|
||||
Module::from_elements(&::parity_wasm::deserialize_buffer(wasm).map_err(Error::Format)?)
|
||||
}
|
||||
|
||||
/// Generate parity-wasm `Module`
|
||||
pub fn generate(f: &Module) -> Result<Vec<u8>, Error> {
|
||||
let pm = f.generate()?;
|
||||
::parity_wasm::serialize(pm).map_err(Error::Format)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use indoc::indoc;
|
||||
use parity_wasm::elements;
|
||||
|
||||
fn load_sample(wat: &'static str) -> super::Module {
|
||||
super::parse(&wabt::wat2wasm(wat).expect("faled to parse wat!")[..])
|
||||
.expect("error making representation")
|
||||
}
|
||||
|
||||
fn validate_sample(module: &super::Module) {
|
||||
let binary = super::generate(module).expect("Failed to generate binary");
|
||||
wabt::Module::read_binary(&binary, &Default::default())
|
||||
.expect("Wabt failed to read final binary")
|
||||
.validate()
|
||||
.expect("Invalid module");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoky() {
|
||||
let sample = load_sample(indoc!(
|
||||
r#"
|
||||
(module
|
||||
(type (func))
|
||||
(func (type 0))
|
||||
(memory 0 1)
|
||||
(export "simple" (func 0)))"#
|
||||
));
|
||||
|
||||
assert_eq!(sample.types.len(), 1);
|
||||
assert_eq!(sample.funcs.len(), 1);
|
||||
assert_eq!(sample.tables.len(), 0);
|
||||
assert_eq!(sample.memory.len(), 1);
|
||||
assert_eq!(sample.exports.len(), 1);
|
||||
|
||||
assert_eq!(sample.types.get_ref(0).link_count(), 1);
|
||||
assert_eq!(sample.funcs.get_ref(0).link_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table() {
|
||||
let mut sample = load_sample(indoc!(
|
||||
r#"
|
||||
(module
|
||||
(import "env" "foo" (func $foo))
|
||||
(func (param i32)
|
||||
get_local 0
|
||||
i32.const 0
|
||||
call $i32.add
|
||||
drop
|
||||
)
|
||||
(func $i32.add (export "i32.add") (param i32 i32) (result i32)
|
||||
get_local 0
|
||||
get_local 1
|
||||
i32.add
|
||||
)
|
||||
(table 10 anyfunc)
|
||||
|
||||
;; Refer all types of functions: imported, defined not exported and defined exported.
|
||||
(elem (i32.const 0) 0 1 2)
|
||||
)"#
|
||||
));
|
||||
|
||||
{
|
||||
let element_func = &sample.elements[0].value[1];
|
||||
let rfunc = element_func.read();
|
||||
let rtype = &**rfunc.type_ref.read();
|
||||
let elements::Type::Function(ftype) = rtype;
|
||||
|
||||
// it's func#1 in the function space
|
||||
assert_eq!(rfunc.order(), Some(1));
|
||||
// it's type#1
|
||||
assert_eq!(ftype.params().len(), 1);
|
||||
}
|
||||
|
||||
sample.funcs.begin_delete().push(0).done();
|
||||
|
||||
{
|
||||
let element_func = &sample.elements[0].value[1];
|
||||
let rfunc = element_func.read();
|
||||
let rtype = &**rfunc.type_ref.read();
|
||||
let elements::Type::Function(ftype) = rtype;
|
||||
|
||||
// import deleted so now it's func #0
|
||||
assert_eq!(rfunc.order(), Some(0));
|
||||
// type should be the same, #1
|
||||
assert_eq!(ftype.params().len(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_import() {
|
||||
let mut sample = load_sample(indoc!(
|
||||
r#"
|
||||
(module
|
||||
(type (;0;) (func))
|
||||
(type (;1;) (func (param i32 i32) (result i32)))
|
||||
(import "env" "foo" (func (type 1)))
|
||||
(func (param i32)
|
||||
get_local 0
|
||||
i32.const 0
|
||||
call 0
|
||||
drop
|
||||
)
|
||||
(func (type 0)
|
||||
i32.const 0
|
||||
call 1
|
||||
)
|
||||
)"#
|
||||
));
|
||||
|
||||
{
|
||||
let type_ref_0 = sample.types.clone_ref(0);
|
||||
let declared_func_2 = sample.funcs.clone_ref(2);
|
||||
|
||||
let mut tx = sample.funcs.begin_insert_not_until(|f| {
|
||||
matches!(f.origin, super::ImportedOrDeclared::Imported(_, _))
|
||||
});
|
||||
|
||||
let new_import_func = tx.push(super::Func {
|
||||
type_ref: type_ref_0,
|
||||
origin: super::ImportedOrDeclared::Imported("env".to_owned(), "bar".to_owned()),
|
||||
});
|
||||
|
||||
tx.done();
|
||||
|
||||
assert_eq!(new_import_func.order(), Some(1));
|
||||
assert_eq!(declared_func_2.order(), Some(3));
|
||||
assert_eq!(
|
||||
match &declared_func_2.read().origin {
|
||||
super::ImportedOrDeclared::Declared(body) => {
|
||||
match &body.code[1] {
|
||||
super::Instruction::Call(called_func) => called_func.order(),
|
||||
_ => panic!("instruction #2 should be a call!"),
|
||||
}
|
||||
},
|
||||
_ => panic!("func #3 should be declared!"),
|
||||
},
|
||||
Some(2),
|
||||
"Call should be recalculated to 2"
|
||||
);
|
||||
}
|
||||
|
||||
validate_sample(&sample);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_opt() {
|
||||
let mut sample = load_sample(indoc!(
|
||||
r#"
|
||||
(module
|
||||
(type (;0;) (func))
|
||||
(type (;1;) (func (param i32 i32) (result i32)))
|
||||
(type (;2;) (func (param i32 i32) (result i32)))
|
||||
(type (;3;) (func (param i32 i32) (result i32)))
|
||||
(import "env" "foo" (func (type 1)))
|
||||
(import "env" "foo2" (func (type 2)))
|
||||
(import "env" "foo3" (func (type 3)))
|
||||
(func (type 0)
|
||||
i32.const 1
|
||||
i32.const 1
|
||||
call 0
|
||||
drop
|
||||
)
|
||||
(func (type 0)
|
||||
i32.const 2
|
||||
i32.const 2
|
||||
call 1
|
||||
drop
|
||||
)
|
||||
(func (type 0)
|
||||
i32.const 3
|
||||
i32.const 3
|
||||
call 2
|
||||
drop
|
||||
)
|
||||
(func (type 0)
|
||||
call 3
|
||||
)
|
||||
)"#
|
||||
));
|
||||
|
||||
validate_sample(&sample);
|
||||
|
||||
// we'll delete functions #4 and #5, nobody references it so it should be fine;
|
||||
|
||||
sample.funcs.begin_delete().push(4).push(5).done();
|
||||
validate_sample(&sample);
|
||||
|
||||
// now we'll delete functions #1 and #2 (imported and called from the deleted above),
|
||||
// should also be fine
|
||||
sample.funcs.begin_delete().push(1).push(2).done();
|
||||
validate_sample(&sample);
|
||||
|
||||
// now the last declared function left should call another one before it (which is index #1)
|
||||
let declared_func_2 = sample.funcs.clone_ref(2);
|
||||
assert_eq!(
|
||||
match &declared_func_2.read().origin {
|
||||
super::ImportedOrDeclared::Declared(body) => {
|
||||
match &body.code[0] {
|
||||
super::Instruction::Call(called_func) => called_func.order(),
|
||||
wrong_instruction => panic!(
|
||||
"instruction #2 should be a call but got {:?}!",
|
||||
wrong_instruction
|
||||
),
|
||||
}
|
||||
},
|
||||
_ => panic!("func #0 should be declared!"),
|
||||
},
|
||||
Some(1),
|
||||
"Call should be recalculated to 1"
|
||||
);
|
||||
}
|
||||
}
|
||||
+3
-80
@@ -1,88 +1,11 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[macro_use]
|
||||
extern crate alloc;
|
||||
|
||||
pub mod rules;
|
||||
|
||||
mod build;
|
||||
#[cfg(feature = "std")]
|
||||
mod export_globals;
|
||||
mod ext;
|
||||
mod gas;
|
||||
mod graph;
|
||||
#[cfg(feature = "cli")]
|
||||
pub mod logger;
|
||||
mod optimizer;
|
||||
mod pack;
|
||||
mod ref_list;
|
||||
mod runtime_type;
|
||||
mod symbols;
|
||||
pub mod gas_metering;
|
||||
mod stack_limiter;
|
||||
|
||||
pub mod stack_height;
|
||||
|
||||
pub use build::{build, Error as BuildError, SourceTarget};
|
||||
#[cfg(feature = "std")]
|
||||
pub use export_globals::export_mutable_globals;
|
||||
pub use ext::{
|
||||
externalize, externalize_mem, shrink_unknown_stack, underscore_funcs, ununderscore_funcs,
|
||||
};
|
||||
pub use gas::inject_gas_counter;
|
||||
pub use graph::{generate as graph_generate, parse as graph_parse, Module};
|
||||
pub use optimizer::{optimize, Error as OptimizerError};
|
||||
pub use pack::{pack_instance, Error as PackingError};
|
||||
pub use parity_wasm;
|
||||
pub use ref_list::{DeleteTransaction, Entry, EntryRef, RefList};
|
||||
pub use runtime_type::inject_runtime_type;
|
||||
|
||||
pub struct TargetSymbols {
|
||||
pub create: &'static str,
|
||||
pub call: &'static str,
|
||||
pub ret: &'static str,
|
||||
}
|
||||
|
||||
pub enum TargetRuntime {
|
||||
Substrate(TargetSymbols),
|
||||
PWasm(TargetSymbols),
|
||||
}
|
||||
|
||||
impl TargetRuntime {
|
||||
pub fn substrate() -> TargetRuntime {
|
||||
TargetRuntime::Substrate(TargetSymbols {
|
||||
create: "deploy",
|
||||
call: "call",
|
||||
ret: "ext_return",
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pwasm() -> TargetRuntime {
|
||||
TargetRuntime::PWasm(TargetSymbols { create: "deploy", call: "call", ret: "ret" })
|
||||
}
|
||||
|
||||
pub fn symbols(&self) -> &TargetSymbols {
|
||||
match self {
|
||||
TargetRuntime::Substrate(s) => s,
|
||||
TargetRuntime::PWasm(s) => s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod std {
|
||||
pub use ::alloc::{borrow, boxed, string, vec};
|
||||
pub use core::*;
|
||||
|
||||
pub mod rc {
|
||||
pub use alloc::rc::Rc;
|
||||
}
|
||||
|
||||
pub mod collections {
|
||||
pub use alloc::collections::{BTreeMap, BTreeSet};
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
mod std {
|
||||
pub use std::*;
|
||||
}
|
||||
pub use stack_limiter::inject as inject_stack_limiter;
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
use env_logger::Builder;
|
||||
use lazy_static::lazy_static;
|
||||
use log::{trace, LevelFilter};
|
||||
|
||||
lazy_static! {
|
||||
static ref LOG_DUMMY: bool = {
|
||||
let mut builder = Builder::new();
|
||||
builder.filter(None, LevelFilter::Info);
|
||||
builder.parse_default_env();
|
||||
builder.init();
|
||||
trace!("logger initialized");
|
||||
true
|
||||
};
|
||||
}
|
||||
|
||||
/// Intialize log with default settings
|
||||
pub fn init() {
|
||||
let _ = *LOG_DUMMY;
|
||||
}
|
||||
@@ -1,806 +0,0 @@
|
||||
#[cfg(not(features = "std"))]
|
||||
use crate::std::collections::BTreeSet as Set;
|
||||
#[cfg(features = "std")]
|
||||
use crate::std::collections::HashSet as Set;
|
||||
use crate::std::{mem, vec::Vec};
|
||||
|
||||
use crate::symbols::{expand_symbols, push_code_symbols, resolve_function, Symbol};
|
||||
use log::trace;
|
||||
use parity_wasm::elements;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Since optimizer starts with export entries, export
|
||||
/// section is supposed to exist.
|
||||
NoExportSection,
|
||||
}
|
||||
|
||||
pub fn optimize(
|
||||
module: &mut elements::Module, // Module to optimize
|
||||
used_exports: Vec<&str>, // List of only exports that will be usable after optimization
|
||||
) -> Result<(), Error> {
|
||||
// WebAssembly exports optimizer
|
||||
// Motivation: emscripten compiler backend compiles in many unused exports
|
||||
// which in turn compile in unused imports and leaves unused functions
|
||||
|
||||
// try to parse name section
|
||||
let module_temp = mem::take(module);
|
||||
let module_temp = module_temp.parse_names().unwrap_or_else(|(_err, module)| module);
|
||||
*module = module_temp;
|
||||
|
||||
// Algo starts from the top, listing all items that should stay
|
||||
let mut stay = Set::new();
|
||||
for (index, entry) in module
|
||||
.export_section()
|
||||
.ok_or(Error::NoExportSection)?
|
||||
.entries()
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
if used_exports.iter().any(|e| *e == entry.field()) {
|
||||
stay.insert(Symbol::Export(index));
|
||||
}
|
||||
}
|
||||
|
||||
// If there is start function in module, it should stary
|
||||
module.start_section().map(|ss| stay.insert(resolve_function(module, ss)));
|
||||
|
||||
// All symbols used in data/element segments are also should be preserved
|
||||
let mut init_symbols = Vec::new();
|
||||
if let Some(data_section) = module.data_section() {
|
||||
for segment in data_section.entries() {
|
||||
push_code_symbols(
|
||||
module,
|
||||
segment
|
||||
.offset()
|
||||
.as_ref()
|
||||
.expect("parity-wasm is compiled without bulk-memory operations")
|
||||
.code(),
|
||||
&mut init_symbols,
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(elements_section) = module.elements_section() {
|
||||
for segment in elements_section.entries() {
|
||||
push_code_symbols(
|
||||
module,
|
||||
segment
|
||||
.offset()
|
||||
.as_ref()
|
||||
.expect("parity-wasm is compiled without bulk-memory operations")
|
||||
.code(),
|
||||
&mut init_symbols,
|
||||
);
|
||||
for func_index in segment.members() {
|
||||
stay.insert(resolve_function(module, *func_index));
|
||||
}
|
||||
}
|
||||
}
|
||||
for symbol in init_symbols.drain(..) {
|
||||
stay.insert(symbol);
|
||||
}
|
||||
|
||||
// Call function which will traverse the list recursively, filling stay with all symbols
|
||||
// that are already used by those which already there
|
||||
expand_symbols(module, &mut stay);
|
||||
|
||||
for symbol in stay.iter() {
|
||||
trace!("symbol to stay: {:?}", symbol);
|
||||
}
|
||||
|
||||
// Keep track of referreable symbols to rewire calls/globals
|
||||
let mut eliminated_funcs = Vec::new();
|
||||
let mut eliminated_globals = Vec::new();
|
||||
let mut eliminated_types = Vec::new();
|
||||
|
||||
// First, iterate through types
|
||||
let mut index = 0;
|
||||
let mut old_index = 0;
|
||||
|
||||
{
|
||||
loop {
|
||||
if type_section(module).map(|section| section.types_mut().len()).unwrap_or(0) == index {
|
||||
break
|
||||
}
|
||||
|
||||
if stay.contains(&Symbol::Type(old_index)) {
|
||||
index += 1;
|
||||
} else {
|
||||
type_section(module)
|
||||
.expect("If type section does not exists, the loop will break at the beginning of first iteration")
|
||||
.types_mut().remove(index);
|
||||
eliminated_types.push(old_index);
|
||||
trace!("Eliminated type({})", old_index);
|
||||
}
|
||||
old_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Second, iterate through imports
|
||||
let mut top_funcs = 0;
|
||||
let mut top_globals = 0;
|
||||
index = 0;
|
||||
old_index = 0;
|
||||
|
||||
if let Some(imports) = import_section(module) {
|
||||
loop {
|
||||
let mut remove = false;
|
||||
match imports.entries()[index].external() {
|
||||
elements::External::Function(_) => {
|
||||
if stay.contains(&Symbol::Import(old_index)) {
|
||||
index += 1;
|
||||
} else {
|
||||
remove = true;
|
||||
eliminated_funcs.push(top_funcs);
|
||||
trace!(
|
||||
"Eliminated import({}) func({}, {})",
|
||||
old_index,
|
||||
top_funcs,
|
||||
imports.entries()[index].field()
|
||||
);
|
||||
}
|
||||
top_funcs += 1;
|
||||
},
|
||||
elements::External::Global(_) => {
|
||||
if stay.contains(&Symbol::Import(old_index)) {
|
||||
index += 1;
|
||||
} else {
|
||||
remove = true;
|
||||
eliminated_globals.push(top_globals);
|
||||
trace!(
|
||||
"Eliminated import({}) global({}, {})",
|
||||
old_index,
|
||||
top_globals,
|
||||
imports.entries()[index].field()
|
||||
);
|
||||
}
|
||||
top_globals += 1;
|
||||
},
|
||||
_ => {
|
||||
index += 1;
|
||||
},
|
||||
}
|
||||
if remove {
|
||||
imports.entries_mut().remove(index);
|
||||
}
|
||||
|
||||
old_index += 1;
|
||||
|
||||
if index == imports.entries().len() {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Third, iterate through globals
|
||||
if let Some(globals) = global_section(module) {
|
||||
index = 0;
|
||||
old_index = 0;
|
||||
|
||||
loop {
|
||||
if globals.entries_mut().len() == index {
|
||||
break
|
||||
}
|
||||
if stay.contains(&Symbol::Global(old_index)) {
|
||||
index += 1;
|
||||
} else {
|
||||
globals.entries_mut().remove(index);
|
||||
eliminated_globals.push(top_globals + old_index);
|
||||
trace!("Eliminated global({})", top_globals + old_index);
|
||||
}
|
||||
old_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Forth, delete orphaned functions
|
||||
if function_section(module).is_some() && code_section(module).is_some() {
|
||||
index = 0;
|
||||
old_index = 0;
|
||||
|
||||
loop {
|
||||
if function_section(module).expect("Functons section to exist").entries_mut().len() ==
|
||||
index
|
||||
{
|
||||
break
|
||||
}
|
||||
if stay.contains(&Symbol::Function(old_index)) {
|
||||
index += 1;
|
||||
} else {
|
||||
function_section(module)
|
||||
.expect("Functons section to exist")
|
||||
.entries_mut()
|
||||
.remove(index);
|
||||
code_section(module).expect("Code section to exist").bodies_mut().remove(index);
|
||||
|
||||
eliminated_funcs.push(top_funcs + old_index);
|
||||
trace!("Eliminated function({})", top_funcs + old_index);
|
||||
}
|
||||
old_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Fifth, eliminate unused exports
|
||||
{
|
||||
let exports = export_section(module).ok_or(Error::NoExportSection)?;
|
||||
|
||||
index = 0;
|
||||
old_index = 0;
|
||||
|
||||
loop {
|
||||
if exports.entries_mut().len() == index {
|
||||
break
|
||||
}
|
||||
if stay.contains(&Symbol::Export(old_index)) {
|
||||
index += 1;
|
||||
} else {
|
||||
trace!(
|
||||
"Eliminated export({}, {})",
|
||||
old_index,
|
||||
exports.entries_mut()[index].field()
|
||||
);
|
||||
exports.entries_mut().remove(index);
|
||||
}
|
||||
old_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if !eliminated_globals.is_empty() ||
|
||||
!eliminated_funcs.is_empty() ||
|
||||
!eliminated_types.is_empty()
|
||||
{
|
||||
// Finaly, rewire all calls, globals references and types to the new indices
|
||||
// (only if there is anything to do)
|
||||
// When sorting primitives sorting unstable is faster without any difference in result.
|
||||
eliminated_globals.sort_unstable();
|
||||
eliminated_funcs.sort_unstable();
|
||||
eliminated_types.sort_unstable();
|
||||
|
||||
for section in module.sections_mut() {
|
||||
match section {
|
||||
elements::Section::Start(func_index) if !eliminated_funcs.is_empty() => {
|
||||
let totalle =
|
||||
eliminated_funcs.iter().take_while(|i| (**i as u32) < *func_index).count();
|
||||
*func_index -= totalle as u32;
|
||||
},
|
||||
elements::Section::Function(function_section) if !eliminated_types.is_empty() =>
|
||||
for func_signature in function_section.entries_mut() {
|
||||
let totalle = eliminated_types
|
||||
.iter()
|
||||
.take_while(|i| (**i as u32) < func_signature.type_ref())
|
||||
.count();
|
||||
*func_signature.type_ref_mut() -= totalle as u32;
|
||||
},
|
||||
elements::Section::Import(import_section) if !eliminated_types.is_empty() => {
|
||||
for import_entry in import_section.entries_mut() {
|
||||
if let elements::External::Function(type_ref) = import_entry.external_mut()
|
||||
{
|
||||
let totalle = eliminated_types
|
||||
.iter()
|
||||
.take_while(|i| (**i as u32) < *type_ref)
|
||||
.count();
|
||||
*type_ref -= totalle as u32;
|
||||
}
|
||||
}
|
||||
},
|
||||
elements::Section::Code(code_section)
|
||||
if !eliminated_globals.is_empty() || !eliminated_funcs.is_empty() =>
|
||||
{
|
||||
for func_body in code_section.bodies_mut() {
|
||||
if !eliminated_funcs.is_empty() {
|
||||
update_call_index(func_body.code_mut(), &eliminated_funcs);
|
||||
}
|
||||
if !eliminated_globals.is_empty() {
|
||||
update_global_index(
|
||||
func_body.code_mut().elements_mut(),
|
||||
&eliminated_globals,
|
||||
)
|
||||
}
|
||||
if !eliminated_types.is_empty() {
|
||||
update_type_index(func_body.code_mut(), &eliminated_types)
|
||||
}
|
||||
}
|
||||
},
|
||||
elements::Section::Export(export_section) => {
|
||||
for export in export_section.entries_mut() {
|
||||
match export.internal_mut() {
|
||||
elements::Internal::Function(func_index) => {
|
||||
let totalle = eliminated_funcs
|
||||
.iter()
|
||||
.take_while(|i| (**i as u32) < *func_index)
|
||||
.count();
|
||||
*func_index -= totalle as u32;
|
||||
},
|
||||
elements::Internal::Global(global_index) => {
|
||||
let totalle = eliminated_globals
|
||||
.iter()
|
||||
.take_while(|i| (**i as u32) < *global_index)
|
||||
.count();
|
||||
*global_index -= totalle as u32;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
},
|
||||
elements::Section::Global(global_section) => {
|
||||
for global_entry in global_section.entries_mut() {
|
||||
update_global_index(
|
||||
global_entry.init_expr_mut().code_mut(),
|
||||
&eliminated_globals,
|
||||
)
|
||||
}
|
||||
},
|
||||
elements::Section::Data(data_section) =>
|
||||
for segment in data_section.entries_mut() {
|
||||
update_global_index(
|
||||
segment
|
||||
.offset_mut()
|
||||
.as_mut()
|
||||
.expect("parity-wasm is compiled without bulk-memory operations")
|
||||
.code_mut(),
|
||||
&eliminated_globals,
|
||||
)
|
||||
},
|
||||
elements::Section::Element(elements_section) => {
|
||||
for segment in elements_section.entries_mut() {
|
||||
update_global_index(
|
||||
segment
|
||||
.offset_mut()
|
||||
.as_mut()
|
||||
.expect("parity-wasm is compiled without bulk-memory operations")
|
||||
.code_mut(),
|
||||
&eliminated_globals,
|
||||
);
|
||||
// update all indirect call addresses initial values
|
||||
for func_index in segment.members_mut() {
|
||||
let totalle = eliminated_funcs
|
||||
.iter()
|
||||
.take_while(|i| (**i as u32) < *func_index)
|
||||
.count();
|
||||
*func_index -= totalle as u32;
|
||||
}
|
||||
}
|
||||
},
|
||||
elements::Section::Name(name_section) => {
|
||||
if let Some(func_name) = name_section.functions_mut() {
|
||||
let mut func_name_map = mem::take(func_name.names_mut());
|
||||
for index in &eliminated_funcs {
|
||||
func_name_map.remove(*index as u32);
|
||||
}
|
||||
let updated_map = func_name_map
|
||||
.into_iter()
|
||||
.map(|(index, value)| {
|
||||
let totalle = eliminated_funcs
|
||||
.iter()
|
||||
.take_while(|i| (**i as u32) < index)
|
||||
.count() as u32;
|
||||
(index - totalle, value)
|
||||
})
|
||||
.collect();
|
||||
*func_name.names_mut() = updated_map;
|
||||
}
|
||||
|
||||
if let Some(local_name) = name_section.locals_mut() {
|
||||
let mut local_names_map = mem::take(local_name.local_names_mut());
|
||||
for index in &eliminated_funcs {
|
||||
local_names_map.remove(*index as u32);
|
||||
}
|
||||
let updated_map = local_names_map
|
||||
.into_iter()
|
||||
.map(|(index, value)| {
|
||||
let totalle = eliminated_funcs
|
||||
.iter()
|
||||
.take_while(|i| (**i as u32) < index)
|
||||
.count() as u32;
|
||||
(index - totalle, value)
|
||||
})
|
||||
.collect();
|
||||
*local_name.local_names_mut() = updated_map;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also drop all custom sections
|
||||
module
|
||||
.sections_mut()
|
||||
.retain(|section| !matches!(section, elements::Section::Custom(_)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_call_index(instructions: &mut elements::Instructions, eliminated_indices: &[usize]) {
|
||||
use parity_wasm::elements::Instruction::*;
|
||||
for instruction in instructions.elements_mut().iter_mut() {
|
||||
if let Call(call_index) = instruction {
|
||||
let totalle =
|
||||
eliminated_indices.iter().take_while(|i| (**i as u32) < *call_index).count();
|
||||
trace!("rewired call {} -> call {}", *call_index, *call_index - totalle as u32);
|
||||
*call_index -= totalle as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates global references considering the _ordered_ list of eliminated indices
|
||||
pub fn update_global_index(
|
||||
instructions: &mut Vec<elements::Instruction>,
|
||||
eliminated_indices: &[usize],
|
||||
) {
|
||||
use parity_wasm::elements::Instruction::*;
|
||||
for instruction in instructions.iter_mut() {
|
||||
match instruction {
|
||||
GetGlobal(index) | SetGlobal(index) => {
|
||||
let totalle =
|
||||
eliminated_indices.iter().take_while(|i| (**i as u32) < *index).count();
|
||||
trace!("rewired global {} -> global {}", *index, *index - totalle as u32);
|
||||
*index -= totalle as u32;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates global references considering the _ordered_ list of eliminated indices
|
||||
pub fn update_type_index(instructions: &mut elements::Instructions, eliminated_indices: &[usize]) {
|
||||
use parity_wasm::elements::Instruction::*;
|
||||
for instruction in instructions.elements_mut().iter_mut() {
|
||||
if let CallIndirect(call_index, _) = instruction {
|
||||
let totalle =
|
||||
eliminated_indices.iter().take_while(|i| (**i as u32) < *call_index).count();
|
||||
trace!(
|
||||
"rewired call_indrect {} -> call_indirect {}",
|
||||
*call_index,
|
||||
*call_index - totalle as u32
|
||||
);
|
||||
*call_index -= totalle as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn import_section(module: &mut elements::Module) -> Option<&mut elements::ImportSection> {
|
||||
for section in module.sections_mut() {
|
||||
if let elements::Section::Import(sect) = section {
|
||||
return Some(sect)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn global_section(module: &mut elements::Module) -> Option<&mut elements::GlobalSection> {
|
||||
for section in module.sections_mut() {
|
||||
if let elements::Section::Global(sect) = section {
|
||||
return Some(sect)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn function_section(module: &mut elements::Module) -> Option<&mut elements::FunctionSection> {
|
||||
for section in module.sections_mut() {
|
||||
if let elements::Section::Function(sect) = section {
|
||||
return Some(sect)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn code_section(module: &mut elements::Module) -> Option<&mut elements::CodeSection> {
|
||||
for section in module.sections_mut() {
|
||||
if let elements::Section::Code(sect) = section {
|
||||
return Some(sect)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn export_section(module: &mut elements::Module) -> Option<&mut elements::ExportSection> {
|
||||
for section in module.sections_mut() {
|
||||
if let elements::Section::Export(sect) = section {
|
||||
return Some(sect)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn type_section(module: &mut elements::Module) -> Option<&mut elements::TypeSection> {
|
||||
for section in module.sections_mut() {
|
||||
if let elements::Section::Type(sect) = section {
|
||||
return Some(sect)
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use parity_wasm::{builder, elements};
|
||||
|
||||
/// @spec 0
|
||||
/// Optimizer presumes that export section exists and contains
|
||||
/// all symbols passed as a second parameter. Since empty module
|
||||
/// obviously contains no export section, optimizer should return
|
||||
/// error on it.
|
||||
#[test]
|
||||
fn empty() {
|
||||
let mut module = builder::module().build();
|
||||
let result = optimize(&mut module, vec!["_call"]);
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
/// @spec 1
|
||||
/// Imagine the unoptimized module has two own functions, `_call` and `_random`
|
||||
/// and exports both of them in the export section. During optimization, the `_random`
|
||||
/// function should vanish completely, given we pass `_call` as the only function to stay
|
||||
/// in the module.
|
||||
#[test]
|
||||
fn minimal() {
|
||||
let mut module = builder::module()
|
||||
.function()
|
||||
.signature()
|
||||
.param()
|
||||
.i32()
|
||||
.build()
|
||||
.build()
|
||||
.function()
|
||||
.signature()
|
||||
.param()
|
||||
.i32()
|
||||
.param()
|
||||
.i32()
|
||||
.build()
|
||||
.build()
|
||||
.export()
|
||||
.field("_call")
|
||||
.internal()
|
||||
.func(0)
|
||||
.build()
|
||||
.export()
|
||||
.field("_random")
|
||||
.internal()
|
||||
.func(1)
|
||||
.build()
|
||||
.build();
|
||||
assert_eq!(
|
||||
module.export_section().expect("export section to be generated").entries().len(),
|
||||
2
|
||||
);
|
||||
|
||||
optimize(&mut module, vec!["_call"]).expect("optimizer to succeed");
|
||||
|
||||
assert_eq!(
|
||||
1,
|
||||
module.export_section().expect("export section to be generated").entries().len(),
|
||||
"There should only 1 (one) export entry in the optimized module"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
1,
|
||||
module
|
||||
.function_section()
|
||||
.expect("functions section to be generated")
|
||||
.entries()
|
||||
.len(),
|
||||
"There should 2 (two) functions in the optimized module"
|
||||
);
|
||||
}
|
||||
|
||||
/// @spec 2
|
||||
/// Imagine there is one exported function in unoptimized module, `_call`, that we specify as the one
|
||||
/// to stay during the optimization. The code of this function uses global during the execution.
|
||||
/// This sayed global should survive the optimization.
|
||||
#[test]
|
||||
fn globals() {
|
||||
let mut module = builder::module()
|
||||
.global()
|
||||
.value_type()
|
||||
.i32()
|
||||
.build()
|
||||
.function()
|
||||
.signature()
|
||||
.param()
|
||||
.i32()
|
||||
.build()
|
||||
.body()
|
||||
.with_instructions(elements::Instructions::new(vec![
|
||||
elements::Instruction::GetGlobal(0),
|
||||
elements::Instruction::End,
|
||||
]))
|
||||
.build()
|
||||
.build()
|
||||
.export()
|
||||
.field("_call")
|
||||
.internal()
|
||||
.func(0)
|
||||
.build()
|
||||
.build();
|
||||
|
||||
optimize(&mut module, vec!["_call"]).expect("optimizer to succeed");
|
||||
|
||||
assert_eq!(
|
||||
1,
|
||||
module.global_section().expect("global section to be generated").entries().len(),
|
||||
"There should 1 (one) global entry in the optimized module, since _call function uses it"
|
||||
);
|
||||
}
|
||||
|
||||
/// @spec 2
|
||||
/// Imagine there is one exported function in unoptimized module, `_call`, that we specify as the one
|
||||
/// to stay during the optimization. The code of this function uses one global during the execution,
|
||||
/// but we have a bunch of other unused globals in the code. Last globals should not survive the optimization,
|
||||
/// while the former should.
|
||||
#[test]
|
||||
fn globals_2() {
|
||||
let mut module = builder::module()
|
||||
.global()
|
||||
.value_type()
|
||||
.i32()
|
||||
.build()
|
||||
.global()
|
||||
.value_type()
|
||||
.i64()
|
||||
.build()
|
||||
.global()
|
||||
.value_type()
|
||||
.f32()
|
||||
.build()
|
||||
.function()
|
||||
.signature()
|
||||
.param()
|
||||
.i32()
|
||||
.build()
|
||||
.body()
|
||||
.with_instructions(elements::Instructions::new(vec![
|
||||
elements::Instruction::GetGlobal(1),
|
||||
elements::Instruction::End,
|
||||
]))
|
||||
.build()
|
||||
.build()
|
||||
.export()
|
||||
.field("_call")
|
||||
.internal()
|
||||
.func(0)
|
||||
.build()
|
||||
.build();
|
||||
|
||||
optimize(&mut module, vec!["_call"]).expect("optimizer to succeed");
|
||||
|
||||
assert_eq!(
|
||||
1,
|
||||
module.global_section().expect("global section to be generated").entries().len(),
|
||||
"There should 1 (one) global entry in the optimized module, since _call function uses only one"
|
||||
);
|
||||
}
|
||||
|
||||
/// @spec 3
|
||||
/// Imagine the unoptimized module has two own functions, `_call` and `_random`
|
||||
/// and exports both of them in the export section. Function `_call` also calls `_random`
|
||||
/// in its function body. The optimization should kick `_random` function from the export section
|
||||
/// but preserve it's body.
|
||||
#[test]
|
||||
fn call_ref() {
|
||||
let mut module = builder::module()
|
||||
.function()
|
||||
.signature()
|
||||
.param()
|
||||
.i32()
|
||||
.build()
|
||||
.body()
|
||||
.with_instructions(elements::Instructions::new(vec![
|
||||
elements::Instruction::Call(1),
|
||||
elements::Instruction::End,
|
||||
]))
|
||||
.build()
|
||||
.build()
|
||||
.function()
|
||||
.signature()
|
||||
.param()
|
||||
.i32()
|
||||
.param()
|
||||
.i32()
|
||||
.build()
|
||||
.build()
|
||||
.export()
|
||||
.field("_call")
|
||||
.internal()
|
||||
.func(0)
|
||||
.build()
|
||||
.export()
|
||||
.field("_random")
|
||||
.internal()
|
||||
.func(1)
|
||||
.build()
|
||||
.build();
|
||||
assert_eq!(
|
||||
module.export_section().expect("export section to be generated").entries().len(),
|
||||
2
|
||||
);
|
||||
|
||||
optimize(&mut module, vec!["_call"]).expect("optimizer to succeed");
|
||||
|
||||
assert_eq!(
|
||||
1,
|
||||
module.export_section().expect("export section to be generated").entries().len(),
|
||||
"There should only 1 (one) export entry in the optimized module"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
2,
|
||||
module
|
||||
.function_section()
|
||||
.expect("functions section to be generated")
|
||||
.entries()
|
||||
.len(),
|
||||
"There should 2 (two) functions in the optimized module"
|
||||
);
|
||||
}
|
||||
|
||||
/// @spec 4
|
||||
/// Imagine the unoptimized module has an indirect call to function of type 1
|
||||
/// The type should persist so that indirect call would work
|
||||
#[test]
|
||||
fn call_indirect() {
|
||||
let mut module = builder::module()
|
||||
.function()
|
||||
.signature()
|
||||
.param()
|
||||
.i32()
|
||||
.param()
|
||||
.i32()
|
||||
.build()
|
||||
.build()
|
||||
.function()
|
||||
.signature()
|
||||
.param()
|
||||
.i32()
|
||||
.param()
|
||||
.i32()
|
||||
.param()
|
||||
.i32()
|
||||
.build()
|
||||
.build()
|
||||
.function()
|
||||
.signature()
|
||||
.param()
|
||||
.i32()
|
||||
.build()
|
||||
.body()
|
||||
.with_instructions(elements::Instructions::new(vec![
|
||||
elements::Instruction::CallIndirect(1, 0),
|
||||
elements::Instruction::End,
|
||||
]))
|
||||
.build()
|
||||
.build()
|
||||
.export()
|
||||
.field("_call")
|
||||
.internal()
|
||||
.func(2)
|
||||
.build()
|
||||
.build();
|
||||
|
||||
optimize(&mut module, vec!["_call"]).expect("optimizer to succeed");
|
||||
|
||||
assert_eq!(
|
||||
2,
|
||||
module.type_section().expect("type section to be generated").types().len(),
|
||||
"There should 2 (two) types left in the module, 1 for indirect call and one for _call"
|
||||
);
|
||||
|
||||
let indirect_opcode =
|
||||
&module.code_section().expect("code section to be generated").bodies()[0]
|
||||
.code()
|
||||
.elements()[0];
|
||||
match *indirect_opcode {
|
||||
elements::Instruction::CallIndirect(0, 0) => {},
|
||||
_ => {
|
||||
panic!(
|
||||
"Expected call_indirect to use index 0 after optimization, since previois 0th was eliminated, but got {:?}",
|
||||
indirect_opcode
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
-384
@@ -1,384 +0,0 @@
|
||||
use crate::std::{borrow::ToOwned, fmt, vec::Vec};
|
||||
|
||||
use super::{gas::update_call_index, TargetRuntime};
|
||||
use parity_wasm::{
|
||||
builder,
|
||||
elements::{
|
||||
self, DataSection, DataSegment, External, ImportCountType, InitExpr, Instruction, Internal,
|
||||
Section,
|
||||
},
|
||||
};
|
||||
|
||||
/// Pack error.
|
||||
///
|
||||
/// Pack has number of assumptions of passed module structure.
|
||||
/// When they are violated, pack_instance returns one of these.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
MalformedModule,
|
||||
NoTypeSection,
|
||||
NoExportSection,
|
||||
NoCodeSection,
|
||||
InvalidCreateSignature(&'static str),
|
||||
NoCreateSymbol(&'static str),
|
||||
InvalidCreateMember(&'static str),
|
||||
NoImportSection,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
Error::MalformedModule => write!(f, "Module internal references are inconsistent"),
|
||||
Error::NoTypeSection => write!(f, "No type section in the module"),
|
||||
Error::NoExportSection => write!(f, "No export section in the module"),
|
||||
Error::NoCodeSection => write!(f, "No code section inthe module"),
|
||||
Error::InvalidCreateSignature(sym) => {
|
||||
write!(f, "Exported symbol `{}` has invalid signature, should be () -> ()", sym)
|
||||
},
|
||||
Error::InvalidCreateMember(sym) => {
|
||||
write!(f, "Exported symbol `{}` should be a function", sym)
|
||||
},
|
||||
Error::NoCreateSymbol(sym) => write!(f, "No exported `{}` symbol", sym),
|
||||
Error::NoImportSection => write!(f, "No import section in the module"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If a pwasm module has an exported function matching "create" symbol we want to pack it into "constructor".
|
||||
/// `raw_module` is the actual contract code
|
||||
/// `ctor_module` is the constructor which should return `raw_module`
|
||||
pub fn pack_instance(
|
||||
raw_module: Vec<u8>,
|
||||
mut ctor_module: elements::Module,
|
||||
target: &TargetRuntime,
|
||||
) -> Result<elements::Module, Error> {
|
||||
// Total number of constructor module import functions
|
||||
let ctor_import_functions = ctor_module.import_section().map(|x| x.functions()).unwrap_or(0);
|
||||
|
||||
// We need to find an internal ID of function which is exported as `symbols().create`
|
||||
// in order to find it in the Code section of the module
|
||||
let mut create_func_id = {
|
||||
let found_entry = ctor_module
|
||||
.export_section()
|
||||
.ok_or(Error::NoExportSection)?
|
||||
.entries()
|
||||
.iter()
|
||||
.find(|entry| target.symbols().create == entry.field())
|
||||
.ok_or_else(|| Error::NoCreateSymbol(target.symbols().create))?;
|
||||
|
||||
let function_index: usize = match found_entry.internal() {
|
||||
Internal::Function(index) => *index as usize,
|
||||
_ => return Err(Error::InvalidCreateMember(target.symbols().create)),
|
||||
};
|
||||
|
||||
// Calculates a function index within module's function section
|
||||
let function_internal_index = function_index - ctor_import_functions;
|
||||
|
||||
// Constructor should be of signature `func()` (void), fail otherwise
|
||||
let type_id = ctor_module
|
||||
.function_section()
|
||||
.ok_or(Error::NoCodeSection)?
|
||||
.entries()
|
||||
.get(function_index - ctor_import_functions)
|
||||
.ok_or(Error::MalformedModule)?
|
||||
.type_ref();
|
||||
|
||||
let elements::Type::Function(func) = ctor_module
|
||||
.type_section()
|
||||
.ok_or(Error::NoTypeSection)?
|
||||
.types()
|
||||
.get(type_id as usize)
|
||||
.ok_or(Error::MalformedModule)?;
|
||||
|
||||
// Deploy should have no arguments and also should return nothing
|
||||
if !func.params().is_empty() {
|
||||
return Err(Error::InvalidCreateSignature(target.symbols().create))
|
||||
}
|
||||
if !func.results().is_empty() {
|
||||
return Err(Error::InvalidCreateSignature(target.symbols().create))
|
||||
}
|
||||
|
||||
function_internal_index
|
||||
};
|
||||
|
||||
let ret_function_id = {
|
||||
let mut id = 0;
|
||||
let mut found = false;
|
||||
for entry in ctor_module.import_section().ok_or(Error::NoImportSection)?.entries().iter() {
|
||||
if let External::Function(_) = *entry.external() {
|
||||
if entry.field() == target.symbols().ret {
|
||||
found = true;
|
||||
break
|
||||
} else {
|
||||
id += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
let mut mbuilder = builder::from_module(ctor_module);
|
||||
let import_sig = mbuilder
|
||||
.push_signature(builder::signature().param().i32().param().i32().build_sig());
|
||||
|
||||
mbuilder.push_import(
|
||||
builder::import()
|
||||
.module("env")
|
||||
.field(target.symbols().ret)
|
||||
.external()
|
||||
.func(import_sig)
|
||||
.build(),
|
||||
);
|
||||
|
||||
ctor_module = mbuilder.build();
|
||||
|
||||
let ret_func = ctor_module.import_count(ImportCountType::Function) as u32 - 1;
|
||||
|
||||
for section in ctor_module.sections_mut() {
|
||||
match section {
|
||||
elements::Section::Code(code_section) => {
|
||||
for func_body in code_section.bodies_mut() {
|
||||
update_call_index(func_body.code_mut(), ret_func);
|
||||
}
|
||||
},
|
||||
elements::Section::Export(export_section) => {
|
||||
for export in export_section.entries_mut() {
|
||||
if let elements::Internal::Function(func_index) = export.internal_mut()
|
||||
{
|
||||
if *func_index >= ret_func {
|
||||
*func_index += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
elements::Section::Element(elements_section) => {
|
||||
for segment in elements_section.entries_mut() {
|
||||
// update all indirect call addresses initial values
|
||||
for func_index in segment.members_mut() {
|
||||
if *func_index >= ret_func {
|
||||
*func_index += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
create_func_id += 1;
|
||||
ret_func
|
||||
} else {
|
||||
id
|
||||
}
|
||||
};
|
||||
|
||||
// If new function is put in ctor module, it will have this callable index
|
||||
let last_function_index = ctor_module.functions_space();
|
||||
|
||||
// We ensure here that module has the DataSection
|
||||
if !ctor_module
|
||||
.sections()
|
||||
.iter()
|
||||
.any(|section| matches!(*section, Section::Data(_)))
|
||||
{
|
||||
// DataSection has to be the last non-custom section according the to the spec
|
||||
ctor_module
|
||||
.sections_mut()
|
||||
.push(Section::Data(DataSection::with_entries(vec![])));
|
||||
}
|
||||
|
||||
// Code data address is an address where we put the contract's code (raw_module)
|
||||
let mut code_data_address = 0i32;
|
||||
|
||||
for section in ctor_module.sections_mut() {
|
||||
if let Section::Data(data_section) = section {
|
||||
let (index, offset) = if let Some(entry) = data_section.entries().iter().last() {
|
||||
let init_expr = entry
|
||||
.offset()
|
||||
.as_ref()
|
||||
.expect("parity-wasm is compiled without bulk-memory operations")
|
||||
.code();
|
||||
if let Instruction::I32Const(offst) = init_expr[0] {
|
||||
let len = entry.value().len() as i32;
|
||||
let offst = offst as i32;
|
||||
(entry.index(), offst + (len + 4) - len % 4)
|
||||
} else {
|
||||
(0, 0)
|
||||
}
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
let code_data = DataSegment::new(
|
||||
index,
|
||||
Some(InitExpr::new(vec![Instruction::I32Const(offset), Instruction::End])),
|
||||
raw_module.clone(),
|
||||
);
|
||||
data_section.entries_mut().push(code_data);
|
||||
code_data_address = offset;
|
||||
}
|
||||
}
|
||||
|
||||
let mut new_module = builder::from_module(ctor_module)
|
||||
.function()
|
||||
.signature()
|
||||
.build()
|
||||
.body()
|
||||
.with_instructions(elements::Instructions::new(vec![
|
||||
Instruction::Call((create_func_id + ctor_import_functions) as u32),
|
||||
Instruction::I32Const(code_data_address),
|
||||
Instruction::I32Const(raw_module.len() as i32),
|
||||
Instruction::Call(ret_function_id as u32),
|
||||
Instruction::End,
|
||||
]))
|
||||
.build()
|
||||
.build()
|
||||
.build();
|
||||
|
||||
for section in new_module.sections_mut() {
|
||||
if let Section::Export(export_section) = section {
|
||||
for entry in export_section.entries_mut().iter_mut() {
|
||||
if target.symbols().create == entry.field() {
|
||||
// change `create` symbol export name into default `call` symbol name.
|
||||
*entry.field_mut() = target.symbols().call.to_owned();
|
||||
*entry.internal_mut() =
|
||||
elements::Internal::Function(last_function_index as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(new_module)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{super::optimize, *};
|
||||
|
||||
fn test_packer(mut module: elements::Module, target_runtime: &TargetRuntime) {
|
||||
let mut ctor_module = module.clone();
|
||||
optimize(&mut module, vec![target_runtime.symbols().call])
|
||||
.expect("Optimizer to finish without errors");
|
||||
optimize(&mut ctor_module, vec![target_runtime.symbols().create])
|
||||
.expect("Optimizer to finish without errors");
|
||||
|
||||
let raw_module = parity_wasm::serialize(module).unwrap();
|
||||
let ctor_module =
|
||||
pack_instance(raw_module.clone(), ctor_module, target_runtime).expect("Packing failed");
|
||||
|
||||
let data_section =
|
||||
ctor_module.data_section().expect("Packed module has to have a data section");
|
||||
let data_segment = data_section
|
||||
.entries()
|
||||
.iter()
|
||||
.last()
|
||||
.expect("Packed module has to have a data section with at least one entry");
|
||||
assert!(
|
||||
data_segment.value() == AsRef::<[u8]>::as_ref(&raw_module),
|
||||
"Last data segment should be equal to the raw module"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_data_section() {
|
||||
let target_runtime = TargetRuntime::pwasm();
|
||||
|
||||
test_packer(
|
||||
builder::module()
|
||||
.import()
|
||||
.module("env")
|
||||
.field("memory")
|
||||
.external()
|
||||
.memory(1, Some(1))
|
||||
.build()
|
||||
.function()
|
||||
.signature()
|
||||
.params()
|
||||
.i32()
|
||||
.i32()
|
||||
.build()
|
||||
.build()
|
||||
.body()
|
||||
.build()
|
||||
.build()
|
||||
.function()
|
||||
.signature()
|
||||
.build()
|
||||
.body()
|
||||
.with_instructions(elements::Instructions::new(vec![elements::Instruction::End]))
|
||||
.build()
|
||||
.build()
|
||||
.function()
|
||||
.signature()
|
||||
.build()
|
||||
.body()
|
||||
.with_instructions(elements::Instructions::new(vec![elements::Instruction::End]))
|
||||
.build()
|
||||
.build()
|
||||
.export()
|
||||
.field(target_runtime.symbols().call)
|
||||
.internal()
|
||||
.func(1)
|
||||
.build()
|
||||
.export()
|
||||
.field(target_runtime.symbols().create)
|
||||
.internal()
|
||||
.func(2)
|
||||
.build()
|
||||
.build(),
|
||||
&target_runtime,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_data_section() {
|
||||
let target_runtime = TargetRuntime::pwasm();
|
||||
|
||||
test_packer(
|
||||
builder::module()
|
||||
.import()
|
||||
.module("env")
|
||||
.field("memory")
|
||||
.external()
|
||||
.memory(1, Some(1))
|
||||
.build()
|
||||
.data()
|
||||
.offset(elements::Instruction::I32Const(16))
|
||||
.value(vec![0u8])
|
||||
.build()
|
||||
.function()
|
||||
.signature()
|
||||
.params()
|
||||
.i32()
|
||||
.i32()
|
||||
.build()
|
||||
.build()
|
||||
.body()
|
||||
.build()
|
||||
.build()
|
||||
.function()
|
||||
.signature()
|
||||
.build()
|
||||
.body()
|
||||
.with_instructions(elements::Instructions::new(vec![elements::Instruction::End]))
|
||||
.build()
|
||||
.build()
|
||||
.function()
|
||||
.signature()
|
||||
.build()
|
||||
.body()
|
||||
.with_instructions(elements::Instructions::new(vec![elements::Instruction::End]))
|
||||
.build()
|
||||
.build()
|
||||
.export()
|
||||
.field(target_runtime.symbols().call)
|
||||
.internal()
|
||||
.func(1)
|
||||
.build()
|
||||
.export()
|
||||
.field(target_runtime.symbols().create)
|
||||
.internal()
|
||||
.func(2)
|
||||
.build()
|
||||
.build(),
|
||||
&target_runtime,
|
||||
);
|
||||
}
|
||||
}
|
||||
-559
@@ -1,559 +0,0 @@
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use crate::std::{cell::RefCell, rc::Rc, slice, vec::Vec};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum EntryOrigin {
|
||||
Index(usize),
|
||||
Detached,
|
||||
}
|
||||
|
||||
impl From<usize> for EntryOrigin {
|
||||
fn from(v: usize) -> Self {
|
||||
EntryOrigin::Index(v)
|
||||
}
|
||||
}
|
||||
|
||||
/// Reference counting, link-handling object.
|
||||
#[derive(Debug)]
|
||||
pub struct Entry<T> {
|
||||
val: T,
|
||||
index: EntryOrigin,
|
||||
}
|
||||
|
||||
impl<T> Entry<T> {
|
||||
/// New entity.
|
||||
pub fn new(val: T, index: usize) -> Entry<T> {
|
||||
Entry { val, index: EntryOrigin::Index(index) }
|
||||
}
|
||||
|
||||
/// New detached entry.
|
||||
pub fn new_detached(val: T) -> Entry<T> {
|
||||
Entry { val, index: EntryOrigin::Detached }
|
||||
}
|
||||
|
||||
/// Index of the element within the reference list.
|
||||
pub fn order(&self) -> Option<usize> {
|
||||
match self.index {
|
||||
EntryOrigin::Detached => None,
|
||||
EntryOrigin::Index(idx) => Some(idx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> crate::std::ops::Deref for Entry<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
&self.val
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> crate::std::ops::DerefMut for Entry<T> {
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
&mut self.val
|
||||
}
|
||||
}
|
||||
|
||||
/// Reference to the entry in the rerence list.
|
||||
#[derive(Debug)]
|
||||
pub struct EntryRef<T>(Rc<RefCell<Entry<T>>>);
|
||||
|
||||
impl<T> Clone for EntryRef<T> {
|
||||
fn clone(&self) -> Self {
|
||||
EntryRef(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Entry<T>> for EntryRef<T> {
|
||||
fn from(v: Entry<T>) -> Self {
|
||||
EntryRef(Rc::new(RefCell::new(v)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> EntryRef<T> {
|
||||
/// Read the reference data.
|
||||
pub fn read(&self) -> crate::std::cell::Ref<Entry<T>> {
|
||||
self.0.borrow()
|
||||
}
|
||||
|
||||
/// Try to modify internal content of the referenced object.
|
||||
///
|
||||
/// May panic if it is already borrowed.
|
||||
pub fn write(&self) -> crate::std::cell::RefMut<Entry<T>> {
|
||||
self.0.borrow_mut()
|
||||
}
|
||||
|
||||
/// Index of the element within the reference list.
|
||||
pub fn order(&self) -> Option<usize> {
|
||||
self.0.borrow().order()
|
||||
}
|
||||
|
||||
/// Number of active links to this entity.
|
||||
pub fn link_count(&self) -> usize {
|
||||
Rc::strong_count(&self.0) - 1
|
||||
}
|
||||
}
|
||||
|
||||
/// List that tracks references and indices.
|
||||
#[derive(Debug)]
|
||||
pub struct RefList<T> {
|
||||
items: Vec<EntryRef<T>>,
|
||||
}
|
||||
|
||||
impl<T> Default for RefList<T> {
|
||||
fn default() -> Self {
|
||||
RefList { items: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> RefList<T> {
|
||||
/// New empty list.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Push new element in the list.
|
||||
///
|
||||
/// Returns refernce tracking entry.
|
||||
pub fn push(&mut self, t: T) -> EntryRef<T> {
|
||||
let idx = self.items.len();
|
||||
let val: EntryRef<_> = Entry::new(t, idx).into();
|
||||
self.items.push(val.clone());
|
||||
val
|
||||
}
|
||||
|
||||
/// Start deleting.
|
||||
///
|
||||
/// Start deleting some entries in the list. Returns transaction
|
||||
/// that can be populated with number of removed entries.
|
||||
/// When transaction is finailized, all entries are deleted and
|
||||
/// internal indices of other entries are updated.
|
||||
pub fn begin_delete(&mut self) -> DeleteTransaction<T> {
|
||||
DeleteTransaction { list: self, deleted: Vec::new() }
|
||||
}
|
||||
|
||||
/// Start inserting.
|
||||
///
|
||||
/// Start inserting some entries in the list at he designated position.
|
||||
/// Returns transaction that can be populated with some entries.
|
||||
/// When transaction is finailized, all entries are inserted and
|
||||
/// internal indices of other entries might be updated.
|
||||
pub fn begin_insert(&mut self, at: usize) -> InsertTransaction<T> {
|
||||
InsertTransaction { at, list: self, items: Vec::new() }
|
||||
}
|
||||
|
||||
/// Start inserting after the condition first matches (or at the end).
|
||||
///
|
||||
/// Start inserting some entries in the list at he designated position.
|
||||
/// Returns transaction that can be populated with some entries.
|
||||
/// When transaction is finailized, all entries are inserted and
|
||||
/// internal indices of other entries might be updated.
|
||||
pub fn begin_insert_after<F>(&mut self, mut f: F) -> InsertTransaction<T>
|
||||
where
|
||||
F: FnMut(&T) -> bool,
|
||||
{
|
||||
let pos = self
|
||||
.items
|
||||
.iter()
|
||||
.position(|rf| f(&**rf.read()))
|
||||
.map(|x| x + 1)
|
||||
.unwrap_or(self.items.len());
|
||||
|
||||
self.begin_insert(pos)
|
||||
}
|
||||
|
||||
/// Start inserting after the condition first no longer true (or at the end).
|
||||
///
|
||||
/// Start inserting some entries in the list at he designated position.
|
||||
/// Returns transaction that can be populated with some entries.
|
||||
/// When transaction is finailized, all entries are inserted and
|
||||
/// internal indices of other entries might be updated.
|
||||
pub fn begin_insert_not_until<F>(&mut self, mut f: F) -> InsertTransaction<T>
|
||||
where
|
||||
F: FnMut(&T) -> bool,
|
||||
{
|
||||
let pos = self.items.iter().take_while(|rf| f(&**rf.read())).count();
|
||||
self.begin_insert(pos)
|
||||
}
|
||||
|
||||
/// Get entry with index (checked).
|
||||
///
|
||||
/// Can return None when index out of bounts.
|
||||
pub fn get(&self, idx: usize) -> Option<EntryRef<T>> {
|
||||
self.items.get(idx).cloned()
|
||||
}
|
||||
|
||||
fn done_delete(&mut self, indices: &[usize]) {
|
||||
for entry in self.items.iter_mut() {
|
||||
let mut entry = entry.write();
|
||||
let total_less = indices
|
||||
.iter()
|
||||
.take_while(|x| {
|
||||
**x < entry.order().expect("Items in the list always have order; qed")
|
||||
})
|
||||
.count();
|
||||
match &mut entry.index {
|
||||
EntryOrigin::Detached => unreachable!("Items in the list always have order!"),
|
||||
EntryOrigin::Index(idx) => {
|
||||
*idx -= total_less;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
for (total_removed, idx) in indices.iter().enumerate() {
|
||||
let detached = self.items.remove(*idx - total_removed);
|
||||
detached.write().index = EntryOrigin::Detached;
|
||||
}
|
||||
}
|
||||
|
||||
fn done_insert(&mut self, index: usize, mut items: Vec<EntryRef<T>>) {
|
||||
let mut offset = 0;
|
||||
for item in items.drain(..) {
|
||||
item.write().index = EntryOrigin::Index(index + offset);
|
||||
self.items.insert(index + offset, item);
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
for idx in (index + offset)..self.items.len() {
|
||||
self.get_ref(idx).write().index = EntryOrigin::Index(idx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete several items.
|
||||
pub fn delete(&mut self, indices: &[usize]) {
|
||||
self.done_delete(indices)
|
||||
}
|
||||
|
||||
/// Delete one item.
|
||||
pub fn delete_one(&mut self, index: usize) {
|
||||
self.done_delete(&[index])
|
||||
}
|
||||
|
||||
/// Initialize from slice.
|
||||
///
|
||||
/// Slice members are cloned.
|
||||
pub fn from_slice(list: &[T]) -> Self
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
let mut res = Self::new();
|
||||
|
||||
for t in list {
|
||||
res.push(t.clone());
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/// Length of the list.
|
||||
pub fn len(&self) -> usize {
|
||||
self.items.len()
|
||||
}
|
||||
|
||||
/// Returns true iff len == 0.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Clone entry (reference counting object to item) by index.
|
||||
///
|
||||
/// Will panic if index out of bounds.
|
||||
pub fn clone_ref(&self, idx: usize) -> EntryRef<T> {
|
||||
self.items[idx].clone()
|
||||
}
|
||||
|
||||
/// Get reference to entry by index.
|
||||
///
|
||||
/// Will panic if index out of bounds.
|
||||
pub fn get_ref(&self, idx: usize) -> &EntryRef<T> {
|
||||
&self.items[idx]
|
||||
}
|
||||
|
||||
/// Iterate through entries.
|
||||
pub fn iter(&self) -> slice::Iter<EntryRef<T>> {
|
||||
self.items.iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete transaction.
|
||||
#[must_use]
|
||||
pub struct DeleteTransaction<'a, T> {
|
||||
list: &'a mut RefList<T>,
|
||||
deleted: Vec<usize>,
|
||||
}
|
||||
|
||||
impl<'a, T> DeleteTransaction<'a, T> {
|
||||
/// Add new element to the delete list.
|
||||
pub fn push(self, idx: usize) -> Self {
|
||||
let mut tx = self;
|
||||
tx.deleted.push(idx);
|
||||
tx
|
||||
}
|
||||
|
||||
/// Commit transaction.
|
||||
pub fn done(self) {
|
||||
let indices = self.deleted;
|
||||
let list = self.list;
|
||||
list.done_delete(&indices[..]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert transaction
|
||||
#[must_use]
|
||||
pub struct InsertTransaction<'a, T> {
|
||||
at: usize,
|
||||
list: &'a mut RefList<T>,
|
||||
items: Vec<EntryRef<T>>,
|
||||
}
|
||||
|
||||
impl<'a, T> InsertTransaction<'a, T> {
|
||||
/// Add new element to the delete list.
|
||||
pub fn push(&mut self, val: T) -> EntryRef<T> {
|
||||
let val: EntryRef<_> = Entry::new_detached(val).into();
|
||||
self.items.push(val.clone());
|
||||
val
|
||||
}
|
||||
|
||||
/// Commit transaction.
|
||||
pub fn done(self) {
|
||||
let items = self.items;
|
||||
let list = self.list;
|
||||
let at = self.at;
|
||||
list.done_insert(at, items);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn order() {
|
||||
let mut list = RefList::<u32>::new();
|
||||
let item00 = list.push(0);
|
||||
let item10 = list.push(10);
|
||||
let item20 = list.push(20);
|
||||
let item30 = list.push(30);
|
||||
|
||||
assert_eq!(item00.order(), Some(0));
|
||||
assert_eq!(item10.order(), Some(1));
|
||||
assert_eq!(item20.order(), Some(2));
|
||||
assert_eq!(item30.order(), Some(3));
|
||||
|
||||
assert_eq!(**item00.read(), 0);
|
||||
assert_eq!(**item10.read(), 10);
|
||||
assert_eq!(**item20.read(), 20);
|
||||
assert_eq!(**item30.read(), 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete() {
|
||||
let mut list = RefList::<u32>::new();
|
||||
let item00 = list.push(0);
|
||||
let item10 = list.push(10);
|
||||
let item20 = list.push(20);
|
||||
let item30 = list.push(30);
|
||||
|
||||
list.begin_delete().push(2).done();
|
||||
|
||||
assert_eq!(item00.order(), Some(0));
|
||||
assert_eq!(item10.order(), Some(1));
|
||||
assert_eq!(item30.order(), Some(2));
|
||||
|
||||
// but this was detached
|
||||
assert_eq!(item20.order(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complex_delete() {
|
||||
let mut list = RefList::<u32>::new();
|
||||
let item00 = list.push(0);
|
||||
let item10 = list.push(10);
|
||||
let item20 = list.push(20);
|
||||
let item30 = list.push(30);
|
||||
let item40 = list.push(40);
|
||||
let item50 = list.push(50);
|
||||
let item60 = list.push(60);
|
||||
let item70 = list.push(70);
|
||||
let item80 = list.push(80);
|
||||
let item90 = list.push(90);
|
||||
|
||||
list.begin_delete().push(1).push(2).push(4).push(6).done();
|
||||
|
||||
assert_eq!(item00.order(), Some(0));
|
||||
assert_eq!(item10.order(), None);
|
||||
assert_eq!(item20.order(), None);
|
||||
assert_eq!(item30.order(), Some(1));
|
||||
assert_eq!(item40.order(), None);
|
||||
assert_eq!(item50.order(), Some(2));
|
||||
assert_eq!(item60.order(), None);
|
||||
assert_eq!(item70.order(), Some(3));
|
||||
assert_eq!(item80.order(), Some(4));
|
||||
assert_eq!(item90.order(), Some(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert() {
|
||||
let mut list = RefList::<u32>::new();
|
||||
let item00 = list.push(0);
|
||||
let item10 = list.push(10);
|
||||
let item20 = list.push(20);
|
||||
let item30 = list.push(30);
|
||||
|
||||
let mut insert_tx = list.begin_insert(3);
|
||||
let item23 = insert_tx.push(23);
|
||||
let item27 = insert_tx.push(27);
|
||||
insert_tx.done();
|
||||
|
||||
assert_eq!(item00.order(), Some(0));
|
||||
assert_eq!(item10.order(), Some(1));
|
||||
assert_eq!(item20.order(), Some(2));
|
||||
assert_eq!(item23.order(), Some(3));
|
||||
assert_eq!(item27.order(), Some(4));
|
||||
assert_eq!(item30.order(), Some(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_end() {
|
||||
let mut list = RefList::<u32>::new();
|
||||
|
||||
let mut insert_tx = list.begin_insert(0);
|
||||
let item0 = insert_tx.push(0);
|
||||
insert_tx.done();
|
||||
|
||||
assert_eq!(item0.order(), Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_end_more() {
|
||||
let mut list = RefList::<u32>::new();
|
||||
let item0 = list.push(0);
|
||||
|
||||
let mut insert_tx = list.begin_insert(1);
|
||||
let item1 = insert_tx.push(1);
|
||||
insert_tx.done();
|
||||
|
||||
assert_eq!(item0.order(), Some(0));
|
||||
assert_eq!(item1.order(), Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_after() {
|
||||
let mut list = RefList::<u32>::new();
|
||||
let item00 = list.push(0);
|
||||
let item10 = list.push(10);
|
||||
let item20 = list.push(20);
|
||||
let item30 = list.push(30);
|
||||
|
||||
let mut insert_tx = list.begin_insert_after(|i| *i == 20);
|
||||
|
||||
let item23 = insert_tx.push(23);
|
||||
let item27 = insert_tx.push(27);
|
||||
insert_tx.done();
|
||||
|
||||
assert_eq!(item00.order(), Some(0));
|
||||
assert_eq!(item10.order(), Some(1));
|
||||
assert_eq!(item20.order(), Some(2));
|
||||
assert_eq!(item23.order(), Some(3));
|
||||
assert_eq!(item27.order(), Some(4));
|
||||
assert_eq!(item30.order(), Some(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_not_until() {
|
||||
let mut list = RefList::<u32>::new();
|
||||
let item10 = list.push(10);
|
||||
let item20 = list.push(20);
|
||||
let item30 = list.push(30);
|
||||
|
||||
let mut insert_tx = list.begin_insert_not_until(|i| *i <= 20);
|
||||
|
||||
let item23 = insert_tx.push(23);
|
||||
let item27 = insert_tx.push(27);
|
||||
insert_tx.done();
|
||||
|
||||
assert_eq!(item10.order(), Some(0));
|
||||
assert_eq!(item20.order(), Some(1));
|
||||
assert_eq!(item23.order(), Some(2));
|
||||
assert_eq!(item27.order(), Some(3));
|
||||
assert_eq!(item30.order(), Some(4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_after_none() {
|
||||
let mut list = RefList::<u32>::new();
|
||||
let item10 = list.push(10);
|
||||
let item20 = list.push(20);
|
||||
let item30 = list.push(30);
|
||||
|
||||
let mut insert_tx = list.begin_insert_after(|i| *i == 50);
|
||||
|
||||
let item55 = insert_tx.push(23);
|
||||
let item59 = insert_tx.push(27);
|
||||
insert_tx.done();
|
||||
|
||||
assert_eq!(item10.order(), Some(0));
|
||||
assert_eq!(item20.order(), Some(1));
|
||||
assert_eq!(item30.order(), Some(2));
|
||||
assert_eq!(item55.order(), Some(3));
|
||||
assert_eq!(item59.order(), Some(4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_not_until_none() {
|
||||
let mut list = RefList::<u32>::new();
|
||||
let item10 = list.push(10);
|
||||
let item20 = list.push(20);
|
||||
let item30 = list.push(30);
|
||||
|
||||
let mut insert_tx = list.begin_insert_not_until(|i| *i < 50);
|
||||
|
||||
let item55 = insert_tx.push(23);
|
||||
let item59 = insert_tx.push(27);
|
||||
insert_tx.done();
|
||||
|
||||
assert_eq!(item10.order(), Some(0));
|
||||
assert_eq!(item20.order(), Some(1));
|
||||
assert_eq!(item30.order(), Some(2));
|
||||
assert_eq!(item55.order(), Some(3));
|
||||
assert_eq!(item59.order(), Some(4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_after_empty() {
|
||||
let mut list = RefList::<u32>::new();
|
||||
|
||||
let mut insert_tx = list.begin_insert_after(|x| *x == 100);
|
||||
let item0 = insert_tx.push(0);
|
||||
insert_tx.done();
|
||||
|
||||
assert_eq!(item0.order(), Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_more() {
|
||||
let mut list = RefList::<u32>::new();
|
||||
let item10 = list.push(10);
|
||||
let item20 = list.push(20);
|
||||
let item30 = list.push(30);
|
||||
let item40 = list.push(10);
|
||||
let item50 = list.push(20);
|
||||
let item60 = list.push(30);
|
||||
|
||||
let mut insert_tx = list.begin_insert(3);
|
||||
let item35 = insert_tx.push(23);
|
||||
let item37 = insert_tx.push(27);
|
||||
insert_tx.done();
|
||||
|
||||
assert_eq!(item10.order(), Some(0));
|
||||
assert_eq!(item20.order(), Some(1));
|
||||
assert_eq!(item30.order(), Some(2));
|
||||
assert_eq!(item35.order(), Some(3));
|
||||
assert_eq!(item37.order(), Some(4));
|
||||
assert_eq!(item40.order(), Some(5));
|
||||
assert_eq!(item50.order(), Some(6));
|
||||
assert_eq!(item60.order(), Some(7));
|
||||
}
|
||||
}
|
||||
-355
@@ -1,355 +0,0 @@
|
||||
#[cfg(not(features = "std"))]
|
||||
use crate::std::collections::BTreeMap as Map;
|
||||
#[cfg(features = "std")]
|
||||
use crate::std::collections::HashMap as Map;
|
||||
|
||||
use crate::std::{num::NonZeroU32, str::FromStr};
|
||||
use parity_wasm::elements::Instruction;
|
||||
|
||||
pub struct UnknownInstruction;
|
||||
|
||||
/// An interface that describes instruction costs.
|
||||
pub trait Rules {
|
||||
/// Returns the cost for the passed `instruction`.
|
||||
///
|
||||
/// Returning `None` makes the gas instrumention end with an error. This is meant
|
||||
/// as a way to have a partial rule set where any instruction that is not specifed
|
||||
/// is considered as forbidden.
|
||||
fn instruction_cost(&self, instruction: &Instruction) -> Option<u32>;
|
||||
|
||||
/// Returns the costs for growing the memory using the `memory.grow` instruction.
|
||||
///
|
||||
/// Please note that these costs are in addition to the costs specified by `instruction_cost`
|
||||
/// for the `memory.grow` instruction. Specifying `None` leads to no additional charge.
|
||||
/// Those are meant as dynamic costs which take the amount of pages that the memory is
|
||||
/// grown by into consideration. This is not possible using `instruction_cost` because
|
||||
/// those costs depend on the stack and must be injected as code into the function calling
|
||||
/// `memory.grow`. Therefore returning `Some` comes with a performance cost.
|
||||
fn memory_grow_cost(&self) -> Option<MemoryGrowCost>;
|
||||
}
|
||||
|
||||
/// Dynamic costs for memory growth.
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum MemoryGrowCost {
|
||||
/// Charge the specified amount for each page that the memory is grown by.
|
||||
Linear(NonZeroU32),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum Metering {
|
||||
Regular,
|
||||
Forbidden,
|
||||
Fixed(u32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
||||
pub enum InstructionType {
|
||||
Bit,
|
||||
Add,
|
||||
Mul,
|
||||
Div,
|
||||
Load,
|
||||
Store,
|
||||
Const,
|
||||
FloatConst,
|
||||
Local,
|
||||
Global,
|
||||
ControlFlow,
|
||||
IntegerComparison,
|
||||
FloatComparison,
|
||||
Float,
|
||||
Conversion,
|
||||
FloatConversion,
|
||||
Reinterpretation,
|
||||
Unreachable,
|
||||
Nop,
|
||||
CurrentMemory,
|
||||
GrowMemory,
|
||||
|
||||
#[cfg(feature = "sign_ext")]
|
||||
SignExt,
|
||||
}
|
||||
|
||||
impl FromStr for InstructionType {
|
||||
type Err = UnknownInstruction;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"bit" => Ok(InstructionType::Bit),
|
||||
"add" => Ok(InstructionType::Add),
|
||||
"mul" => Ok(InstructionType::Mul),
|
||||
"div" => Ok(InstructionType::Div),
|
||||
"load" => Ok(InstructionType::Load),
|
||||
"store" => Ok(InstructionType::Store),
|
||||
"const" => Ok(InstructionType::Const),
|
||||
"local" => Ok(InstructionType::Local),
|
||||
"global" => Ok(InstructionType::Global),
|
||||
"flow" => Ok(InstructionType::ControlFlow),
|
||||
"integer_comp" => Ok(InstructionType::IntegerComparison),
|
||||
"float_comp" => Ok(InstructionType::FloatComparison),
|
||||
"float" => Ok(InstructionType::Float),
|
||||
"conversion" => Ok(InstructionType::Conversion),
|
||||
"float_conversion" => Ok(InstructionType::FloatConversion),
|
||||
"reinterpret" => Ok(InstructionType::Reinterpretation),
|
||||
"unreachable" => Ok(InstructionType::Unreachable),
|
||||
"nop" => Ok(InstructionType::Nop),
|
||||
"current_mem" => Ok(InstructionType::CurrentMemory),
|
||||
"grow_mem" => Ok(InstructionType::GrowMemory),
|
||||
|
||||
#[cfg(feature = "sign_ext")]
|
||||
"sign_ext" => Ok(InstructionType::SignExt),
|
||||
|
||||
_ => Err(UnknownInstruction),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InstructionType {
|
||||
pub fn op(instruction: &Instruction) -> Self {
|
||||
use Instruction::*;
|
||||
|
||||
match *instruction {
|
||||
Unreachable => InstructionType::Unreachable,
|
||||
Nop => InstructionType::Nop,
|
||||
Block(_) => InstructionType::ControlFlow,
|
||||
Loop(_) => InstructionType::ControlFlow,
|
||||
If(_) => InstructionType::ControlFlow,
|
||||
Else => InstructionType::ControlFlow,
|
||||
End => InstructionType::ControlFlow,
|
||||
Br(_) => InstructionType::ControlFlow,
|
||||
BrIf(_) => InstructionType::ControlFlow,
|
||||
BrTable(_) => InstructionType::ControlFlow,
|
||||
Return => InstructionType::ControlFlow,
|
||||
Call(_) => InstructionType::ControlFlow,
|
||||
CallIndirect(_, _) => InstructionType::ControlFlow,
|
||||
Drop => InstructionType::ControlFlow,
|
||||
Select => InstructionType::ControlFlow,
|
||||
|
||||
GetLocal(_) => InstructionType::Local,
|
||||
SetLocal(_) => InstructionType::Local,
|
||||
TeeLocal(_) => InstructionType::Local,
|
||||
GetGlobal(_) => InstructionType::Global,
|
||||
SetGlobal(_) => InstructionType::Global,
|
||||
|
||||
I32Load(_, _) => InstructionType::Load,
|
||||
I64Load(_, _) => InstructionType::Load,
|
||||
F32Load(_, _) => InstructionType::Load,
|
||||
F64Load(_, _) => InstructionType::Load,
|
||||
I32Load8S(_, _) => InstructionType::Load,
|
||||
I32Load8U(_, _) => InstructionType::Load,
|
||||
I32Load16S(_, _) => InstructionType::Load,
|
||||
I32Load16U(_, _) => InstructionType::Load,
|
||||
I64Load8S(_, _) => InstructionType::Load,
|
||||
I64Load8U(_, _) => InstructionType::Load,
|
||||
I64Load16S(_, _) => InstructionType::Load,
|
||||
I64Load16U(_, _) => InstructionType::Load,
|
||||
I64Load32S(_, _) => InstructionType::Load,
|
||||
I64Load32U(_, _) => InstructionType::Load,
|
||||
|
||||
I32Store(_, _) => InstructionType::Store,
|
||||
I64Store(_, _) => InstructionType::Store,
|
||||
F32Store(_, _) => InstructionType::Store,
|
||||
F64Store(_, _) => InstructionType::Store,
|
||||
I32Store8(_, _) => InstructionType::Store,
|
||||
I32Store16(_, _) => InstructionType::Store,
|
||||
I64Store8(_, _) => InstructionType::Store,
|
||||
I64Store16(_, _) => InstructionType::Store,
|
||||
I64Store32(_, _) => InstructionType::Store,
|
||||
|
||||
CurrentMemory(_) => InstructionType::CurrentMemory,
|
||||
GrowMemory(_) => InstructionType::GrowMemory,
|
||||
|
||||
I32Const(_) => InstructionType::Const,
|
||||
I64Const(_) => InstructionType::Const,
|
||||
|
||||
F32Const(_) => InstructionType::FloatConst,
|
||||
F64Const(_) => InstructionType::FloatConst,
|
||||
|
||||
I32Eqz => InstructionType::IntegerComparison,
|
||||
I32Eq => InstructionType::IntegerComparison,
|
||||
I32Ne => InstructionType::IntegerComparison,
|
||||
I32LtS => InstructionType::IntegerComparison,
|
||||
I32LtU => InstructionType::IntegerComparison,
|
||||
I32GtS => InstructionType::IntegerComparison,
|
||||
I32GtU => InstructionType::IntegerComparison,
|
||||
I32LeS => InstructionType::IntegerComparison,
|
||||
I32LeU => InstructionType::IntegerComparison,
|
||||
I32GeS => InstructionType::IntegerComparison,
|
||||
I32GeU => InstructionType::IntegerComparison,
|
||||
|
||||
I64Eqz => InstructionType::IntegerComparison,
|
||||
I64Eq => InstructionType::IntegerComparison,
|
||||
I64Ne => InstructionType::IntegerComparison,
|
||||
I64LtS => InstructionType::IntegerComparison,
|
||||
I64LtU => InstructionType::IntegerComparison,
|
||||
I64GtS => InstructionType::IntegerComparison,
|
||||
I64GtU => InstructionType::IntegerComparison,
|
||||
I64LeS => InstructionType::IntegerComparison,
|
||||
I64LeU => InstructionType::IntegerComparison,
|
||||
I64GeS => InstructionType::IntegerComparison,
|
||||
I64GeU => InstructionType::IntegerComparison,
|
||||
|
||||
F32Eq => InstructionType::FloatComparison,
|
||||
F32Ne => InstructionType::FloatComparison,
|
||||
F32Lt => InstructionType::FloatComparison,
|
||||
F32Gt => InstructionType::FloatComparison,
|
||||
F32Le => InstructionType::FloatComparison,
|
||||
F32Ge => InstructionType::FloatComparison,
|
||||
|
||||
F64Eq => InstructionType::FloatComparison,
|
||||
F64Ne => InstructionType::FloatComparison,
|
||||
F64Lt => InstructionType::FloatComparison,
|
||||
F64Gt => InstructionType::FloatComparison,
|
||||
F64Le => InstructionType::FloatComparison,
|
||||
F64Ge => InstructionType::FloatComparison,
|
||||
|
||||
I32Clz => InstructionType::Bit,
|
||||
I32Ctz => InstructionType::Bit,
|
||||
I32Popcnt => InstructionType::Bit,
|
||||
I32Add => InstructionType::Add,
|
||||
I32Sub => InstructionType::Add,
|
||||
I32Mul => InstructionType::Mul,
|
||||
I32DivS => InstructionType::Div,
|
||||
I32DivU => InstructionType::Div,
|
||||
I32RemS => InstructionType::Div,
|
||||
I32RemU => InstructionType::Div,
|
||||
I32And => InstructionType::Bit,
|
||||
I32Or => InstructionType::Bit,
|
||||
I32Xor => InstructionType::Bit,
|
||||
I32Shl => InstructionType::Bit,
|
||||
I32ShrS => InstructionType::Bit,
|
||||
I32ShrU => InstructionType::Bit,
|
||||
I32Rotl => InstructionType::Bit,
|
||||
I32Rotr => InstructionType::Bit,
|
||||
|
||||
I64Clz => InstructionType::Bit,
|
||||
I64Ctz => InstructionType::Bit,
|
||||
I64Popcnt => InstructionType::Bit,
|
||||
I64Add => InstructionType::Add,
|
||||
I64Sub => InstructionType::Add,
|
||||
I64Mul => InstructionType::Mul,
|
||||
I64DivS => InstructionType::Div,
|
||||
I64DivU => InstructionType::Div,
|
||||
I64RemS => InstructionType::Div,
|
||||
I64RemU => InstructionType::Div,
|
||||
I64And => InstructionType::Bit,
|
||||
I64Or => InstructionType::Bit,
|
||||
I64Xor => InstructionType::Bit,
|
||||
I64Shl => InstructionType::Bit,
|
||||
I64ShrS => InstructionType::Bit,
|
||||
I64ShrU => InstructionType::Bit,
|
||||
I64Rotl => InstructionType::Bit,
|
||||
I64Rotr => InstructionType::Bit,
|
||||
|
||||
F32Abs => InstructionType::Float,
|
||||
F32Neg => InstructionType::Float,
|
||||
F32Ceil => InstructionType::Float,
|
||||
F32Floor => InstructionType::Float,
|
||||
F32Trunc => InstructionType::Float,
|
||||
F32Nearest => InstructionType::Float,
|
||||
F32Sqrt => InstructionType::Float,
|
||||
F32Add => InstructionType::Float,
|
||||
F32Sub => InstructionType::Float,
|
||||
F32Mul => InstructionType::Float,
|
||||
F32Div => InstructionType::Float,
|
||||
F32Min => InstructionType::Float,
|
||||
F32Max => InstructionType::Float,
|
||||
F32Copysign => InstructionType::Float,
|
||||
F64Abs => InstructionType::Float,
|
||||
F64Neg => InstructionType::Float,
|
||||
F64Ceil => InstructionType::Float,
|
||||
F64Floor => InstructionType::Float,
|
||||
F64Trunc => InstructionType::Float,
|
||||
F64Nearest => InstructionType::Float,
|
||||
F64Sqrt => InstructionType::Float,
|
||||
F64Add => InstructionType::Float,
|
||||
F64Sub => InstructionType::Float,
|
||||
F64Mul => InstructionType::Float,
|
||||
F64Div => InstructionType::Float,
|
||||
F64Min => InstructionType::Float,
|
||||
F64Max => InstructionType::Float,
|
||||
F64Copysign => InstructionType::Float,
|
||||
|
||||
I32WrapI64 => InstructionType::Conversion,
|
||||
I64ExtendSI32 => InstructionType::Conversion,
|
||||
I64ExtendUI32 => InstructionType::Conversion,
|
||||
|
||||
I32TruncSF32 => InstructionType::FloatConversion,
|
||||
I32TruncUF32 => InstructionType::FloatConversion,
|
||||
I32TruncSF64 => InstructionType::FloatConversion,
|
||||
I32TruncUF64 => InstructionType::FloatConversion,
|
||||
I64TruncSF32 => InstructionType::FloatConversion,
|
||||
I64TruncUF32 => InstructionType::FloatConversion,
|
||||
I64TruncSF64 => InstructionType::FloatConversion,
|
||||
I64TruncUF64 => InstructionType::FloatConversion,
|
||||
F32ConvertSI32 => InstructionType::FloatConversion,
|
||||
F32ConvertUI32 => InstructionType::FloatConversion,
|
||||
F32ConvertSI64 => InstructionType::FloatConversion,
|
||||
F32ConvertUI64 => InstructionType::FloatConversion,
|
||||
F32DemoteF64 => InstructionType::FloatConversion,
|
||||
F64ConvertSI32 => InstructionType::FloatConversion,
|
||||
F64ConvertUI32 => InstructionType::FloatConversion,
|
||||
F64ConvertSI64 => InstructionType::FloatConversion,
|
||||
F64ConvertUI64 => InstructionType::FloatConversion,
|
||||
F64PromoteF32 => InstructionType::FloatConversion,
|
||||
|
||||
I32ReinterpretF32 => InstructionType::Reinterpretation,
|
||||
I64ReinterpretF64 => InstructionType::Reinterpretation,
|
||||
F32ReinterpretI32 => InstructionType::Reinterpretation,
|
||||
F64ReinterpretI64 => InstructionType::Reinterpretation,
|
||||
|
||||
#[cfg(feature = "sign_ext")]
|
||||
SignExt(_) => InstructionType::SignExt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Set {
|
||||
regular: u32,
|
||||
entries: Map<InstructionType, Metering>,
|
||||
grow: u32,
|
||||
}
|
||||
|
||||
impl Default for Set {
|
||||
fn default() -> Self {
|
||||
Set { regular: 1, entries: Map::new(), grow: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Set {
|
||||
pub fn new(regular: u32, entries: Map<InstructionType, Metering>) -> Self {
|
||||
Set { regular, entries, grow: 0 }
|
||||
}
|
||||
|
||||
pub fn grow_cost(&self) -> u32 {
|
||||
self.grow
|
||||
}
|
||||
|
||||
pub fn with_grow_cost(mut self, val: u32) -> Self {
|
||||
self.grow = val;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_forbidden_floats(mut self) -> Self {
|
||||
self.entries.insert(InstructionType::Float, Metering::Forbidden);
|
||||
self.entries.insert(InstructionType::FloatComparison, Metering::Forbidden);
|
||||
self.entries.insert(InstructionType::FloatConst, Metering::Forbidden);
|
||||
self.entries.insert(InstructionType::FloatConversion, Metering::Forbidden);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Rules for Set {
|
||||
fn instruction_cost(&self, instruction: &Instruction) -> Option<u32> {
|
||||
match self.entries.get(&InstructionType::op(instruction)) {
|
||||
None | Some(Metering::Regular) => Some(self.regular),
|
||||
Some(Metering::Fixed(val)) => Some(*val),
|
||||
Some(Metering::Forbidden) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn memory_grow_cost(&self) -> Option<MemoryGrowCost> {
|
||||
NonZeroU32::new(self.grow).map(MemoryGrowCost::Linear)
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
use self::elements::{
|
||||
ExportEntry, External, GlobalEntry, GlobalType, InitExpr, Instruction, Internal, Module,
|
||||
ValueType,
|
||||
};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use parity_wasm::{builder, elements};
|
||||
|
||||
pub fn inject_runtime_type(module: Module, runtime_type: [u8; 4], runtime_version: u32) -> Module {
|
||||
let runtime_type: u32 = LittleEndian::read_u32(&runtime_type);
|
||||
let globals_count: u32 = match module.global_section() {
|
||||
Some(section) => section.entries().len() as u32,
|
||||
None => 0,
|
||||
};
|
||||
let imported_globals_count: u32 = match module.import_section() {
|
||||
Some(section) => section
|
||||
.entries()
|
||||
.iter()
|
||||
.filter(|e| matches!(*e.external(), External::Global(_)))
|
||||
.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![Instruction::I32Const(runtime_type as i32), Instruction::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![Instruction::I32Const(runtime_version as i32), Instruction::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![Instruction::I32Const(42)]),
|
||||
))
|
||||
.build();
|
||||
let mut runtime_type: [u8; 4] = Default::default();
|
||||
runtime_type.copy_from_slice(b"emcc");
|
||||
module = inject_runtime_type(module, runtime_type, 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().any(|e| e.field() == "RUNTIME_TYPE"));
|
||||
assert!(export_section.entries().iter().any(|e| e.field() == "RUNTIME_VERSION"));
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
use crate::std::vec::Vec;
|
||||
|
||||
use super::{resolve_func_type, Error};
|
||||
use log::trace;
|
||||
use super::resolve_func_type;
|
||||
use alloc::vec::Vec;
|
||||
use parity_wasm::elements::{self, BlockType, Type};
|
||||
|
||||
#[cfg(feature = "sign_ext")]
|
||||
@@ -48,59 +46,44 @@ impl Stack {
|
||||
|
||||
/// Returns a reference to a frame by specified depth relative to the top of
|
||||
/// control stack.
|
||||
fn frame(&self, rel_depth: u32) -> Result<&Frame, Error> {
|
||||
fn frame(&self, rel_depth: u32) -> Result<&Frame, &'static str> {
|
||||
let control_stack_height: usize = self.control_stack.len();
|
||||
let last_idx = control_stack_height
|
||||
.checked_sub(1)
|
||||
.ok_or_else(|| Error("control stack is empty".into()))?;
|
||||
let idx = last_idx
|
||||
.checked_sub(rel_depth as usize)
|
||||
.ok_or_else(|| Error("control stack out-of-bounds".into()))?;
|
||||
let last_idx = control_stack_height.checked_sub(1).ok_or("control stack is empty")?;
|
||||
let idx = last_idx.checked_sub(rel_depth as usize).ok_or("control stack out-of-bounds")?;
|
||||
Ok(&self.control_stack[idx])
|
||||
}
|
||||
|
||||
/// Mark successive instructions as unreachable.
|
||||
///
|
||||
/// This effectively makes stack polymorphic.
|
||||
fn mark_unreachable(&mut self) -> Result<(), Error> {
|
||||
trace!(target: "max_height", "unreachable");
|
||||
let top_frame = self
|
||||
.control_stack
|
||||
.last_mut()
|
||||
.ok_or_else(|| Error("stack must be non-empty".into()))?;
|
||||
fn mark_unreachable(&mut self) -> Result<(), &'static str> {
|
||||
let top_frame = self.control_stack.last_mut().ok_or("stack must be non-empty")?;
|
||||
top_frame.is_polymorphic = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Push control frame into the control stack.
|
||||
fn push_frame(&mut self, frame: Frame) {
|
||||
trace!(target: "max_height", "push_frame: {:?}", frame);
|
||||
self.control_stack.push(frame);
|
||||
}
|
||||
|
||||
/// Pop control frame from the control stack.
|
||||
///
|
||||
/// Returns `Err` if the control stack is empty.
|
||||
fn pop_frame(&mut self) -> Result<Frame, Error> {
|
||||
trace!(target: "max_height", "pop_frame: {:?}", self.control_stack.last());
|
||||
self.control_stack.pop().ok_or_else(|| Error("stack must be non-empty".into()))
|
||||
fn pop_frame(&mut self) -> Result<Frame, &'static str> {
|
||||
self.control_stack.pop().ok_or("stack must be non-empty")
|
||||
}
|
||||
|
||||
/// Truncate the height of value stack to the specified height.
|
||||
fn trunc(&mut self, new_height: u32) {
|
||||
trace!(target: "max_height", "trunc: {}", new_height);
|
||||
self.height = new_height;
|
||||
}
|
||||
|
||||
/// Push specified number of values into the value stack.
|
||||
///
|
||||
/// Returns `Err` if the height overflow usize value.
|
||||
fn push_values(&mut self, value_count: u32) -> Result<(), Error> {
|
||||
trace!(target: "max_height", "push: {}", value_count);
|
||||
self.height = self
|
||||
.height
|
||||
.checked_add(value_count)
|
||||
.ok_or_else(|| Error("stack overflow".into()))?;
|
||||
fn push_values(&mut self, value_count: u32) -> Result<(), &'static str> {
|
||||
self.height = self.height.checked_add(value_count).ok_or("stack overflow")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -108,8 +91,7 @@ impl Stack {
|
||||
///
|
||||
/// Returns `Err` if the stack happen to be negative value after
|
||||
/// values popped.
|
||||
fn pop_values(&mut self, value_count: u32) -> Result<(), Error> {
|
||||
trace!(target: "max_height", "pop: {}", value_count);
|
||||
fn pop_values(&mut self, value_count: u32) -> Result<(), &'static str> {
|
||||
if value_count == 0 {
|
||||
return Ok(())
|
||||
}
|
||||
@@ -122,45 +104,39 @@ impl Stack {
|
||||
return if top_frame.is_polymorphic {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error("trying to pop more values than pushed".into()))
|
||||
return Err("trying to pop more values than pushed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.height = self
|
||||
.height
|
||||
.checked_sub(value_count)
|
||||
.ok_or_else(|| Error("stack underflow".into()))?;
|
||||
self.height = self.height.checked_sub(value_count).ok_or("stack underflow")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This function expects the function to be validated.
|
||||
pub(crate) fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, Error> {
|
||||
pub fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, &'static str> {
|
||||
use parity_wasm::elements::Instruction::*;
|
||||
|
||||
let func_section =
|
||||
module.function_section().ok_or_else(|| Error("No function section".into()))?;
|
||||
let code_section = module.code_section().ok_or_else(|| Error("No code section".into()))?;
|
||||
let type_section = module.type_section().ok_or_else(|| Error("No type section".into()))?;
|
||||
|
||||
trace!(target: "max_height", "func_idx: {}", func_idx);
|
||||
let func_section = module.function_section().ok_or("No function section")?;
|
||||
let code_section = module.code_section().ok_or("No code section")?;
|
||||
let type_section = module.type_section().ok_or("No type section")?;
|
||||
|
||||
// Get a signature and a body of the specified function.
|
||||
let func_sig_idx = func_section
|
||||
.entries()
|
||||
.get(func_idx as usize)
|
||||
.ok_or_else(|| Error("Function is not found in func section".into()))?
|
||||
.ok_or("Function is not found in func section")?
|
||||
.type_ref();
|
||||
let Type::Function(func_signature) = type_section
|
||||
.types()
|
||||
.get(func_sig_idx as usize)
|
||||
.ok_or_else(|| Error("Function is not found in func section".into()))?;
|
||||
.ok_or("Function is not found in func section")?;
|
||||
let body = code_section
|
||||
.bodies()
|
||||
.get(func_idx as usize)
|
||||
.ok_or_else(|| Error("Function body for the index isn't found".into()))?;
|
||||
.ok_or("Function body for the index isn't found")?;
|
||||
let instructions = body.code();
|
||||
|
||||
let mut stack = Stack::new();
|
||||
@@ -190,7 +166,6 @@ pub(crate) fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, E
|
||||
}
|
||||
|
||||
let opcode = &instructions.elements()[pc];
|
||||
trace!(target: "max_height", "{:?}", opcode);
|
||||
|
||||
match opcode {
|
||||
Nop => {},
|
||||
@@ -247,7 +222,7 @@ pub(crate) fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, E
|
||||
for target in &*br_table_data.table {
|
||||
let arity = stack.frame(*target)?.branch_arity;
|
||||
if arity != arity_of_default {
|
||||
return Err(Error("Arity of all jump-targets must be equal".into()))
|
||||
return Err("Arity of all jump-targets must be equal")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,10 +251,8 @@ pub(crate) fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, E
|
||||
stack.push_values(callee_arity)?;
|
||||
},
|
||||
CallIndirect(x, _) => {
|
||||
let Type::Function(ty) = type_section
|
||||
.types()
|
||||
.get(*x as usize)
|
||||
.ok_or_else(|| Error("Type not found".into()))?;
|
||||
let Type::Function(ty) =
|
||||
type_section.types().get(*x as usize).ok_or("Type not found")?;
|
||||
|
||||
// Pop the offset into the function table.
|
||||
stack.pop_values(1)?;
|
||||
@@ -1,56 +1,7 @@
|
||||
//! The pass that tries to make stack overflows deterministic, by introducing
|
||||
//! an upper bound of the stack size.
|
||||
//!
|
||||
//! This pass introduces a global mutable variable to track stack height,
|
||||
//! and instruments all calls with preamble and postamble.
|
||||
//!
|
||||
//! Stack height is increased prior the call. Otherwise, the check would
|
||||
//! be made after the stack frame is allocated.
|
||||
//!
|
||||
//! The preamble is inserted before the call. It increments
|
||||
//! the global stack height variable with statically determined "stack cost"
|
||||
//! of the callee. If after the increment the stack height exceeds
|
||||
//! the limit (specified by the `rules`) then execution traps.
|
||||
//! Otherwise, the call is executed.
|
||||
//!
|
||||
//! The postamble is inserted after the call. The purpose of the postamble is to decrease
|
||||
//! the stack height by the "stack cost" of the callee function.
|
||||
//!
|
||||
//! Note, that we can't instrument all possible ways to return from the function. The simplest
|
||||
//! example would be a trap issued by the host function.
|
||||
//! That means stack height global won't be equal to zero upon the next execution after such trap.
|
||||
//!
|
||||
//! # Thunks
|
||||
//!
|
||||
//! Because stack height is increased prior the call few problems arises:
|
||||
//!
|
||||
//! - Stack height isn't increased upon an entry to the first function, i.e. exported function.
|
||||
//! - Start function is executed externally (similar to exported functions).
|
||||
//! - It is statically unknown what function will be invoked in an indirect call.
|
||||
//!
|
||||
//! The solution for this problems is to generate a intermediate functions, called 'thunks', which
|
||||
//! will increase before and decrease the stack height after the call to original function, and
|
||||
//! then make exported function and table entries, start section to point to a corresponding thunks.
|
||||
//!
|
||||
//! # Stack cost
|
||||
//!
|
||||
//! Stack cost of the function is calculated as a sum of it's locals
|
||||
//! and the maximal height of the value stack.
|
||||
//!
|
||||
//! All values are treated equally, as they have the same size.
|
||||
//!
|
||||
//! The rationale is that this makes it possible to use the following very naive wasm executor:
|
||||
//!
|
||||
//! - values are implemented by a union, so each value takes a size equal to
|
||||
//! the size of the largest possible value type this union can hold. (In MVP it is 8 bytes)
|
||||
//! - each value from the value stack is placed on the native stack.
|
||||
//! - each local variable and function argument is placed on the native stack.
|
||||
//! - arguments pushed by the caller are copied into callee stack rather than shared
|
||||
//! between the frames.
|
||||
//! - upon entry into the function entire stack frame is allocated.
|
||||
|
||||
use crate::std::{mem, string::String, vec::Vec};
|
||||
//! Contains the code for the stack height limiter instrumentation.
|
||||
|
||||
use alloc::{vec, vec::Vec};
|
||||
use core::mem;
|
||||
use parity_wasm::{
|
||||
builder,
|
||||
elements::{self, Instruction, Instructions, Type},
|
||||
@@ -87,13 +38,7 @@ macro_rules! instrument_call {
|
||||
mod max_height;
|
||||
mod thunk;
|
||||
|
||||
/// Error that occured during processing the module.
|
||||
///
|
||||
/// This means that the module is invalid.
|
||||
#[derive(Debug)]
|
||||
pub struct Error(String);
|
||||
|
||||
pub(crate) struct Context {
|
||||
pub struct Context {
|
||||
stack_height_global_idx: u32,
|
||||
func_stack_costs: Vec<u32>,
|
||||
stack_limit: u32,
|
||||
@@ -116,17 +61,60 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
/// Instrument a module with stack height limiter.
|
||||
/// Inject the instumentation that makes stack overflows deterministic, by introducing
|
||||
/// an upper bound of the stack size.
|
||||
///
|
||||
/// See module-level documentation for more details.
|
||||
/// This pass introduces a global mutable variable to track stack height,
|
||||
/// and instruments all calls with preamble and postamble.
|
||||
///
|
||||
/// # Errors
|
||||
/// Stack height is increased prior the call. Otherwise, the check would
|
||||
/// be made after the stack frame is allocated.
|
||||
///
|
||||
/// Returns `Err` if module is invalid and can't be
|
||||
pub fn inject_limiter(
|
||||
/// The preamble is inserted before the call. It increments
|
||||
/// the global stack height variable with statically determined "stack cost"
|
||||
/// of the callee. If after the increment the stack height exceeds
|
||||
/// the limit (specified by the `rules`) then execution traps.
|
||||
/// Otherwise, the call is executed.
|
||||
///
|
||||
/// The postamble is inserted after the call. The purpose of the postamble is to decrease
|
||||
/// the stack height by the "stack cost" of the callee function.
|
||||
///
|
||||
/// Note, that we can't instrument all possible ways to return from the function. The simplest
|
||||
/// example would be a trap issued by the host function.
|
||||
/// That means stack height global won't be equal to zero upon the next execution after such trap.
|
||||
///
|
||||
/// # Thunks
|
||||
///
|
||||
/// Because stack height is increased prior the call few problems arises:
|
||||
///
|
||||
/// - Stack height isn't increased upon an entry to the first function, i.e. exported function.
|
||||
/// - Start function is executed externally (similar to exported functions).
|
||||
/// - It is statically unknown what function will be invoked in an indirect call.
|
||||
///
|
||||
/// The solution for this problems is to generate a intermediate functions, called 'thunks', which
|
||||
/// will increase before and decrease the stack height after the call to original function, and
|
||||
/// then make exported function and table entries, start section to point to a corresponding thunks.
|
||||
///
|
||||
/// # Stack cost
|
||||
///
|
||||
/// Stack cost of the function is calculated as a sum of it's locals
|
||||
/// and the maximal height of the value stack.
|
||||
///
|
||||
/// All values are treated equally, as they have the same size.
|
||||
///
|
||||
/// The rationale is that this makes it possible to use the following very naive wasm executor:
|
||||
///
|
||||
/// - values are implemented by a union, so each value takes a size equal to the size of the largest
|
||||
/// possible value type this union can hold. (In MVP it is 8 bytes)
|
||||
/// - each value from the value stack is placed on the native stack.
|
||||
/// - each local variable and function argument is placed on the native stack.
|
||||
/// - arguments pushed by the caller are copied into callee stack rather than shared between the
|
||||
/// frames.
|
||||
/// - upon entry into the function entire stack frame is allocated.
|
||||
pub fn inject(
|
||||
mut module: elements::Module,
|
||||
stack_limit: u32,
|
||||
) -> Result<elements::Module, Error> {
|
||||
) -> Result<elements::Module, &'static str> {
|
||||
let mut ctx = Context {
|
||||
stack_height_global_idx: generate_stack_height_global(&mut module),
|
||||
func_stack_costs: compute_stack_costs(&module)?,
|
||||
@@ -166,7 +154,7 @@ fn generate_stack_height_global(module: &mut elements::Module) -> u32 {
|
||||
/// Calculate stack costs for all functions.
|
||||
///
|
||||
/// Returns a vector with a stack cost for each function, including imports.
|
||||
fn compute_stack_costs(module: &elements::Module) -> Result<Vec<u32>, Error> {
|
||||
fn compute_stack_costs(module: &elements::Module) -> Result<Vec<u32>, &'static str> {
|
||||
let func_imports = module.import_count(elements::ImportCountType::Function);
|
||||
|
||||
// TODO: optimize!
|
||||
@@ -185,37 +173,38 @@ fn compute_stack_costs(module: &elements::Module) -> Result<Vec<u32>, Error> {
|
||||
/// Stack cost of the given *defined* function is the sum of it's locals count (that is,
|
||||
/// number of arguments plus number of local variables) and the maximal stack
|
||||
/// height.
|
||||
fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result<u32, Error> {
|
||||
fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result<u32, &'static str> {
|
||||
// To calculate the cost of a function we need to convert index from
|
||||
// function index space to defined function spaces.
|
||||
let func_imports = module.import_count(elements::ImportCountType::Function) as u32;
|
||||
let defined_func_idx = func_idx
|
||||
.checked_sub(func_imports)
|
||||
.ok_or_else(|| Error("This should be a index of a defined function".into()))?;
|
||||
.ok_or("This should be a index of a defined function")?;
|
||||
|
||||
let code_section = module
|
||||
.code_section()
|
||||
.ok_or_else(|| Error("Due to validation code section should exists".into()))?;
|
||||
let code_section =
|
||||
module.code_section().ok_or("Due to validation code section should exists")?;
|
||||
let body = &code_section
|
||||
.bodies()
|
||||
.get(defined_func_idx as usize)
|
||||
.ok_or_else(|| Error("Function body is out of bounds".into()))?;
|
||||
.ok_or("Function body is out of bounds")?;
|
||||
|
||||
let mut locals_count: u32 = 0;
|
||||
for local_group in body.locals() {
|
||||
locals_count = locals_count
|
||||
.checked_add(local_group.count())
|
||||
.ok_or_else(|| Error("Overflow in local count".into()))?;
|
||||
locals_count =
|
||||
locals_count.checked_add(local_group.count()).ok_or("Overflow in local count")?;
|
||||
}
|
||||
|
||||
let max_stack_height = max_height::compute(defined_func_idx, module)?;
|
||||
|
||||
locals_count
|
||||
.checked_add(max_stack_height)
|
||||
.ok_or_else(|| Error("Overflow in adding locals_count and max_stack_height".into()))
|
||||
.ok_or("Overflow in adding locals_count and max_stack_height")
|
||||
}
|
||||
|
||||
fn instrument_functions(ctx: &mut Context, module: &mut elements::Module) -> Result<(), Error> {
|
||||
fn instrument_functions(
|
||||
ctx: &mut Context,
|
||||
module: &mut elements::Module,
|
||||
) -> Result<(), &'static str> {
|
||||
for section in module.sections_mut() {
|
||||
if let elements::Section::Code(code_section) = section {
|
||||
for func_body in code_section.bodies_mut() {
|
||||
@@ -253,7 +242,7 @@ fn instrument_functions(ctx: &mut Context, module: &mut elements::Module) -> Res
|
||||
///
|
||||
/// drop
|
||||
/// ```
|
||||
fn instrument_function(ctx: &mut Context, func: &mut Instructions) -> Result<(), Error> {
|
||||
fn instrument_function(ctx: &mut Context, func: &mut Instructions) -> Result<(), &'static str> {
|
||||
use Instruction::*;
|
||||
|
||||
struct InstrumentCall {
|
||||
@@ -314,7 +303,7 @@ fn instrument_function(ctx: &mut Context, func: &mut Instructions) -> Result<(),
|
||||
}
|
||||
|
||||
if calls.next().is_some() {
|
||||
return Err(Error("Not all calls were used".into()))
|
||||
return Err("Not all calls were used")
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -323,7 +312,7 @@ fn instrument_function(ctx: &mut Context, func: &mut Instructions) -> Result<(),
|
||||
fn resolve_func_type(
|
||||
func_idx: u32,
|
||||
module: &elements::Module,
|
||||
) -> Result<&elements::FunctionType, Error> {
|
||||
) -> Result<&elements::FunctionType, &'static str> {
|
||||
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
|
||||
let functions = module.function_section().map(|fs| fs.entries()).unwrap_or(&[]);
|
||||
|
||||
@@ -347,12 +336,12 @@ fn resolve_func_type(
|
||||
} else {
|
||||
functions
|
||||
.get(func_idx as usize - func_imports)
|
||||
.ok_or_else(|| Error(format!("Function at index {} is not defined", func_idx)))?
|
||||
.ok_or("Function at the specified index is not defined")?
|
||||
.type_ref()
|
||||
};
|
||||
let Type::Function(ty) = types.get(sig_idx as usize).ok_or_else(|| {
|
||||
Error(format!("Signature {} (specified by func {}) isn't defined", sig_idx, func_idx))
|
||||
})?;
|
||||
let Type::Function(ty) = types
|
||||
.get(sig_idx as usize)
|
||||
.ok_or("The signature as specified by a function isn't defined")?;
|
||||
Ok(ty)
|
||||
}
|
||||
|
||||
@@ -388,7 +377,7 @@ mod tests {
|
||||
"#,
|
||||
);
|
||||
|
||||
let module = inject_limiter(module, 1024).expect("Failed to inject stack counter");
|
||||
let module = inject(module, 1024).expect("Failed to inject stack counter");
|
||||
validate_module(module);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
#[cfg(not(features = "std"))]
|
||||
use crate::std::collections::BTreeMap as Map;
|
||||
#[cfg(features = "std")]
|
||||
use crate::std::collections::HashMap as Map;
|
||||
use crate::std::vec::Vec;
|
||||
|
||||
use alloc::collections::BTreeMap as Map;
|
||||
use alloc::vec::Vec;
|
||||
use parity_wasm::{
|
||||
builder,
|
||||
elements::{self, FunctionType, Internal},
|
||||
};
|
||||
#[cfg(features = "std")]
|
||||
use std::collections::HashMap as Map;
|
||||
|
||||
use super::{resolve_func_type, Context, Error};
|
||||
use super::{resolve_func_type, Context};
|
||||
|
||||
struct Thunk {
|
||||
signature: FunctionType,
|
||||
@@ -18,12 +17,11 @@ struct Thunk {
|
||||
callee_stack_cost: u32,
|
||||
}
|
||||
|
||||
pub(crate) fn generate_thunks(
|
||||
pub fn generate_thunks(
|
||||
ctx: &mut Context,
|
||||
module: elements::Module,
|
||||
) -> Result<elements::Module, Error> {
|
||||
) -> Result<elements::Module, &'static str> {
|
||||
// First, we need to collect all function indices that should be replaced by thunks
|
||||
|
||||
let mut replacement_map: Map<u32, Thunk> = {
|
||||
let exports = module.export_section().map(|es| es.entries()).unwrap_or(&[]);
|
||||
let elem_segments = module.elements_section().map(|es| es.entries()).unwrap_or(&[]);
|
||||
@@ -43,9 +41,7 @@ pub(crate) fn generate_thunks(
|
||||
.chain(table_func_indices)
|
||||
.chain(start_func_idx.into_iter())
|
||||
{
|
||||
let callee_stack_cost = ctx
|
||||
.stack_cost(func_idx)
|
||||
.ok_or_else(|| Error(format!("function with idx {} isn't found", func_idx)))?;
|
||||
let callee_stack_cost = ctx.stack_cost(func_idx).ok_or("function index isn't found")?;
|
||||
|
||||
// Don't generate a thunk if stack_cost of a callee is zero.
|
||||
if callee_stack_cost != 0 {
|
||||
-155
@@ -1,155 +0,0 @@
|
||||
#[cfg(not(features = "std"))]
|
||||
use crate::std::collections::BTreeSet as Set;
|
||||
#[cfg(features = "std")]
|
||||
use crate::std::collections::HashSet as Set;
|
||||
use crate::std::vec::Vec;
|
||||
|
||||
use log::trace;
|
||||
use parity_wasm::elements;
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone, Debug)]
|
||||
pub enum Symbol {
|
||||
Type(usize),
|
||||
Import(usize),
|
||||
Global(usize),
|
||||
Function(usize),
|
||||
Export(usize),
|
||||
}
|
||||
|
||||
pub fn resolve_function(module: &elements::Module, index: u32) -> Symbol {
|
||||
let mut functions = 0;
|
||||
if let Some(import_section) = module.import_section() {
|
||||
for (item_index, item) in import_section.entries().iter().enumerate() {
|
||||
if let elements::External::Function(_) = item.external() {
|
||||
if functions == index {
|
||||
return Symbol::Import(item_index as usize)
|
||||
}
|
||||
functions += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Symbol::Function(index as usize - functions as usize)
|
||||
}
|
||||
|
||||
pub fn resolve_global(module: &elements::Module, index: u32) -> Symbol {
|
||||
let mut globals = 0;
|
||||
if let Some(import_section) = module.import_section() {
|
||||
for (item_index, item) in import_section.entries().iter().enumerate() {
|
||||
if let elements::External::Global(_) = item.external() {
|
||||
if globals == index {
|
||||
return Symbol::Import(item_index as usize)
|
||||
}
|
||||
globals += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Symbol::Global(index as usize - globals as usize)
|
||||
}
|
||||
|
||||
pub fn push_code_symbols(
|
||||
module: &elements::Module,
|
||||
instructions: &[elements::Instruction],
|
||||
dest: &mut Vec<Symbol>,
|
||||
) {
|
||||
use parity_wasm::elements::Instruction::*;
|
||||
|
||||
for instruction in instructions {
|
||||
match instruction {
|
||||
&Call(idx) => {
|
||||
dest.push(resolve_function(module, idx));
|
||||
},
|
||||
&CallIndirect(idx, _) => {
|
||||
dest.push(Symbol::Type(idx as usize));
|
||||
},
|
||||
&GetGlobal(idx) | &SetGlobal(idx) => dest.push(resolve_global(module, idx)),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expand_symbols(module: &elements::Module, set: &mut Set<Symbol>) {
|
||||
use self::Symbol::*;
|
||||
|
||||
// symbols that were already processed
|
||||
let mut stop: Set<Symbol> = Set::new();
|
||||
let mut fringe = set.iter().cloned().collect::<Vec<Symbol>>();
|
||||
loop {
|
||||
let next = match fringe.pop() {
|
||||
Some(s) if stop.contains(&s) => continue,
|
||||
Some(s) => s,
|
||||
_ => break,
|
||||
};
|
||||
trace!("Processing symbol {:?}", next);
|
||||
|
||||
match next {
|
||||
Export(idx) => {
|
||||
let entry =
|
||||
&module.export_section().expect("Export section to exist").entries()[idx];
|
||||
match entry.internal() {
|
||||
elements::Internal::Function(func_idx) => {
|
||||
let symbol = resolve_function(module, *func_idx);
|
||||
if !stop.contains(&symbol) {
|
||||
fringe.push(symbol);
|
||||
}
|
||||
set.insert(symbol);
|
||||
},
|
||||
elements::Internal::Global(global_idx) => {
|
||||
let symbol = resolve_global(module, *global_idx);
|
||||
if !stop.contains(&symbol) {
|
||||
fringe.push(symbol);
|
||||
}
|
||||
set.insert(symbol);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
Import(idx) => {
|
||||
let entry =
|
||||
&module.import_section().expect("Import section to exist").entries()[idx];
|
||||
if let elements::External::Function(type_idx) = entry.external() {
|
||||
let type_symbol = Symbol::Type(*type_idx as usize);
|
||||
if !stop.contains(&type_symbol) {
|
||||
fringe.push(type_symbol);
|
||||
}
|
||||
set.insert(type_symbol);
|
||||
}
|
||||
},
|
||||
Function(idx) => {
|
||||
let body = &module.code_section().expect("Code section to exist").bodies()[idx];
|
||||
let mut code_symbols = Vec::new();
|
||||
push_code_symbols(module, body.code().elements(), &mut code_symbols);
|
||||
for symbol in code_symbols.drain(..) {
|
||||
if !stop.contains(&symbol) {
|
||||
fringe.push(symbol);
|
||||
}
|
||||
set.insert(symbol);
|
||||
}
|
||||
|
||||
let signature =
|
||||
&module.function_section().expect("Functions section to exist").entries()[idx];
|
||||
let type_symbol = Symbol::Type(signature.type_ref() as usize);
|
||||
if !stop.contains(&type_symbol) {
|
||||
fringe.push(type_symbol);
|
||||
}
|
||||
set.insert(type_symbol);
|
||||
},
|
||||
Global(idx) => {
|
||||
let entry =
|
||||
&module.global_section().expect("Global section to exist").entries()[idx];
|
||||
let mut code_symbols = Vec::new();
|
||||
push_code_symbols(module, entry.init_expr().code(), &mut code_symbols);
|
||||
for symbol in code_symbols.drain(..) {
|
||||
if !stop.contains(&symbol) {
|
||||
fringe.push(symbol);
|
||||
}
|
||||
set.insert(symbol);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
stop.insert(next);
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -1,10 +1,10 @@
|
||||
use parity_wasm::elements;
|
||||
use pwasm_utils as utils;
|
||||
use std::{
|
||||
fs,
|
||||
io::{self, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use wasm_instrument as instrument;
|
||||
|
||||
fn slurp<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
|
||||
let mut f = fs::File::open(path)?;
|
||||
@@ -41,7 +41,7 @@ fn run_diff_test<F: FnOnce(&[u8]) -> Vec<u8>>(test_dir: &str, name: &str, test:
|
||||
validate_wasm(&fixture_wasm).expect("Fixture is invalid");
|
||||
|
||||
let expected_wat = slurp(&expected_path).unwrap_or_default();
|
||||
let expected_wat = String::from_utf8_lossy(&expected_wat);
|
||||
let expected_wat = std::str::from_utf8(&expected_wat).expect("Failed to decode expected wat");
|
||||
|
||||
let actual_wasm = test(fixture_wasm.as_ref());
|
||||
validate_wasm(&actual_wasm).expect("Result module is invalid");
|
||||
@@ -52,7 +52,7 @@ fn run_diff_test<F: FnOnce(&[u8]) -> Vec<u8>>(test_dir: &str, name: &str, test:
|
||||
println!("difference!");
|
||||
println!("--- {}", expected_path.display());
|
||||
println!("+++ {} test {}", test_dir, name);
|
||||
for diff in diff::lines(&expected_wat, &actual_wat) {
|
||||
for diff in diff::lines(expected_wat, &actual_wat) {
|
||||
match diff {
|
||||
diff::Result::Left(l) => println!("-{}", l),
|
||||
diff::Result::Both(l, _) => println!(" {}", l),
|
||||
@@ -78,7 +78,7 @@ mod stack_height {
|
||||
run_diff_test("stack-height", concat!(stringify!($name), ".wat"), |input| {
|
||||
let module =
|
||||
elements::deserialize_buffer(input).expect("Failed to deserialize");
|
||||
let instrumented = utils::stack_height::inject_limiter(module, 1024)
|
||||
let instrumented = instrument::inject_stack_limiter(module, 1024)
|
||||
.expect("Failed to instrument with stack counter");
|
||||
elements::serialize(instrumented).expect("Failed to serialize")
|
||||
});
|
||||
@@ -102,11 +102,11 @@ mod gas {
|
||||
#[test]
|
||||
fn $name() {
|
||||
run_diff_test("gas", concat!(stringify!($name), ".wat"), |input| {
|
||||
let rules = utils::rules::Set::default();
|
||||
let rules = instrument::gas_metering::ConstantCostRules::default();
|
||||
|
||||
let module =
|
||||
elements::deserialize_buffer(input).expect("Failed to deserialize");
|
||||
let instrumented = utils::inject_gas_counter(module, &rules, "env")
|
||||
let instrumented = instrument::gas_metering::inject(module, &rules, "env")
|
||||
.expect("Failed to instrument with gas metering");
|
||||
elements::serialize(instrumented).expect("Failed to serialize")
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user