mirror of
https://github.com/pezkuwichain/wasm-instrument.git
synced 2026-04-22 16:07:57 +00:00
Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 10fd3529f2 | |||
| 32ae9f8478 | |||
| 8bfdb41d01 | |||
| 659d3bf12c | |||
| 5b2f75a066 | |||
| 25ff883bbd | |||
| 3b932b11ad | |||
| d10bbdf554 | |||
| 28ef7f550c | |||
| 4a394c5f88 | |||
| d1648be274 | |||
| 4a51f16874 | |||
| 8380823e62 | |||
| 88d652e69a | |||
| 4713aa760f | |||
| 4548a86329 | |||
| ff68bee449 | |||
| 7c843842a7 | |||
| 374afe5700 | |||
| 8291876394 | |||
| 184b3f8b3a | |||
| 57da96fb50 | |||
| 4e3e6b598a | |||
| c5eaffd229 | |||
| e882111f92 | |||
| b22696aaa5 | |||
| 5259f1c922 | |||
| da2f0a0f23 | |||
| 1d62dc1270 | |||
| abd5d4f6df | |||
| c9b837c80a | |||
| 2bf8068571 | |||
| 2293760964 | |||
| 2f88f49ef6 | |||
| 6ff274f0b8 | |||
| 3d3e3010b3 | |||
| ef4e09fff9 | |||
| b8b18be419 | |||
| 72626a566a | |||
| b8e6b9e319 | |||
| cb023973e8 | |||
| a0b548b37d | |||
| 77ad07e347 | |||
| c5043a47ac | |||
| 9f15b8cd21 | |||
| 1ef05d71ed | |||
| 0cead7ba6d | |||
| e1c8ce90a1 | |||
| 9e98400de0 | |||
| 19760b5835 | |||
| 717aa8f0cb | |||
| d891cddcb3 | |||
| 95a711bc33 | |||
| 1b0ed1b383 | |||
| ca45220af5 | |||
| 2d1f4daed7 | |||
| ad01d9b41c | |||
| b2272f39bc | |||
| d9432bafa9 | |||
| 1131240d39 | |||
| 988ac32095 | |||
| 712c696c2d | |||
| 9a0f992cb3 | |||
| d6127afd1d | |||
| 7da376062a | |||
| 3ab49836be | |||
| 2430b18633 | |||
| aebfc0fbd7 | |||
| a2653cff5a | |||
| 3142a74de2 | |||
| f556bde4a4 | |||
| f05f43b883 | |||
| af761da031 | |||
| fbaae277fc | |||
| e0a05c6329 | |||
| e0dbaef676 | |||
| 880d273861 | |||
| 3568667ecb | |||
| c09a924a81 | |||
| f59eb121e5 | |||
| 1f8e6dd5b2 | |||
| 5e3b06de05 | |||
| 2306999c9c | |||
| 016425a25b | |||
| 409ced002a | |||
| b61f6dd52f | |||
| e026abe166 | |||
| 8c6dec11a4 | |||
| 3f2935df9c | |||
| dcf189b822 | |||
| 19b5fb50cb | |||
| 1e8953a9cb | |||
| 377684f9c7 | |||
| e89abb0c17 | |||
| e6336a4a90 | |||
| a881cf12bb | |||
| 2fe761f8c4 |
+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
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# For details about syntax, see:
|
||||
# https://help.github.com/en/articles/about-code-owners
|
||||
|
||||
* @athei @pepyakin
|
||||
@@ -0,0 +1,11 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: cargo
|
||||
directory: "/"
|
||||
labels: []
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: github-actions
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: daily
|
||||
@@ -0,0 +1,97 @@
|
||||
name: Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
rustfmt:
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Cargo fmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
clippy:
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
components: clippy
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Cargo clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
command: clippy
|
||||
args: --all-targets --all-features -- -D warnings
|
||||
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
|
||||
toolchain: ["stable", "nightly"]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Install Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
target: wasm32-unknown-unknown
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
|
||||
- name: Set git to use LF
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Cargo build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
command: build
|
||||
|
||||
- name: Cargo build (no_std)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
command: build
|
||||
args: --no-default-features
|
||||
|
||||
- name: Cargo build (wasm)
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
command: build
|
||||
args: --no-default-features --target wasm32-unknown-unknown
|
||||
|
||||
- name: Cargo test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
command: test
|
||||
args: --all-features
|
||||
@@ -3,3 +3,4 @@ target
|
||||
.cargo
|
||||
.DS_Store
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
language: rust
|
||||
rust:
|
||||
- nightly
|
||||
- stable
|
||||
|
||||
script:
|
||||
- cargo build --all --release --verbose
|
||||
- cargo test --all --verbose
|
||||
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo build --no-default-features; fi
|
||||
@@ -0,0 +1,34 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
The semantic versioning guarantees cover the interface to the substrate runtime which
|
||||
includes this pallet as a dependency. This module will also add storage migrations whenever
|
||||
changes require it. Stability with regard to offchain tooling is explicitly excluded from
|
||||
this guarantee: For example adding a new field to an in-storage data structure will require
|
||||
changes to frontends to properly display it. However, those changes will still be regarded
|
||||
as a minor version bump.
|
||||
|
||||
The interface provided to smart contracts will adhere to semver with one exception: Even
|
||||
major version bumps will be backwards compatible with regard to already deployed contracts.
|
||||
In other words: Upgrading this pallet will not break pre-existing contracts.
|
||||
|
||||
## [v0.2.0] 2022-06-06
|
||||
- Adjust debug information (if already parsed) when injecting gas metering
|
||||
[#16](https://github.com/paritytech/wasm-instrument/pull/16)
|
||||
|
||||
## [v0.1.1] 2022-01-18
|
||||
|
||||
### Fixed
|
||||
|
||||
- Stack metering disregarded the activiation frame.
|
||||
[#2](https://github.com/paritytech/wasm-instrument/pull/2)
|
||||
|
||||
## [v0.1.0] 2022-01-11
|
||||
|
||||
### Changed
|
||||
|
||||
- Created from [pwasm-utils](https://github.com/paritytech/wasm-utils) by removing unused code and cleaning up docs.
|
||||
+31
-24
@@ -1,32 +1,39 @@
|
||||
[package]
|
||||
name = "pwasm-utils"
|
||||
version = "0.11.0"
|
||||
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"]
|
||||
name = "wasm-instrument"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.56.1"
|
||||
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"]
|
||||
|
||||
[[bench]]
|
||||
name = "benches"
|
||||
harness = false
|
||||
path = "benches/benches.rs"
|
||||
|
||||
[profile.bench]
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
|
||||
[dependencies]
|
||||
# If you add the feature "bulk", make sure you fixed all expects that say
|
||||
# "parity-wasm is compiled without bulk-memory operations"
|
||||
parity-wasm = { version = "0.40.1", default-features = false }
|
||||
log = { version = "0.4", default-features = false }
|
||||
byteorder = { version = "1", default-features = false }
|
||||
parity-wasm = { version = "0.45", default-features = false }
|
||||
log = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
tempdir = "0.3"
|
||||
wabt = "0.2"
|
||||
diff = "0.1.11"
|
||||
indoc = "0.3"
|
||||
rand = "0.7"
|
||||
binaryen = "0.8"
|
||||
binaryen = "0.12"
|
||||
criterion = "0.3"
|
||||
diff = "0.1"
|
||||
rand = "0.8"
|
||||
wat = "1"
|
||||
wasmparser = "0.88"
|
||||
wasmprinter = "0.2"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["parity-wasm/std", "log/std", "byteorder/std"]
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"./cli",
|
||||
]
|
||||
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,48 +1,33 @@
|
||||
# wasm-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.
|
||||
|
||||
Collection of WASM utilities used in pwasm-ethereum and substrate contract development
|
||||
## Provided functionality
|
||||
|
||||
## Build tools for cargo
|
||||
This is a non exhaustive list of provided functionality. Please check out the [documentation](https://docs.rs/wasm-instrument/latest/wasm_instrument/) for details.
|
||||
|
||||
Easiest way to use is to install via `cargo install`:
|
||||
### Gas Metering
|
||||
|
||||
```
|
||||
cargo install pwasm-utils-cli --bin wasm-build
|
||||
```
|
||||
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
|
||||
|
||||
```
|
||||
cargo install pwasm-utils-cli --bin wasm-prune
|
||||
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 what any reasonably parameterized execution engine would support solves the issue: All execution engines would reach the injected limit before hitting any implementation specific limitation.
|
||||
|
||||
For development puposes, raw WASM contract can be injected with gas counters (the same way as it done by pwasm-ethereum/substrate runtime when running contracts)
|
||||
## License
|
||||
|
||||
```
|
||||
cargo install pwasm-utils-cli --bin wasm-gas
|
||||
wasm-gas <input_wasm_binary.wasm> <output_wasm_binary.wasm>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
All executables use corresponding api methods of the root crate and can be combined in other build tools.
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
use criterion::{
|
||||
criterion_group, criterion_main, measurement::Measurement, BenchmarkGroup, Criterion,
|
||||
Throughput,
|
||||
};
|
||||
use std::{
|
||||
fs::{read, read_dir},
|
||||
path::PathBuf,
|
||||
};
|
||||
use wasm_instrument::{
|
||||
gas_metering, inject_stack_limiter,
|
||||
parity_wasm::{deserialize_buffer, elements::Module},
|
||||
};
|
||||
|
||||
fn fixture_dir() -> PathBuf {
|
||||
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
path.push("benches");
|
||||
path.push("fixtures");
|
||||
path
|
||||
}
|
||||
|
||||
fn any_fixture<F, M>(group: &mut BenchmarkGroup<M>, f: F)
|
||||
where
|
||||
F: Fn(Module),
|
||||
M: Measurement,
|
||||
{
|
||||
for entry in read_dir(fixture_dir()).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let bytes = read(&entry.path()).unwrap();
|
||||
group.throughput(Throughput::Bytes(bytes.len().try_into().unwrap()));
|
||||
group.bench_with_input(entry.file_name().to_str().unwrap(), &bytes, |bench, input| {
|
||||
bench.iter(|| f(deserialize_buffer(input).unwrap()))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn gas_metering(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Gas Metering");
|
||||
any_fixture(&mut group, |module| {
|
||||
gas_metering::inject(module, &gas_metering::ConstantCostRules::default(), "env").unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn stack_height_limiter(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Stack Height Limiter");
|
||||
any_fixture(&mut group, |module| {
|
||||
inject_stack_limiter(module, 128).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, gas_metering, stack_height_limiter);
|
||||
criterion_main!(benches);
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,50 +0,0 @@
|
||||
[package]
|
||||
name = "pwasm-utils-cli"
|
||||
version = "0.10.0"
|
||||
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"]
|
||||
|
||||
[lib]
|
||||
|
||||
[[bin]]
|
||||
name = "wasm-prune"
|
||||
path = "prune/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "wasm-ext"
|
||||
path = "ext/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "wasm-gas"
|
||||
path = "gas/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "wasm-build"
|
||||
path = "build/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "wasm-stack-height"
|
||||
path = "stack_height/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "wasm-pack"
|
||||
path = "pack/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "wasm-check"
|
||||
path = "check/main.rs"
|
||||
|
||||
[dependencies]
|
||||
parity-wasm = "0.40.1"
|
||||
pwasm-utils = { path = "..", version = "0.11" }
|
||||
glob = "0.2"
|
||||
clap = "2.24"
|
||||
log = "0.4"
|
||||
env_logger = "0.5"
|
||||
lazy_static = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempdir = "0.3"
|
||||
@@ -1,11 +0,0 @@
|
||||
# pwasm-utils-cli
|
||||
|
||||
Collection of WASM utilities used in Parity and WASM contract devepment
|
||||
|
||||
## Install
|
||||
|
||||
Easiest way to use is to install via `cargo install`:
|
||||
|
||||
```
|
||||
cargo install pwasm-utils-cli
|
||||
```
|
||||
@@ -1,245 +0,0 @@
|
||||
//! Experimental build tool for cargo
|
||||
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
extern crate glob;
|
||||
extern crate pwasm_utils as utils;
|
||||
extern crate parity_wasm;
|
||||
extern crate pwasm_utils_cli as logger;
|
||||
|
||||
mod source;
|
||||
|
||||
use std::{fs, io};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use parity_wasm::elements;
|
||||
use utils::{build, BuildError, SourceTarget, TargetRuntime};
|
||||
|
||||
#[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(ref io) => write!(f, "Generic i/o error: {}", io),
|
||||
FailedToCopy(ref msg) => write!(f, "{}. Have you tried to run \"cargo build\"?", msg),
|
||||
Decoding(ref err, ref file) => write!(f, "Decoding error ({}). Must be a valid wasm file {}. Pointed wrong file?", err, file),
|
||||
Encoding(ref err) => write!(f, "Encoding error ({}). Almost impossible to happen, no free disk space?", err),
|
||||
Build(ref 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_log();
|
||||
|
||||
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_else(|| 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 = matches.value_of("public_api")
|
||||
.map(|val| val.split(",").collect())
|
||||
.unwrap_or(Vec::new());
|
||||
|
||||
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_else(|| "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 {
|
||||
extern crate tempdir;
|
||||
|
||||
use self::tempdir::TempDir;
|
||||
use std::fs;
|
||||
|
||||
use super::process_output;
|
||||
use super::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(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,57 +0,0 @@
|
||||
//! Configuration of source binaries
|
||||
|
||||
pub const UNKNOWN_TRIPLET: &str = "wasm32-unknown-unknown";
|
||||
pub const EMSCRIPTEN_TRIPLET: &str = "wasm32-unknown-emscripten";
|
||||
|
||||
use 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: target_dir,
|
||||
bin_name: 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,102 +0,0 @@
|
||||
extern crate parity_wasm;
|
||||
extern crate pwasm_utils as utils;
|
||||
extern crate pwasm_utils_cli as logger;
|
||||
extern crate clap;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use parity_wasm::elements;
|
||||
|
||||
fn fail(msg: &str) -> ! {
|
||||
eprintln!("{}", msg);
|
||||
std::process::exit(1)
|
||||
}
|
||||
|
||||
const ALLOWED_IMPORTS: &'static [&'static 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_log();
|
||||
|
||||
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(ref 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,23 +0,0 @@
|
||||
extern crate parity_wasm;
|
||||
extern crate pwasm_utils as utils;
|
||||
extern crate pwasm_utils_cli as logger;
|
||||
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
|
||||
logger::init_log();
|
||||
|
||||
let args = env::args().collect::<Vec<_>>();
|
||||
if args.len() != 3 {
|
||||
println!("Usage: {} input_file.wasm output_file.wasm", args[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
let module = 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,24 +0,0 @@
|
||||
extern crate parity_wasm;
|
||||
extern crate pwasm_utils as utils;
|
||||
extern crate pwasm_utils_cli as logger;
|
||||
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
logger::init_log();
|
||||
|
||||
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, &Default::default()
|
||||
).expect("Failed to inject gas. Some forbidden opcodes?");
|
||||
|
||||
parity_wasm::serialize_to_file(&args[2], result).expect("Module serialization to succeed")
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
extern crate parity_wasm;
|
||||
extern crate pwasm_utils as utils;
|
||||
extern crate pwasm_utils_cli as logger;
|
||||
extern crate clap;
|
||||
|
||||
use clap::{App, Arg};
|
||||
|
||||
fn main() {
|
||||
logger::init_log();
|
||||
|
||||
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,47 +0,0 @@
|
||||
extern crate parity_wasm;
|
||||
extern crate pwasm_utils as utils;
|
||||
extern crate pwasm_utils_cli as logger;
|
||||
extern crate clap;
|
||||
|
||||
use clap::{App, Arg};
|
||||
|
||||
fn main() {
|
||||
logger::init_log();
|
||||
|
||||
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,27 +0,0 @@
|
||||
#[macro_use] extern crate log;
|
||||
#[macro_use] extern crate lazy_static;
|
||||
extern crate env_logger;
|
||||
|
||||
use std::env;
|
||||
use log::LevelFilter;
|
||||
use env_logger::Builder;
|
||||
|
||||
lazy_static! {
|
||||
static ref LOG_DUMMY: bool = {
|
||||
let mut builder = Builder::new();
|
||||
builder.filter(None, LevelFilter::Info);
|
||||
|
||||
if let Ok(log) = env::var("RUST_LOG") {
|
||||
builder.parse(&log);
|
||||
}
|
||||
|
||||
builder.init();
|
||||
trace!("logger initialized");
|
||||
true
|
||||
};
|
||||
}
|
||||
|
||||
/// Intialize log with default settings
|
||||
pub fn init_log() {
|
||||
let _ = *LOG_DUMMY;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
extern crate pwasm_utils as utils;
|
||||
extern crate parity_wasm;
|
||||
extern crate pwasm_utils_cli as logger;
|
||||
|
||||
use std::env;
|
||||
use utils::stack_height;
|
||||
|
||||
fn main() {
|
||||
logger::init_log();
|
||||
|
||||
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,28 +0,0 @@
|
||||
extern crate pwasm_utils as utils;
|
||||
|
||||
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 = 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")
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
_default:
|
||||
just --list
|
||||
|
||||
# Run rustfmt and ensure the code meets the expectation of the checks in the CI
|
||||
format:
|
||||
cargo +nightly fmt --all
|
||||
|
||||
# Run basic checks similar to what the CI does to ensure your code is fine
|
||||
check:
|
||||
cargo +nightly fmt --all -- --check
|
||||
cargo +stable clippy --all-targets --all-features -- -D warnings
|
||||
|
||||
# Run the tests
|
||||
test:
|
||||
cargo test --all-features
|
||||
|
||||
# So you are ready? This runs format, check and test
|
||||
ready: format check test
|
||||
@@ -0,0 +1,23 @@
|
||||
# Basic
|
||||
hard_tabs = true
|
||||
max_width = 100
|
||||
use_small_heuristics = "Max"
|
||||
# Imports
|
||||
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
|
||||
binop_separator = "Back"
|
||||
reorder_impl_items = false
|
||||
match_arm_leading_pipes = "Preserve"
|
||||
match_arm_blocks = false
|
||||
match_block_trailing_comma = true
|
||||
trailing_comma = "Vertical"
|
||||
trailing_semicolon = false
|
||||
use_field_init_shorthand = true
|
||||
-123
@@ -1,123 +0,0 @@
|
||||
use std;
|
||||
use super::{
|
||||
optimize,
|
||||
pack_instance,
|
||||
ununderscore_funcs,
|
||||
externalize_mem,
|
||||
shrink_unknown_stack,
|
||||
inject_runtime_type,
|
||||
PackingError,
|
||||
OptimizerError,
|
||||
TargetRuntime,
|
||||
};
|
||||
use parity_wasm;
|
||||
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 std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||
use self::Error::*;
|
||||
match *self {
|
||||
Encoding(ref err) => write!(f, "Encoding error ({})", err),
|
||||
Optimizer => write!(f, "Optimization error due to missing export section. Pointed wrong file?"),
|
||||
Packing(ref 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(ref section) = module.export_section() {
|
||||
section.entries().iter().any(|e| target_runtime.symbols().create == e.field())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
use alloc::{format, vec::Vec};
|
||||
use parity_wasm::elements;
|
||||
|
||||
/// 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: &str) {
|
||||
let exports = global_section(module)
|
||||
.map(|section| {
|
||||
section
|
||||
.entries()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(
|
||||
|(index, global)| {
|
||||
if global.global_type().is_mutable() {
|
||||
Some(index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
if module.export_section().is_none() {
|
||||
module
|
||||
.sections_mut()
|
||||
.push(elements::Section::Export(elements::ExportSection::default()));
|
||||
}
|
||||
|
||||
for (symbol_index, export) in exports.into_iter().enumerate() {
|
||||
let new_entry = elements::ExportEntry::new(
|
||||
format!("{}_{}", prefix, symbol_index),
|
||||
elements::Internal::Global(
|
||||
(module.import_count(elements::ImportCountType::Global) + export) as _,
|
||||
),
|
||||
);
|
||||
export_section(module)
|
||||
.expect("added above if does not exists")
|
||||
.entries_mut()
|
||||
.push(new_entry);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
use super::export_mutable_globals;
|
||||
use parity_wasm::elements;
|
||||
|
||||
fn parse_wat(source: &str) -> elements::Module {
|
||||
let module_bytes = wat::parse_str(source).unwrap();
|
||||
wasmparser::validate(&module_bytes).unwrap();
|
||||
elements::deserialize_buffer(module_bytes.as_ref()).expect("failed to parse module")
|
||||
}
|
||||
|
||||
macro_rules! test_export_global {
|
||||
(name = $name:ident; input = $input:expr; expected = $expected:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let mut input_module = parse_wat($input);
|
||||
let expected_module = parse_wat($expected);
|
||||
|
||||
export_mutable_globals(&mut input_module, "exported_internal_global");
|
||||
|
||||
let actual_bytes = elements::serialize(input_module)
|
||||
.expect("injected module must have a function body");
|
||||
|
||||
let expected_bytes = elements::serialize(expected_module)
|
||||
.expect("injected module must have a function body");
|
||||
|
||||
let actual_wat = wasmprinter::print_bytes(actual_bytes).unwrap();
|
||||
let expected_wat = wasmprinter::print_bytes(expected_bytes).unwrap();
|
||||
|
||||
if actual_wat != expected_wat {
|
||||
for diff in diff::lines(&expected_wat, &actual_wat) {
|
||||
match diff {
|
||||
diff::Result::Left(l) => println!("-{}", l),
|
||||
diff::Result::Both(l, _) => println!(" {}", l),
|
||||
diff::Result::Right(r) => println!("+{}", r),
|
||||
}
|
||||
}
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test_export_global! {
|
||||
name = simple;
|
||||
input = r#"
|
||||
(module
|
||||
(global (;0;) (mut i32) (i32.const 1))
|
||||
(global (;1;) (mut i32) (i32.const 0)))
|
||||
"#;
|
||||
expected = r#"
|
||||
(module
|
||||
(global (;0;) (mut i32) (i32.const 1))
|
||||
(global (;1;) (mut i32) (i32.const 0))
|
||||
(export "exported_internal_global_0" (global 0))
|
||||
(export "exported_internal_global_1" (global 1)))
|
||||
"#
|
||||
}
|
||||
|
||||
test_export_global! {
|
||||
name = with_import;
|
||||
input = r#"
|
||||
(module
|
||||
(import "env" "global" (global $global i64))
|
||||
(global (;0;) (mut i32) (i32.const 1))
|
||||
(global (;1;) (mut i32) (i32.const 0)))
|
||||
"#;
|
||||
expected = r#"
|
||||
(module
|
||||
(import "env" "global" (global $global i64))
|
||||
(global (;0;) (mut i32) (i32.const 1))
|
||||
(global (;1;) (mut i32) (i32.const 0))
|
||||
(export "exported_internal_global_0" (global 1))
|
||||
(export "exported_internal_global_1" (global 2)))
|
||||
"#
|
||||
}
|
||||
|
||||
test_export_global! {
|
||||
name = with_import_and_some_are_immutable;
|
||||
input = r#"
|
||||
(module
|
||||
(import "env" "global" (global $global i64))
|
||||
(global (;0;) i32 (i32.const 1))
|
||||
(global (;1;) (mut i32) (i32.const 0)))
|
||||
"#;
|
||||
expected = r#"
|
||||
(module
|
||||
(import "env" "global" (global $global i64))
|
||||
(global (;0;) i32 (i32.const 1))
|
||||
(global (;1;) (mut i32) (i32.const 0))
|
||||
(export "exported_internal_global_0" (global 2)))
|
||||
"#
|
||||
}
|
||||
}
|
||||
-203
@@ -1,203 +0,0 @@
|
||||
use std::string::String;
|
||||
use std::vec::Vec;
|
||||
use std::borrow::ToOwned;
|
||||
|
||||
use parity_wasm::{elements, builder};
|
||||
use optimizer::{import_section, export_section};
|
||||
use byteorder::{LittleEndian, ByteOrder};
|
||||
|
||||
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 &mut Call(ref mut 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<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::MemorySection> {
|
||||
for section in module.sections_mut() {
|
||||
if let &mut elements::Section::Memory(ref mut 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)
|
||||
{
|
||||
import_section(&mut module).map(|is| {
|
||||
for entry in is.entries_mut() {
|
||||
if let elements::External::Function(_) = *entry.external() {
|
||||
f(entry.field_mut())
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export_section(&mut module).map(|es| {
|
||||
for entry in es.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 {
|
||||
&mut elements::Section::Data(ref mut data_section) => {
|
||||
for ref mut 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| if let &elements::External::Function(_) = e.external() { true } else { false })
|
||||
.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, 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 {
|
||||
&mut elements::Section::Code(ref mut code_section) => {
|
||||
for ref mut func_body in code_section.bodies_mut() {
|
||||
update_call_index(func_body.code_mut(), import_funcs_total, &replaces);
|
||||
}
|
||||
},
|
||||
&mut elements::Section::Export(ref mut export_section) => {
|
||||
for ref mut export in export_section.entries_mut() {
|
||||
if let &mut elements::Internal::Function(ref mut func_index) = export.internal_mut() {
|
||||
if *func_index >= import_funcs_total as u32 { *func_index += replaces.len() as u32; }
|
||||
}
|
||||
}
|
||||
},
|
||||
&mut elements::Section::Element(ref mut elements_section) => {
|
||||
for ref mut 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
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,18 +8,16 @@
|
||||
//! searching through all paths, which may take exponential time in the size of the function body in
|
||||
//! the worst case.
|
||||
|
||||
use super::MeteredBlock;
|
||||
use rules::Set as RuleSet;
|
||||
use super::{ConstantCostRules, MeteredBlock, Rules};
|
||||
use parity_wasm::elements::{FuncBody, Instruction};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::BTreeMap as Map;
|
||||
|
||||
/// An ID for a node in a ControlFlowGraph.
|
||||
type NodeId = usize;
|
||||
|
||||
/// A node in a control flow graph is commonly known as a basic block. This is a sequence of
|
||||
/// operations that are always executed sequentially.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
struct ControlFlowNode {
|
||||
/// The index of the first instruction in the basic block. This is only used for debugging.
|
||||
first_instr_pos: Option<usize>,
|
||||
@@ -42,19 +40,6 @@ struct ControlFlowNode {
|
||||
loopback_edges: Vec<NodeId>,
|
||||
}
|
||||
|
||||
impl Default for ControlFlowNode {
|
||||
fn default() -> Self {
|
||||
ControlFlowNode {
|
||||
first_instr_pos: None,
|
||||
actual_cost: 0,
|
||||
charged_cost: 0,
|
||||
is_loop_target: false,
|
||||
forward_edges: Vec::new(),
|
||||
loopback_edges: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A control flow graph where nodes are basic blocks and edges represent possible transitions
|
||||
/// between them in execution flow. The graph has two types of edges, forward and loop-back edges.
|
||||
/// The subgraph with only the forward edges forms a directed acyclic graph (DAG); including the
|
||||
@@ -66,9 +51,7 @@ pub struct ControlFlowGraph {
|
||||
|
||||
impl ControlFlowGraph {
|
||||
fn new() -> Self {
|
||||
ControlFlowGraph {
|
||||
nodes: Vec::new(),
|
||||
}
|
||||
ControlFlowGraph { nodes: Vec::new() }
|
||||
}
|
||||
|
||||
fn get_node(&self, node_id: NodeId) -> &ControlFlowNode {
|
||||
@@ -139,8 +122,8 @@ 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,
|
||||
blocks: &[MeteredBlock]
|
||||
rules: &impl Rules,
|
||||
blocks: &[MeteredBlock],
|
||||
) -> Result<ControlFlowGraph, ()> {
|
||||
let mut graph = ControlFlowGraph::new();
|
||||
|
||||
@@ -149,32 +132,31 @@ fn build_control_flow_graph(
|
||||
|
||||
graph.set_first_instr_pos(entry_node_id, 0);
|
||||
|
||||
let mut stack = Vec::new();
|
||||
stack.push(ControlFrame::new(entry_node_id, terminal_node_id, false));
|
||||
|
||||
let mut stack = vec![ControlFrame::new(entry_node_id, terminal_node_id, false)];
|
||||
let mut metered_blocks_iter = blocks.iter().peekable();
|
||||
for (cursor, instruction) in body.code().elements().iter().enumerate() {
|
||||
let active_node_id = stack.last()
|
||||
let active_node_id = stack
|
||||
.last()
|
||||
.expect("module is valid by pre-condition; control stack must not be empty; qed")
|
||||
.active_node;
|
||||
|
||||
// Increment the charged cost if there are metering instructions to be inserted here.
|
||||
let apply_block = metered_blocks_iter.peek()
|
||||
.map_or(false, |block| block.start_pos == cursor);
|
||||
let apply_block =
|
||||
metered_blocks_iter.peek().map_or(false, |block| block.start_pos == cursor);
|
||||
if apply_block {
|
||||
let next_metered_block = metered_blocks_iter.next()
|
||||
.expect("peek returned an item; qed");
|
||||
let next_metered_block =
|
||||
metered_blocks_iter.next().expect("peek returned an item; qed");
|
||||
graph.increment_charged_cost(active_node_id, next_metered_block.cost);
|
||||
}
|
||||
|
||||
let instruction_cost = rules.process(instruction)?;
|
||||
match *instruction {
|
||||
let instruction_cost = rules.instruction_cost(instruction).ok_or(())?;
|
||||
match instruction {
|
||||
Instruction::Block(_) => {
|
||||
graph.increment_actual_cost(active_node_id, instruction_cost);
|
||||
|
||||
let exit_node_id = graph.add_node();
|
||||
stack.push(ControlFrame::new(active_node_id, exit_node_id, false));
|
||||
}
|
||||
},
|
||||
Instruction::If(_) => {
|
||||
graph.increment_actual_cost(active_node_id, instruction_cost);
|
||||
|
||||
@@ -184,7 +166,7 @@ fn build_control_flow_graph(
|
||||
stack.push(ControlFrame::new(then_node_id, exit_node_id, false));
|
||||
graph.new_forward_edge(active_node_id, then_node_id);
|
||||
graph.set_first_instr_pos(then_node_id, cursor + 1);
|
||||
}
|
||||
},
|
||||
Instruction::Loop(_) => {
|
||||
graph.increment_actual_cost(active_node_id, instruction_cost);
|
||||
|
||||
@@ -194,7 +176,7 @@ fn build_control_flow_graph(
|
||||
stack.push(ControlFrame::new(loop_node_id, exit_node_id, true));
|
||||
graph.new_forward_edge(active_node_id, loop_node_id);
|
||||
graph.set_first_instr_pos(loop_node_id, cursor + 1);
|
||||
}
|
||||
},
|
||||
Instruction::Else => {
|
||||
let active_frame_idx = stack.len() - 1;
|
||||
let prev_frame_idx = stack.len() - 2;
|
||||
@@ -205,7 +187,7 @@ fn build_control_flow_graph(
|
||||
let prev_node_id = stack[prev_frame_idx].active_node;
|
||||
graph.new_forward_edge(prev_node_id, else_node_id);
|
||||
graph.set_first_instr_pos(else_node_id, cursor + 1);
|
||||
}
|
||||
},
|
||||
Instruction::End => {
|
||||
let closing_frame = stack.pop()
|
||||
.expect("module is valid by pre-condition; ends correspond to control stack frames; qed");
|
||||
@@ -216,32 +198,32 @@ fn build_control_flow_graph(
|
||||
if let Some(active_frame) = stack.last_mut() {
|
||||
active_frame.active_node = closing_frame.exit_node;
|
||||
}
|
||||
}
|
||||
},
|
||||
Instruction::Br(label) => {
|
||||
graph.increment_actual_cost(active_node_id, instruction_cost);
|
||||
|
||||
let active_frame_idx = stack.len() - 1;
|
||||
let target_frame_idx = active_frame_idx - (label as usize);
|
||||
let target_frame_idx = active_frame_idx - (*label as usize);
|
||||
graph.new_edge(active_node_id, &stack[target_frame_idx]);
|
||||
|
||||
// Next instruction is unreachable, but carry on anyway.
|
||||
let new_node_id = graph.add_node();
|
||||
stack[active_frame_idx].active_node = new_node_id;
|
||||
graph.set_first_instr_pos(new_node_id, cursor + 1);
|
||||
}
|
||||
},
|
||||
Instruction::BrIf(label) => {
|
||||
graph.increment_actual_cost(active_node_id, instruction_cost);
|
||||
|
||||
let active_frame_idx = stack.len() - 1;
|
||||
let target_frame_idx = active_frame_idx - (label as usize);
|
||||
let target_frame_idx = active_frame_idx - (*label as usize);
|
||||
graph.new_edge(active_node_id, &stack[target_frame_idx]);
|
||||
|
||||
let new_node_id = graph.add_node();
|
||||
stack[active_frame_idx].active_node = new_node_id;
|
||||
graph.new_forward_edge(active_node_id, new_node_id);
|
||||
graph.set_first_instr_pos(new_node_id, cursor + 1);
|
||||
}
|
||||
Instruction::BrTable(ref br_table_data) => {
|
||||
},
|
||||
Instruction::BrTable(br_table_data) => {
|
||||
graph.increment_actual_cost(active_node_id, instruction_cost);
|
||||
|
||||
let active_frame_idx = stack.len() - 1;
|
||||
@@ -253,7 +235,7 @@ fn build_control_flow_graph(
|
||||
let new_node_id = graph.add_node();
|
||||
stack[active_frame_idx].active_node = new_node_id;
|
||||
graph.set_first_instr_pos(new_node_id, cursor + 1);
|
||||
}
|
||||
},
|
||||
Instruction::Return => {
|
||||
graph.increment_actual_cost(active_node_id, instruction_cost);
|
||||
|
||||
@@ -263,7 +245,7 @@ fn build_control_flow_graph(
|
||||
let new_node_id = graph.add_node();
|
||||
stack[active_frame_idx].active_node = new_node_id;
|
||||
graph.set_first_instr_pos(new_node_id, cursor + 1);
|
||||
}
|
||||
},
|
||||
_ => graph.increment_actual_cost(active_node_id, instruction_cost),
|
||||
}
|
||||
}
|
||||
@@ -287,7 +269,7 @@ fn validate_graph_gas_costs(graph: &ControlFlowGraph) -> bool {
|
||||
node_id: NodeId,
|
||||
mut total_actual: u32,
|
||||
mut total_charged: u32,
|
||||
loop_costs: &mut HashMap<NodeId, (u32, u32)>,
|
||||
loop_costs: &mut Map<NodeId, (u32, u32)>,
|
||||
) -> bool {
|
||||
let node = graph.get_node(node_id);
|
||||
|
||||
@@ -299,20 +281,21 @@ fn validate_graph_gas_costs(graph: &ControlFlowGraph) -> bool {
|
||||
}
|
||||
|
||||
if node.forward_edges.is_empty() && total_actual != total_charged {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
for loop_node_id in node.loopback_edges.iter() {
|
||||
let (ref mut loop_actual, ref mut loop_charged) = loop_costs.get_mut(loop_node_id)
|
||||
let (loop_actual, loop_charged) = loop_costs
|
||||
.get_mut(loop_node_id)
|
||||
.expect("cannot arrive at loopback edge without visiting loop entry node");
|
||||
if loop_actual != loop_charged {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for next_node_id in node.forward_edges.iter() {
|
||||
if !visit(graph, *next_node_id, total_actual, total_charged, loop_costs) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,7 +307,7 @@ fn validate_graph_gas_costs(graph: &ControlFlowGraph) -> bool {
|
||||
}
|
||||
|
||||
// Recursively explore all paths through the execution graph starting from the entry node.
|
||||
visit(graph, 0, 0, 0, &mut HashMap::new())
|
||||
visit(graph, 0, 0, 0, &mut Map::new())
|
||||
}
|
||||
|
||||
/// Validate that the metered blocks are correct with respect to the function body by exhaustively
|
||||
@@ -333,19 +316,18 @@ 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,
|
||||
blocks: &[MeteredBlock]
|
||||
rules: &impl Rules,
|
||||
blocks: &[MeteredBlock],
|
||||
) -> Result<bool, ()> {
|
||||
let graph = build_control_flow_graph(body, rules, blocks)?;
|
||||
Ok(validate_graph_gas_costs(&graph))
|
||||
}
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::super::determine_metered_blocks;
|
||||
use super::{super::determine_metered_blocks, *};
|
||||
|
||||
use parity_wasm::elements;
|
||||
use binaryen::tools::translate_to_fuzz_mvp;
|
||||
use parity_wasm::elements;
|
||||
use rand::{thread_rng, RngCore};
|
||||
|
||||
#[test]
|
||||
@@ -359,10 +341,11 @@ 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 = validate_metering_injections(func_body, &rules, &metered_blocks).unwrap();
|
||||
let success =
|
||||
validate_metering_injections(func_body, &rules, &metered_blocks).unwrap();
|
||||
assert!(success);
|
||||
}
|
||||
}
|
||||
-990
@@ -1,990 +0,0 @@
|
||||
//! Wasm binary graph format
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use parity_wasm::elements;
|
||||
use super::ref_list::{RefList, EntryRef};
|
||||
use std::{
|
||||
vec::Vec,
|
||||
borrow::ToOwned,
|
||||
string::String,
|
||||
collections::BTreeMap,
|
||||
};
|
||||
|
||||
/// 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 idx = 0;
|
||||
let mut res = Module::default();
|
||||
|
||||
let mut imported_functions = 0;
|
||||
|
||||
for section in module.sections() {
|
||||
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().clone(),
|
||||
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().clone(),
|
||||
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().clone(),
|
||||
origin: ImportedOrDeclared::Declared(()),
|
||||
});
|
||||
}
|
||||
},
|
||||
elements::Section::Memory(table_section) => {
|
||||
for t in table_section.entries() {
|
||||
res.memory.push(Memory {
|
||||
limits: t.limits().clone(),
|
||||
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: 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: location,
|
||||
});
|
||||
}
|
||||
},
|
||||
elements::Section::Code(code_section) => {
|
||||
let mut idx = 0;
|
||||
for func_body in code_section.bodies() {
|
||||
let code = res.map_instructions(func_body.code().elements());
|
||||
|
||||
let mut func = res.funcs.get_ref(imported_functions + idx).write();
|
||||
match func.origin {
|
||||
ImportedOrDeclared::Declared(ref mut body) => {
|
||||
body.code = code;
|
||||
body.locals = func_body.locals().iter().cloned().collect();
|
||||
},
|
||||
_ => { return Err(Error::InconsistentSource); }
|
||||
}
|
||||
|
||||
idx += 1;
|
||||
}
|
||||
},
|
||||
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: location,
|
||||
});
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
res.other.insert(idx, section.clone());
|
||||
}
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
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.len() > 0 {
|
||||
// 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(ref module, ref 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(ref module, ref 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(ref module, ref 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(ref module, ref 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.len() > 0
|
||||
};
|
||||
|
||||
if add {
|
||||
sections.push(elements::Section::Import(import_section));
|
||||
idx += 1;
|
||||
custom_round(&self.other, &mut idx, &mut sections);
|
||||
}
|
||||
|
||||
if self.funcs.len() > 0 {
|
||||
// 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.len() > 0 {
|
||||
// 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.len() > 0 {
|
||||
// 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.len() > 0 {
|
||||
// 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(ref 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.len() > 0 {
|
||||
// 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(ref func_ref) => {
|
||||
elements::Internal::Function(func_ref.order().ok_or(Error::DetachedEntry)? as u32)
|
||||
},
|
||||
ExportLocal::Global(ref global_ref) => {
|
||||
elements::Internal::Global(global_ref.order().ok_or(Error::DetachedEntry)? as u32)
|
||||
},
|
||||
ExportLocal::Table(ref table_ref) => {
|
||||
elements::Internal::Table(table_ref.order().ok_or(Error::DetachedEntry)? as u32)
|
||||
},
|
||||
ExportLocal::Memory(ref 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(ref func_ref) = self.start {
|
||||
// START SECTION (8)
|
||||
sections.push(elements::Section::Start(
|
||||
func_ref.order().ok_or(Error::DetachedEntry)? as u32
|
||||
));
|
||||
}
|
||||
|
||||
if self.elements.len() > 0 {
|
||||
// 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(ref 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.len() > 0 {
|
||||
// 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(ref 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.len() > 0 {
|
||||
// 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(ref 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 {
|
||||
|
||||
extern crate wabt;
|
||||
|
||||
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(ref 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(ref 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| match f.origin {
|
||||
super::ImportedOrDeclared::Imported(_, _) => true,
|
||||
_ => false,
|
||||
}
|
||||
);
|
||||
|
||||
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(ref body) => {
|
||||
match body.code[1] {
|
||||
super::Instruction::Call(ref 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(ref body) => {
|
||||
match body.code[0] {
|
||||
super::Instruction::Call(ref called_func) => called_func.order(),
|
||||
ref 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
+8
-86
@@ -1,91 +1,13 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![cfg_attr(not(feature = "std"), feature(alloc))]
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[macro_use]
|
||||
extern crate alloc;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
extern crate byteorder;
|
||||
extern crate parity_wasm;
|
||||
#[macro_use] extern crate log;
|
||||
#[cfg(test)] #[macro_use] extern crate indoc;
|
||||
#[cfg(test)] extern crate rand;
|
||||
#[cfg(test)] extern crate binaryen;
|
||||
mod export_globals;
|
||||
pub mod gas_metering;
|
||||
mod stack_limiter;
|
||||
|
||||
|
||||
pub mod rules;
|
||||
|
||||
mod build;
|
||||
mod ext;
|
||||
mod gas;
|
||||
mod optimizer;
|
||||
mod pack;
|
||||
mod runtime_type;
|
||||
mod graph;
|
||||
mod ref_list;
|
||||
mod symbols;
|
||||
|
||||
pub mod stack_height;
|
||||
|
||||
pub use build::{build, Error as BuildError, SourceTarget};
|
||||
pub use ext::{
|
||||
externalize, externalize_mem, shrink_unknown_stack, underscore_funcs, ununderscore_funcs,
|
||||
};
|
||||
pub use gas::inject_gas_counter;
|
||||
pub use optimizer::{optimize, Error as OptimizerError};
|
||||
pub use pack::{pack_instance, Error as PackingError};
|
||||
pub use runtime_type::inject_runtime_type;
|
||||
pub use graph::{Module, parse as graph_parse, generate as graph_generate};
|
||||
pub use ref_list::{RefList, Entry, EntryRef, DeleteTransaction};
|
||||
|
||||
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};
|
||||
}
|
||||
}
|
||||
pub use export_globals::export_mutable_globals;
|
||||
pub use parity_wasm;
|
||||
pub use stack_limiter::{compute_stack_cost, inject as inject_stack_limiter};
|
||||
|
||||
@@ -1,663 +0,0 @@
|
||||
#[cfg(features = "std")]
|
||||
use std::collections::{HashSet as Set};
|
||||
#[cfg(not(features = "std"))]
|
||||
use std::collections::{BTreeSet as Set};
|
||||
use std::vec::Vec;
|
||||
use std::mem;
|
||||
|
||||
use parity_wasm::elements;
|
||||
|
||||
use symbols::{Symbol, expand_symbols, push_code_symbols, resolve_function};
|
||||
|
||||
#[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::replace(module, elements::Module::default());
|
||||
let module_temp = module_temp
|
||||
.parse_names()
|
||||
.unwrap_or_else(|(_err, module)| module);
|
||||
mem::replace(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().find(|e| **e == entry.field()).is_some() {
|
||||
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.len() > 0 || eliminated_funcs.len() > 0 || eliminated_types.len() > 0 {
|
||||
// Finaly, rewire all calls, globals references and types to the new indices
|
||||
// (only if there is anything to do)
|
||||
eliminated_globals.sort();
|
||||
eliminated_funcs.sort();
|
||||
eliminated_types.sort();
|
||||
|
||||
for section in module.sections_mut() {
|
||||
match section {
|
||||
&mut elements::Section::Start(ref mut func_index) if eliminated_funcs.len() > 0 => {
|
||||
let totalle = eliminated_funcs.iter().take_while(|i| (**i as u32) < *func_index).count();
|
||||
*func_index -= totalle as u32;
|
||||
},
|
||||
&mut elements::Section::Function(ref mut function_section) if eliminated_types.len() > 0 => {
|
||||
for ref mut 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;
|
||||
}
|
||||
},
|
||||
&mut elements::Section::Import(ref mut import_section) if eliminated_types.len() > 0 => {
|
||||
for ref mut import_entry in import_section.entries_mut() {
|
||||
if let &mut elements::External::Function(ref mut 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;
|
||||
}
|
||||
}
|
||||
},
|
||||
&mut elements::Section::Code(ref mut code_section) if eliminated_globals.len() > 0 || eliminated_funcs.len() > 0 => {
|
||||
for ref mut func_body in code_section.bodies_mut() {
|
||||
if eliminated_funcs.len() > 0 {
|
||||
update_call_index(func_body.code_mut(), &eliminated_funcs);
|
||||
}
|
||||
if eliminated_globals.len() > 0 {
|
||||
update_global_index(func_body.code_mut().elements_mut(), &eliminated_globals)
|
||||
}
|
||||
if eliminated_types.len() > 0 {
|
||||
update_type_index(func_body.code_mut(), &eliminated_types)
|
||||
}
|
||||
}
|
||||
},
|
||||
&mut elements::Section::Export(ref mut export_section) => {
|
||||
for ref mut export in export_section.entries_mut() {
|
||||
match export.internal_mut() {
|
||||
&mut elements::Internal::Function(ref mut func_index) => {
|
||||
let totalle = eliminated_funcs.iter().take_while(|i| (**i as u32) < *func_index).count();
|
||||
*func_index -= totalle as u32;
|
||||
},
|
||||
&mut elements::Internal::Global(ref mut global_index) => {
|
||||
let totalle = eliminated_globals.iter().take_while(|i| (**i as u32) < *global_index).count();
|
||||
*global_index -= totalle as u32;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
},
|
||||
&mut elements::Section::Global(ref mut global_section) => {
|
||||
for ref mut global_entry in global_section.entries_mut() {
|
||||
update_global_index(global_entry.init_expr_mut().code_mut(), &eliminated_globals)
|
||||
}
|
||||
},
|
||||
&mut elements::Section::Data(ref mut data_section) => {
|
||||
for ref mut 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,
|
||||
)
|
||||
}
|
||||
},
|
||||
&mut elements::Section::Element(ref mut elements_section) => {
|
||||
for ref mut 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;
|
||||
}
|
||||
}
|
||||
},
|
||||
&mut elements::Section::Name(ref mut name_section) => {
|
||||
if let Some(ref mut func_name) = name_section.functions_mut() {
|
||||
let mut func_name_map = mem::replace(func_name.names_mut(), elements::IndexMap::default());
|
||||
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();
|
||||
mem::replace(func_name.names_mut(), updated_map);
|
||||
}
|
||||
|
||||
if let Some(ref mut local_name) = name_section.locals_mut() {
|
||||
let mut local_names_map = mem::replace(local_name.local_names_mut(), elements::IndexMap::default());
|
||||
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();
|
||||
|
||||
mem::replace(local_name.local_names_mut(), updated_map);
|
||||
}
|
||||
}
|
||||
_ => { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 &mut Call(ref mut 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 {
|
||||
&mut GetGlobal(ref mut index) | &mut SetGlobal(ref mut 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 &mut CallIndirect(ref mut 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<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::ImportSection> {
|
||||
for section in module.sections_mut() {
|
||||
if let &mut elements::Section::Import(ref mut sect) = section {
|
||||
return Some(sect);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn global_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::GlobalSection> {
|
||||
for section in module.sections_mut() {
|
||||
if let &mut elements::Section::Global(ref mut sect) = section {
|
||||
return Some(sect);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn function_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::FunctionSection> {
|
||||
for section in module.sections_mut() {
|
||||
if let &mut elements::Section::Function(ref mut sect) = section {
|
||||
return Some(sect);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn code_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::CodeSection> {
|
||||
for section in module.sections_mut() {
|
||||
if let &mut elements::Section::Code(ref mut sect) = section {
|
||||
return Some(sect);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn export_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::ExportSection> {
|
||||
for section in module.sections_mut() {
|
||||
if let &mut elements::Section::Export(ref mut sect) = section {
|
||||
return Some(sect);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn type_section<'a>(module: &'a mut elements::Module) -> Option<&'a mut elements::TypeSection> {
|
||||
for section in module.sections_mut() {
|
||||
if let &mut elements::Section::Type(ref mut sect) = section {
|
||||
return Some(sect);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use parity_wasm::{builder, elements};
|
||||
use super::*;
|
||||
|
||||
/// @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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-340
@@ -1,340 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::vec::Vec;
|
||||
use std::borrow::ToOwned;
|
||||
|
||||
use parity_wasm::elements::{
|
||||
self, Section, DataSection, Instruction, DataSegment, InitExpr, Internal, External,
|
||||
ImportCountType,
|
||||
};
|
||||
use parity_wasm::builder;
|
||||
use super::TargetRuntime;
|
||||
use super::gas::update_call_index;
|
||||
|
||||
/// 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(ref 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.return_type().is_some() {
|
||||
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(ref mut code_section) => {
|
||||
for ref mut func_body in code_section.bodies_mut() {
|
||||
update_call_index(func_body.code_mut(), ret_func);
|
||||
}
|
||||
},
|
||||
elements::Section::Export(ref mut export_section) => {
|
||||
for ref mut export in export_section.entries_mut() {
|
||||
if let &mut elements::Internal::Function(ref mut func_index) = export.internal_mut() {
|
||||
if *func_index >= ret_func { *func_index += 1}
|
||||
}
|
||||
}
|
||||
},
|
||||
elements::Section::Element(ref mut elements_section) => {
|
||||
for ref mut 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()
|
||||
.find(|section| match **section { Section::Data(ref _d) => true, _ => false })
|
||||
.is_none() {
|
||||
// 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 &mut Section::Data(ref mut data_section) = section {
|
||||
let (index, offset) = if let Some(ref 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 &mut Section::Export(ref mut 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 {
|
||||
extern crate parity_wasm;
|
||||
|
||||
use parity_wasm::builder;
|
||||
use super::*;
|
||||
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 as u32, Some(1 as u32))
|
||||
.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 as u32, Some(1 as u32))
|
||||
.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,
|
||||
);
|
||||
}
|
||||
}
|
||||
-562
@@ -1,562 +0,0 @@
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use std::vec::Vec;
|
||||
use std::slice;
|
||||
|
||||
#[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: val,
|
||||
index: EntryOrigin::Index(index),
|
||||
}
|
||||
}
|
||||
|
||||
/// New detached entry.
|
||||
pub fn new_detached(val: T) -> Entry<T> {
|
||||
Entry {
|
||||
val: 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> ::std::ops::Deref for Entry<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T {
|
||||
&self.val
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ::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) -> ::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) -> ::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: 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 entry.index {
|
||||
EntryOrigin::Detached => unreachable!("Items in the list always have order!"),
|
||||
EntryOrigin::Index(ref mut idx) => { *idx -= total_less; },
|
||||
};
|
||||
}
|
||||
|
||||
let mut total_removed = 0;
|
||||
for idx in indices {
|
||||
let detached = self.items.remove(*idx - total_removed);
|
||||
detached.write().index = EntryOrigin::Detached;
|
||||
total_removed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
/// 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));
|
||||
}
|
||||
}
|
||||
-315
@@ -1,315 +0,0 @@
|
||||
#[cfg(features = "std")]
|
||||
use std::collections::{HashMap as Map};
|
||||
#[cfg(not(features = "std"))]
|
||||
use std::collections::{BTreeMap as Map};
|
||||
|
||||
use parity_wasm::elements;
|
||||
|
||||
pub struct UnknownInstruction;
|
||||
|
||||
#[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,
|
||||
IntegerComparsion,
|
||||
FloatComparsion,
|
||||
Float,
|
||||
Conversion,
|
||||
FloatConversion,
|
||||
Reinterpretation,
|
||||
Unreachable,
|
||||
Nop,
|
||||
CurrentMemory,
|
||||
GrowMemory,
|
||||
}
|
||||
|
||||
impl ::std::str::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::IntegerComparsion),
|
||||
"float_comp" => Ok(InstructionType::FloatComparsion),
|
||||
"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),
|
||||
_ => Err(UnknownInstruction),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InstructionType {
|
||||
pub fn op(instruction: &elements::Instruction) -> Self {
|
||||
use parity_wasm::elements::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::Local,
|
||||
SetGlobal(_) => InstructionType::Local,
|
||||
|
||||
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::IntegerComparsion,
|
||||
I32Eq => InstructionType::IntegerComparsion,
|
||||
I32Ne => InstructionType::IntegerComparsion,
|
||||
I32LtS => InstructionType::IntegerComparsion,
|
||||
I32LtU => InstructionType::IntegerComparsion,
|
||||
I32GtS => InstructionType::IntegerComparsion,
|
||||
I32GtU => InstructionType::IntegerComparsion,
|
||||
I32LeS => InstructionType::IntegerComparsion,
|
||||
I32LeU => InstructionType::IntegerComparsion,
|
||||
I32GeS => InstructionType::IntegerComparsion,
|
||||
I32GeU => InstructionType::IntegerComparsion,
|
||||
|
||||
I64Eqz => InstructionType::IntegerComparsion,
|
||||
I64Eq => InstructionType::IntegerComparsion,
|
||||
I64Ne => InstructionType::IntegerComparsion,
|
||||
I64LtS => InstructionType::IntegerComparsion,
|
||||
I64LtU => InstructionType::IntegerComparsion,
|
||||
I64GtS => InstructionType::IntegerComparsion,
|
||||
I64GtU => InstructionType::IntegerComparsion,
|
||||
I64LeS => InstructionType::IntegerComparsion,
|
||||
I64LeU => InstructionType::IntegerComparsion,
|
||||
I64GeS => InstructionType::IntegerComparsion,
|
||||
I64GeU => InstructionType::IntegerComparsion,
|
||||
|
||||
F32Eq => InstructionType::FloatComparsion,
|
||||
F32Ne => InstructionType::FloatComparsion,
|
||||
F32Lt => InstructionType::FloatComparsion,
|
||||
F32Gt => InstructionType::FloatComparsion,
|
||||
F32Le => InstructionType::FloatComparsion,
|
||||
F32Ge => InstructionType::FloatComparsion,
|
||||
|
||||
F64Eq => InstructionType::FloatComparsion,
|
||||
F64Ne => InstructionType::FloatComparsion,
|
||||
F64Lt => InstructionType::FloatComparsion,
|
||||
F64Gt => InstructionType::FloatComparsion,
|
||||
F64Le => InstructionType::FloatComparsion,
|
||||
F64Ge => InstructionType::FloatComparsion,
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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: regular, entries: entries, grow: 0 }
|
||||
}
|
||||
|
||||
pub fn process(&self, instruction: &elements::Instruction) -> Result<u32, ()> {
|
||||
match self.entries.get(&InstructionType::op(instruction)).map(|x| *x) {
|
||||
None | Some(Metering::Regular) => Ok(self.regular),
|
||||
Some(Metering::Forbidden) => Err(()),
|
||||
Some(Metering::Fixed(val)) => Ok(val),
|
||||
}
|
||||
}
|
||||
|
||||
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::FloatComparsion, Metering::Forbidden);
|
||||
self.entries.insert(InstructionType::FloatConst, Metering::Forbidden);
|
||||
self.entries.insert(InstructionType::FloatConversion, Metering::Forbidden);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
use parity_wasm::{elements, builder};
|
||||
use self::elements::{ Module, GlobalEntry, External, ExportEntry, GlobalType, ValueType, InitExpr, Instruction, Internal };
|
||||
use byteorder::{ LittleEndian, ByteOrder };
|
||||
|
||||
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(ref section) => section.entries().len() as u32,
|
||||
None => 0
|
||||
};
|
||||
let imported_globals_count: u32 = match module.import_section() {
|
||||
Some(ref section) => section.entries().iter().filter(|e| match *e.external() {
|
||||
External::Global(ref _a) => true,
|
||||
_ => false
|
||||
}).count() as u32,
|
||||
None => 0
|
||||
};
|
||||
let total_globals_count: u32 = globals_count + imported_globals_count;
|
||||
|
||||
builder::from_module(module)
|
||||
.with_global(GlobalEntry::new(GlobalType::new(ValueType::I32, false), InitExpr::new(vec![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 as i32)])))
|
||||
.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().find(|e| e.field() == "RUNTIME_TYPE" ).is_some());
|
||||
assert!(export_section.entries().iter().find(|e| e.field() == "RUNTIME_VERSION" ).is_some());
|
||||
}
|
||||
}
|
||||
@@ -1,526 +0,0 @@
|
||||
use std::vec::Vec;
|
||||
|
||||
use parity_wasm::elements::{self, BlockType, Type};
|
||||
use super::{resolve_func_type, Error};
|
||||
|
||||
/// Control stack frame.
|
||||
#[derive(Debug)]
|
||||
struct Frame {
|
||||
/// Stack becomes polymorphic only after an instruction that
|
||||
/// never passes control further was executed.
|
||||
is_polymorphic: bool,
|
||||
|
||||
/// Count of values which will be pushed after the exit
|
||||
/// from the current block.
|
||||
end_arity: u32,
|
||||
|
||||
/// Count of values which should be poped upon a branch to
|
||||
/// this frame.
|
||||
///
|
||||
/// This might be diffirent from `end_arity` since branch
|
||||
/// to the loop header can't take any values.
|
||||
branch_arity: u32,
|
||||
|
||||
/// Stack height before entering in the block.
|
||||
start_height: u32,
|
||||
}
|
||||
|
||||
/// This is a compound stack that abstracts tracking height of the value stack
|
||||
/// and manipulation of the control stack.
|
||||
struct Stack {
|
||||
height: u32,
|
||||
control_stack: Vec<Frame>,
|
||||
}
|
||||
|
||||
impl Stack {
|
||||
fn new() -> Stack {
|
||||
Stack {
|
||||
height: 0,
|
||||
control_stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns current height of the value stack.
|
||||
fn height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
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()))?;
|
||||
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()))?;
|
||||
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());
|
||||
Ok(self.control_stack
|
||||
.pop()
|
||||
.ok_or_else(|| Error("stack must be non-empty".into()))?)
|
||||
}
|
||||
|
||||
/// 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()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pop specified number of values from the value 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);
|
||||
if value_count == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
{
|
||||
let top_frame = self.frame(0)?;
|
||||
if self.height == top_frame.start_height {
|
||||
// It is an error to pop more values than was pushed in the current frame
|
||||
// (ie pop values pushed in the parent frame), unless the frame became
|
||||
// polymorphic.
|
||||
return if top_frame.is_polymorphic {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error("trying to pop more values than pushed".into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.height = self.height
|
||||
.checked_sub(value_count)
|
||||
.ok_or_else(|| Error("stack underflow".into()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This function expects the function to be validated.
|
||||
pub(crate) fn compute(func_idx: u32, module: &elements::Module) -> Result<u32, Error> {
|
||||
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);
|
||||
|
||||
// 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()))?
|
||||
.type_ref();
|
||||
let Type::Function(ref func_signature) = *type_section
|
||||
.types()
|
||||
.get(func_sig_idx as usize)
|
||||
.ok_or_else(|| Error("Function is not found in func section".into()))?;
|
||||
let body = code_section
|
||||
.bodies()
|
||||
.get(func_idx as usize)
|
||||
.ok_or_else(|| Error("Function body for the index isn't found".into()))?;
|
||||
let instructions = body.code();
|
||||
|
||||
let mut stack = Stack::new();
|
||||
let mut max_height: u32 = 0;
|
||||
let mut pc = 0;
|
||||
|
||||
// Add implicit frame for the function. Breaks to this frame and execution of
|
||||
// the last end should deal with this frame.
|
||||
let func_arity: u32 = if func_signature.return_type().is_some() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
stack.push_frame(Frame {
|
||||
is_polymorphic: false,
|
||||
end_arity: func_arity,
|
||||
branch_arity: func_arity,
|
||||
start_height: 0,
|
||||
});
|
||||
|
||||
loop {
|
||||
if pc >= instructions.elements().len() {
|
||||
break;
|
||||
}
|
||||
|
||||
// If current value stack is higher than maximal height observed so far,
|
||||
// save the new height.
|
||||
// However, we don't increase maximal value in unreachable code.
|
||||
if stack.height() > max_height && !stack.frame(0)?.is_polymorphic {
|
||||
max_height = stack.height();
|
||||
}
|
||||
|
||||
let opcode = &instructions.elements()[pc];
|
||||
trace!(target: "max_height", "{:?}", opcode);
|
||||
|
||||
match *opcode {
|
||||
Nop => {}
|
||||
Block(ty) | Loop(ty) | If(ty) => {
|
||||
let end_arity = if ty == BlockType::NoResult { 0 } else { 1 };
|
||||
let branch_arity = if let Loop(_) = *opcode { 0 } else { end_arity };
|
||||
let height = stack.height();
|
||||
stack.push_frame(Frame {
|
||||
is_polymorphic: false,
|
||||
end_arity,
|
||||
branch_arity,
|
||||
start_height: height,
|
||||
});
|
||||
}
|
||||
Else => {
|
||||
// The frame at the top should be pushed by `If`. So we leave
|
||||
// it as is.
|
||||
}
|
||||
End => {
|
||||
let frame = stack.pop_frame()?;
|
||||
stack.trunc(frame.start_height);
|
||||
stack.push_values(frame.end_arity)?;
|
||||
}
|
||||
Unreachable => {
|
||||
stack.mark_unreachable()?;
|
||||
}
|
||||
Br(target) => {
|
||||
// Pop values for the destination block result.
|
||||
let target_arity = stack.frame(target)?.branch_arity;
|
||||
stack.pop_values(target_arity)?;
|
||||
|
||||
// This instruction unconditionally transfers control to the specified block,
|
||||
// thus all instruction until the end of the current block is deemed unreachable
|
||||
stack.mark_unreachable()?;
|
||||
}
|
||||
BrIf(target) => {
|
||||
// Pop values for the destination block result.
|
||||
let target_arity = stack.frame(target)?.branch_arity;
|
||||
stack.pop_values(target_arity)?;
|
||||
|
||||
// Pop condition value.
|
||||
stack.pop_values(1)?;
|
||||
|
||||
// Push values back.
|
||||
stack.push_values(target_arity)?;
|
||||
}
|
||||
BrTable(ref br_table_data) => {
|
||||
let arity_of_default = stack.frame(br_table_data.default)?.branch_arity;
|
||||
|
||||
// Check that all jump targets have an equal arities.
|
||||
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()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Because all jump targets have an equal arities, we can just take arity of
|
||||
// the default branch.
|
||||
stack.pop_values(arity_of_default)?;
|
||||
|
||||
// This instruction doesn't let control flow to go further, since the control flow
|
||||
// should take either one of branches depending on the value or the default branch.
|
||||
stack.mark_unreachable()?;
|
||||
}
|
||||
Return => {
|
||||
// Pop return values of the function. Mark successive instructions as unreachable
|
||||
// since this instruction doesn't let control flow to go further.
|
||||
stack.pop_values(func_arity)?;
|
||||
stack.mark_unreachable()?;
|
||||
}
|
||||
Call(idx) => {
|
||||
let ty = resolve_func_type(idx, module)?;
|
||||
|
||||
// Pop values for arguments of the function.
|
||||
stack.pop_values(ty.params().len() as u32)?;
|
||||
|
||||
// Push result of the function execution to the stack.
|
||||
let callee_arity = if ty.return_type().is_some() { 1 } else { 0 };
|
||||
stack.push_values(callee_arity)?;
|
||||
}
|
||||
CallIndirect(x, _) => {
|
||||
let Type::Function(ref ty) = *type_section
|
||||
.types()
|
||||
.get(x as usize)
|
||||
.ok_or_else(|| Error("Type not found".into()))?;
|
||||
|
||||
// Pop values for arguments of the function.
|
||||
stack.pop_values(ty.params().len() as u32)?;
|
||||
|
||||
// Push result of the function execution to the stack.
|
||||
let callee_arity = if ty.return_type().is_some() { 1 } else { 0 };
|
||||
stack.push_values(callee_arity)?;
|
||||
}
|
||||
Drop => {
|
||||
stack.pop_values(1)?;
|
||||
}
|
||||
Select => {
|
||||
// Pop two values and one condition.
|
||||
stack.pop_values(2)?;
|
||||
stack.pop_values(1)?;
|
||||
|
||||
// Push the selected value.
|
||||
stack.push_values(1)?;
|
||||
}
|
||||
GetLocal(_) => {
|
||||
stack.push_values(1)?;
|
||||
}
|
||||
SetLocal(_) => {
|
||||
stack.pop_values(1)?;
|
||||
}
|
||||
TeeLocal(_) => {
|
||||
// This instruction pops and pushes the value, so
|
||||
// effectively it doesn't modify the stack height.
|
||||
stack.pop_values(1)?;
|
||||
stack.push_values(1)?;
|
||||
}
|
||||
GetGlobal(_) => {
|
||||
stack.push_values(1)?;
|
||||
}
|
||||
SetGlobal(_) => {
|
||||
stack.pop_values(1)?;
|
||||
}
|
||||
I32Load(_, _)
|
||||
| I64Load(_, _)
|
||||
| F32Load(_, _)
|
||||
| F64Load(_, _)
|
||||
| I32Load8S(_, _)
|
||||
| I32Load8U(_, _)
|
||||
| I32Load16S(_, _)
|
||||
| I32Load16U(_, _)
|
||||
| I64Load8S(_, _)
|
||||
| I64Load8U(_, _)
|
||||
| I64Load16S(_, _)
|
||||
| I64Load16U(_, _)
|
||||
| I64Load32S(_, _)
|
||||
| I64Load32U(_, _) => {
|
||||
// These instructions pop the address and pushes the result,
|
||||
// which effictively don't modify the stack height.
|
||||
stack.pop_values(1)?;
|
||||
stack.push_values(1)?;
|
||||
}
|
||||
|
||||
I32Store(_, _)
|
||||
| I64Store(_, _)
|
||||
| F32Store(_, _)
|
||||
| F64Store(_, _)
|
||||
| I32Store8(_, _)
|
||||
| I32Store16(_, _)
|
||||
| I64Store8(_, _)
|
||||
| I64Store16(_, _)
|
||||
| I64Store32(_, _) => {
|
||||
// These instructions pop the address and the value.
|
||||
stack.pop_values(2)?;
|
||||
}
|
||||
|
||||
CurrentMemory(_) => {
|
||||
// Pushes current memory size
|
||||
stack.push_values(1)?;
|
||||
}
|
||||
GrowMemory(_) => {
|
||||
// Grow memory takes the value of pages to grow and pushes
|
||||
stack.pop_values(1)?;
|
||||
stack.push_values(1)?;
|
||||
}
|
||||
|
||||
I32Const(_) | I64Const(_) | F32Const(_) | F64Const(_) => {
|
||||
// These instructions just push the single literal value onto the stack.
|
||||
stack.push_values(1)?;
|
||||
}
|
||||
|
||||
I32Eqz | I64Eqz => {
|
||||
// These instructions pop the value and compare it against zero, and pushes
|
||||
// the result of the comparison.
|
||||
stack.pop_values(1)?;
|
||||
stack.push_values(1)?;
|
||||
}
|
||||
|
||||
I32Eq | I32Ne | I32LtS | I32LtU | I32GtS | I32GtU | I32LeS | I32LeU | I32GeS
|
||||
| I32GeU | I64Eq | I64Ne | I64LtS | I64LtU | I64GtS | I64GtU | I64LeS | I64LeU
|
||||
| I64GeS | I64GeU | F32Eq | F32Ne | F32Lt | F32Gt | F32Le | F32Ge | F64Eq | F64Ne
|
||||
| F64Lt | F64Gt | F64Le | F64Ge => {
|
||||
// Comparison operations take two operands and produce one result.
|
||||
stack.pop_values(2)?;
|
||||
stack.push_values(1)?;
|
||||
}
|
||||
|
||||
I32Clz | I32Ctz | I32Popcnt | I64Clz | I64Ctz | I64Popcnt | F32Abs | F32Neg
|
||||
| F32Ceil | F32Floor | F32Trunc | F32Nearest | F32Sqrt | F64Abs | F64Neg | F64Ceil
|
||||
| F64Floor | F64Trunc | F64Nearest | F64Sqrt => {
|
||||
// Unary operators take one operand and produce one result.
|
||||
stack.pop_values(1)?;
|
||||
stack.push_values(1)?;
|
||||
}
|
||||
|
||||
I32Add | I32Sub | I32Mul | I32DivS | I32DivU | I32RemS | I32RemU | I32And | I32Or
|
||||
| I32Xor | I32Shl | I32ShrS | I32ShrU | I32Rotl | I32Rotr | I64Add | I64Sub
|
||||
| I64Mul | I64DivS | I64DivU | I64RemS | I64RemU | I64And | I64Or | I64Xor | I64Shl
|
||||
| I64ShrS | I64ShrU | I64Rotl | I64Rotr | F32Add | F32Sub | F32Mul | F32Div
|
||||
| F32Min | F32Max | F32Copysign | F64Add | F64Sub | F64Mul | F64Div | F64Min
|
||||
| F64Max | F64Copysign => {
|
||||
// Binary operators take two operands and produce one result.
|
||||
stack.pop_values(2)?;
|
||||
stack.push_values(1)?;
|
||||
}
|
||||
|
||||
I32WrapI64 | I32TruncSF32 | I32TruncUF32 | I32TruncSF64 | I32TruncUF64
|
||||
| I64ExtendSI32 | I64ExtendUI32 | I64TruncSF32 | I64TruncUF32 | I64TruncSF64
|
||||
| I64TruncUF64 | F32ConvertSI32 | F32ConvertUI32 | F32ConvertSI64 | F32ConvertUI64
|
||||
| F32DemoteF64 | F64ConvertSI32 | F64ConvertUI32 | F64ConvertSI64 | F64ConvertUI64
|
||||
| F64PromoteF32 | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32
|
||||
| F64ReinterpretI64 => {
|
||||
// Conversion operators take one value and produce one result.
|
||||
stack.pop_values(1)?;
|
||||
stack.push_values(1)?;
|
||||
}
|
||||
}
|
||||
pc += 1;
|
||||
}
|
||||
|
||||
Ok(max_height)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
extern crate wabt;
|
||||
use parity_wasm::elements;
|
||||
use super::*;
|
||||
|
||||
fn parse_wat(source: &str) -> elements::Module {
|
||||
elements::deserialize_buffer(&wabt::wat2wasm(source).expect("Failed to wat2wasm"))
|
||||
.expect("Failed to deserialize the module")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_test() {
|
||||
let module = parse_wat(
|
||||
r#"
|
||||
(module
|
||||
(func
|
||||
i32.const 1
|
||||
i32.const 2
|
||||
i32.const 3
|
||||
drop
|
||||
drop
|
||||
drop
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
|
||||
let height = compute(0, &module).unwrap();
|
||||
assert_eq!(height, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn implicit_and_explicit_return() {
|
||||
let module = parse_wat(
|
||||
r#"
|
||||
(module
|
||||
(func (result i32)
|
||||
i32.const 0
|
||||
return
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
|
||||
let height = compute(0, &module).unwrap();
|
||||
assert_eq!(height, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dont_count_in_unreachable() {
|
||||
let module = parse_wat(
|
||||
r#"
|
||||
(module
|
||||
(memory 0)
|
||||
(func (result i32)
|
||||
unreachable
|
||||
grow_memory
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
|
||||
let height = compute(0, &module).unwrap();
|
||||
assert_eq!(height, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yet_another_test() {
|
||||
const SOURCE: &'static str = r#"
|
||||
(module
|
||||
(memory 0)
|
||||
(func
|
||||
;; Push two values and then pop them.
|
||||
;; This will make max depth to be equal to 2.
|
||||
i32.const 0
|
||||
i32.const 1
|
||||
drop
|
||||
drop
|
||||
|
||||
;; Code after `unreachable` shouldn't have an effect
|
||||
;; on the max depth.
|
||||
unreachable
|
||||
i32.const 0
|
||||
i32.const 1
|
||||
i32.const 2
|
||||
)
|
||||
)
|
||||
"#;
|
||||
let module = elements::deserialize_buffer(&wabt::Wat2Wasm::new()
|
||||
.validate(false)
|
||||
.convert(SOURCE)
|
||||
.expect("Failed to wat2wasm")
|
||||
.as_ref())
|
||||
.expect("Failed to deserialize the module");
|
||||
|
||||
let height = compute(0, &module).unwrap();
|
||||
assert_eq!(height, 2);
|
||||
}
|
||||
}
|
||||
@@ -1,412 +0,0 @@
|
||||
//! 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 std::string::String;
|
||||
use std::vec::Vec;
|
||||
|
||||
use parity_wasm::elements::{self, Type};
|
||||
use parity_wasm::builder;
|
||||
|
||||
/// Macro to generate preamble and postamble.
|
||||
macro_rules! instrument_call {
|
||||
($callee_idx: expr, $callee_stack_cost: expr, $stack_height_global_idx: expr, $stack_limit: expr) => {{
|
||||
use $crate::parity_wasm::elements::Instruction::*;
|
||||
[
|
||||
// stack_height += stack_cost(F)
|
||||
GetGlobal($stack_height_global_idx),
|
||||
I32Const($callee_stack_cost),
|
||||
I32Add,
|
||||
SetGlobal($stack_height_global_idx),
|
||||
// if stack_counter > LIMIT: unreachable
|
||||
GetGlobal($stack_height_global_idx),
|
||||
I32Const($stack_limit as i32),
|
||||
I32GtU,
|
||||
If(elements::BlockType::NoResult),
|
||||
Unreachable,
|
||||
End,
|
||||
// Original call
|
||||
Call($callee_idx),
|
||||
// stack_height -= stack_cost(F)
|
||||
GetGlobal($stack_height_global_idx),
|
||||
I32Const($callee_stack_cost),
|
||||
I32Sub,
|
||||
SetGlobal($stack_height_global_idx),
|
||||
]
|
||||
}};
|
||||
}
|
||||
|
||||
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 {
|
||||
stack_height_global_idx: u32,
|
||||
func_stack_costs: Vec<u32>,
|
||||
stack_limit: u32,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Returns index in a global index space of a stack_height global variable.
|
||||
fn stack_height_global_idx(&self) -> u32 {
|
||||
self.stack_height_global_idx
|
||||
}
|
||||
|
||||
/// Returns `stack_cost` for `func_idx`.
|
||||
fn stack_cost(&self, func_idx: u32) -> Option<u32> {
|
||||
self.func_stack_costs.get(func_idx as usize).cloned()
|
||||
}
|
||||
|
||||
/// Returns stack limit specified by the rules.
|
||||
fn stack_limit(&self) -> u32 {
|
||||
self.stack_limit
|
||||
}
|
||||
}
|
||||
|
||||
/// Instrument a module with stack height limiter.
|
||||
///
|
||||
/// See module-level documentation for more details.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if module is invalid and can't be
|
||||
pub fn inject_limiter(
|
||||
mut module: elements::Module,
|
||||
stack_limit: u32,
|
||||
) -> Result<elements::Module, Error> {
|
||||
let mut ctx = Context {
|
||||
stack_height_global_idx: generate_stack_height_global(&mut module),
|
||||
func_stack_costs: compute_stack_costs(&module)?,
|
||||
stack_limit,
|
||||
};
|
||||
|
||||
instrument_functions(&mut ctx, &mut module)?;
|
||||
let module = thunk::generate_thunks(&mut ctx, module)?;
|
||||
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
/// Generate a new global that will be used for tracking current stack height.
|
||||
fn generate_stack_height_global(module: &mut elements::Module) -> u32 {
|
||||
let global_entry = builder::global()
|
||||
.value_type()
|
||||
.i32()
|
||||
.mutable()
|
||||
.init_expr(elements::Instruction::I32Const(0))
|
||||
.build();
|
||||
|
||||
// Try to find an existing global section.
|
||||
for section in module.sections_mut() {
|
||||
if let elements::Section::Global(ref mut gs) = *section {
|
||||
gs.entries_mut().push(global_entry);
|
||||
return (gs.entries().len() as u32) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Existing section not found, create one!
|
||||
module.sections_mut().push(elements::Section::Global(
|
||||
elements::GlobalSection::with_entries(vec![global_entry]),
|
||||
));
|
||||
0
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
let func_imports = module.import_count(elements::ImportCountType::Function);
|
||||
|
||||
// TODO: optimize!
|
||||
(0..module.functions_space())
|
||||
.map(|func_idx| {
|
||||
if func_idx < func_imports {
|
||||
// We can't calculate stack_cost of the import functions.
|
||||
Ok(0)
|
||||
} else {
|
||||
compute_stack_cost(func_idx as u32, &module)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
// 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())
|
||||
})?;
|
||||
|
||||
let code_section = module.code_section().ok_or_else(|| {
|
||||
Error("Due to validation code section should exists".into())
|
||||
})?;
|
||||
let body = &code_section
|
||||
.bodies()
|
||||
.get(defined_func_idx as usize)
|
||||
.ok_or_else(|| Error("Function body is out of bounds".into()))?;
|
||||
let locals_count = body.locals().len() as u32;
|
||||
|
||||
let max_stack_height =
|
||||
max_height::compute(
|
||||
defined_func_idx,
|
||||
module
|
||||
)?;
|
||||
|
||||
Ok(locals_count + max_stack_height)
|
||||
}
|
||||
|
||||
fn instrument_functions(ctx: &mut Context, module: &mut elements::Module) -> Result<(), Error> {
|
||||
for section in module.sections_mut() {
|
||||
if let elements::Section::Code(ref mut code_section) = *section {
|
||||
for func_body in code_section.bodies_mut() {
|
||||
let opcodes = func_body.code_mut();
|
||||
instrument_function(ctx, opcodes)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This function searches `call` instructions and wrap each call
|
||||
/// with preamble and postamble.
|
||||
///
|
||||
/// Before:
|
||||
///
|
||||
/// ```text
|
||||
/// get_local 0
|
||||
/// get_local 1
|
||||
/// call 228
|
||||
/// drop
|
||||
/// ```
|
||||
///
|
||||
/// After:
|
||||
///
|
||||
/// ```text
|
||||
/// get_local 0
|
||||
/// get_local 1
|
||||
///
|
||||
/// < ... preamble ... >
|
||||
///
|
||||
/// call 228
|
||||
///
|
||||
/// < .. postamble ... >
|
||||
///
|
||||
/// drop
|
||||
/// ```
|
||||
fn instrument_function(
|
||||
ctx: &mut Context,
|
||||
instructions: &mut elements::Instructions,
|
||||
) -> Result<(), Error> {
|
||||
use parity_wasm::elements::Instruction::*;
|
||||
|
||||
let mut cursor = 0;
|
||||
loop {
|
||||
if cursor >= instructions.elements().len() {
|
||||
break;
|
||||
}
|
||||
|
||||
enum Action {
|
||||
InstrumentCall {
|
||||
callee_idx: u32,
|
||||
callee_stack_cost: u32,
|
||||
},
|
||||
Nop,
|
||||
}
|
||||
|
||||
let action: Action = {
|
||||
let instruction = &instructions.elements()[cursor];
|
||||
match *instruction {
|
||||
Call(ref callee_idx) => {
|
||||
let callee_stack_cost = ctx
|
||||
.stack_cost(*callee_idx)
|
||||
.ok_or_else(||
|
||||
Error(
|
||||
format!("Call to function that out-of-bounds: {}", callee_idx)
|
||||
)
|
||||
)?;
|
||||
|
||||
// Instrument only calls to a functions which stack_cost is
|
||||
// non-zero.
|
||||
if callee_stack_cost > 0 {
|
||||
Action::InstrumentCall {
|
||||
callee_idx: *callee_idx,
|
||||
callee_stack_cost,
|
||||
}
|
||||
} else {
|
||||
Action::Nop
|
||||
}
|
||||
},
|
||||
_ => Action::Nop,
|
||||
}
|
||||
};
|
||||
|
||||
match action {
|
||||
// We need to wrap a `call idx` instruction
|
||||
// with a code that adjusts stack height counter
|
||||
// and then restores it.
|
||||
Action::InstrumentCall { callee_idx, callee_stack_cost } => {
|
||||
let new_seq = instrument_call!(
|
||||
callee_idx,
|
||||
callee_stack_cost as i32,
|
||||
ctx.stack_height_global_idx(),
|
||||
ctx.stack_limit()
|
||||
);
|
||||
|
||||
// Replace the original `call idx` instruction with
|
||||
// a wrapped call sequence.
|
||||
//
|
||||
// To splice actually take a place, we need to consume iterator
|
||||
// splice returns. So we just `count()` it.
|
||||
let _ = instructions
|
||||
.elements_mut()
|
||||
.splice(cursor..(cursor + 1), new_seq.iter().cloned())
|
||||
.count();
|
||||
|
||||
// Advance cursor to be after the inserted sequence.
|
||||
cursor += new_seq.len();
|
||||
}
|
||||
// Do nothing for other instructions.
|
||||
_ => {
|
||||
cursor += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_func_type(
|
||||
func_idx: u32,
|
||||
module: &elements::Module,
|
||||
) -> Result<&elements::FunctionType, Error> {
|
||||
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
|
||||
let functions = module
|
||||
.function_section()
|
||||
.map(|fs| fs.entries())
|
||||
.unwrap_or(&[]);
|
||||
|
||||
let func_imports = module.import_count(elements::ImportCountType::Function);
|
||||
let sig_idx = if func_idx < func_imports as u32 {
|
||||
module
|
||||
.import_section()
|
||||
.expect("function import count is not zero; import section must exists; qed")
|
||||
.entries()
|
||||
.iter()
|
||||
.filter_map(|entry| match *entry.external() {
|
||||
elements::External::Function(ref idx) => Some(*idx),
|
||||
_ => None,
|
||||
})
|
||||
.nth(func_idx as usize)
|
||||
.expect(
|
||||
"func_idx is less than function imports count;
|
||||
nth function import must be `Some`;
|
||||
qed",
|
||||
)
|
||||
} else {
|
||||
functions
|
||||
.get(func_idx as usize - func_imports)
|
||||
.ok_or_else(|| Error(format!("Function at index {} is not defined", func_idx)))?
|
||||
.type_ref()
|
||||
};
|
||||
let Type::Function(ref ty) = *types.get(sig_idx as usize).ok_or_else(|| {
|
||||
Error(format!(
|
||||
"Signature {} (specified by func {}) isn't defined",
|
||||
sig_idx, func_idx
|
||||
))
|
||||
})?;
|
||||
Ok(ty)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
extern crate wabt;
|
||||
use parity_wasm::elements;
|
||||
use super::*;
|
||||
|
||||
fn parse_wat(source: &str) -> elements::Module {
|
||||
elements::deserialize_buffer(&wabt::wat2wasm(source).expect("Failed to wat2wasm"))
|
||||
.expect("Failed to deserialize the module")
|
||||
}
|
||||
|
||||
fn validate_module(module: elements::Module) {
|
||||
let binary = elements::serialize(module).expect("Failed to serialize");
|
||||
wabt::Module::read_binary(&binary, &Default::default())
|
||||
.expect("Wabt failed to read final binary")
|
||||
.validate()
|
||||
.expect("Invalid module");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_params_and_result() {
|
||||
let module = parse_wat(
|
||||
r#"
|
||||
(module
|
||||
(func (export "i32.add") (param i32 i32) (result i32)
|
||||
get_local 0
|
||||
get_local 1
|
||||
i32.add
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
|
||||
let module = inject_limiter(module, 1024)
|
||||
.expect("Failed to inject stack counter");
|
||||
validate_module(module);
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
#[cfg(features = "std")]
|
||||
use std::collections::{HashMap as Map};
|
||||
#[cfg(not(features = "std"))]
|
||||
use std::collections::{BTreeMap as Map};
|
||||
use std::vec::Vec;
|
||||
|
||||
use parity_wasm::elements::{self, FunctionType, Internal};
|
||||
use parity_wasm::builder;
|
||||
|
||||
use super::{resolve_func_type, Context, Error};
|
||||
|
||||
struct Thunk {
|
||||
signature: FunctionType,
|
||||
// Index in function space of this thunk.
|
||||
idx: Option<u32>,
|
||||
original_func_idx: u32,
|
||||
callee_stack_cost: u32,
|
||||
}
|
||||
|
||||
pub(crate) fn generate_thunks(
|
||||
ctx: &mut Context,
|
||||
module: elements::Module,
|
||||
) -> Result<elements::Module, Error> {
|
||||
// First, we need to collect all function indices that should be replaced by thunks
|
||||
|
||||
// Function indices which needs to generate thunks.
|
||||
let mut need_thunks: Vec<u32> = Vec::new();
|
||||
|
||||
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(&[]);
|
||||
let start_func_idx = module
|
||||
.start_section();
|
||||
|
||||
let exported_func_indices = exports.iter().filter_map(|entry| match *entry.internal() {
|
||||
Internal::Function(ref function_idx) => Some(*function_idx),
|
||||
_ => None,
|
||||
});
|
||||
let table_func_indices = elem_segments
|
||||
.iter()
|
||||
.flat_map(|segment| segment.members())
|
||||
.cloned();
|
||||
|
||||
// Replacement map is at least export section size.
|
||||
let mut replacement_map: Map<u32, Thunk> = Map::new();
|
||||
|
||||
for func_idx in exported_func_indices.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))
|
||||
})?;
|
||||
|
||||
// Don't generate a thunk if stack_cost of a callee is zero.
|
||||
if callee_stack_cost != 0 {
|
||||
need_thunks.push(func_idx);
|
||||
replacement_map.insert(func_idx, Thunk {
|
||||
signature: resolve_func_type(func_idx, &module)?.clone(),
|
||||
idx: None,
|
||||
callee_stack_cost,
|
||||
original_func_idx: func_idx,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
replacement_map
|
||||
};
|
||||
|
||||
// Then, we generate a thunk for each original function.
|
||||
|
||||
// Save current func_idx
|
||||
let mut next_func_idx = module.functions_space() as u32;
|
||||
|
||||
let mut mbuilder = builder::from_module(module);
|
||||
for func_idx in need_thunks {
|
||||
let mut thunk = replacement_map
|
||||
.get_mut(&func_idx)
|
||||
.expect(
|
||||
"`func_idx` should come from `need_thunks`;
|
||||
`need_thunks` is populated with the same items that in `replacement_map`;
|
||||
qed"
|
||||
);
|
||||
|
||||
let instrumented_call = instrument_call!(
|
||||
thunk.original_func_idx as u32,
|
||||
thunk.callee_stack_cost as i32,
|
||||
ctx.stack_height_global_idx(),
|
||||
ctx.stack_limit()
|
||||
);
|
||||
// Thunk body consist of:
|
||||
// - argument pushing
|
||||
// - instrumented call
|
||||
// - end
|
||||
let mut thunk_body: Vec<elements::Instruction> = Vec::with_capacity(
|
||||
thunk.signature.params().len() +
|
||||
instrumented_call.len() +
|
||||
1
|
||||
);
|
||||
|
||||
for (arg_idx, _) in thunk.signature.params().iter().enumerate() {
|
||||
thunk_body.push(elements::Instruction::GetLocal(arg_idx as u32));
|
||||
}
|
||||
thunk_body.extend(instrumented_call.iter().cloned());
|
||||
thunk_body.push(elements::Instruction::End);
|
||||
|
||||
// TODO: Don't generate a signature, but find an existing one.
|
||||
|
||||
mbuilder = mbuilder.function()
|
||||
// Signature of the thunk should match the original function signature.
|
||||
.signature()
|
||||
.with_params(thunk.signature.params().to_vec())
|
||||
.with_return_type(thunk.signature.return_type().clone())
|
||||
.build()
|
||||
.body()
|
||||
.with_instructions(elements::Instructions::new(
|
||||
thunk_body
|
||||
))
|
||||
.build()
|
||||
.build();
|
||||
|
||||
thunk.idx = Some(next_func_idx);
|
||||
next_func_idx += 1;
|
||||
}
|
||||
let mut module = mbuilder.build();
|
||||
|
||||
// And finally, fixup thunks in export and table sections.
|
||||
|
||||
// Fixup original function index to a index of a thunk generated earlier.
|
||||
let fixup = |function_idx: &mut u32| {
|
||||
// Check whether this function is in replacement_map, since
|
||||
// we can skip thunk generation (e.g. if stack_cost of function is 0).
|
||||
if let Some(ref thunk) = replacement_map.get(function_idx) {
|
||||
*function_idx = thunk
|
||||
.idx
|
||||
.expect("At this point an index must be assigned to each thunk");
|
||||
}
|
||||
};
|
||||
|
||||
for section in module.sections_mut() {
|
||||
match *section {
|
||||
elements::Section::Export(ref mut export_section) => {
|
||||
for entry in export_section.entries_mut() {
|
||||
if let Internal::Function(ref mut function_idx) = *entry.internal_mut() {
|
||||
fixup(function_idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
elements::Section::Element(ref mut elem_section) => {
|
||||
for segment in elem_section.entries_mut() {
|
||||
for function_idx in segment.members_mut() {
|
||||
fixup(function_idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
elements::Section::Start(ref mut start_idx) => {
|
||||
fixup(start_idx)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(module)
|
||||
}
|
||||
@@ -0,0 +1,679 @@
|
||||
use super::resolve_func_type;
|
||||
use alloc::vec::Vec;
|
||||
use parity_wasm::elements::{self, BlockType, Type};
|
||||
|
||||
#[cfg(feature = "sign_ext")]
|
||||
use parity_wasm::elements::SignExtInstruction;
|
||||
|
||||
// The cost in stack items that should be charged per call of a function. This is
|
||||
// is a static cost that is added to each function call. This makes sense because even
|
||||
// if a function does not use any parameters or locals some stack space on the host
|
||||
// machine might be consumed to hold some context.
|
||||
const ACTIVATION_FRAME_COST: u32 = 1;
|
||||
|
||||
#[derive(Debug,PartialEq,Default,Clone,Copy)]
|
||||
pub struct StackHeightStats {
|
||||
pub activation_cost: u32,
|
||||
pub max_height: u32,
|
||||
pub max_control_height: u32,
|
||||
pub locals_count: u32,
|
||||
pub params_count: u32,
|
||||
pub blocks_count: u32,
|
||||
pub condbr_count: u32,
|
||||
pub push_count: u32,
|
||||
pub local_set_count: u32,
|
||||
pub opcode_count: u32,
|
||||
pub total_cost: u32,
|
||||
}
|
||||
|
||||
/// Control stack frame.
|
||||
#[derive(Debug)]
|
||||
struct Frame {
|
||||
/// Counts the nesting level of unreachable code. 0 if currently processed code is reachable
|
||||
unreachable_depth: u32,
|
||||
|
||||
/// Count of values which will be pushed after the exit
|
||||
/// from the current block.
|
||||
end_arity: u32,
|
||||
|
||||
/// Count of values which should be poped upon a branch to
|
||||
/// this frame.
|
||||
///
|
||||
/// This might be diffirent from `end_arity` since branch
|
||||
/// to the loop header can't take any values.
|
||||
branch_arity: u32,
|
||||
|
||||
/// Stack height before entering in the block.
|
||||
start_height: u32,
|
||||
}
|
||||
|
||||
/// This is a compound stack that abstracts tracking height of the value stack
|
||||
/// and manipulation of the control stack.
|
||||
struct Stack {
|
||||
height: u32,
|
||||
control_stack: Vec<Frame>,
|
||||
}
|
||||
|
||||
impl Stack {
|
||||
fn new() -> Stack {
|
||||
Stack { height: 0, control_stack: Vec::new() }
|
||||
}
|
||||
|
||||
/// Returns current height of the value stack.
|
||||
fn height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn control_height(&self) -> u32 {
|
||||
self.control_stack.len() as u32
|
||||
}
|
||||
|
||||
/// Returns a reference to a frame by specified depth relative to the top of
|
||||
/// control stack.
|
||||
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("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.
|
||||
fn mark_unreachable(&mut self) -> Result<(), &'static str> {
|
||||
let top_frame = self.control_stack.last_mut().ok_or("control stack must be non-empty")?;
|
||||
top_frame.unreachable_depth = 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Increase nesting level of unreachable code
|
||||
fn push_unreachable(&mut self) -> Result<(), &'static str> {
|
||||
let top_frame = self.control_stack.last_mut().ok_or("control stack must be non-empty")?;
|
||||
top_frame.unreachable_depth += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Decrease nesting level of unrechable code (probably making it reachable)
|
||||
fn pop_unreachable(&mut self) -> Result<u32, &'static str> {
|
||||
let top_frame = self.control_stack.last_mut().ok_or("control stack must be non-empty")?;
|
||||
top_frame.unreachable_depth =
|
||||
top_frame.unreachable_depth.checked_sub(1).ok_or("unreachable code underflow")?;
|
||||
Ok(top_frame.unreachable_depth)
|
||||
}
|
||||
|
||||
/// Push control frame into the control stack.
|
||||
fn push_frame(&mut self, 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, &'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) {
|
||||
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<(), &'static str> {
|
||||
self.height = self.height.checked_add(value_count).ok_or("stack overflow")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pop specified number of values from the value stack.
|
||||
///
|
||||
/// Returns `Err` if the stack happen to be negative value after
|
||||
/// values popped.
|
||||
fn pop_values(&mut self, value_count: u32) -> Result<(), &'static str> {
|
||||
if value_count == 0 {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
self.height = self.height.checked_sub(value_count).ok_or("stack underflow")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// This function expects the function to be validated.
|
||||
pub fn compute(func_idx: u32, module: &elements::Module) -> Result<StackHeightStats, &'static str> {
|
||||
use parity_wasm::elements::Instruction::*;
|
||||
|
||||
trace!("Processing function index {}", 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("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("Function is not found in func section")?;
|
||||
let body = code_section
|
||||
.bodies()
|
||||
.get(func_idx as usize)
|
||||
.ok_or("Function body for the index isn't found")?;
|
||||
let instructions = body.code();
|
||||
|
||||
let mut stack = Stack::new();
|
||||
let mut max_height: u32 = 0;
|
||||
let mut max_control_height: u32 = 0;
|
||||
|
||||
let mut locals_count: u32 = 0;
|
||||
for local_group in body.locals() {
|
||||
locals_count =
|
||||
locals_count.checked_add(local_group.count()).ok_or("Overflow in local count")?;
|
||||
}
|
||||
|
||||
let params_count = func_signature.params().len() as u32;
|
||||
let mut blocks_count = 0u32;
|
||||
let mut condbr_count = 0u32;
|
||||
let mut push_count = 0u32;
|
||||
let mut local_set_count = 0u32;
|
||||
|
||||
// Add implicit frame for the function. Breaks to this frame and execution of
|
||||
// the last end should deal with this frame.
|
||||
let func_arity = func_signature.results().len() as u32;
|
||||
stack.push_frame(Frame {
|
||||
unreachable_depth: 0,
|
||||
end_arity: func_arity,
|
||||
branch_arity: func_arity,
|
||||
start_height: 0,
|
||||
});
|
||||
|
||||
for opcode in instructions.elements() {
|
||||
if stack.frame(0)?.unreachable_depth > 0 {
|
||||
match opcode {
|
||||
Block(_) | Loop(_) | If(_) => {
|
||||
trace!("Entering unreachable block {:?}", opcode);
|
||||
stack.push_unreachable()?;
|
||||
continue
|
||||
},
|
||||
Else => {
|
||||
let depth = stack.pop_unreachable()?;
|
||||
if depth == 0 {
|
||||
trace!("Transiting from unreachable If body to reachable Else block");
|
||||
} else {
|
||||
trace!("Processing unreachable Else");
|
||||
stack.push_unreachable()?;
|
||||
continue
|
||||
}
|
||||
},
|
||||
End => {
|
||||
let depth = stack.pop_unreachable()?;
|
||||
if depth == 0 {
|
||||
trace!("Exiting unreachable code");
|
||||
} else {
|
||||
trace!("Exiting unreachable block");
|
||||
continue
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
trace!("Skipping unreachable instruction {:?}", opcode);
|
||||
continue
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(stack.frame(0)?.unreachable_depth, 0);
|
||||
trace!("Processing opcode {:?}", opcode);
|
||||
|
||||
match opcode {
|
||||
Nop => {},
|
||||
Block(ty) | Loop(ty) | If(ty) => {
|
||||
let end_arity = if *ty == BlockType::NoResult { 0 } else { 1 };
|
||||
let branch_arity = if let Loop(_) = *opcode { 0 } else { end_arity };
|
||||
if let If(_) = *opcode {
|
||||
stack.pop_values(1)?;
|
||||
}
|
||||
stack.push_frame(Frame {
|
||||
unreachable_depth: 0,
|
||||
end_arity,
|
||||
branch_arity,
|
||||
start_height: stack.height(),
|
||||
});
|
||||
blocks_count += 1;
|
||||
},
|
||||
Else => {
|
||||
let frame = stack.pop_frame()?;
|
||||
stack.trunc(frame.start_height);
|
||||
stack.push_frame(frame);
|
||||
},
|
||||
End => {
|
||||
let frame = stack.pop_frame()?;
|
||||
stack.trunc(frame.start_height);
|
||||
stack.push_values(frame.end_arity)?;
|
||||
},
|
||||
Unreachable => {
|
||||
stack.mark_unreachable()?;
|
||||
},
|
||||
Br(target) => {
|
||||
// Pop values for the destination block result.
|
||||
let target_arity = stack.frame(*target)?.branch_arity;
|
||||
stack.pop_values(target_arity)?;
|
||||
|
||||
// This instruction unconditionally transfers control to the specified block,
|
||||
// thus all instruction until the end of the current block is deemed unreachable
|
||||
stack.mark_unreachable()?;
|
||||
},
|
||||
BrIf(target) => {
|
||||
// Pop values for the destination block result.
|
||||
let target_arity = stack.frame(*target)?.branch_arity;
|
||||
stack.pop_values(target_arity)?;
|
||||
|
||||
// Pop condition value.
|
||||
stack.pop_values(1)?;
|
||||
|
||||
// Push values back.
|
||||
stack.push_values(target_arity)?;
|
||||
|
||||
condbr_count += 1;
|
||||
},
|
||||
BrTable(br_table_data) => {
|
||||
let arity_of_default = stack.frame(br_table_data.default)?.branch_arity;
|
||||
|
||||
// Because all jump targets have an equal arities, we can just take arity of
|
||||
// the default branch.
|
||||
stack.pop_values(arity_of_default)?;
|
||||
|
||||
// This instruction doesn't let control flow to go further, since the control flow
|
||||
// should take either one of branches depending on the value or the default branch.
|
||||
stack.mark_unreachable()?;
|
||||
|
||||
condbr_count += 1;
|
||||
},
|
||||
Return => {
|
||||
// Pop return values of the function. Mark successive instructions as unreachable
|
||||
// since this instruction doesn't let control flow to go further.
|
||||
stack.pop_values(func_arity)?;
|
||||
stack.mark_unreachable()?;
|
||||
},
|
||||
Call(idx) => {
|
||||
let ty = resolve_func_type(*idx, module)?;
|
||||
|
||||
// Pop values for arguments of the function.
|
||||
stack.pop_values(ty.params().len() as u32)?;
|
||||
|
||||
// Push result of the function execution to the stack.
|
||||
let callee_arity = ty.results().len() as u32;
|
||||
stack.push_values(callee_arity)?;
|
||||
},
|
||||
CallIndirect(x, _) => {
|
||||
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)?;
|
||||
|
||||
// Pop values for arguments of the function.
|
||||
stack.pop_values(ty.params().len() as u32)?;
|
||||
|
||||
// Push result of the function execution to the stack.
|
||||
let callee_arity = ty.results().len() as u32;
|
||||
stack.push_values(callee_arity)?;
|
||||
},
|
||||
Drop => {
|
||||
stack.pop_values(1)?;
|
||||
},
|
||||
Select => {
|
||||
// Pop two values and one condition.
|
||||
stack.pop_values(2)?;
|
||||
stack.pop_values(1)?;
|
||||
|
||||
// Push the selected value.
|
||||
stack.push_values(1)?;
|
||||
push_count += 1;
|
||||
},
|
||||
GetLocal(_) => {
|
||||
stack.push_values(1)?;
|
||||
push_count += 1;
|
||||
},
|
||||
SetLocal(_) => {
|
||||
stack.pop_values(1)?;
|
||||
local_set_count += 1;
|
||||
},
|
||||
TeeLocal(_) => {
|
||||
// This instruction pops and pushes the value, so
|
||||
// effectively it doesn't modify the stack height.
|
||||
stack.pop_values(1)?;
|
||||
stack.push_values(1)?;
|
||||
local_set_count += 1;
|
||||
},
|
||||
GetGlobal(_) => {
|
||||
stack.push_values(1)?;
|
||||
push_count += 1;
|
||||
},
|
||||
SetGlobal(_) => {
|
||||
stack.pop_values(1)?;
|
||||
},
|
||||
I32Load(_, _) |
|
||||
I64Load(_, _) |
|
||||
F32Load(_, _) |
|
||||
F64Load(_, _) |
|
||||
I32Load8S(_, _) |
|
||||
I32Load8U(_, _) |
|
||||
I32Load16S(_, _) |
|
||||
I32Load16U(_, _) |
|
||||
I64Load8S(_, _) |
|
||||
I64Load8U(_, _) |
|
||||
I64Load16S(_, _) |
|
||||
I64Load16U(_, _) |
|
||||
I64Load32S(_, _) |
|
||||
I64Load32U(_, _) => {
|
||||
// These instructions pop the address and pushes the result,
|
||||
// which effictively don't modify the stack height.
|
||||
stack.pop_values(1)?;
|
||||
stack.push_values(1)?;
|
||||
push_count += 1;
|
||||
},
|
||||
|
||||
I32Store(_, _) |
|
||||
I64Store(_, _) |
|
||||
F32Store(_, _) |
|
||||
F64Store(_, _) |
|
||||
I32Store8(_, _) |
|
||||
I32Store16(_, _) |
|
||||
I64Store8(_, _) |
|
||||
I64Store16(_, _) |
|
||||
I64Store32(_, _) => {
|
||||
// These instructions pop the address and the value.
|
||||
stack.pop_values(2)?;
|
||||
},
|
||||
|
||||
CurrentMemory(_) => {
|
||||
// Pushes current memory size
|
||||
stack.push_values(1)?;
|
||||
push_count += 1;
|
||||
},
|
||||
GrowMemory(_) => {
|
||||
// Grow memory takes the value of pages to grow and pushes
|
||||
stack.pop_values(1)?;
|
||||
stack.push_values(1)?;
|
||||
push_count += 1;
|
||||
},
|
||||
|
||||
I32Const(_) | I64Const(_) | F32Const(_) | F64Const(_) => {
|
||||
// These instructions just push the single literal value onto the stack.
|
||||
stack.push_values(1)?;
|
||||
push_count += 1;
|
||||
},
|
||||
|
||||
I32Eqz | I64Eqz => {
|
||||
// These instructions pop the value and compare it against zero, and pushes
|
||||
// the result of the comparison.
|
||||
stack.pop_values(1)?;
|
||||
stack.push_values(1)?;
|
||||
push_count += 1;
|
||||
},
|
||||
|
||||
I32Eq | I32Ne | I32LtS | I32LtU | I32GtS | I32GtU | I32LeS | I32LeU | I32GeS |
|
||||
I32GeU | I64Eq | I64Ne | I64LtS | I64LtU | I64GtS | I64GtU | I64LeS | I64LeU |
|
||||
I64GeS | I64GeU | F32Eq | F32Ne | F32Lt | F32Gt | F32Le | F32Ge | F64Eq | F64Ne |
|
||||
F64Lt | F64Gt | F64Le | F64Ge => {
|
||||
// Comparison operations take two operands and produce one result.
|
||||
stack.pop_values(2)?;
|
||||
stack.push_values(1)?;
|
||||
push_count += 1;
|
||||
},
|
||||
|
||||
I32Clz | I32Ctz | I32Popcnt | I64Clz | I64Ctz | I64Popcnt | F32Abs | F32Neg |
|
||||
F32Ceil | F32Floor | F32Trunc | F32Nearest | F32Sqrt | F64Abs | F64Neg | F64Ceil |
|
||||
F64Floor | F64Trunc | F64Nearest | F64Sqrt => {
|
||||
// Unary operators take one operand and produce one result.
|
||||
stack.pop_values(1)?;
|
||||
stack.push_values(1)?;
|
||||
push_count += 1;
|
||||
},
|
||||
|
||||
I32Add | I32Sub | I32Mul | I32DivS | I32DivU | I32RemS | I32RemU | I32And | I32Or |
|
||||
I32Xor | I32Shl | I32ShrS | I32ShrU | I32Rotl | I32Rotr | I64Add | I64Sub |
|
||||
I64Mul | I64DivS | I64DivU | I64RemS | I64RemU | I64And | I64Or | I64Xor | I64Shl |
|
||||
I64ShrS | I64ShrU | I64Rotl | I64Rotr | F32Add | F32Sub | F32Mul | F32Div |
|
||||
F32Min | F32Max | F32Copysign | F64Add | F64Sub | F64Mul | F64Div | F64Min |
|
||||
F64Max | F64Copysign => {
|
||||
// Binary operators take two operands and produce one result.
|
||||
stack.pop_values(2)?;
|
||||
stack.push_values(1)?;
|
||||
push_count += 1;
|
||||
},
|
||||
|
||||
I32WrapI64 | I32TruncSF32 | I32TruncUF32 | I32TruncSF64 | I32TruncUF64 |
|
||||
I64ExtendSI32 | I64ExtendUI32 | I64TruncSF32 | I64TruncUF32 | I64TruncSF64 |
|
||||
I64TruncUF64 | F32ConvertSI32 | F32ConvertUI32 | F32ConvertSI64 | F32ConvertUI64 |
|
||||
F32DemoteF64 | F64ConvertSI32 | F64ConvertUI32 | F64ConvertSI64 | F64ConvertUI64 |
|
||||
F64PromoteF32 | I32ReinterpretF32 | I64ReinterpretF64 | F32ReinterpretI32 |
|
||||
F64ReinterpretI64 => {
|
||||
// Conversion operators take one value and produce one result.
|
||||
stack.pop_values(1)?;
|
||||
stack.push_values(1)?;
|
||||
push_count += 1;
|
||||
},
|
||||
|
||||
#[cfg(feature = "sign_ext")]
|
||||
SignExt(SignExtInstruction::I32Extend8S) |
|
||||
SignExt(SignExtInstruction::I32Extend16S) |
|
||||
SignExt(SignExtInstruction::I64Extend8S) |
|
||||
SignExt(SignExtInstruction::I64Extend16S) |
|
||||
SignExt(SignExtInstruction::I64Extend32S) => {
|
||||
stack.pop_values(1)?;
|
||||
stack.push_values(1)?;
|
||||
push_count += 1;
|
||||
},
|
||||
}
|
||||
|
||||
// If current value/control stack is higher than maximal height observed so far,
|
||||
// save the new height.
|
||||
|
||||
if stack.height() > max_height {
|
||||
max_height = stack.height();
|
||||
}
|
||||
if stack.control_height() > max_control_height {
|
||||
max_control_height = stack.control_height();
|
||||
}
|
||||
trace!(
|
||||
" Stack height: {}, control stack height: {}",
|
||||
stack.height(),
|
||||
stack.control_height()
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(stack.control_height(), 0);
|
||||
assert_eq!(stack.height(), func_signature.results().len() as u32);
|
||||
|
||||
let res = StackHeightStats {
|
||||
activation_cost: ACTIVATION_FRAME_COST,
|
||||
max_height: max_height,
|
||||
max_control_height: max_control_height,
|
||||
locals_count: locals_count,
|
||||
params_count: params_count,
|
||||
blocks_count: blocks_count,
|
||||
condbr_count: condbr_count,
|
||||
push_count: push_count,
|
||||
local_set_count: local_set_count,
|
||||
opcode_count: instructions.elements().len() as u32,
|
||||
// total_cost: (11.749 * params_count as f64 - 0.4888 * locals_count as f64 + 14.8169 * max_height as f64 - 5.1594 * max_control_height as f64 - 24.4941) as u32
|
||||
total_cost: ACTIVATION_FRAME_COST + 2 * max_height + max_control_height + locals_count + 2 * params_count,
|
||||
};
|
||||
|
||||
trace!("Result: {:?}", res);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use parity_wasm::elements;
|
||||
|
||||
// fn parse_wat(source: &str) -> elements::Module {
|
||||
// elements::deserialize_buffer(&wat::parse_str(source).expect("Failed to wat2wasm"))
|
||||
// .expect("Failed to deserialize the module")
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn simple_test() {
|
||||
// let module = parse_wat(
|
||||
// r#"
|
||||
// (module
|
||||
// (func
|
||||
// i32.const 1
|
||||
// i32.const 2
|
||||
// i32.const 3
|
||||
// drop
|
||||
// drop
|
||||
// drop
|
||||
// )
|
||||
// )
|
||||
// "#,
|
||||
// );
|
||||
|
||||
// let height = compute(0, &module).unwrap();
|
||||
// assert_eq!(height, ACTIVATION_FRAME_COST + 3 + 1 + 0 + 0);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn implicit_and_explicit_return() {
|
||||
// let module = parse_wat(
|
||||
// r#"
|
||||
// (module
|
||||
// (func (result i32)
|
||||
// i32.const 0
|
||||
// return
|
||||
// )
|
||||
// )
|
||||
// "#,
|
||||
// );
|
||||
|
||||
// let height = compute(0, &module).unwrap();
|
||||
// assert_eq!(height, ACTIVATION_FRAME_COST + 1 + 1 + 0 + 0);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn dont_count_in_unreachable() {
|
||||
// let module = parse_wat(
|
||||
// r#"
|
||||
// (module
|
||||
// (memory 0)
|
||||
// (func (result i32)
|
||||
// unreachable
|
||||
// grow_memory
|
||||
// )
|
||||
// )
|
||||
// "#,
|
||||
// );
|
||||
|
||||
// let height = compute(0, &module).unwrap();
|
||||
// assert_eq!(height, ACTIVATION_FRAME_COST + 0 + 1 + 0 + 0);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn yet_another_test() {
|
||||
// let module = parse_wat(
|
||||
// r#"
|
||||
// (module
|
||||
// (memory 0)
|
||||
// (func
|
||||
// ;; Push two values and then pop them.
|
||||
// ;; This will make max depth to be equal to 2.
|
||||
// i32.const 0
|
||||
// i32.const 1
|
||||
// drop
|
||||
// drop
|
||||
|
||||
// ;; Code after `unreachable` shouldn't have an effect
|
||||
// ;; on the max depth.
|
||||
// unreachable
|
||||
// i32.const 0
|
||||
// i32.const 1
|
||||
// i32.const 2
|
||||
// )
|
||||
// )
|
||||
// "#,
|
||||
// );
|
||||
|
||||
// let height = compute(0, &module).unwrap();
|
||||
// assert_eq!(height, 2 + ACTIVATION_FRAME_COST);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn call_indirect() {
|
||||
// let module = parse_wat(
|
||||
// r#"
|
||||
// (module
|
||||
// (table $ptr 1 1 funcref)
|
||||
// (elem $ptr (i32.const 0) func 1)
|
||||
// (func $main
|
||||
// (call_indirect (i32.const 0))
|
||||
// (call_indirect (i32.const 0))
|
||||
// (call_indirect (i32.const 0))
|
||||
// )
|
||||
// (func $callee
|
||||
// i64.const 42
|
||||
// drop
|
||||
// )
|
||||
// )
|
||||
// "#,
|
||||
// );
|
||||
|
||||
// let height = compute(0, &module).unwrap();
|
||||
// assert_eq!(height, 1 + ACTIVATION_FRAME_COST);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn breaks() {
|
||||
// let module = parse_wat(
|
||||
// r#"
|
||||
// (module
|
||||
// (func $main
|
||||
// block (result i32)
|
||||
// block (result i32)
|
||||
// i32.const 99
|
||||
// br 1
|
||||
// end
|
||||
// end
|
||||
// drop
|
||||
// )
|
||||
// )
|
||||
// "#,
|
||||
// );
|
||||
|
||||
// let height = compute(0, &module).unwrap();
|
||||
// assert_eq!(height, 1 + ACTIVATION_FRAME_COST);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn if_else_works() {
|
||||
// let module = parse_wat(
|
||||
// r#"
|
||||
// (module
|
||||
// (func $main
|
||||
// i32.const 7
|
||||
// i32.const 1
|
||||
// if (result i32)
|
||||
// i32.const 42
|
||||
// else
|
||||
// i32.const 99
|
||||
// end
|
||||
// i32.const 97
|
||||
// drop
|
||||
// drop
|
||||
// drop
|
||||
// )
|
||||
// )
|
||||
// "#,
|
||||
// );
|
||||
|
||||
// let height = compute(0, &module).unwrap();
|
||||
// assert_eq!(height, 3 + ACTIVATION_FRAME_COST);
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,368 @@
|
||||
//! 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},
|
||||
};
|
||||
pub use max_height::StackHeightStats;
|
||||
|
||||
/// Macro to generate preamble and postamble.
|
||||
macro_rules! instrument_call {
|
||||
($callee_idx: expr, $callee_stack_cost: expr, $stack_height_global_idx: expr, $stack_limit: expr) => {{
|
||||
use $crate::parity_wasm::elements::Instruction::*;
|
||||
[
|
||||
// stack_height += stack_cost(F)
|
||||
GetGlobal($stack_height_global_idx),
|
||||
I32Const($callee_stack_cost),
|
||||
I32Add,
|
||||
SetGlobal($stack_height_global_idx),
|
||||
// if stack_counter > LIMIT: unreachable
|
||||
GetGlobal($stack_height_global_idx),
|
||||
I32Const($stack_limit as i32),
|
||||
I32GtU,
|
||||
If(elements::BlockType::NoResult),
|
||||
Unreachable,
|
||||
End,
|
||||
// Original call
|
||||
Call($callee_idx),
|
||||
// stack_height -= stack_cost(F)
|
||||
GetGlobal($stack_height_global_idx),
|
||||
I32Const($callee_stack_cost),
|
||||
I32Sub,
|
||||
SetGlobal($stack_height_global_idx),
|
||||
]
|
||||
}};
|
||||
}
|
||||
|
||||
mod max_height;
|
||||
mod thunk;
|
||||
|
||||
pub struct Context {
|
||||
stack_height_global_idx: u32,
|
||||
func_stack_costs: Vec<StackHeightStats>,
|
||||
stack_limit: u32,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Returns index in a global index space of a stack_height global variable.
|
||||
fn stack_height_global_idx(&self) -> u32 {
|
||||
self.stack_height_global_idx
|
||||
}
|
||||
|
||||
/// Returns `stack_cost` for `func_idx`.
|
||||
fn stack_cost(&self, func_idx: u32) -> Option<u32> {
|
||||
if let Some(stats) = self.func_stack_costs.get(func_idx as usize) {
|
||||
Some(stats.total_cost)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns stack limit specified by the rules.
|
||||
fn stack_limit(&self) -> u32 {
|
||||
self.stack_limit
|
||||
}
|
||||
}
|
||||
|
||||
/// Inject the instumentation that makes 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.
|
||||
pub fn inject(
|
||||
mut module: elements::Module,
|
||||
stack_limit: u32,
|
||||
) -> 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)?,
|
||||
stack_limit,
|
||||
};
|
||||
|
||||
instrument_functions(&mut ctx, &mut module)?;
|
||||
let module = thunk::generate_thunks(&mut ctx, module)?;
|
||||
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
/// Generate a new global that will be used for tracking current stack height.
|
||||
fn generate_stack_height_global(module: &mut elements::Module) -> u32 {
|
||||
let global_entry = builder::global()
|
||||
.value_type()
|
||||
.i32()
|
||||
.mutable()
|
||||
.init_expr(Instruction::I32Const(0))
|
||||
.build();
|
||||
|
||||
// Try to find an existing global section.
|
||||
for section in module.sections_mut() {
|
||||
if let elements::Section::Global(gs) = section {
|
||||
gs.entries_mut().push(global_entry);
|
||||
return (gs.entries().len() as u32) - 1
|
||||
}
|
||||
}
|
||||
|
||||
// Existing section not found, create one!
|
||||
module
|
||||
.sections_mut()
|
||||
.push(elements::Section::Global(elements::GlobalSection::with_entries(vec![global_entry])));
|
||||
0
|
||||
}
|
||||
|
||||
/// 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<StackHeightStats>, &'static str> {
|
||||
let func_imports = module.import_count(elements::ImportCountType::Function);
|
||||
|
||||
// TODO: optimize!
|
||||
(0..module.functions_space())
|
||||
.map(|func_idx| {
|
||||
if func_idx < func_imports {
|
||||
// We can't calculate stack_cost of the import functions.
|
||||
Ok(Default::default())
|
||||
} else {
|
||||
compute_stack_cost(func_idx as u32, module)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn compute_stack_cost(func_idx: u32, module: &elements::Module) -> Result<StackHeightStats, &'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("This should be a index of a defined function")?;
|
||||
|
||||
max_height::compute(defined_func_idx, module)
|
||||
}
|
||||
|
||||
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() {
|
||||
let opcodes = func_body.code_mut();
|
||||
instrument_function(ctx, opcodes)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This function searches `call` instructions and wrap each call
|
||||
/// with preamble and postamble.
|
||||
///
|
||||
/// Before:
|
||||
///
|
||||
/// ```text
|
||||
/// get_local 0
|
||||
/// get_local 1
|
||||
/// call 228
|
||||
/// drop
|
||||
/// ```
|
||||
///
|
||||
/// After:
|
||||
///
|
||||
/// ```text
|
||||
/// get_local 0
|
||||
/// get_local 1
|
||||
///
|
||||
/// < ... preamble ... >
|
||||
///
|
||||
/// call 228
|
||||
///
|
||||
/// < .. postamble ... >
|
||||
///
|
||||
/// drop
|
||||
/// ```
|
||||
fn instrument_function(ctx: &mut Context, func: &mut Instructions) -> Result<(), &'static str> {
|
||||
use Instruction::*;
|
||||
|
||||
struct InstrumentCall {
|
||||
offset: usize,
|
||||
callee: u32,
|
||||
cost: u32,
|
||||
}
|
||||
|
||||
let calls: Vec<_> = func
|
||||
.elements()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(offset, instruction)| {
|
||||
if let Call(callee) = instruction {
|
||||
ctx.stack_cost(*callee).and_then(|cost| {
|
||||
if cost > 0 {
|
||||
Some(InstrumentCall { callee: *callee, offset, cost })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// The `instrumented_call!` contains the call itself. This is why we need to subtract one.
|
||||
let len = func.elements().len() + calls.len() * (instrument_call!(0, 0, 0, 0).len() - 1);
|
||||
let original_instrs = mem::replace(func.elements_mut(), Vec::with_capacity(len));
|
||||
let new_instrs = func.elements_mut();
|
||||
|
||||
let mut calls = calls.into_iter().peekable();
|
||||
for (original_pos, instr) in original_instrs.into_iter().enumerate() {
|
||||
// whether there is some call instruction at this position that needs to be instrumented
|
||||
let did_instrument = if let Some(call) = calls.peek() {
|
||||
if call.offset == original_pos {
|
||||
let new_seq = instrument_call!(
|
||||
call.callee,
|
||||
call.cost as i32,
|
||||
ctx.stack_height_global_idx(),
|
||||
ctx.stack_limit()
|
||||
);
|
||||
new_instrs.extend_from_slice(&new_seq);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if did_instrument {
|
||||
calls.next();
|
||||
} else {
|
||||
new_instrs.push(instr);
|
||||
}
|
||||
}
|
||||
|
||||
if calls.next().is_some() {
|
||||
return Err("Not all calls were used")
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_func_type(
|
||||
func_idx: u32,
|
||||
module: &elements::Module,
|
||||
) -> 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(&[]);
|
||||
|
||||
let func_imports = module.import_count(elements::ImportCountType::Function);
|
||||
let sig_idx = if func_idx < func_imports as u32 {
|
||||
module
|
||||
.import_section()
|
||||
.expect("function import count is not zero; import section must exists; qed")
|
||||
.entries()
|
||||
.iter()
|
||||
.filter_map(|entry| match entry.external() {
|
||||
elements::External::Function(idx) => Some(*idx),
|
||||
_ => None,
|
||||
})
|
||||
.nth(func_idx as usize)
|
||||
.expect(
|
||||
"func_idx is less than function imports count;
|
||||
nth function import must be `Some`;
|
||||
qed",
|
||||
)
|
||||
} else {
|
||||
functions
|
||||
.get(func_idx as usize - func_imports)
|
||||
.ok_or("Function at the specified index is not defined")?
|
||||
.type_ref()
|
||||
};
|
||||
let Type::Function(ty) = types
|
||||
.get(sig_idx as usize)
|
||||
.ok_or("The signature as specified by a function isn't defined")?;
|
||||
Ok(ty)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use parity_wasm::elements;
|
||||
|
||||
fn parse_wat(source: &str) -> elements::Module {
|
||||
elements::deserialize_buffer(&wat::parse_str(source).expect("Failed to wat2wasm"))
|
||||
.expect("Failed to deserialize the module")
|
||||
}
|
||||
|
||||
fn validate_module(module: elements::Module) {
|
||||
let binary = elements::serialize(module).expect("Failed to serialize");
|
||||
wasmparser::validate(&binary).expect("Invalid module");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_params_and_result() {
|
||||
let module = parse_wat(
|
||||
r#"
|
||||
(module
|
||||
(func (export "i32.add") (param i32 i32) (result i32)
|
||||
get_local 0
|
||||
get_local 1
|
||||
i32.add
|
||||
)
|
||||
)
|
||||
"#,
|
||||
);
|
||||
|
||||
let module = inject(module, 1024).expect("Failed to inject stack counter");
|
||||
validate_module(module);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
#[cfg(not(features = "std"))]
|
||||
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};
|
||||
|
||||
struct Thunk {
|
||||
signature: FunctionType,
|
||||
// Index in function space of this thunk.
|
||||
idx: Option<u32>,
|
||||
callee_stack_cost: u32,
|
||||
}
|
||||
|
||||
pub fn generate_thunks(
|
||||
ctx: &mut Context,
|
||||
module: elements::Module,
|
||||
) -> 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(&[]);
|
||||
let start_func_idx = module.start_section();
|
||||
|
||||
let exported_func_indices = exports.iter().filter_map(|entry| match entry.internal() {
|
||||
Internal::Function(function_idx) => Some(*function_idx),
|
||||
_ => None,
|
||||
});
|
||||
let table_func_indices =
|
||||
elem_segments.iter().flat_map(|segment| segment.members()).cloned();
|
||||
|
||||
// Replacement map is at least export section size.
|
||||
let mut replacement_map: Map<u32, Thunk> = Map::new();
|
||||
|
||||
for func_idx in exported_func_indices
|
||||
.chain(table_func_indices)
|
||||
.chain(start_func_idx.into_iter())
|
||||
{
|
||||
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 {
|
||||
replacement_map.insert(
|
||||
func_idx,
|
||||
Thunk {
|
||||
signature: resolve_func_type(func_idx, &module)?.clone(),
|
||||
idx: None,
|
||||
callee_stack_cost,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
replacement_map
|
||||
};
|
||||
|
||||
// Then, we generate a thunk for each original function.
|
||||
|
||||
// Save current func_idx
|
||||
let mut next_func_idx = module.functions_space() as u32;
|
||||
|
||||
let mut mbuilder = builder::from_module(module);
|
||||
for (func_idx, thunk) in replacement_map.iter_mut() {
|
||||
let instrumented_call = instrument_call!(
|
||||
*func_idx,
|
||||
thunk.callee_stack_cost as i32,
|
||||
ctx.stack_height_global_idx(),
|
||||
ctx.stack_limit()
|
||||
);
|
||||
// Thunk body consist of:
|
||||
// - argument pushing
|
||||
// - instrumented call
|
||||
// - end
|
||||
let mut thunk_body: Vec<elements::Instruction> =
|
||||
Vec::with_capacity(thunk.signature.params().len() + instrumented_call.len() + 1);
|
||||
|
||||
for (arg_idx, _) in thunk.signature.params().iter().enumerate() {
|
||||
thunk_body.push(elements::Instruction::GetLocal(arg_idx as u32));
|
||||
}
|
||||
thunk_body.extend_from_slice(&instrumented_call);
|
||||
thunk_body.push(elements::Instruction::End);
|
||||
|
||||
// TODO: Don't generate a signature, but find an existing one.
|
||||
|
||||
mbuilder = mbuilder
|
||||
.function()
|
||||
// Signature of the thunk should match the original function signature.
|
||||
.signature()
|
||||
.with_params(thunk.signature.params().to_vec())
|
||||
.with_results(thunk.signature.results().to_vec())
|
||||
.build()
|
||||
.body()
|
||||
.with_instructions(elements::Instructions::new(thunk_body))
|
||||
.build()
|
||||
.build();
|
||||
|
||||
thunk.idx = Some(next_func_idx);
|
||||
next_func_idx += 1;
|
||||
}
|
||||
let mut module = mbuilder.build();
|
||||
|
||||
// And finally, fixup thunks in export and table sections.
|
||||
|
||||
// Fixup original function index to a index of a thunk generated earlier.
|
||||
let fixup = |function_idx: &mut u32| {
|
||||
// Check whether this function is in replacement_map, since
|
||||
// we can skip thunk generation (e.g. if stack_cost of function is 0).
|
||||
if let Some(thunk) = replacement_map.get(function_idx) {
|
||||
*function_idx =
|
||||
thunk.idx.expect("At this point an index must be assigned to each thunk");
|
||||
}
|
||||
};
|
||||
|
||||
for section in module.sections_mut() {
|
||||
match section {
|
||||
elements::Section::Export(export_section) =>
|
||||
for entry in export_section.entries_mut() {
|
||||
if let Internal::Function(function_idx) = entry.internal_mut() {
|
||||
fixup(function_idx)
|
||||
}
|
||||
},
|
||||
elements::Section::Element(elem_section) =>
|
||||
for segment in elem_section.entries_mut() {
|
||||
for function_idx in segment.members_mut() {
|
||||
fixup(function_idx)
|
||||
}
|
||||
},
|
||||
elements::Section::Start(start_idx) => fixup(start_idx),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(module)
|
||||
}
|
||||
-148
@@ -1,148 +0,0 @@
|
||||
#[cfg(features = "std")]
|
||||
use std::collections::{HashSet as Set};
|
||||
#[cfg(not(features = "std"))]
|
||||
use std::collections::{BTreeSet as Set};
|
||||
use std::vec::Vec;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
+39
-43
@@ -1,12 +1,11 @@
|
||||
extern crate diff;
|
||||
extern crate pwasm_utils as utils;
|
||||
extern crate wabt;
|
||||
extern crate parity_wasm;
|
||||
|
||||
use std::fs;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use parity_wasm::elements;
|
||||
use parity_wasm::elements::Module;
|
||||
use std::{
|
||||
fs,
|
||||
io::{self, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use wasm_instrument::{self as instrument, parity_wasm::elements};
|
||||
use wasmparser::validate;
|
||||
|
||||
fn slurp<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
|
||||
let mut f = fs::File::open(path)?;
|
||||
@@ -21,48 +20,36 @@ fn dump<P: AsRef<Path>>(path: P, buf: &[u8]) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_wasm(binary: &[u8]) -> Result<(), wabt::Error> {
|
||||
wabt::Module::read_binary(
|
||||
&binary,
|
||||
&Default::default()
|
||||
)?.validate()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_diff_test<F: FnOnce(&[u8]) -> Vec<u8>>(test_dir: &str, name: &str, test: F) {
|
||||
// FIXME: not going to work on windows?
|
||||
let mut fixture_path = PathBuf::from(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/tests/fixtures/",
|
||||
));
|
||||
let mut fixture_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
fixture_path.push("tests");
|
||||
fixture_path.push("fixtures");
|
||||
fixture_path.push(test_dir);
|
||||
fixture_path.push(name);
|
||||
|
||||
// FIXME: not going to work on windows?
|
||||
let mut expected_path = PathBuf::from(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/tests/expectations/"
|
||||
));
|
||||
let mut expected_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
expected_path.push("tests");
|
||||
expected_path.push("expectations");
|
||||
expected_path.push(test_dir);
|
||||
expected_path.push(name);
|
||||
|
||||
let fixture_wat = slurp(&fixture_path).expect("Failed to read fixture");
|
||||
let fixture_wasm = wabt::wat2wasm(fixture_wat).expect("Failed to read fixture");
|
||||
validate_wasm(&fixture_wasm).expect("Fixture is invalid");
|
||||
let fixture_wasm = wat::parse_file(&fixture_path).expect("Failed to read fixture");
|
||||
validate(&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");
|
||||
validate(&actual_wasm).expect("Result module is invalid");
|
||||
|
||||
let actual_wat = wabt::wasm2wat(&actual_wasm).expect("Failed to convert result wasm to wat");
|
||||
let actual_wat =
|
||||
wasmprinter::print_bytes(&actual_wasm).expect("Failed to convert result wasm to wat");
|
||||
|
||||
if actual_wat != expected_wat {
|
||||
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),
|
||||
@@ -70,9 +57,11 @@ fn run_diff_test<F: FnOnce(&[u8]) -> Vec<u8>>(test_dir: &str, name: &str, test:
|
||||
}
|
||||
}
|
||||
|
||||
dump(&expected_path, actual_wat.as_bytes()).expect("Failed to write to expected");
|
||||
|
||||
panic!();
|
||||
if std::env::var_os("BLESS").is_some() {
|
||||
dump(&expected_path, actual_wat.as_bytes()).expect("Failed to write to expected");
|
||||
} else {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,8 +73,10 @@ mod stack_height {
|
||||
#[test]
|
||||
fn $name() {
|
||||
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).expect("Failed to instrument with stack counter");
|
||||
let module =
|
||||
elements::deserialize_buffer(input).expect("Failed to deserialize");
|
||||
let instrumented = instrument::inject_stack_limiter(module, 1024)
|
||||
.expect("Failed to instrument with stack counter");
|
||||
elements::serialize(instrumented).expect("Failed to serialize")
|
||||
});
|
||||
}
|
||||
@@ -97,6 +88,8 @@ mod stack_height {
|
||||
def_stack_height_test!(table);
|
||||
def_stack_height_test!(global);
|
||||
def_stack_height_test!(imports);
|
||||
def_stack_height_test!(many_locals);
|
||||
def_stack_height_test!(empty_functions);
|
||||
}
|
||||
|
||||
mod gas {
|
||||
@@ -107,17 +100,20 @@ 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).expect("Failed to instrument with gas metering");
|
||||
let module: Module =
|
||||
elements::deserialize_buffer(input).expect("Failed to deserialize");
|
||||
let module = module.parse_names().expect("Failed to parse names");
|
||||
|
||||
let instrumented = instrument::gas_metering::inject(module, &rules, "env")
|
||||
.expect("Failed to instrument with gas metering");
|
||||
elements::serialize(instrumented).expect("Failed to serialize")
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
def_gas_test!(ifs);
|
||||
def_gas_test!(simple);
|
||||
def_gas_test!(start);
|
||||
|
||||
@@ -2,28 +2,30 @@
|
||||
(type (;0;) (func (result i32)))
|
||||
(type (;1;) (func (param i32)))
|
||||
(import "env" "gas" (func (;0;) (type 1)))
|
||||
(func (;1;) (type 0) (result i32)
|
||||
(func $fibonacci_with_break (;1;) (type 0) (result i32)
|
||||
(local i32 i32)
|
||||
i32.const 13
|
||||
call 0
|
||||
block ;; label = @1
|
||||
i32.const 0
|
||||
set_local 0
|
||||
local.set 0
|
||||
i32.const 1
|
||||
set_local 1
|
||||
get_local 0
|
||||
get_local 1
|
||||
tee_local 0
|
||||
local.set 1
|
||||
local.get 0
|
||||
local.get 1
|
||||
local.tee 0
|
||||
i32.add
|
||||
set_local 1
|
||||
local.set 1
|
||||
i32.const 1
|
||||
br_if 0 (;@1;)
|
||||
i32.const 5
|
||||
call 0
|
||||
get_local 0
|
||||
get_local 1
|
||||
tee_local 0
|
||||
local.get 0
|
||||
local.get 1
|
||||
local.tee 0
|
||||
i32.add
|
||||
set_local 1
|
||||
local.set 1
|
||||
end
|
||||
get_local 1))
|
||||
local.get 1
|
||||
)
|
||||
)
|
||||
@@ -2,18 +2,21 @@
|
||||
(type (;0;) (func (param i32 i32) (result i32)))
|
||||
(type (;1;) (func (param i32)))
|
||||
(import "env" "gas" (func (;0;) (type 1)))
|
||||
(func (;1;) (type 0) (param i32 i32) (result i32)
|
||||
(func $add_locals (;1;) (type 0) (param $x i32) (param $y i32) (result i32)
|
||||
(local i32)
|
||||
i32.const 5
|
||||
call 0
|
||||
get_local 0
|
||||
get_local 1
|
||||
call 2
|
||||
set_local 2
|
||||
get_local 2)
|
||||
(func (;2;) (type 0) (param i32 i32) (result i32)
|
||||
local.get $x
|
||||
local.get $y
|
||||
call $add
|
||||
local.set 2
|
||||
local.get 2
|
||||
)
|
||||
(func $add (;2;) (type 0) (param i32 i32) (result i32)
|
||||
i32.const 3
|
||||
call 0
|
||||
get_local 0
|
||||
get_local 1
|
||||
i32.add))
|
||||
local.get 0
|
||||
local.get 1
|
||||
i32.add
|
||||
)
|
||||
)
|
||||
@@ -9,12 +9,14 @@
|
||||
if (result i32) ;; label = @1
|
||||
i32.const 3
|
||||
call 0
|
||||
get_local 0
|
||||
local.get 0
|
||||
i32.const 1
|
||||
i32.add
|
||||
else
|
||||
i32.const 2
|
||||
call 0
|
||||
get_local 0
|
||||
local.get 0
|
||||
i32.popcnt
|
||||
end))
|
||||
end
|
||||
)
|
||||
)
|
||||
@@ -15,10 +15,13 @@
|
||||
i32.const 123
|
||||
drop
|
||||
end
|
||||
end)
|
||||
end
|
||||
)
|
||||
(func (;2;) (type 0)
|
||||
i32.const 1
|
||||
call 0
|
||||
block ;; label = @1
|
||||
end)
|
||||
(export "simple" (func 1)))
|
||||
end
|
||||
)
|
||||
(export "simple" (func 1))
|
||||
)
|
||||
@@ -2,17 +2,19 @@
|
||||
(type (;0;) (func (param i32 i32)))
|
||||
(type (;1;) (func))
|
||||
(type (;2;) (func (param i32)))
|
||||
(import "env" "ext_return" (func (;0;) (type 0)))
|
||||
(import "env" "ext_return" (func $ext_return (;0;) (type 0)))
|
||||
(import "env" "memory" (memory (;0;) 1 1))
|
||||
(import "env" "gas" (func (;1;) (type 2)))
|
||||
(func (;2;) (type 1)
|
||||
(func $start (;2;) (type 1)
|
||||
i32.const 4
|
||||
call 1
|
||||
i32.const 8
|
||||
i32.const 4
|
||||
call 0
|
||||
unreachable)
|
||||
call $ext_return
|
||||
unreachable
|
||||
)
|
||||
(func (;3;) (type 1))
|
||||
(export "call" (func 3))
|
||||
(start 2)
|
||||
(data (i32.const 8) "\01\02\03\04"))
|
||||
(start $start)
|
||||
(data (;0;) (i32.const 8) "\01\02\03\04")
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
(module
|
||||
(type (;0;) (func))
|
||||
(func (;0;) (type 0)
|
||||
global.get 0
|
||||
i32.const 2
|
||||
i32.add
|
||||
global.set 0
|
||||
global.get 0
|
||||
i32.const 1024
|
||||
i32.gt_u
|
||||
if ;; label = @1
|
||||
unreachable
|
||||
end
|
||||
call 0
|
||||
global.get 0
|
||||
i32.const 2
|
||||
i32.sub
|
||||
global.set 0
|
||||
)
|
||||
(func (;1;) (type 0)
|
||||
global.get 0
|
||||
i32.const 2
|
||||
i32.add
|
||||
global.set 0
|
||||
global.get 0
|
||||
i32.const 1024
|
||||
i32.gt_u
|
||||
if ;; label = @1
|
||||
unreachable
|
||||
end
|
||||
call 0
|
||||
global.get 0
|
||||
i32.const 2
|
||||
i32.sub
|
||||
global.set 0
|
||||
)
|
||||
(func (;2;) (type 0)
|
||||
global.get 0
|
||||
i32.const 2
|
||||
i32.add
|
||||
global.set 0
|
||||
global.get 0
|
||||
i32.const 1024
|
||||
i32.gt_u
|
||||
if ;; label = @1
|
||||
unreachable
|
||||
end
|
||||
call 1
|
||||
global.get 0
|
||||
i32.const 2
|
||||
i32.sub
|
||||
global.set 0
|
||||
)
|
||||
(global (;0;) (mut i32) i32.const 0)
|
||||
(export "main" (func 2))
|
||||
)
|
||||
@@ -2,54 +2,58 @@
|
||||
(type (;0;) (func))
|
||||
(type (;1;) (func (param i32 i32) (result i32)))
|
||||
(type (;2;) (func (param i32)))
|
||||
(import "env" "foo" (func (;0;) (type 0)))
|
||||
(func (;1;) (type 1) (param i32 i32) (result i32)
|
||||
get_local 0
|
||||
get_local 1
|
||||
i32.add)
|
||||
(func (;2;) (type 2) (param i32)
|
||||
(local i32)
|
||||
get_global 0
|
||||
(import "env" "foo" (func $foo (;0;) (type 0)))
|
||||
(func $i32.add (;1;) (type 1) (param i32 i32) (result i32)
|
||||
local.get 0
|
||||
local.get 1
|
||||
i32.add
|
||||
)
|
||||
(func (;2;) (type 2) (param $arg i32)
|
||||
(local $tmp i32)
|
||||
global.get $counter
|
||||
i32.const 1
|
||||
i32.add
|
||||
tee_local 1
|
||||
set_global 0
|
||||
get_local 1
|
||||
get_local 0
|
||||
get_global 1
|
||||
i32.const 2
|
||||
local.tee $tmp
|
||||
global.set $counter
|
||||
local.get $tmp
|
||||
local.get $arg
|
||||
global.get 1
|
||||
i32.const 4
|
||||
i32.add
|
||||
set_global 1
|
||||
get_global 1
|
||||
global.set 1
|
||||
global.get 1
|
||||
i32.const 1024
|
||||
i32.gt_u
|
||||
if ;; label = @1
|
||||
unreachable
|
||||
end
|
||||
call 1
|
||||
get_global 1
|
||||
i32.const 2
|
||||
call $i32.add
|
||||
global.get 1
|
||||
i32.const 4
|
||||
i32.sub
|
||||
set_global 1
|
||||
drop)
|
||||
global.set 1
|
||||
drop
|
||||
)
|
||||
(func (;3;) (type 1) (param i32 i32) (result i32)
|
||||
get_local 0
|
||||
get_local 1
|
||||
get_global 1
|
||||
i32.const 2
|
||||
local.get 0
|
||||
local.get 1
|
||||
global.get 1
|
||||
i32.const 4
|
||||
i32.add
|
||||
set_global 1
|
||||
get_global 1
|
||||
global.set 1
|
||||
global.get 1
|
||||
i32.const 1024
|
||||
i32.gt_u
|
||||
if ;; label = @1
|
||||
unreachable
|
||||
end
|
||||
call 1
|
||||
get_global 1
|
||||
i32.const 2
|
||||
call $i32.add
|
||||
global.get 1
|
||||
i32.const 4
|
||||
i32.sub
|
||||
set_global 1)
|
||||
(global (;0;) (mut i32) (i32.const 1))
|
||||
(global (;1;) (mut i32) (i32.const 0))
|
||||
(export "i32.add" (func 3)))
|
||||
global.set 1
|
||||
)
|
||||
(global $counter (;0;) (mut i32) i32.const 1)
|
||||
(global (;1;) (mut i32) i32.const 0)
|
||||
(export "i32.add" (func 3))
|
||||
)
|
||||
@@ -1,31 +1,34 @@
|
||||
(module
|
||||
(type (;0;) (func))
|
||||
(type (;1;) (func (param i32 i32) (result i32)))
|
||||
(import "env" "foo" (func (;0;) (type 0)))
|
||||
(import "env" "boo" (func (;1;) (type 0)))
|
||||
(import "env" "foo" (func $foo (;0;) (type 0)))
|
||||
(import "env" "boo" (func $boo (;1;) (type 0)))
|
||||
(func (;2;) (type 1) (param i32 i32) (result i32)
|
||||
call 0
|
||||
call 1
|
||||
get_local 0
|
||||
get_local 1
|
||||
i32.add)
|
||||
(func (;3;) (type 1) (param i32 i32) (result i32)
|
||||
get_local 0
|
||||
get_local 1
|
||||
get_global 0
|
||||
i32.const 2
|
||||
call $foo
|
||||
call $boo
|
||||
local.get 0
|
||||
local.get 1
|
||||
i32.add
|
||||
set_global 0
|
||||
get_global 0
|
||||
)
|
||||
(func (;3;) (type 1) (param i32 i32) (result i32)
|
||||
local.get 0
|
||||
local.get 1
|
||||
global.get 0
|
||||
i32.const 4
|
||||
i32.add
|
||||
global.set 0
|
||||
global.get 0
|
||||
i32.const 1024
|
||||
i32.gt_u
|
||||
if ;; label = @1
|
||||
unreachable
|
||||
end
|
||||
call 2
|
||||
get_global 0
|
||||
i32.const 2
|
||||
global.get 0
|
||||
i32.const 4
|
||||
i32.sub
|
||||
set_global 0)
|
||||
(global (;0;) (mut i32) (i32.const 0))
|
||||
(export "i32.add" (func 3)))
|
||||
global.set 0
|
||||
)
|
||||
(global (;0;) (mut i32) i32.const 0)
|
||||
(export "i32.add" (func 3))
|
||||
)
|
||||
@@ -0,0 +1,24 @@
|
||||
(module
|
||||
(type (;0;) (func))
|
||||
(func $one-group-many-locals (;0;) (type 0)
|
||||
(local i64 i64 i32)
|
||||
)
|
||||
(func $main (;1;) (type 0)
|
||||
global.get 0
|
||||
i32.const 5
|
||||
i32.add
|
||||
global.set 0
|
||||
global.get 0
|
||||
i32.const 1024
|
||||
i32.gt_u
|
||||
if ;; label = @1
|
||||
unreachable
|
||||
end
|
||||
call $one-group-many-locals
|
||||
global.get 0
|
||||
i32.const 5
|
||||
i32.sub
|
||||
global.set 0
|
||||
)
|
||||
(global (;0;) (mut i32) i32.const 0)
|
||||
)
|
||||
@@ -2,22 +2,25 @@
|
||||
(type (;0;) (func))
|
||||
(func (;0;) (type 0)
|
||||
i32.const 123
|
||||
drop)
|
||||
drop
|
||||
)
|
||||
(func (;1;) (type 0)
|
||||
get_global 0
|
||||
i32.const 1
|
||||
global.get 0
|
||||
i32.const 3
|
||||
i32.add
|
||||
set_global 0
|
||||
get_global 0
|
||||
global.set 0
|
||||
global.get 0
|
||||
i32.const 1024
|
||||
i32.gt_u
|
||||
if ;; label = @1
|
||||
unreachable
|
||||
end
|
||||
call 0
|
||||
get_global 0
|
||||
i32.const 1
|
||||
global.get 0
|
||||
i32.const 3
|
||||
i32.sub
|
||||
set_global 0)
|
||||
(global (;0;) (mut i32) (i32.const 0))
|
||||
(export "simple" (func 1)))
|
||||
global.set 0
|
||||
)
|
||||
(global (;0;) (mut i32) i32.const 0)
|
||||
(export "simple" (func 1))
|
||||
)
|
||||
@@ -1,44 +1,47 @@
|
||||
(module
|
||||
(type (;0;) (func (param i32 i32)))
|
||||
(type (;1;) (func))
|
||||
(import "env" "ext_return" (func (;0;) (type 0)))
|
||||
(import "env" "ext_return" (func $ext_return (;0;) (type 0)))
|
||||
(import "env" "memory" (memory (;0;) 1 1))
|
||||
(func (;1;) (type 1)
|
||||
(local i32))
|
||||
(func $start (;1;) (type 1)
|
||||
(local i32)
|
||||
)
|
||||
(func (;2;) (type 1))
|
||||
(func (;3;) (type 1)
|
||||
get_global 0
|
||||
i32.const 1
|
||||
global.get 0
|
||||
i32.const 3
|
||||
i32.add
|
||||
set_global 0
|
||||
get_global 0
|
||||
global.set 0
|
||||
global.get 0
|
||||
i32.const 1024
|
||||
i32.gt_u
|
||||
if ;; label = @1
|
||||
unreachable
|
||||
end
|
||||
call 1
|
||||
get_global 0
|
||||
i32.const 1
|
||||
call $start
|
||||
global.get 0
|
||||
i32.const 3
|
||||
i32.sub
|
||||
set_global 0)
|
||||
global.set 0
|
||||
)
|
||||
(func (;4;) (type 1)
|
||||
get_global 0
|
||||
i32.const 1
|
||||
global.get 0
|
||||
i32.const 2
|
||||
i32.add
|
||||
set_global 0
|
||||
get_global 0
|
||||
global.set 0
|
||||
global.get 0
|
||||
i32.const 1024
|
||||
i32.gt_u
|
||||
if ;; label = @1
|
||||
unreachable
|
||||
end
|
||||
call 1
|
||||
get_global 0
|
||||
i32.const 1
|
||||
call 2
|
||||
global.get 0
|
||||
i32.const 2
|
||||
i32.sub
|
||||
set_global 0)
|
||||
(global (;0;) (mut i32) (i32.const 0))
|
||||
(export "exported_start" (func 4))
|
||||
(export "call" (func 2))
|
||||
(start 4))
|
||||
global.set 0
|
||||
)
|
||||
(global (;0;) (mut i32) i32.const 0)
|
||||
(export "call" (func 4))
|
||||
(start 3)
|
||||
)
|
||||
@@ -2,84 +2,71 @@
|
||||
(type (;0;) (func))
|
||||
(type (;1;) (func (param i32)))
|
||||
(type (;2;) (func (param i32 i32) (result i32)))
|
||||
(import "env" "foo" (func (;0;) (type 0)))
|
||||
(import "env" "foo" (func $foo (;0;) (type 0)))
|
||||
(func (;1;) (type 1) (param i32)
|
||||
get_local 0
|
||||
local.get 0
|
||||
i32.const 0
|
||||
get_global 0
|
||||
i32.const 2
|
||||
global.get 0
|
||||
i32.const 4
|
||||
i32.add
|
||||
set_global 0
|
||||
get_global 0
|
||||
global.set 0
|
||||
global.get 0
|
||||
i32.const 1024
|
||||
i32.gt_u
|
||||
if ;; label = @1
|
||||
unreachable
|
||||
end
|
||||
call 2
|
||||
get_global 0
|
||||
i32.const 2
|
||||
call $i32.add
|
||||
global.get 0
|
||||
i32.const 4
|
||||
i32.sub
|
||||
set_global 0
|
||||
drop)
|
||||
(func (;2;) (type 2) (param i32 i32) (result i32)
|
||||
get_local 0
|
||||
get_local 1
|
||||
i32.add)
|
||||
(func (;3;) (type 2) (param i32 i32) (result i32)
|
||||
get_local 0
|
||||
get_local 1
|
||||
get_global 0
|
||||
i32.const 2
|
||||
global.set 0
|
||||
drop
|
||||
)
|
||||
(func $i32.add (;2;) (type 2) (param i32 i32) (result i32)
|
||||
local.get 0
|
||||
local.get 1
|
||||
i32.add
|
||||
set_global 0
|
||||
get_global 0
|
||||
i32.const 1024
|
||||
i32.gt_u
|
||||
if ;; label = @1
|
||||
unreachable
|
||||
end
|
||||
call 2
|
||||
get_global 0
|
||||
i32.const 2
|
||||
i32.sub
|
||||
set_global 0)
|
||||
(func (;4;) (type 1) (param i32)
|
||||
get_local 0
|
||||
get_global 0
|
||||
i32.const 2
|
||||
)
|
||||
(func (;3;) (type 1) (param i32)
|
||||
local.get 0
|
||||
global.get 0
|
||||
i32.const 4
|
||||
i32.add
|
||||
set_global 0
|
||||
get_global 0
|
||||
global.set 0
|
||||
global.get 0
|
||||
i32.const 1024
|
||||
i32.gt_u
|
||||
if ;; label = @1
|
||||
unreachable
|
||||
end
|
||||
call 1
|
||||
get_global 0
|
||||
i32.const 2
|
||||
global.get 0
|
||||
i32.const 4
|
||||
i32.sub
|
||||
set_global 0)
|
||||
(func (;5;) (type 2) (param i32 i32) (result i32)
|
||||
get_local 0
|
||||
get_local 1
|
||||
get_global 0
|
||||
i32.const 2
|
||||
global.set 0
|
||||
)
|
||||
(func (;4;) (type 2) (param i32 i32) (result i32)
|
||||
local.get 0
|
||||
local.get 1
|
||||
global.get 0
|
||||
i32.const 4
|
||||
i32.add
|
||||
set_global 0
|
||||
get_global 0
|
||||
global.set 0
|
||||
global.get 0
|
||||
i32.const 1024
|
||||
i32.gt_u
|
||||
if ;; label = @1
|
||||
unreachable
|
||||
end
|
||||
call 2
|
||||
get_global 0
|
||||
i32.const 2
|
||||
call $i32.add
|
||||
global.get 0
|
||||
i32.const 4
|
||||
i32.sub
|
||||
set_global 0)
|
||||
(table (;0;) 10 anyfunc)
|
||||
(global (;0;) (mut i32) (i32.const 0))
|
||||
(export "i32.add" (func 5))
|
||||
(elem (i32.const 0) 0 4 5))
|
||||
global.set 0
|
||||
)
|
||||
(table (;0;) 10 funcref)
|
||||
(global (;0;) (mut i32) i32.const 0)
|
||||
(export "i32.add" (func 4))
|
||||
(elem (;0;) (i32.const 0) func $foo 3 4)
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
(module
|
||||
(func (;0;)
|
||||
call 0
|
||||
)
|
||||
(func (;1;) (export "main")
|
||||
call 0
|
||||
)
|
||||
)
|
||||
+5
-5
@@ -6,17 +6,17 @@
|
||||
|
||||
(func $i32.add (export "i32.add") (param i32 i32) (result i32)
|
||||
get_local 0
|
||||
get_local 1
|
||||
i32.add
|
||||
get_local 1
|
||||
i32.add
|
||||
)
|
||||
(func (param $arg i32)
|
||||
(local $tmp i32)
|
||||
|
||||
get_global 0
|
||||
global.get 0
|
||||
i32.const 1
|
||||
i32.add
|
||||
tee_local $tmp
|
||||
set_global $counter
|
||||
tee_local $tmp
|
||||
global.set $counter
|
||||
|
||||
get_local $tmp
|
||||
get_local $arg
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
(module
|
||||
(func $one-group-many-locals
|
||||
(local i64) (local i64) (local i32)
|
||||
)
|
||||
(func $main
|
||||
(call
|
||||
$one-group-many-locals
|
||||
)
|
||||
)
|
||||
)
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(start $start)
|
||||
(func $start (export "exported_start")
|
||||
(func $start
|
||||
(local i32)
|
||||
)
|
||||
(func (export "call")
|
||||
|
||||
+6
-6
@@ -1,15 +1,15 @@
|
||||
(module
|
||||
(import "env" "foo" (func $foo))
|
||||
(func (param i32)
|
||||
get_local 0
|
||||
i32.const 0
|
||||
call $i32.add
|
||||
drop
|
||||
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
|
||||
get_local 1
|
||||
i32.add
|
||||
)
|
||||
(table 10 anyfunc)
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
use std::{
|
||||
fs::{read, read_dir},
|
||||
path::PathBuf,
|
||||
};
|
||||
use wasm_instrument::{
|
||||
gas_metering, inject_stack_limiter,
|
||||
parity_wasm::{deserialize_buffer, elements::Module, serialize},
|
||||
};
|
||||
|
||||
fn fixture_dir() -> PathBuf {
|
||||
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
path.push("benches");
|
||||
path.push("fixtures");
|
||||
path
|
||||
}
|
||||
|
||||
/// Print the overhead of applying gas metering, stack height limiting or both.
|
||||
///
|
||||
/// Use `cargo test print_overhead -- --nocapture`.
|
||||
#[test]
|
||||
fn print_size_overhead() {
|
||||
let mut results: Vec<_> = read_dir(fixture_dir())
|
||||
.unwrap()
|
||||
.map(|entry| {
|
||||
let entry = entry.unwrap();
|
||||
let (orig_len, orig_module) = {
|
||||
let bytes = read(&entry.path()).unwrap();
|
||||
let len = bytes.len();
|
||||
let module: Module = deserialize_buffer(&bytes).unwrap();
|
||||
(len, module)
|
||||
};
|
||||
let (gas_metering_len, gas_module) = {
|
||||
let module = gas_metering::inject(
|
||||
orig_module.clone(),
|
||||
&gas_metering::ConstantCostRules::default(),
|
||||
"env",
|
||||
)
|
||||
.unwrap();
|
||||
let bytes = serialize(module.clone()).unwrap();
|
||||
let len = bytes.len();
|
||||
(len, module)
|
||||
};
|
||||
let stack_height_len = {
|
||||
let module = inject_stack_limiter(orig_module, 128).unwrap();
|
||||
let bytes = serialize(module).unwrap();
|
||||
bytes.len()
|
||||
};
|
||||
let both_len = {
|
||||
let module = inject_stack_limiter(gas_module, 128).unwrap();
|
||||
let bytes = serialize(module).unwrap();
|
||||
bytes.len()
|
||||
};
|
||||
|
||||
let overhead = both_len * 100 / orig_len;
|
||||
|
||||
(
|
||||
overhead,
|
||||
format!(
|
||||
"{:30}: orig = {:4} kb, gas_metering = {} %, stack_limiter = {} %, both = {} %",
|
||||
entry.file_name().to_str().unwrap(),
|
||||
orig_len / 1024,
|
||||
gas_metering_len * 100 / orig_len,
|
||||
stack_height_len * 100 / orig_len,
|
||||
overhead,
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
results.sort_unstable_by(|a, b| b.0.cmp(&a.0));
|
||||
for entry in results {
|
||||
println!("{}", entry.1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user