Compare commits

...

102 Commits

Author SHA1 Message Date
Cadey Ratio 0b6dd64219 fix no_std building 2019-10-24 10:29:04 -04:00
adam-rhebo d2ea44e37c Avoid temporary allocations during function context initialization (#217)
* Avoid temporary allocation when push locals during function invocation.

* Extend value stack for all locals at once.
2019-10-22 17:23:25 +02:00
Sergei Pepyakin f19e1c27fc
Fix tiny_keccak (#215) 2019-09-28 19:05:17 +02:00
Sergei Pepyakin 59ab1c8d78
Don't use `cache: cargo` in Travis CI's config (#213) 2019-09-26 14:13:29 +02:00
Sergei Pepyakin e6bdaf76f6
Bump wabt up to 0.9. (#212) 2019-09-26 13:18:57 +02:00
Pierre Krieger 390f4b2c4a Use a Cow for the resumable parameters (#210)
* Use a Cow for the resumable parameters

* Try fixing tests
2019-09-09 12:34:49 +02:00
Sergei Pepyakin 08c09adbf2
Bump wasmi-validation (#209) 2019-09-05 23:49:30 +02:00
Sergei Pepyakin 990e6698cb
Bump wasmi (#208) 2019-09-05 23:26:48 +02:00
DemiMarie-parity 7b1e5820c3 Update parity-wasm (#207) 2019-09-05 22:59:10 +02:00
thiolliere 9d998c7289 Update README.md (#205) 2019-08-27 22:20:17 +02:00
Sergei Pepyakin b1ea069c4a
Update parity-wasm (#198) 2019-07-17 14:24:36 +03:00
Sergei Pepyakin b67af25899
Apply cargo-fix on wasmi (#191)
* cargo-fix wasmi

* fmt

* Remove allow_failures

* Add dyn in benches

* Fix nightly
2019-07-10 17:45:06 +03:00
NikVolf 57cc6c6a3d bump to 0.5 2019-07-09 18:45:31 +03:00
Sergey Pepyakin 1bf3cbe5d0 Bump version to 0.4.6. 2019-07-09 15:55:24 +02:00
Sergei Pepyakin 1a6e5b30de
Use mmap for allocation (#190) 2019-07-08 13:53:04 +02:00
adam-rhebo f29f301e6e Make clearing value stack between export invocations optional (#188)
This avoids the main overhead of repeated export invocations by making
it optional to clear the value stack after each interpreter run.

This is especially useful if different exports of the same module are
invoked repeated so that no unintended information leaks are possible.
2019-06-21 11:03:58 +02:00
adam-rhebo 7fe6ef4e35 Add ARMv7 as CI test target (#186)
* Add ARMv7 as CI test target

* Avoid UB in conversions from floating point

When truncating floating point values to integer values, we need to
avoid undefined behavior if the argument does not fit into the target
type which is currently impossible using casts of primitive types.

Hence, this reimplements those conversions using arbitrary precision
integers and rationals from the num crate.
2019-06-18 15:06:16 +02:00
adam-rhebo 8dac328ea7 Remove hashbrown and use BTree{Map,Set} from the alloc crate (#187)
* Remove hashbrown and use BTree{Map,Set} from the alloc crate

wasmi-validation must handle untrusted input and hence we switch from
Hash{Set,Map} (whether std's or hashbrown's) to BTree{Set,Map} to avoid
algorithmic complexity attacks while retaining no_std support.

Closes #183

* Improve memory locality of checking for duplicate exports

Using a sorted slice gives us the same O(N log N) worst case execution
time as using a BTreeMap, but using a single allocation as with HashMap,
so that we should see better memory locality and hence better constant
factors when checking for duplicate exports.
2019-06-12 11:30:10 +02:00
adam-rhebo 284c907b29 Recycle value stacks to avoid allocation costs (#184) 2019-06-12 10:51:04 +02:00
Björn Wagner 2520bfc5a8
fix typo 2019-06-07 14:56:19 +08:00
Elichai Turkel 5be300c99f Replaced std with core in the TryInto import (#181)
* Replaced std with core

* Changed rust nightly to not allowed to fail in travis CI
2019-05-16 18:46:22 +02:00
Sergei Pepyakin 2960f1b4ec
Exclude benches from workspaces (#180) 2019-05-06 16:24:07 +02:00
Sergey Pepyakin b73996a794 wasmi-validation version 0.1 2019-05-06 14:47:51 +02:00
Niklas Adolfsson 25429407fe fix(compile module): remove unused `mut` (#179) 2019-05-03 14:46:01 +01:00
Sergei Pepyakin a3aad8a549
Extract validation into a separate crate (#176)
* Add some docs.

* return_type isn't failable

* Add comment about safety of top_label

* Attempt number 10

* Rework.

Now we will a compiler which wraps and uses info from a evaluation simulator.

* Get rid of outcome

* Introduce StartedWith

* Actually use started_with.

* Mirror label_stack.

* Avoid using frame_type.

* Finally get rid from frame_type.

* Extract compilation

* Refactoring cleaning

* Validation separated from compilation.

* Move sink to FunctionReader

* Rename to compiler.

* fmt

* Move push_label under validation context.

* Add Validation traits

* Express the compiler using validation trait

* Move code under prepare

* Comments.

* WIP

* The great move of validation

* Make validation compile

* Make it compile.

* Format it.

* Fix warnings.

* Clean.

* Make it work under no_std

* Move deny_floating_point to wasmi

* Rename validate_module2 → validate_module

* Make validation tests work

* Make wasmi compilation tests work

* Renamings.

* Get rid of memory_units dependency in validation

* Rename.

* Clean.

* Estimate capacity.

* fmt.

* Clean and detail End opcode.

* Add comment about top_label safety

* Remove another TODO

* Comment access to require_target

* Remove redundant PartialEq

* Print value that can't be coerced to u32

* s/with_instruction_capacity/with_capacity

* fmt.

* fmt

* Proofs

* Add better proof

* Get rid of unreachable in StackValueType

* Propagate error if frame stack overflown on create

* use checked sub instead of -

* Keep::count
2019-04-19 16:05:09 +02:00
Sergey Pepyakin 0267b20e6e Bump version to 0.4.4 2019-03-15 12:13:10 +01:00
Elichai Turkel b90fcaf2dd Added the nightly feature to hashbrown (#174) 2019-03-15 12:08:25 +01:00
Elichai Turkel 8403cc3411 Add a check if NotStartedModuleRef has a start function (#173)
* Add a check if NotStartedModuleRef has a start function

* Removed redundant doc comment

Co-Authored-By: elichai <elichai.turkel@gmail.com>
2019-02-28 17:19:04 +01:00
Sergei Pepyakin 188ad62955
Update readme (#167)
* Restructure README

* Remove point of README

Since it dated since it was introduced. Priorities have changed since then, but on the other hand, at the moment of writing we already have a PR.

* Remove the note about parity-wasm

This now only has historic significance : )

* WASM → Wasm

* Expand our vision in more detail.
2019-02-26 15:45:55 +01:00
Elichai Turkel e88d5d32e5 Removed Byteorder now that rust supports it natively (#171)
* Removed byteorder now that from_le_bytes is stabilized

* Rust fmt
2019-02-26 11:37:14 +01:00
Elichai Turkel 23b054c0e5 Replaced hashmap_core with hashbrown (#161) 2019-01-20 17:59:26 +01:00
Leonardo Yvens ad14d82bce Expose globals to host (#158)
* Make `global_by_index` pub, add `globals` getter

* simplify access to globals
2019-01-18 00:27:56 +01:00
Arkadiy Paronyan 073e4e7f1f Fixed documentation for used_size (#156) 2019-01-08 16:16:49 +01:00
Sergey Pepyakin 7740f6b690 Bump wasmi to 0.4.3 2019-01-03 12:40:20 +01:00
Sergei Pepyakin c3b21b337d
Update editorconfig (#155) 2019-01-03 12:38:23 +01:00
Sergei Pepyakin d52ba8849a
Run rustfmt check on CI (#154)
* Run rustfmt check on CI

* Reformat.
2019-01-02 23:50:38 +01:00
Arkadiy Paronyan e047f508fa Track memory usage (#153)
* Track memory usage

* Track lowest_used in copy, etc.

* Extra comment for used_size
2019-01-02 22:13:21 +01:00
Jef 617be0198d Check type when resuming function (#152)
* Check type when resuming function

* Remove pub(crate)

* Update lib.rs
2019-01-02 12:18:24 +01:00
Jef 899cc32e45 rustfmt (#151) 2018-12-11 12:54:06 +01:00
Ivan Enderlin da558c7ce7 doc(host) Fix the `Externals` example (#149)
* doc(host) Fix the `Externals` example

The example is missing two things:

  * `index` is computed but not used,
  * `check_signature` is never used.

This patch tries to fix that.

* doc(host) Fix `check_signature` example
2018-11-28 14:03:03 +01:00
Jef c7f9196df6 Export LittleEndianConvert (#148) 2018-11-28 14:01:51 +01:00
Sergey Pepyakin 7191998216
Bump version to 0.4.2 (#144) 2018-11-15 13:19:53 +01:00
Sergey Pepyakin 15e9461bae
Allow failures on nightly (#143) 2018-11-15 13:11:10 +01:00
Jef e11ba15373 Remove `Box<[Target]>` from `Instruction` (#141)
This also allows `Instruction` to be `Copy`, which massively speeds
up `<Instructions as Clone>::clone` since it can now just `memcpy`
the bytes using SIMD instead of having to switch on every single
element. I haven't looked at the disassembly of `InstructionIter::next`
yet, it could be that there are even more improvements yet to be gained
from either:

* Only doing work on `BrTable` (this might already be the case depending
  on the whims of the optimiser)
* Using `unsafe` to make it a noop (we really don't want to do this,
  obviously, since it means that `Instructions` has to be immovable)
2018-11-15 12:18:47 +01:00
Eric Findlay 7b4c648acb Minor edits to documentation. (#140) 2018-11-09 15:39:40 +01:00
Jef c877d64508 Remove tag from RuntimeValue (#133)
* Remove tag from runtime value

* Add explanation of `RuntimeValueInternal`, move it so I don't need to do `pub(crate)`
2018-10-31 16:01:20 +01:00
Wei Tang 1c04be64f8 Remove redundent check_function_args (#135)
* Remove redundent check_function_args

* Remove unused format convertion in check_function_args

* Remove unnecessary alloc
2018-10-29 18:29:46 +01:00
Julius Rakow 20154c5e24 Add no_std support (#122)
* add default-enabled std feature

* use parity-wasm/std feature only if std is enabled

* drop dependency on std::io

* use hashmap_core instead of std::collections::HashMap

* disable std::error in no_std

* core and alloc all the things

* mention no_std in readme

* add no_std feature and use hashmap_core only on no_std

* rename the no_std feature to core

* drop dependency on byteorder/std

* simplify float impl macro

* remove some trailing whitespace

* use libm for float math in no_std

* add note about no_std panics of libm to readme

* Embed nan-preserving-float crate.

* Add no_std check to the Travis CI config

* add missing dev-dependency
2018-10-29 11:16:55 +01:00
Sergey Pepyakin 2f7505d120
Travis maintenance (#132)
*  Use gcc-8 for builds

* Don't run cargo-deadlinks

* Update wabt to 0.6.
2018-10-26 15:03:01 +02:00
Jef 3854ecdad6 Remove some unsafety (#131)
* Remove unsafety

* Remove transmutes and pointer casts
2018-10-25 14:17:25 +02:00
Sergey Pepyakin ad4236263a
Don't update PC on every instruction (#130) 2018-10-19 16:55:07 +02:00
Will Glynn 7509477a61 Hide instruction storage details (#129)
* Hide Instructions implementation behind an iterator

* Hide instruction encoding behind isa::Instructions::push()

* Consistently use u32 for program counter storage

* Refer to instructions by position rather than index
2018-10-10 19:02:27 +02:00
Sergey Pepyakin 9170303aad Bump wasmi to 0.4.1 2018-10-02 11:36:23 +01:00
Sergey Pepyakin 36582c32b6
Transfer function (#128) 2018-10-02 11:01:18 +01:00
Sergey Pepyakin 438eab9ada
Don't run cargo deadlinks for nightly (#121) 2018-08-27 11:11:21 +03:00
Tobias Bucher 167e4845ef Proofread the library-level docs (#123)
* Proofread the library-level docs

* Fix extra word, remove mention of linear memory "space"
2018-08-27 11:10:45 +03:00
Guanqun Lu 0409913a26 Typo fixes (#119)
* typo fixes

* more typo fixes
2018-08-03 16:05:10 +03:00
Guanqun Lu 929ac564a5 we don't need this TODO anymore (#120) 2018-08-03 16:04:33 +03:00
Guanqun Lu 8ca6b4b860 Add a section in README to show how to build and test (#117) 2018-08-01 12:21:50 +03:00
Sergey Pepyakin 0a34f1d0f6
Bump version to 0.4.0 (#116) 2018-07-31 17:05:21 +03:00
Arkadiy Paronyan 118396851a Allocate memory on demand (#115)
* Allocate mem on demand

* More control in with_direct_access_mut
2018-07-31 16:25:46 +03:00
Guanqun Lu 5c86c1c753 Bump wabt's version to 0.4 and add two more test cases (#114)
* bump wabt version to 0.4

It has some interface changes.

* bump up testsuite and add two more test cases

* use the same expect string
2018-07-30 19:43:18 +03:00
Guanqun Lu 43b8d52bca typo fixes in lib.rs (#113) 2018-07-30 15:16:17 +03:00
Arkadiy Paronyan 9ed95e49c1 Avoid allocations on memory operations (#112) 2018-07-26 14:50:05 +03:00
Wei Tang a605175abe Resumable function invocation (#110)
* Move call_stack to Interpreter struct

* Accept func and args when creating the Interpreter

* Create a RunState to indicate whether the current interpreter is recoverable

* Add functionality to resume execution in Interpreter level

* Implement resumable execution in func

* Expose FuncInvocation and ResumableError

* Fix missing docs for FuncInvocation

* Add test for resumable invoke and move external parameter passing to start/resume_invocation

* Add comments why assert is always true

* Add note why value stack is always empty after execution

* Use as_func

* Document `resume_execution` on conditions for `is_resumable` and `resumable_value_type`

* Document conditions where NotResumable and AlreadyStarted error is returned

* Warn user that invoke_resumable is experimental
2018-07-09 19:06:44 +03:00
Sergey Pepyakin df0e3ddd46
Bump version to 0.3.0 (#107) 2018-07-04 12:22:08 +03:00
Sergey Pepyakin dc5052aadb
Export nan_preserving_float (#109) 2018-07-04 12:13:57 +03:00
Sergey Pepyakin 5d99077e17
Bump limits arbitrary (#108) 2018-07-04 11:18:54 +03:00
Sergey Pepyakin f6657bace4
Flat Stack (#98)
* Define Instruction Set.

* WIP

* WIP 2

* Tests

* Working

* Bunch of other tests.

* WIP

* WIP

* Use Vec instead of VecDeque.

* Calibrate the limits.

* Clean

* Clean

* Another round of cleaning.

* Ignore traces.

* Optimize value stack

* Optimize a bit more.

* Cache memory index.

* Inline always instruction dispatch function.

* Comments.

* Clean

* Clean

* Use vector to keep unresolved references.

* Estimate resulting size.

* do refactoring

* Validate the locals count in the begging

* Introduce Keep and DropKeep structs in isa

* Rename/Split Validator into Reader

* Document stack layout

* Remove println!

* Fix typo.

* Use .last / .last_mut in stack

* Update docs for BrTable.

* Review fixes.

* Merge.

* Add an assert that stack is empty after the exec
2018-07-04 10:08:45 +03:00
Sergey Pepyakin 3a96a8399f
Bump version to 0.2.1 (#106) 2018-07-02 13:17:36 +03:00
Wei Tang f91dc92119 Update parity-wasm dependency to 0.31 (#105)
* Update parity-wasm dependency to 0.31
* Fix tests
2018-06-29 14:10:04 +03:00
Sergey Pepyakin 2fb793c8b8
Add hfuzz into repo (#103)
* Add fuzzing against spec interpreter.

* Redirect output of spec to /dev/null

* Also stderr

* Refactor

* Oops. Revert to temp file creation.

* Version of libfuzzer pinned

* Add honggfuzz.

* Impl hfuzz

* Update parity-wasm.

* Update honggfuzz to 0.5.9.

* Update parity-wasm

* Stack hash.

* Update script a bit.

* Unpin parity-wasm version

* Indentation
2018-06-25 17:46:13 +03:00
Leonardo Yvens 9db7896e48 implement from and into RuntimeValue for i8, u8, i16 and u16 (#104)
This works under the assumption that these values
are represented as an `I32` in WASM, which seems
reasonable.
2018-06-25 16:59:30 +03:00
Leonardo Yvens 75406dd8ff Use transmute instead of casts In RuntimeValue conversion. (#102)
Casts have arithmetic semantics, and under some build configurations
Rust will panic when encountering an arithmetic overflow.
Use a transmute instead since it's what we mean.
The previous code worked, but still I added a test for good measure.
2018-06-21 15:47:49 +03:00
Sergey Pepyakin 94b797de44
Add some more benches (#97)
* Add rev_complement test

# Conflicts:
#	benches/src/lib.rs
#	benches/wasm-kernel/src/lib.rs

* Add redux_regex test.

# Conflicts:
#	benches/wasm-kernel/Cargo.toml

* Fmt and return an original header

* Use ManuallyDrop

* Really initialize lazy static.
2018-06-19 16:29:33 +03:00
Sergey Pepyakin f305b3cd1f
Fix wasm benches on nightly (#93) 2018-06-12 14:00:57 +03:00
Sergey Pepyakin 724a32ad60
Provide direct access to the underlying buffer (#91)
This allows zero-copy access to the linear memory.
2018-05-31 16:01:15 +02:00
Sergey Pepyakin 6cf0ebc79e
Add a simple bench (#90)
* Add first bench

* Refactor travis.yml

* Use assert_matches!

* sha3_256 → keccak256
2018-05-24 16:31:15 +03:00
Sergey Pepyakin d926993c6c Bump wabt version to 0.3 2018-04-25 18:00:11 +03:00
Sergey Pepyakin 89c3a9286f Bump version 0.2.0 2018-04-25 10:25:33 +03:00
Jef 3890dd379f Preserve signalling bit in NaNs (#87)
* Preserve signalling bit in NaNs

* Fix warnings
2018-04-25 10:18:14 +03:00
Sergey Pepyakin b95e11c414 Bump version to 0.1.3. 2018-04-20 17:59:16 +03:00
Sergey Pepyakin 730c918a80
Don't expand locals. (#86) 2018-04-20 17:55:07 +03:00
Sergey Pepyakin 5cda9a05da Check the signature of host function. (#84) 2018-04-18 17:44:10 +03:00
Jef 22b260a3b9 Optionally deny floating point operations (#83)
* Optionally deny floating point operations

* Deny floating-point parameters and fix docs/indentation

* Test denial of floating-point parameters
2018-04-18 15:09:09 +03:00
Sergey Pepyakin a2aa3ddb25 Update README.md 2018-04-17 09:41:26 +03:00
Sergey Pepyakin 7aecc55173 Bump version to 0.1.2 2018-04-05 13:12:39 +03:00
Pierre Krieger 4c2995ca98 Fix the name of the 32bits feature (#82)
So that it matches the Cargo.toml
2018-04-04 18:14:43 +03:00
Sergey Pepyakin d12a04f8ff
Publish with externvals (#81)
* Publish with_externvals constructor.
* Add examples to Signature::new.
* Use Iterators for ExternVal imports
2018-03-29 18:43:44 +03:00
NikVolf a6b5574704 bump version 2018-03-24 17:57:28 +03:00
Sergey Pepyakin 86bbd96a33
Require func_type (not func) for func import. (#79) 2018-03-22 17:49:30 +03:00
Sergey Pepyakin 654426b147 Rename feature opt-in-32bit 2018-03-21 11:01:13 +03:00
Sergey Pepyakin 527b9e0cbc Prepare for 0.1.0 (#77) 2018-03-21 10:51:51 +03:00
Sergey Pepyakin 522fa20983
Fix br_if and then tee_local validation. (#76) 2018-03-20 13:09:51 +03:00
Sergey Pepyakin 6253dd6fdf Args refactor (#71)
* Refactor TryInto → FromRuntimeValue.

Replace `TryInto<T, E>` with `FromRuntimeValue`.

The main difference is that `FromRuntimeValue` is implemented for the concrete type of the value we create, rather than on `RuntimeValue`. This makes more sense to me and seems more clear.

The `try_into` method is now implemented on `RuntimeValue` itself.

And finally, `FromRuntimeValue` has been made public.

* Impl AsRef<[RuntimeValue]> for RuntimeArgs

This impl can be used as an escape hatch if the user wants to use the inner slice.

* Little doc fixes for RuntimeArgs.
2018-03-14 01:23:12 +03:00
Sergey Pepyakin 9fa933ccd6
Use f32/f64::from_bits. (#72)
And also enable:

- "conversions",
- "float_exprs",
- "float_literals",
- "float_memory",

tests.
2018-03-12 12:37:43 +01:00
Sergey Pepyakin 0c277abacb
Implement Error for Trap (#73) 2018-03-12 12:37:12 +01:00
Sergey Pepyakin 7c88c6ad65
Fix br_table validation (#67)
Fixes #63

Fix is simple: all labels refered by the br_table instruction must have same value type (or NoResult in case if they don't have result). So we just take the default label and check other labels against it.
2018-02-23 18:00:16 +03:00
Sergey Pepyakin 8a96bc2649
Make it easier to work with spec testsuite (#68)
If module will failed to load, one could just check line number of the original script.
2018-02-23 17:57:35 +03:00
Nikolay Volf 5bb5b6809c Exclude test directory for package (#65)
* Exclude test directory for package

* Update Cargo.toml

* Also exclude fuzz and deexclude non-existant spec
2018-02-23 00:45:27 +03:00
Nikolay Volf f1a3f06d5e
remove stray println (#66) 2018-02-21 23:00:31 +03:00
Nikolay Volf 6b6961bcb6
Bump parity-wasm version (#64) 2018-02-21 18:00:32 +03:00
68 changed files with 13209 additions and 6426 deletions

2
.cargo/config Normal file
View File

@ -0,0 +1,2 @@
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"

View File

@ -1,11 +1,9 @@
root = true root = true
[*] [*]
indent_style=tab indent_style=space
indent_size=tab indent_size = 4
tab_width=4
end_of_line=lf end_of_line=lf
charset=utf-8 charset=utf-8
trim_trailing_whitespace=true trim_trailing_whitespace=true
max_line_length=120 max_line_length=120
insert_final_newline=true insert_final_newline=true

1
.gitignore vendored
View File

@ -3,3 +3,4 @@
**/*.rs.bk **/*.rs.bk
Cargo.lock Cargo.lock
spec/target spec/target
.idea

View File

@ -1,31 +1,36 @@
dist: trusty dist: xenial
sudo: required
language: language:
- rust - rust
- cpp - cpp
addons:
apt: matrix:
sources: fast_finish: true
- ubuntu-toolchain-r-test include:
packages: - rust: nightly
- gcc-6 - rust: stable
- g++-6 - rust: stable
- cmake env: TARGET=armv7-unknown-linux-gnueabihf
env:
- NIGHTLY_TOOLCHAIN=nightly-2018-02-05
install: install:
# Install `cargo-deadlinks` unless it is currently installed. - if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then rustup target add wasm32-unknown-unknown; fi
- command -v cargo-deadlinks &> /dev/null || cargo install cargo-deadlinks - if [ -n "$TARGET" ]; then rustup target add "$TARGET" && sudo apt-get install --yes qemu-user-static; fi
# Install nightly toolchain. - if [ "$TARGET" == "armv7-unknown-linux-gnueabihf" ]; then sudo apt-get install --yes crossbuild-essential-armhf && export QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf; fi
- rustup toolchain install $NIGHTLY_TOOLCHAIN - rustup component add rustfmt
- sudo apt-get install --yes cmake
script: script:
- export CC=/usr/bin/gcc-6 - cargo fmt --all -- --check
- export CXX=/usr/bin/g++-6 # Make sure nightly targets are not broken.
# Make sure fuzz targets are not broken. - if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --tests --manifest-path=fuzz/Cargo.toml; fi
- rustup run $NIGHTLY_TOOLCHAIN cargo check --tests --manifest-path=fuzz/Cargo.toml - if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --benches --manifest-path=benches/Cargo.toml; fi
- ./test.sh # Make sure `no_std` version checks.
- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo +nightly check --no-default-features --features core; fi
# Check that `vec_memory` feature works.
- cargo check --features vec_memory
- travis_wait 60 ./test.sh
- ./doc.sh - ./doc.sh
after_success: | after_success: |
# Build documentation and deploy it to github pages. # Build documentation and deploy it to github pages.
[ $TRAVIS_BRANCH = master ] && [ $TRAVIS_BRANCH = master ] &&
@ -34,7 +39,18 @@ after_success: |
sudo pip install ghp-import && sudo pip install ghp-import &&
ghp-import -n target/doc && ghp-import -n target/doc &&
git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages
cache: cargo
cache:
# Don't use `cache: cargo` since it adds the `target` directory and that can be huge.
# Saving and loading this directory dwarfes actual compilation and test times. But what is more
# important, is that travis timeouts the build since the job doesn't produce any output for more
# than 10 minutes.
#
# So we just cache ~/.cargo directory
directories:
- /home/travis/.cargo
before_cache: before_cache:
# Travis can't cache files that are not readable by "others" # Travis can't cache files that are not readable by "others"
- chmod -R a+r $HOME/.cargo - chmod -R a+r $HOME/.cargo
# According to the Travis CI docs for building Rust project this is done by,
- rm -rf /home/travis/.cargo/registry

View File

@ -1,23 +1,52 @@
[package] [package]
name = "wasmi" name = "wasmi"
version = "0.0.0" version = "0.5.1"
authors = ["Nikolay Volf <nikvolf@gmail.com>", "Svyatoslav Nikolsky <svyatonik@yandex.ru>", "Sergey Pepyakin <s.pepyakin@gmail.com>"] authors = ["Nikolay Volf <nikvolf@gmail.com>", "Svyatoslav Nikolsky <svyatonik@yandex.ru>", "Sergey Pepyakin <s.pepyakin@gmail.com>"]
license = "MIT/Apache-2.0" license = "MIT/Apache-2.0"
readme = "README.md" readme = "README.md"
repository = "https://github.com/paritytech/wasmi"
documentation = "https://paritytech.github.io/wasmi/"
description = "WebAssembly interpreter" description = "WebAssembly interpreter"
keywords = ["wasm", "webassembly", "bytecode", "interpreter"] keywords = ["wasm", "webassembly", "bytecode", "interpreter"]
exclude = [ "res/*", "spec/*" ] exclude = [ "/res/*", "/tests/*", "/fuzz/*", "/benches/*" ]
[dependencies] [dependencies]
parity-wasm = "0.25" wasmi-validation = { version = "0.2", path = "validation", default-features = false }
byteorder = "1.0" parity-wasm = { version = "0.40.1", default-features = false }
memory_units = "0.3.0" memory_units = "0.3.0"
libm = { version = "0.1.2", optional = true }
num-rational = { version = "0.2.2", default-features = false }
num-traits = { version = "0.2.8", default-features = false }
[dev-dependencies] [dev-dependencies]
wabt = "0.2.0" assert_matches = "1.1"
rand = "0.4.2"
wabt = "0.9"
[features] [features]
# 32-bit platforms are not supported and not tested. Use this flag if you really want to use default = ["std"]
# wasmi on these platforms. # Disable for no_std support
# See https://github.com/pepyakin/wasmi/issues/43 std = [
32bit_opt_in = [] "parity-wasm/std",
"wasmi-validation/std",
"num-rational/std",
"num-rational/bigint-std",
"num-traits/std"
]
# Enable for no_std support
core = [
# `core` doesn't support vec_memory
"vec_memory",
"wasmi-validation/core",
"libm"
]
# Enforce using the linear memory implementation based on `Vec` instead of
# mmap on unix systems.
#
# Useful for tests and if you need to minimize unsafe usage at the cost of performance on some
# workloads.
vec_memory = []
[workspace]
members = ["validation"]
exclude = ["benches"]

View File

@ -1,10 +1,43 @@
# wasmi [![crates.io link](https://img.shields.io/crates/v/wasmi.svg)](https://crates.io/crates/wasmi)
[![Build Status](https://travis-ci.org/paritytech/wasmi.svg?branch=master)](https://travis-ci.org/paritytech/wasmi)
WASM interpreter (previously lived in [parity-wasm](https://github.com/paritytech/parity-wasm)) # `wasmi`
Primary purpose of `wasmi` is to be used with [parity](https://github.com/paritytech/parity) (ethereum-like contracts in wasm) and with [Polkadot](https://github.com/paritytech/polkadot). However, `wasmi` is designed to be as flexible as possible and might be suited well for other purposes. `wasmi` - a Wasm interpreter.
At the moment, the API is rather low-level (especially, in the part related to host functions). But some high-level API is on the roadmap. `wasmi` was conceived as a component of [parity-ethereum](https://github.com/paritytech/parity-ethereum) (ethereum-like contracts in wasm) and [substrate](https://github.com/paritytech/substrate). These projects are related to blockchain and require a high degree of correctness, even if that might be over conservative. This specifically means that we are not trying to be involved in any implementation of any of work-in-progress Wasm proposals. We are also trying to be as close as possible to the spec, which means we are trying to avoid features that is not directly supported by the spec. This means that it is flexible on the one hand and on the other hand there shouldn't be a problem migrating to another spec compliant execution engine.
With all that said, `wasmi` should be a good option for initial prototyping.
# Build & Test
As `wasmi` contains a git submodule, you need to use `--recursive` for cloning or to checkout the submodule explicitly, otherwise the testing would fail.
```
git clone https://github.com/paritytech/wasmi.git --recursive
cd wasmi
cargo build
cargo test
```
# `no_std` support
This crate supports `no_std` environments.
Enable the `core` feature and disable default features:
```toml
[dependencies]
wasmi = {
version = "*",
default-features = false,
features = "core"
}
```
The `core` feature requires the `core` and `alloc` libraries and a nightly compiler.
Also, code related to `std::error` is disabled.
Floating point operations in `no_std` use [`libm`](https://crates.io/crates/libm), which sometimes panics in debug mode (https://github.com/japaric/libm/issues/4).
So make sure to either use release builds or avoid WASM with floating point operations, for example by using [`deny_floating_point`](https://docs.rs/wasmi/0.4.0/wasmi/struct.Module.html#method.deny_floating_point).
# License # License

3
benches/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
*.trace

12
benches/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "benches"
version = "0.1.0"
authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
[dependencies]
wasmi = { path = ".." }
assert_matches = "1.2"
wabt = "0.9"
[profile.bench]
debug = true

31
benches/build.rs Normal file
View File

@ -0,0 +1,31 @@
use std::env;
use std::process;
fn main() {
println!("cargo:rerun-if-changed=./wasm-kernel/");
// The CARGO environment variable provides a path to the executable that
// runs this build process.
let cargo_bin = env::var("CARGO").expect("CARGO env variable should be defined");
// Build a release version of wasm-kernel. The code in the output wasm binary
// will be used in benchmarks.
let output = process::Command::new(cargo_bin)
.arg("build")
.arg("--target=wasm32-unknown-unknown")
.arg("--release")
.arg("--manifest-path=./wasm-kernel/Cargo.toml")
.arg("--verbose")
.output()
.expect("failed to execute `cargo`");
if !output.status.success() {
let msg = format!(
"status: {status}\nstdout: {stdout}\nstderr: {stderr}\n",
status = output.status,
stdout = String::from_utf8_lossy(&output.stdout),
stderr = String::from_utf8_lossy(&output.stderr),
);
panic!("{}", msg);
}
}

278
benches/src/lib.rs Normal file
View File

@ -0,0 +1,278 @@
#![feature(test)]
extern crate test;
extern crate wasmi;
#[macro_use]
extern crate assert_matches;
extern crate wabt;
use std::error;
use std::fs::File;
use wasmi::{ImportsBuilder, Module, ModuleInstance, NopExternals, RuntimeValue};
use test::Bencher;
// Load a module from a file.
fn load_from_file(filename: &str) -> Result<Module, Box<dyn error::Error>> {
use std::io::prelude::*;
let mut file = File::open(filename)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
Ok(Module::from_buffer(buf)?)
}
const REVCOMP_INPUT: &'static [u8] = include_bytes!("./revcomp-input.txt");
const REVCOMP_OUTPUT: &'static [u8] = include_bytes!("./revcomp-output.txt");
#[bench]
fn bench_tiny_keccak(b: &mut Bencher) {
let wasm_kernel = load_from_file(
"./wasm-kernel/target/wasm32-unknown-unknown/release/wasm_kernel.wasm",
).expect("failed to load wasm_kernel. Is `build.rs` broken?");
let instance = ModuleInstance::new(&wasm_kernel, &ImportsBuilder::default())
.expect("failed to instantiate wasm module")
.assert_no_start();
let test_data_ptr = assert_matches!(
instance.invoke_export("prepare_tiny_keccak", &[], &mut NopExternals),
Ok(Some(v @ RuntimeValue::I32(_))) => v
);
b.iter(|| {
instance
.invoke_export("bench_tiny_keccak", &[test_data_ptr], &mut NopExternals)
.unwrap();
});
}
#[bench]
fn bench_rev_comp(b: &mut Bencher) {
let wasm_kernel = load_from_file(
"./wasm-kernel/target/wasm32-unknown-unknown/release/wasm_kernel.wasm",
).expect("failed to load wasm_kernel. Is `build.rs` broken?");
let instance = ModuleInstance::new(&wasm_kernel, &ImportsBuilder::default())
.expect("failed to instantiate wasm module")
.assert_no_start();
// Allocate buffers for the input and output.
let test_data_ptr: RuntimeValue = {
let input_size = RuntimeValue::I32(REVCOMP_INPUT.len() as i32);
assert_matches!(
instance.invoke_export("prepare_rev_complement", &[input_size], &mut NopExternals),
Ok(Some(v @ RuntimeValue::I32(_))) => v,
"",
)
};
// Get the pointer to the input buffer.
let input_data_mem_offset = assert_matches!(
instance.invoke_export("rev_complement_input_ptr", &[test_data_ptr], &mut NopExternals),
Ok(Some(RuntimeValue::I32(v))) => v as u32,
"",
);
// Copy test data inside the wasm memory.
let memory = instance.export_by_name("memory")
.expect("Expected export with a name 'memory'")
.as_memory()
.expect("'memory' should be a memory instance")
.clone();
memory
.set(input_data_mem_offset, REVCOMP_INPUT)
.expect("can't load test data into a wasm memory");
b.iter(|| {
instance
.invoke_export("bench_rev_complement", &[test_data_ptr], &mut NopExternals)
.unwrap();
});
// Verify the result.
let output_data_mem_offset = assert_matches!(
instance.invoke_export("rev_complement_output_ptr", &[test_data_ptr], &mut NopExternals),
Ok(Some(RuntimeValue::I32(v))) => v as u32,
"",
);
let result = memory
.get(output_data_mem_offset, REVCOMP_OUTPUT.len())
.expect("can't get result data from a wasm memory");
assert_eq!(&*result, REVCOMP_OUTPUT);
}
#[bench]
fn bench_regex_redux(b: &mut Bencher) {
let wasm_kernel = load_from_file(
"./wasm-kernel/target/wasm32-unknown-unknown/release/wasm_kernel.wasm",
).expect("failed to load wasm_kernel. Is `build.rs` broken?");
let instance = ModuleInstance::new(&wasm_kernel, &ImportsBuilder::default())
.expect("failed to instantiate wasm module")
.assert_no_start();
// Allocate buffers for the input and output.
let test_data_ptr: RuntimeValue = {
let input_size = RuntimeValue::I32(REVCOMP_INPUT.len() as i32);
assert_matches!(
instance.invoke_export("prepare_regex_redux", &[input_size], &mut NopExternals),
Ok(Some(v @ RuntimeValue::I32(_))) => v,
"",
)
};
// Get the pointer to the input buffer.
let input_data_mem_offset = assert_matches!(
instance.invoke_export("regex_redux_input_ptr", &[test_data_ptr], &mut NopExternals),
Ok(Some(RuntimeValue::I32(v))) => v as u32,
"",
);
// Copy test data inside the wasm memory.
let memory = instance.export_by_name("memory")
.expect("Expected export with a name 'memory'")
.as_memory()
.expect("'memory' should be a memory instance")
.clone();
memory
.set(input_data_mem_offset, REVCOMP_INPUT)
.expect("can't load test data into a wasm memory");
b.iter(|| {
instance
.invoke_export("bench_regex_redux", &[test_data_ptr], &mut NopExternals)
.unwrap();
});
}
#[bench]
fn fac_recursive(b: &mut Bencher) {
let wasm = wabt::wat2wasm(
r#"
;; Recursive factorial
(func (export "fac-rec") (param i64) (result i64)
(if (result i64) (i64.eq (get_local 0) (i64.const 0))
(then (i64.const 1))
(else
(i64.mul (get_local 0) (call 0 (i64.sub (get_local 0) (i64.const 1))))
)
)
)
"#
).unwrap();
let module = Module::from_buffer(&wasm).unwrap();
let instance = ModuleInstance::new(&module, &ImportsBuilder::default())
.expect("failed to instantiate wasm module")
.assert_no_start();
b.iter(|| {
let value = instance
.invoke_export("fac-rec", &[RuntimeValue::I64(25)], &mut NopExternals);
assert_matches!(value, Ok(Some(RuntimeValue::I64(7034535277573963776))));
});
}
#[bench]
fn fac_opt(b: &mut Bencher) {
let wasm = wabt::wat2wasm(
r#"
;; Optimized factorial.
(func (export "fac-opt") (param i64) (result i64)
(local i64)
(set_local 1 (i64.const 1))
(block
(br_if 0 (i64.lt_s (get_local 0) (i64.const 2)))
(loop
(set_local 1 (i64.mul (get_local 1) (get_local 0)))
(set_local 0 (i64.add (get_local 0) (i64.const -1)))
(br_if 0 (i64.gt_s (get_local 0) (i64.const 1)))
)
)
(get_local 1)
)
"#
).unwrap();
let module = Module::from_buffer(&wasm).unwrap();
let instance = ModuleInstance::new(&module, &ImportsBuilder::default())
.expect("failed to instantiate wasm module")
.assert_no_start();
b.iter(|| {
let value = instance
.invoke_export("fac-opt", &[RuntimeValue::I64(25)], &mut NopExternals);
assert_matches!(value, Ok(Some(RuntimeValue::I64(7034535277573963776))));
});
}
// This is used for testing overhead of a function call
// is not too large.
#[bench]
fn recursive_ok(b: &mut Bencher) {
let wasm = wabt::wat2wasm(
r#"
(module
(func $call (export "call") (param i32) (result i32)
block (result i32)
get_local 0
get_local 0
i32.eqz
br_if 0
i32.const 1
i32.sub
call $call
end
)
)
"#
).unwrap();
let module = Module::from_buffer(&wasm).unwrap();
let instance = ModuleInstance::new(&module, &ImportsBuilder::default())
.expect("failed to instantiate wasm module")
.assert_no_start();
b.iter(|| {
let value = instance
.invoke_export("call", &[RuntimeValue::I32(8000)], &mut NopExternals);
assert_matches!(value, Ok(Some(RuntimeValue::I32(0))));
});
}
#[bench]
fn recursive_trap(b: &mut Bencher) {
let wasm = wabt::wat2wasm(
r#"
(module
(func $call (export "call") (param i32) (result i32)
block (result i32)
get_local 0
get_local 0
i32.eqz
br_if 0
i32.const 1
i32.sub
call $call
end
unreachable
)
)
"#
).unwrap();
let module = Module::from_buffer(&wasm).unwrap();
let instance = ModuleInstance::new(&module, &ImportsBuilder::default())
.expect("failed to instantiate wasm module")
.assert_no_start();
b.iter(|| {
let value = instance
.invoke_export("call", &[RuntimeValue::I32(1000)], &mut NopExternals);
assert_matches!(value, Err(_));
});
}

View File

@ -0,0 +1,171 @@
>ONE Homo sapiens alu
GGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGA
TCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACT
AAAAATACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAG
GCTGAGGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCG
CCACTGCACTCCAGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAAGGCCGGGCGCGGT
GGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCA
GGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAATACAAAAA
TTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAG
AATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTGCACTCCA
GCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAAGGCCGGGCGCGGTGGCTCACGCCTGT
AATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGTTCGAGACC
AGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAATACAAAAATTAGCCGGGCGTG
GTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAGAATCGCTTGAACC
CGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTGCACTCCAGCCTGGGCGACAG
AGCGAGACTCCGTCTCAAAAAGGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTT
TGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACA
TGGTGAAACCCCGTCTCTACTAAAAATACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCT
GTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGG
TTGCAGTGAGCCGAGATCGCGCCACTGCACTCCAGCCTGGGCGACAGAGCGAGACTCCGT
CTCAAAAAGGCCGGGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGG
CGGGCGGATCACCTGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCG
TCTCTACTAAAAATACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTA
CTCGGGAGGCTGAGGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCG
AGATCGCGCCACTGCACTCCAGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAAGGCCG
GGCGCGGTGGCTCACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACC
TGAGGTCAGGAGTTCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAA
TACAAAAATTAGCCGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGA
GGCAGGAGAATCGCTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACT
GCACTCCAGCCTGGGCGACAGAGCGAGACTCCGTCTCAAAAAGGCCGGGCGCGGTGGCTC
ACGCCTGTAATCCCAGCACTTTGGGAGGCCGAGGCGGGCGGATCACCTGAGGTCAGGAGT
TCGAGACCAGCCTGGCCAACATGGTGAAACCCCGTCTCTACTAAAAATACAAAAATTAGC
CGGGCGTGGTGGCGCGCGCCTGTAATCCCAGCTACTCGGGAGGCTGAGGCAGGAGAATCG
CTTGAACCCGGGAGGCGGAGGTTGCAGTGAGCCGAGATCGCGCCACTGCACTCCAGCCTG
GGCGACAGAGCGAGACTCCG
>TWO IUB ambiguity codes
cttBtatcatatgctaKggNcataaaSatgtaaaDcDRtBggDtctttataattcBgtcg
tactDtDagcctatttSVHtHttKtgtHMaSattgWaHKHttttagacatWatgtRgaaa
NtactMcSMtYtcMgRtacttctWBacgaaatatagScDtttgaagacacatagtVgYgt
cattHWtMMWcStgttaggKtSgaYaaccWStcgBttgcgaMttBYatcWtgacaYcaga
gtaBDtRacttttcWatMttDBcatWtatcttactaBgaYtcttgttttttttYaaScYa
HgtgttNtSatcMtcVaaaStccRcctDaataataStcYtRDSaMtDttgttSagtRRca
tttHatSttMtWgtcgtatSSagactYaaattcaMtWatttaSgYttaRgKaRtccactt
tattRggaMcDaWaWagttttgacatgttctacaaaRaatataataaMttcgDacgaSSt
acaStYRctVaNMtMgtaggcKatcttttattaaaaagVWaHKYagtttttatttaacct
tacgtVtcVaattVMBcttaMtttaStgacttagattWWacVtgWYagWVRctDattBYt
gtttaagaagattattgacVatMaacattVctgtBSgaVtgWWggaKHaatKWcBScSWa
accRVacacaaactaccScattRatatKVtactatatttHttaagtttSKtRtacaaagt
RDttcaaaaWgcacatWaDgtDKacgaacaattacaRNWaatHtttStgttattaaMtgt
tgDcgtMgcatBtgcttcgcgaDWgagctgcgaggggVtaaScNatttacttaatgacag
cccccacatYScaMgtaggtYaNgttctgaMaacNaMRaacaaacaKctacatagYWctg
ttWaaataaaataRattagHacacaagcgKatacBttRttaagtatttccgatctHSaat
actcNttMaagtattMtgRtgaMgcataatHcMtaBSaRattagttgatHtMttaaKagg
YtaaBataSaVatactWtataVWgKgttaaaacagtgcgRatatacatVtHRtVYataSa
KtWaStVcNKHKttactatccctcatgWHatWaRcttactaggatctataDtDHBttata
aaaHgtacVtagaYttYaKcctattcttcttaataNDaaggaaaDYgcggctaaWSctBa
aNtgctggMBaKctaMVKagBaactaWaDaMaccYVtNtaHtVWtKgRtcaaNtYaNacg
gtttNattgVtttctgtBaWgtaattcaagtcaVWtactNggattctttaYtaaagccgc
tcttagHVggaYtgtNcDaVagctctctKgacgtatagYcctRYHDtgBattDaaDgccK
tcHaaStttMcctagtattgcRgWBaVatHaaaataYtgtttagMDMRtaataaggatMt
ttctWgtNtgtgaaaaMaatatRtttMtDgHHtgtcattttcWattRSHcVagaagtacg
ggtaKVattKYagactNaatgtttgKMMgYNtcccgSKttctaStatatNVataYHgtNa
BKRgNacaactgatttcctttaNcgatttctctataScaHtataRagtcRVttacDSDtt
aRtSatacHgtSKacYagttMHtWataggatgactNtatSaNctataVtttRNKtgRacc
tttYtatgttactttttcctttaaacatacaHactMacacggtWataMtBVacRaSaatc
cgtaBVttccagccBcttaRKtgtgcctttttRtgtcagcRttKtaaacKtaaatctcac
aattgcaNtSBaaccgggttattaaBcKatDagttactcttcattVtttHaaggctKKga
tacatcBggScagtVcacattttgaHaDSgHatRMaHWggtatatRgccDttcgtatcga
aacaHtaagttaRatgaVacttagattVKtaaYttaaatcaNatccRttRRaMScNaaaD
gttVHWgtcHaaHgacVaWtgttScactaagSgttatcttagggDtaccagWattWtRtg
ttHWHacgattBtgVcaYatcggttgagKcWtKKcaVtgaYgWctgYggVctgtHgaNcV
taBtWaaYatcDRaaRtSctgaHaYRttagatMatgcatttNattaDttaattgttctaa
ccctcccctagaWBtttHtBccttagaVaatMcBHagaVcWcagBVttcBtaYMccagat
gaaaaHctctaacgttagNWRtcggattNatcRaNHttcagtKttttgWatWttcSaNgg
gaWtactKKMaacatKatacNattgctWtatctaVgagctatgtRaHtYcWcttagccaa
tYttWttaWSSttaHcaaaaagVacVgtaVaRMgattaVcDactttcHHggHRtgNcctt
tYatcatKgctcctctatVcaaaaKaaaagtatatctgMtWtaaaacaStttMtcgactt
taSatcgDataaactaaacaagtaaVctaggaSccaatMVtaaSKNVattttgHccatca
cBVctgcaVatVttRtactgtVcaattHgtaaattaaattttYtatattaaRSgYtgBag
aHSBDgtagcacRHtYcBgtcacttacactaYcgctWtattgSHtSatcataaatataHt
cgtYaaMNgBaatttaRgaMaatatttBtttaaaHHKaatctgatWatYaacttMctctt
ttVctagctDaaagtaVaKaKRtaacBgtatccaaccactHHaagaagaaggaNaaatBW
attccgStaMSaMatBttgcatgRSacgttVVtaaDMtcSgVatWcaSatcttttVatag
ttactttacgatcaccNtaDVgSRcgVcgtgaacgaNtaNatatagtHtMgtHcMtagaa
attBgtataRaaaacaYKgtRccYtatgaagtaataKgtaaMttgaaRVatgcagaKStc
tHNaaatctBBtcttaYaBWHgtVtgacagcaRcataWctcaBcYacYgatDgtDHccta
>THREE Homo sapiens frequency
aacacttcaccaggtatcgtgaaggctcaagattacccagagaacctttgcaatataaga
atatgtatgcagcattaccctaagtaattatattctttttctgactcaaagtgacaagcc
ctagtgtatattaaatcggtatatttgggaaattcctcaaactatcctaatcaggtagcc
atgaaagtgatcaaaaaagttcgtacttataccatacatgaattctggccaagtaaaaaa
tagattgcgcaaaattcgtaccttaagtctctcgccaagatattaggatcctattactca
tatcgtgtttttctttattgccgccatccccggagtatctcacccatccttctcttaaag
gcctaatattacctatgcaaataaacatatattgttgaaaattgagaacctgatcgtgat
tcttatgtgtaccatatgtatagtaatcacgcgactatatagtgctttagtatcgcccgt
gggtgagtgaatattctgggctagcgtgagatagtttcttgtcctaatatttttcagatc
gaatagcttctatttttgtgtttattgacatatgtcgaaactccttactcagtgaaagtc
atgaccagatccacgaacaatcttcggaatcagtctcgttttacggcggaatcttgagtc
taacttatatcccgtcgcttactttctaacaccccttatgtatttttaaaattacgttta
ttcgaacgtacttggcggaagcgttattttttgaagtaagttacattgggcagactcttg
acattttcgatacgactttctttcatccatcacaggactcgttcgtattgatatcagaag
ctcgtgatgattagttgtcttctttaccaatactttgaggcctattctgcgaaatttttg
ttgccctgcgaacttcacataccaaggaacacctcgcaacatgccttcatatccatcgtt
cattgtaattcttacacaatgaatcctaagtaattacatccctgcgtaaaagatggtagg
ggcactgaggatatattaccaagcatttagttatgagtaatcagcaatgtttcttgtatt
aagttctctaaaatagttacatcgtaatgttatctcgggttccgcgaataaacgagatag
attcattatatatggccctaagcaaaaacctcctcgtattctgttggtaattagaatcac
acaatacgggttgagatattaattatttgtagtacgaagagatataaaaagatgaacaat
tactcaagtcaagatgtatacgggatttataataaaaatcgggtagagatctgctttgca
attcagacgtgccactaaatcgtaatatgtcgcgttacatcagaaagggtaactattatt
aattaataaagggcttaatcactacatattagatcttatccgatagtcttatctattcgt
tgtatttttaagcggttctaattcagtcattatatcagtgctccgagttctttattattg
ttttaaggatgacaaaatgcctcttgttataacgctgggagaagcagactaagagtcgga
gcagttggtagaatgaggctgcaaaagacggtctcgacgaatggacagactttactaaac
caatgaaagacagaagtagagcaaagtctgaagtggtatcagcttaattatgacaaccct
taatacttccctttcgccgaatactggcgtggaaaggttttaaaagtcgaagtagttaga
ggcatctctcgctcataaataggtagactactcgcaatccaatgtgactatgtaatactg
ggaacatcagtccgcgatgcagcgtgtttatcaaccgtccccactcgcctggggagacat
gagaccacccccgtggggattattagtccgcagtaatcgactcttgacaatccttttcga
ttatgtcatagcaatttacgacagttcagcgaagtgactactcggcgaaatggtattact
aaagcattcgaacccacatgaatgtgattcttggcaatttctaatccactaaagcttttc
cgttgaatctggttgtagatatttatataagttcactaattaagatcacggtagtatatt
gatagtgatgtctttgcaagaggttggccgaggaatttacggattctctattgatacaat
ttgtctggcttataactcttaaggctgaaccaggcgtttttagacgacttgatcagctgt
tagaatggtttggactccctctttcatgtcagtaacatttcagccgttattgttacgata
tgcttgaacaatattgatctaccacacacccatagtatattttataggtcatgctgttac
ctacgagcatggtattccacttcccattcaatgagtattcaacatcactagcctcagaga
tgatgacccacctctaataacgtcacgttgcggccatgtgaaacctgaacttgagtagac
gatatcaagcgctttaaattgcatataacatttgagggtaaagctaagcggatgctttat
ataatcaatactcaataataagatttgattgcattttagagttatgacacgacatagttc
actaacgagttactattcccagatctagactgaagtactgatcgagacgatccttacgtc
gatgatcgttagttatcgacttaggtcgggtctctagcggtattggtacttaaccggaca
ctatactaataacccatgatcaaagcataacagaatacagacgataatttcgccaacata
tatgtacagaccccaagcatgagaagctcattgaaagctatcattgaagtcccgctcaca
atgtgtcttttccagacggtttaactggttcccgggagtcctggagtttcgacttacata
aatggaaacaatgtattttgctaatttatctatagcgtcatttggaccaatacagaatat
tatgttgcctagtaatccactataacccgcaagtgctgatagaaaatttttagacgattt
ataaatgccccaagtatccctcccgtgaatcctccgttatactaattagtattcgttcat
acgtataccgcgcatatatgaacatttggcgataaggcgcgtgaattgttacgtgacaga
gatagcagtttcttgtgatatggttaacagacgtacatgaagggaaactttatatctata
gtgatgcttccgtagaaataccgccactggtctgccaatgatgaagtatgtagctttagg
tttgtactatgaggctttcgtttgtttgcagagtataacagttgcgagtgaaaaaccgac
gaatttatactaatacgctttcactattggctacaaaatagggaagagtttcaatcatga
gagggagtatatggatgctttgtagctaaaggtagaacgtatgtatatgctgccgttcat
tcttgaaagatacataagcgataagttacgacaattataagcaacatccctaccttcgta
acgatttcactgttactgcgcttgaaatacactatggggctattggcggagagaagcaga
tcgcgccgagcatatacgagacctataatgttgatgatagagaaggcgtctgaattgata
catcgaagtacactttctttcgtagtatctctcgtcctctttctatctccggacacaaga
attaagttatatatatagagtcttaccaatcatgttgaatcctgattctcagagttcttt
ggcgggccttgtgatgactgagaaacaatgcaatattgctccaaatttcctaagcaaatt
ctcggttatgttatgttatcagcaaagcgttacgttatgttatttaaatctggaatgacg
gagcgaagttcttatgtcggtgtgggaataattcttttgaagacagcactccttaaataa
tatcgctccgtgtttgtatttatcgaatgggtctgtaaccttgcacaagcaaatcggtgg
tgtatatatcggataacaattaatacgatgttcatagtgacagtatactgatcgagtcct
ctaaagtcaattacctcacttaacaatctcattgatgttgtgtcattcccggtatcgccc
gtagtatgtgctctgattgaccgagtgtgaaccaaggaacatctactaatgcctttgtta
ggtaagatctctctgaattccttcgtgccaacttaaaacattatcaaaatttcttctact
tggattaactacttttacgagcatggcaaattcccctgtggaagacggttcattattatc
ggaaaccttatagaaattgcgtgttgactgaaattagatttttattgtaagagttgcatc
tttgcgattcctctggtctagcttccaatgaacagtcctcccttctattcgacatcgggt
ccttcgtacatgtctttgcgatgtaataattaggttcggagtgtggccttaatgggtgca
actaggaatacaacgcaaatttgctgacatgatagcaaatcggtatgccggcaccaaaac
gtgctccttgcttagcttgtgaatgagactcagtagttaaataaatccatatctgcaatc
gattccacaggtattgtccactatctttgaactactctaagagatacaagcttagctgag
accgaggtgtatatgactacgctgatatctgtaaggtaccaatgcaggcaaagtatgcga
gaagctaataccggctgtttccagctttataagattaaaatttggctgtcctggcggcct
cagaattgttctatcgtaatcagttggttcattaattagctaagtacgaggtacaactta
tctgtcccagaacagctccacaagtttttttacagccgaaacccctgtgtgaatcttaat
atccaagcgcgttatctgattagagtttacaactcagtattttatcagtacgttttgttt
ccaacattacccggtatgacaaaatgacgccacgtgtcgaataatggtctgaccaatgta
ggaagtgaaaagataaatat

View File

@ -0,0 +1,171 @@
>ONE Homo sapiens alu
CGGAGTCTCGCTCTGTCGCCCAGGCTGGAGTGCAGTGGCGCGATCTCGGCTCACTGCAAC
CTCCGCCTCCCGGGTTCAAGCGATTCTCCTGCCTCAGCCTCCCGAGTAGCTGGGATTACA
GGCGCGCGCCACCACGCCCGGCTAATTTTTGTATTTTTAGTAGAGACGGGGTTTCACCAT
GTTGGCCAGGCTGGTCTCGAACTCCTGACCTCAGGTGATCCGCCCGCCTCGGCCTCCCAA
AGTGCTGGGATTACAGGCGTGAGCCACCGCGCCCGGCCTTTTTGAGACGGAGTCTCGCTC
TGTCGCCCAGGCTGGAGTGCAGTGGCGCGATCTCGGCTCACTGCAACCTCCGCCTCCCGG
GTTCAAGCGATTCTCCTGCCTCAGCCTCCCGAGTAGCTGGGATTACAGGCGCGCGCCACC
ACGCCCGGCTAATTTTTGTATTTTTAGTAGAGACGGGGTTTCACCATGTTGGCCAGGCTG
GTCTCGAACTCCTGACCTCAGGTGATCCGCCCGCCTCGGCCTCCCAAAGTGCTGGGATTA
CAGGCGTGAGCCACCGCGCCCGGCCTTTTTGAGACGGAGTCTCGCTCTGTCGCCCAGGCT
GGAGTGCAGTGGCGCGATCTCGGCTCACTGCAACCTCCGCCTCCCGGGTTCAAGCGATTC
TCCTGCCTCAGCCTCCCGAGTAGCTGGGATTACAGGCGCGCGCCACCACGCCCGGCTAAT
TTTTGTATTTTTAGTAGAGACGGGGTTTCACCATGTTGGCCAGGCTGGTCTCGAACTCCT
GACCTCAGGTGATCCGCCCGCCTCGGCCTCCCAAAGTGCTGGGATTACAGGCGTGAGCCA
CCGCGCCCGGCCTTTTTGAGACGGAGTCTCGCTCTGTCGCCCAGGCTGGAGTGCAGTGGC
GCGATCTCGGCTCACTGCAACCTCCGCCTCCCGGGTTCAAGCGATTCTCCTGCCTCAGCC
TCCCGAGTAGCTGGGATTACAGGCGCGCGCCACCACGCCCGGCTAATTTTTGTATTTTTA
GTAGAGACGGGGTTTCACCATGTTGGCCAGGCTGGTCTCGAACTCCTGACCTCAGGTGAT
CCGCCCGCCTCGGCCTCCCAAAGTGCTGGGATTACAGGCGTGAGCCACCGCGCCCGGCCT
TTTTGAGACGGAGTCTCGCTCTGTCGCCCAGGCTGGAGTGCAGTGGCGCGATCTCGGCTC
ACTGCAACCTCCGCCTCCCGGGTTCAAGCGATTCTCCTGCCTCAGCCTCCCGAGTAGCTG
GGATTACAGGCGCGCGCCACCACGCCCGGCTAATTTTTGTATTTTTAGTAGAGACGGGGT
TTCACCATGTTGGCCAGGCTGGTCTCGAACTCCTGACCTCAGGTGATCCGCCCGCCTCGG
CCTCCCAAAGTGCTGGGATTACAGGCGTGAGCCACCGCGCCCGGCCTTTTTGAGACGGAG
TCTCGCTCTGTCGCCCAGGCTGGAGTGCAGTGGCGCGATCTCGGCTCACTGCAACCTCCG
CCTCCCGGGTTCAAGCGATTCTCCTGCCTCAGCCTCCCGAGTAGCTGGGATTACAGGCGC
GCGCCACCACGCCCGGCTAATTTTTGTATTTTTAGTAGAGACGGGGTTTCACCATGTTGG
CCAGGCTGGTCTCGAACTCCTGACCTCAGGTGATCCGCCCGCCTCGGCCTCCCAAAGTGC
TGGGATTACAGGCGTGAGCCACCGCGCCCGGCCTTTTTGAGACGGAGTCTCGCTCTGTCG
CCCAGGCTGGAGTGCAGTGGCGCGATCTCGGCTCACTGCAACCTCCGCCTCCCGGGTTCA
AGCGATTCTCCTGCCTCAGCCTCCCGAGTAGCTGGGATTACAGGCGCGCGCCACCACGCC
CGGCTAATTTTTGTATTTTTAGTAGAGACGGGGTTTCACCATGTTGGCCAGGCTGGTCTC
GAACTCCTGACCTCAGGTGATCCGCCCGCCTCGGCCTCCCAAAGTGCTGGGATTACAGGC
GTGAGCCACCGCGCCCGGCC
>TWO IUB ambiguity codes
TAGGDHACHATCRGTRGVTGAGWTATGYTGCTGTCABACDWVTRTAAGAVVAGATTTNDA
GASMTCTGCATBYTTCAAKTTACMTATTACTTCATARGGYACMRTGTTTTYTATACVAAT
TTCTAKGDACKADACTATATNTANTCGTTCACGBCGYSCBHTANGGTGATCGTAAAGTAA
CTATBAAAAGATSTGWATBCSGAKHTTABBAACGTSYCATGCAAVATKTSKTASCGGAAT
WVATTTNTCCTTCTTCTTDDAGTGGTTGGATACVGTTAYMTMTBTACTTTHAGCTAGBAA
AAGAGKAAGTTRATWATCAGATTMDDTTTAAAVAAATATTKTCYTAAATTVCNKTTRACG
ADTATATTTATGATSADSCAATAWAGCGRTAGTGTAAGTGACVGRADYGTGCTACHVSDT
CTVCARCSYTTAATATARAAAATTTAATTTACDAATTGBACAGTAYAABATBTGCAGBVG
TGATGGDCAAAATBNMSTTABKATTGGSTCCTAGBTTACTTGTTTAGTTTATHCGATSTA
AAGTCGAKAAASTGTTTTAWAKCAGATATACTTTTMTTTTGBATAGAGGAGCMATGATRA
AAGGNCAYDCCDDGAAAGTHGBTAATCKYTBTACBGTBCTTTTTGDTAASSWTAAWAARA
TTGGCTAAGWGRADTYACATAGCTCBTAGATAWAGCAATNGTATMATGTTKMMAGTAWTC
CCNTSGAAWATWCAAAAMACTGAADNTYGATNAATCCGAYWNCTAACGTTAGAGDTTTTC
ATCTGGKRTAVGAABVCTGWGBTCTDVGKATTBTCTAAGGVADAAAVWTCTAGGGGAGGG
TTAGAACAATTAAHTAATNAAATGCATKATCTAAYRTDTCAGSAYTTYHGATRTTWAVTA
BGNTCDACAGBCCRCAGWCRTCABTGMMAWGMCTCAACCGATRTGBCAVAATCGTDWDAA
CAYAWAATWCTGGTAHCCCTAAGATAACSCTTAGTGSAACAWTBGTCDTTDGACWDBAAC
HTTTNGSKTYYAAYGGATNTGATTTAARTTAMBAATCTAAGTBTCATYTAACTTADTGTT
TCGATACGAAHGGCYATATACCWDTKYATDCSHTDTCAAAATGTGBACTGSCCVGATGTA
TCMMAGCCTTDAAABAATGAAGAGTAACTHATMGVTTAATAACCCGGTTVSANTGCAATT
GTGAGATTTAMGTTTAMAAYGCTGACAYAAAAAGGCACAMYTAAGVGGCTGGAABVTACG
GATTSTYGTBVAKTATWACCGTGTKAGTDTGTATGTTTAAAGGAAAAAGTAACATARAAA
GGTYCAMNYAAABTATAGNTSATANAGTCATCCTATWADKAACTRGTMSACDGTATSAYT
AAHSHGTAABYGACTYTATADTGSTATAGAGAAATCGNTAAAGGAAATCAGTTGTNCYMV
TNACDRTATBNATATASTAGAAMSCGGGANRCKKMCAAACATTNAGTCTRMAATBMTACC
CGTACTTCTBGDSYAATWGAAAATGACADDCHAKAAAYATATTKTTTTCACANACWAGAA
AKATCCTTATTAYKHKCTAAACARTATTTTDATBTVWCYGCAATACTAGGKAAASTTDGA
MGGCHTTHAATVCAHDRYAGGRCTATACGTCMAGAGAGCTBTHGNACARTCCBDCTAAGA
GCGGCTTTARTAAAGAATCCNAGTAWBTGACTTGAATTACWTVACAGAAABCAATNAAAC
CGTNTRANTTGAYCMAWBADTANABRGGTKTHTWTAGTTVCTMBKTAGMTVKCCAGCANT
TVAGSWTTAGCCGCRHTTTCCTTHNTATTAAGAAGAATAGGMTRAARTCTABGTACDTTT
TATAAVDHAHTATAGATCCTAGTAAGYTWATDWCATGAGGGATAGTAAMDMNGBASTWAM
TSTATRBAYDABATGTATATYCGCACTGTTTTAACMCWBTATAWAGTATBTSTATVTTAR
CCTMTTAAKADATCAACTAATYTSVTAKGDATTATGCKTCAYCAKAATACTTKAANGAGT
ATTSDAGATCGGAAATACTTAAYAAVGTATMCGCTTGTGTDCTAATYTATTTTATTTWAA
CAGWRCTATGTAGMTGTTTGTTYKTNGTTKTCAGAACNTRACCTACKTGSRATGTGGGGG
CTGTCATTAAGTAAATNGSTTABCCCCTCGCAGCTCWHTCGCGAAGCAVATGCKACGHCA
ACAKTTAATAACASAAADATTWNYTGTAATTGTTCGTMHACHTWATGTGCWTTTTGAAHY
ACTTTGTAYAMSAAACTTAADAAATATAGTABMATATYAATGSGGTAGTTTGTGTBYGGT
TWSGSVGWMATTDMTCCWWCABTCSVACAGBAATGTTKATBGTCAATAATCTTCTTAAAC
ARVAATHAGYBWCTRWCABGTWWAATCTAAGTCASTAAAKTAAGVKBAATTBGABACGTA
AGGTTAAATAAAAACTRMDTWBCTTTTTAATAAAAGATMGCCTACKAKNTBAGYRASTGT
ASSTCGTHCGAAKTTATTATATTYTTTGTAGAACATGTCAAAACTWTWTHGKTCCYAATA
AAGTGGAYTMCYTAARCSTAAATWAKTGAATTTRAGTCTSSATACGACWAKAASATDAAA
TGYYACTSAACAAHAKTSHYARGASTATTATTHAGGYGGASTTTBGAKGATSANAACACD
TRGSTTRAAAAAAAACAAGARTCVTAGTAAGATAWATGVHAAKATWGAAAAGTYAHVTAC
TCTGRTGTCAWGATRVAAKTCGCAAVCGASWGGTTRTCSAMCCTAACASGWKKAWDAATG
ACRCBACTATGTGTCTTCAAAHGSCTATATTTCGTVWAGAAGTAYCKGARAKSGKAGTAN
TTTCYACATWATGTCTAAAADMDTWCAATSTKDACAMAADADBSAAATAGGCTHAHAGTA
CGACVGAATTATAAAGAHCCVAYHGHTTTACATSTTTATGNCCMTAGCATATGATAVAAG
>THREE Homo sapiens frequency
ATATTTATCTTTTCACTTCCTACATTGGTCAGACCATTATTCGACACGTGGCGTCATTTT
GTCATACCGGGTAATGTTGGAAACAAAACGTACTGATAAAATACTGAGTTGTAAACTCTA
ATCAGATAACGCGCTTGGATATTAAGATTCACACAGGGGTTTCGGCTGTAAAAAAACTTG
TGGAGCTGTTCTGGGACAGATAAGTTGTACCTCGTACTTAGCTAATTAATGAACCAACTG
ATTACGATAGAACAATTCTGAGGCCGCCAGGACAGCCAAATTTTAATCTTATAAAGCTGG
AAACAGCCGGTATTAGCTTCTCGCATACTTTGCCTGCATTGGTACCTTACAGATATCAGC
GTAGTCATATACACCTCGGTCTCAGCTAAGCTTGTATCTCTTAGAGTAGTTCAAAGATAG
TGGACAATACCTGTGGAATCGATTGCAGATATGGATTTATTTAACTACTGAGTCTCATTC
ACAAGCTAAGCAAGGAGCACGTTTTGGTGCCGGCATACCGATTTGCTATCATGTCAGCAA
ATTTGCGTTGTATTCCTAGTTGCACCCATTAAGGCCACACTCCGAACCTAATTATTACAT
CGCAAAGACATGTACGAAGGACCCGATGTCGAATAGAAGGGAGGACTGTTCATTGGAAGC
TAGACCAGAGGAATCGCAAAGATGCAACTCTTACAATAAAAATCTAATTTCAGTCAACAC
GCAATTTCTATAAGGTTTCCGATAATAATGAACCGTCTTCCACAGGGGAATTTGCCATGC
TCGTAAAAGTAGTTAATCCAAGTAGAAGAAATTTTGATAATGTTTTAAGTTGGCACGAAG
GAATTCAGAGAGATCTTACCTAACAAAGGCATTAGTAGATGTTCCTTGGTTCACACTCGG
TCAATCAGAGCACATACTACGGGCGATACCGGGAATGACACAACATCAATGAGATTGTTA
AGTGAGGTAATTGACTTTAGAGGACTCGATCAGTATACTGTCACTATGAACATCGTATTA
ATTGTTATCCGATATATACACCACCGATTTGCTTGTGCAAGGTTACAGACCCATTCGATA
AATACAAACACGGAGCGATATTATTTAAGGAGTGCTGTCTTCAAAAGAATTATTCCCACA
CCGACATAAGAACTTCGCTCCGTCATTCCAGATTTAAATAACATAACGTAACGCTTTGCT
GATAACATAACATAACCGAGAATTTGCTTAGGAAATTTGGAGCAATATTGCATTGTTTCT
CAGTCATCACAAGGCCCGCCAAAGAACTCTGAGAATCAGGATTCAACATGATTGGTAAGA
CTCTATATATATAACTTAATTCTTGTGTCCGGAGATAGAAAGAGGACGAGAGATACTACG
AAAGAAAGTGTACTTCGATGTATCAATTCAGACGCCTTCTCTATCATCAACATTATAGGT
CTCGTATATGCTCGGCGCGATCTGCTTCTCTCCGCCAATAGCCCCATAGTGTATTTCAAG
CGCAGTAACAGTGAAATCGTTACGAAGGTAGGGATGTTGCTTATAATTGTCGTAACTTAT
CGCTTATGTATCTTTCAAGAATGAACGGCAGCATATACATACGTTCTACCTTTAGCTACA
AAGCATCCATATACTCCCTCTCATGATTGAAACTCTTCCCTATTTTGTAGCCAATAGTGA
AAGCGTATTAGTATAAATTCGTCGGTTTTTCACTCGCAACTGTTATACTCTGCAAACAAA
CGAAAGCCTCATAGTACAAACCTAAAGCTACATACTTCATCATTGGCAGACCAGTGGCGG
TATTTCTACGGAAGCATCACTATAGATATAAAGTTTCCCTTCATGTACGTCTGTTAACCA
TATCACAAGAAACTGCTATCTCTGTCACGTAACAATTCACGCGCCTTATCGCCAAATGTT
CATATATGCGCGGTATACGTATGAACGAATACTAATTAGTATAACGGAGGATTCACGGGA
GGGATACTTGGGGCATTTATAAATCGTCTAAAAATTTTCTATCAGCACTTGCGGGTTATA
GTGGATTACTAGGCAACATAATATTCTGTATTGGTCCAAATGACGCTATAGATAAATTAG
CAAAATACATTGTTTCCATTTATGTAAGTCGAAACTCCAGGACTCCCGGGAACCAGTTAA
ACCGTCTGGAAAAGACACATTGTGAGCGGGACTTCAATGATAGCTTTCAATGAGCTTCTC
ATGCTTGGGGTCTGTACATATATGTTGGCGAAATTATCGTCTGTATTCTGTTATGCTTTG
ATCATGGGTTATTAGTATAGTGTCCGGTTAAGTACCAATACCGCTAGAGACCCGACCTAA
GTCGATAACTAACGATCATCGACGTAAGGATCGTCTCGATCAGTACTTCAGTCTAGATCT
GGGAATAGTAACTCGTTAGTGAACTATGTCGTGTCATAACTCTAAAATGCAATCAAATCT
TATTATTGAGTATTGATTATATAAAGCATCCGCTTAGCTTTACCCTCAAATGTTATATGC
AATTTAAAGCGCTTGATATCGTCTACTCAAGTTCAGGTTTCACATGGCCGCAACGTGACG
TTATTAGAGGTGGGTCATCATCTCTGAGGCTAGTGATGTTGAATACTCATTGAATGGGAA
GTGGAATACCATGCTCGTAGGTAACAGCATGACCTATAAAATATACTATGGGTGTGTGGT
AGATCAATATTGTTCAAGCATATCGTAACAATAACGGCTGAAATGTTACTGACATGAAAG
AGGGAGTCCAAACCATTCTAACAGCTGATCAAGTCGTCTAAAAACGCCTGGTTCAGCCTT
AAGAGTTATAAGCCAGACAAATTGTATCAATAGAGAATCCGTAAATTCCTCGGCCAACCT
CTTGCAAAGACATCACTATCAATATACTACCGTGATCTTAATTAGTGAACTTATATAAAT
ATCTACAACCAGATTCAACGGAAAAGCTTTAGTGGATTAGAAATTGCCAAGAATCACATT
CATGTGGGTTCGAATGCTTTAGTAATACCATTTCGCCGAGTAGTCACTTCGCTGAACTGT
CGTAAATTGCTATGACATAATCGAAAAGGATTGTCAAGAGTCGATTACTGCGGACTAATA
ATCCCCACGGGGGTGGTCTCATGTCTCCCCAGGCGAGTGGGGACGGTTGATAAACACGCT
GCATCGCGGACTGATGTTCCCAGTATTACATAGTCACATTGGATTGCGAGTAGTCTACCT
ATTTATGAGCGAGAGATGCCTCTAACTACTTCGACTTTTAAAACCTTTCCACGCCAGTAT
TCGGCGAAAGGGAAGTATTAAGGGTTGTCATAATTAAGCTGATACCACTTCAGACTTTGC
TCTACTTCTGTCTTTCATTGGTTTAGTAAAGTCTGTCCATTCGTCGAGACCGTCTTTTGC
AGCCTCATTCTACCAACTGCTCCGACTCTTAGTCTGCTTCTCCCAGCGTTATAACAAGAG
GCATTTTGTCATCCTTAAAACAATAATAAAGAACTCGGAGCACTGATATAATGACTGAAT
TAGAACCGCTTAAAAATACAACGAATAGATAAGACTATCGGATAAGATCTAATATGTAGT
GATTAAGCCCTTTATTAATTAATAATAGTTACCCTTTCTGATGTAACGCGACATATTACG
ATTTAGTGGCACGTCTGAATTGCAAAGCAGATCTCTACCCGATTTTTATTATAAATCCCG
TATACATCTTGACTTGAGTAATTGTTCATCTTTTTATATCTCTTCGTACTACAAATAATT
AATATCTCAACCCGTATTGTGTGATTCTAATTACCAACAGAATACGAGGAGGTTTTTGCT
TAGGGCCATATATAATGAATCTATCTCGTTTATTCGCGGAACCCGAGATAACATTACGAT
GTAACTATTTTAGAGAACTTAATACAAGAAACATTGCTGATTACTCATAACTAAATGCTT
GGTAATATATCCTCAGTGCCCCTACCATCTTTTACGCAGGGATGTAATTACTTAGGATTC
ATTGTGTAAGAATTACAATGAACGATGGATATGAAGGCATGTTGCGAGGTGTTCCTTGGT
ATGTGAAGTTCGCAGGGCAACAAAAATTTCGCAGAATAGGCCTCAAAGTATTGGTAAAGA
AGACAACTAATCATCACGAGCTTCTGATATCAATACGAACGAGTCCTGTGATGGATGAAA
GAAAGTCGTATCGAAAATGTCAAGAGTCTGCCCAATGTAACTTACTTCAAAAAATAACGC
TTCCGCCAAGTACGTTCGAATAAACGTAATTTTAAAAATACATAAGGGGTGTTAGAAAGT
AAGCGACGGGATATAAGTTAGACTCAAGATTCCGCCGTAAAACGAGACTGATTCCGAAGA
TTGTTCGTGGATCTGGTCATGACTTTCACTGAGTAAGGAGTTTCGACATATGTCAATAAA
CACAAAAATAGAAGCTATTCGATCTGAAAAATATTAGGACAAGAAACTATCTCACGCTAG
CCCAGAATATTCACTCACCCACGGGCGATACTAAAGCACTATATAGTCGCGTGATTACTA
TACATATGGTACACATAAGAATCACGATCAGGTTCTCAATTTTCAACAATATATGTTTAT
TTGCATAGGTAATATTAGGCCTTTAAGAGAAGGATGGGTGAGATACTCCGGGGATGGCGG
CAATAAAGAAAAACACGATATGAGTAATAGGATCCTAATATCTTGGCGAGAGACTTAAGG
TACGAATTTTGCGCAATCTATTTTTTACTTGGCCAGAATTCATGTATGGTATAAGTACGA
ACTTTTTTGATCACTTTCATGGCTACCTGATTAGGATAGTTTGAGGAATTTCCCAAATAT
ACCGATTTAATATACACTAGGGCTTGTCACTTTGAGTCAGAAAAAGAATATAATTACTTA
GGGTAATGCTGCATACATATTCTTATATTGCAAAGGTTCTCTGGGTAATCTTGAGCCTTC
ACGATACCTGGTGAAGTGTT

1
benches/wasm-kernel/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

View File

@ -0,0 +1,17 @@
[package]
name = "wasm-kernel"
version = "0.1.0"
authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
[lib]
crate-type = ["cdylib"]
[dependencies]
tiny-keccak = "1.4.2"
regex = "0.2.10"
lazy_static = "1.0"
[profile.release]
panic = "abort"
lto = true
opt-level = "z"

View File

@ -0,0 +1,123 @@
extern crate tiny_keccak;
extern crate regex;
#[macro_use]
extern crate lazy_static;
use std::mem::ManuallyDrop;
use tiny_keccak::Keccak;
mod rev_complement;
mod regex_redux;
pub struct TinyKeccakTestData {
data: &'static [u8],
result: &'static mut [u8],
}
#[no_mangle]
pub extern "C" fn prepare_tiny_keccak() -> *const TinyKeccakTestData {
static DATA: [u8; 4096] = [254u8; 4096];
static mut RESULT: [u8; 32] = [0u8; 32];
static mut TEST_DATA: Option<TinyKeccakTestData> = None;
unsafe {
if let None = TEST_DATA {
TEST_DATA = Some(TinyKeccakTestData {
data: &DATA,
result: &mut RESULT,
});
}
TEST_DATA.as_ref().unwrap() as *const TinyKeccakTestData
}
}
#[no_mangle]
pub extern "C" fn bench_tiny_keccak(test_data: *mut TinyKeccakTestData) {
unsafe {
let mut keccak = Keccak::new_keccak256();
keccak.update((*test_data).data);
keccak.finalize((*test_data).result);
}
}
pub struct RevComplementTestData {
input: ManuallyDrop<Box<[u8]>>,
output: ManuallyDrop<Box<[u8]>>,
}
#[no_mangle]
pub extern "C" fn prepare_rev_complement(size: usize) -> *mut RevComplementTestData {
let input = vec![0; size];
let output = vec![0; size];
let test_data = Box::new(
RevComplementTestData {
input: ManuallyDrop::new(input.into_boxed_slice()),
output: ManuallyDrop::new(output.into_boxed_slice()),
}
);
// Basically leak the pointer to the test data. This shouldn't be harmful since `prepare` is called
// only once per bench run (not for the iteration), and afterwards whole memory instance is discarded.
Box::into_raw(test_data)
}
#[no_mangle]
pub extern "C" fn rev_complement_input_ptr(test_data: *mut RevComplementTestData) -> *mut u8 {
unsafe {
(*test_data).input.as_mut_ptr()
}
}
#[no_mangle]
pub extern "C" fn rev_complement_output_ptr(test_data: *mut RevComplementTestData) -> *const u8 {
unsafe {
(*test_data).output.as_ptr()
}
}
#[no_mangle]
pub extern "C" fn bench_rev_complement(test_data: *mut RevComplementTestData) {
unsafe {
let result = rev_complement::run(&*(*test_data).input);
(*test_data).output.copy_from_slice(&result);
}
}
pub struct RegexReduxTestData {
input: ManuallyDrop<Box<[u8]>>,
output: Option<usize>,
}
#[no_mangle]
pub extern "C" fn prepare_regex_redux(size: usize) -> *mut RegexReduxTestData {
regex_redux::prepare();
let input = vec![0; size];
let test_data = Box::new(
RegexReduxTestData {
input: ManuallyDrop::new(input.into_boxed_slice()),
output: None,
}
);
// Basically leak the pointer to the test data. This shouldn't be harmful since `prepare` is called
// only once per bench run (not for the iteration), and afterwards whole memory instance is discarded.
Box::into_raw(test_data)
}
#[no_mangle]
pub extern "C" fn regex_redux_input_ptr(test_data: *mut RegexReduxTestData) -> *mut u8 {
unsafe {
(*test_data).input.as_mut_ptr()
}
}
#[no_mangle]
pub extern "C" fn bench_regex_redux(test_data: *mut RegexReduxTestData) {
unsafe {
let result = regex_redux::run(&*(*test_data).input);
(*test_data).output = Some(result);
}
}

View File

@ -0,0 +1,17 @@
//! Initially it supposed to be like [1]. However it turned out
//! that executing this code in wasmi way too slow.
//!
//! [1]: https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/regexredux-rust-2.html
lazy_static! {
static ref REGEX: ::regex::bytes::Regex =
{ ::regex::bytes::Regex::new("agggtaa[cgt]|[acg]ttaccct").unwrap() };
}
pub fn prepare() {
::lazy_static::initialize(&REGEX);
}
pub fn run(seq: &[u8]) -> usize {
REGEX.find_iter(seq).count()
}

View File

@ -0,0 +1,164 @@
// Adapted version from benchmarks game. In particular
// rayon is removed.
//
// https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/revcomp-rust-3.html
// The Computer Language Benchmarks Game
// https://salsa.debian.org/benchmarksgame-team/benchmarksgame/
//
// contributed by the Rust Project Developers
// contributed by Cristi Cobzarenco
// contributed by TeXitoi
// contributed by Matt Brubeck
use std::io::BufRead;
use std::mem::replace;
use std::{cmp, io};
/// Lookup table to find the complement of a single FASTA code.
fn build_table() -> [u8; 256] {
let mut table = [0; 256];
for (i, x) in table.iter_mut().enumerate() {
*x = match i as u8 as char {
'A' | 'a' => 'T',
'C' | 'c' => 'G',
'G' | 'g' => 'C',
'T' | 't' => 'A',
'U' | 'u' => 'A',
'M' | 'm' => 'K',
'R' | 'r' => 'Y',
'W' | 'w' => 'W',
'S' | 's' => 'S',
'Y' | 'y' => 'R',
'K' | 'k' => 'M',
'V' | 'v' => 'B',
'H' | 'h' => 'D',
'D' | 'd' => 'H',
'B' | 'b' => 'V',
'N' | 'n' => 'N',
i => i,
} as u8;
}
table
}
/// Utilities for splitting chunks off of slices.
trait SplitOff {
fn split_off_left(&mut self, n: usize) -> Self;
fn split_off_right(&mut self, n: usize) -> Self;
}
impl<'a, T> SplitOff for &'a mut [T] {
/// Split the left `n` items from self and return them as a separate slice.
fn split_off_left(&mut self, n: usize) -> Self {
let n = cmp::min(self.len(), n);
let data = replace(self, &mut []);
let (left, data) = data.split_at_mut(n);
*self = data;
left
}
/// Split the right `n` items from self and return them as a separate slice.
fn split_off_right(&mut self, n: usize) -> Self {
let len = self.len();
let n = cmp::min(len, n);
let data = replace(self, &mut []);
let (data, right) = data.split_at_mut(len - n);
*self = data;
right
}
}
/// Length of a normal line including the terminating \n.
const LINE_LEN: usize = 61;
/// Compute the reverse complement for two contiguous chunks without line breaks.
fn reverse_chunks(left: &mut [u8], right: &mut [u8], table: &[u8; 256]) {
for (x, y) in left.iter_mut().zip(right.iter_mut().rev()) {
*y = table[replace(x, table[*y as usize]) as usize];
}
}
/// Compute the reverse complement on chunks from opposite ends of a sequence.
///
/// `left` must start at the beginning of a line. If there are an odd number of
/// bytes, `right` will initially be 1 byte longer than `left`; otherwise they
/// will have equal lengths.
fn reverse_complement_left_right(
mut left: &mut [u8],
mut right: &mut [u8],
trailing_len: usize,
table: &[u8; 256],
) {
// Each iteration swaps one line from the start of the sequence with one
// from the end.
while left.len() > 0 || right.len() > 0 {
// Get the chunk up to the newline in `right`.
let mut a = left.split_off_left(trailing_len);
let mut b = right.split_off_right(trailing_len);
right.split_off_right(1); // Skip the newline in `right`.
// If we've reached the middle of the sequence here and there is an
// odd number of bytes remaining, the odd one will be on the right.
if b.len() > a.len() {
let mid = b.split_off_left(1);
mid[0] = table[mid[0] as usize];
}
reverse_chunks(a, b, table);
// Get the chunk up to the newline in `left`.
let n = LINE_LEN - 1 - trailing_len;
a = left.split_off_left(n);
b = right.split_off_right(n);
left.split_off_left(1); // Skip the newline in `left`.
// If we've reached the middle of the sequence and there is an odd
// number of bytes remaining, the odd one will now be on the left.
if a.len() > b.len() {
let mid = a.split_off_right(1);
mid[0] = table[mid[0] as usize]
}
reverse_chunks(a, b, table);
}
}
/// Compute the reverse complement of one sequence.
fn reverse_complement(seq: &mut [u8], table: &[u8; 256]) {
let len = seq.len() - 1;
let seq = &mut seq[..len]; // Drop the last newline
let trailing_len = len % LINE_LEN;
let (left, right) = seq.split_at_mut(len / 2);
reverse_complement_left_right(left, right, trailing_len, table);
}
/// Read sequences from stdin and print the reverse complement to stdout.
pub fn run(input: &[u8]) -> Vec<u8> {
let mut buf = Vec::with_capacity(input.len());
let mut input = io::Cursor::new(input);
// Read the first header line.
input.read_until(b'\n', &mut buf).unwrap();
// Read sequence data line-by-line, splitting on headers.
let mut line_start = buf.len();
let mut seq_start = line_start;
let mut seqs = vec![];
while input.read_until(b'\n', &mut buf).unwrap() > 0 {
if buf[line_start] == b'>' {
// Found the start of a new sequence.
seqs.push(seq_start..line_start);
seq_start = buf.len();
}
line_start = buf.len();
}
seqs.push(seq_start..buf.len());
// Compute the reverse complements of each sequence.
let table = build_table();
for seq in seqs {
reverse_complement(&mut buf[seq], &table);
}
buf
}

6
doc.sh
View File

@ -4,11 +4,7 @@ set -eux
cd $(dirname $0) cd $(dirname $0)
if [ -s NIGHTLY_TOOLCHAIN ]; then cargo doc
rustup run $NIGHTLY_TOOLCHAIN cargo doc
else
cargo doc
fi;
# cargo-deadlinks will check any links in docs generated by `cargo doc`. # cargo-deadlinks will check any links in docs generated by `cargo doc`.
# This is useful as rustdoc uses raw links which are error prone. # This is useful as rustdoc uses raw links which are error prone.

View File

@ -4,14 +4,14 @@ extern crate wasmi;
use std::env::args; use std::env::args;
use std::fs::File; use std::fs::File;
use wasmi::{ModuleInstance, NopExternals, RuntimeValue, ImportsBuilder, Module}; use wasmi::{ImportsBuilder, Module, ModuleInstance, NopExternals, RuntimeValue};
fn load_from_file(filename: &str) -> Module { fn load_from_file(filename: &str) -> Module {
use std::io::prelude::*; use std::io::prelude::*;
let mut file = File::open(filename).unwrap(); let mut file = File::open(filename).unwrap();
let mut buf = Vec::new(); let mut buf = Vec::new();
file.read_to_end(&mut buf).unwrap(); file.read_to_end(&mut buf).unwrap();
Module::from_buffer(buf).unwrap() Module::from_buffer(buf).unwrap()
} }
fn main() { fn main() {
@ -27,10 +27,10 @@ fn main() {
let module = load_from_file(&args[1]); let module = load_from_file(&args[1]);
// Intialize deserialized module. It adds module into It expects 3 parameters: // Intialize deserialized module. It adds module into It expects 3 parameters:
// - a name for the module // - a name for the module
// - a module declaration // - a module declaration
// - "main" module doesn't import native module(s) this is why we don't need to provide external native modules here // - "main" module doesn't import native module(s) this is why we don't need to provide external native modules here
// This test shows how to implement native module https://github.com/NikVolf/parity-wasm/blob/master/src/interpreter/tests/basics.rs#L197 // This test shows how to implement native module https://github.com/NikVolf/parity-wasm/blob/master/src/interpreter/tests/basics.rs#L197
let main = ModuleInstance::new(&module, &ImportsBuilder::default()) let main = ModuleInstance::new(&module, &ImportsBuilder::default())
.expect("Failed to instantiate module") .expect("Failed to instantiate module")
.run_start(&mut NopExternals) .run_start(&mut NopExternals)
@ -40,5 +40,8 @@ fn main() {
let argument: i32 = args[2].parse().expect("Integer argument required"); let argument: i32 = args[2].parse().expect("Integer argument required");
// "_call" export of function to be executed with an i32 argument and prints the result of execution // "_call" export of function to be executed with an i32 argument and prints the result of execution
println!("Result: {:?}", main.invoke_export("_call", &[RuntimeValue::I32(argument)], &mut NopExternals)); println!(
"Result: {:?}",
main.invoke_export("_call", &[RuntimeValue::I32(argument)], &mut NopExternals)
);
} }

View File

@ -3,9 +3,8 @@ extern crate wasmi;
use std::env::args; use std::env::args;
use parity_wasm::elements::{Internal, External, Type, FunctionType, ValueType}; use parity_wasm::elements::{External, FunctionType, Internal, Type, ValueType};
use wasmi::{RuntimeValue, ModuleInstance, NopExternals, ImportsBuilder}; use wasmi::{ImportsBuilder, ModuleInstance, NopExternals, RuntimeValue};
fn main() { fn main() {
let args: Vec<_> = args().collect(); let args: Vec<_> = args().collect();
@ -23,14 +22,19 @@ fn main() {
// Export section has an entry with a func_name with an index inside a module // Export section has an entry with a func_name with an index inside a module
let export_section = module.export_section().expect("No export section found"); let export_section = module.export_section().expect("No export section found");
// It's a section with function declarations (which are references to the type section entries) // It's a section with function declarations (which are references to the type section entries)
let function_section = module.function_section().expect("No function section found"); let function_section = module
.function_section()
.expect("No function section found");
// Type section stores function types which are referenced by function_section entries // Type section stores function types which are referenced by function_section entries
let type_section = module.type_section().expect("No type section found"); let type_section = module.type_section().expect("No type section found");
// Given function name used to find export section entry which contains // Given function name used to find export section entry which contains
// an `internal` field which points to the index in the function index space // an `internal` field which points to the index in the function index space
let found_entry = export_section.entries().iter() let found_entry = export_section
.find(|entry| func_name == entry.field()).expect(&format!("No export with name {} found", func_name)); .entries()
.iter()
.find(|entry| func_name == entry.field())
.expect(&format!("No export with name {} found", func_name));
// Function index in the function index space (internally-defined + imported) // Function index in the function index space (internally-defined + imported)
let function_index: usize = match found_entry.internal() { let function_index: usize = match found_entry.internal() {
@ -41,11 +45,14 @@ fn main() {
// We need to count import section entries (functions only!) to subtract it from function_index // We need to count import section entries (functions only!) to subtract it from function_index
// and obtain the index within the function section // and obtain the index within the function section
let import_section_len: usize = match module.import_section() { let import_section_len: usize = match module.import_section() {
Some(import) => Some(import) => import
import.entries().iter().filter(|entry| match entry.external() { .entries()
.iter()
.filter(|entry| match entry.external() {
&External::Function(_) => true, &External::Function(_) => true,
_ => false, _ => false,
}).count(), })
.count(),
None => 0, None => 0,
}; };
@ -53,7 +60,8 @@ fn main() {
let function_index_in_section = function_index - import_section_len; let function_index_in_section = function_index - import_section_len;
// Getting a type reference from a function section entry // Getting a type reference from a function section entry
let func_type_ref: usize = function_section.entries()[function_index_in_section].type_ref() as usize; let func_type_ref: usize =
function_section.entries()[function_index_in_section].type_ref() as usize;
// Use the reference to get an actual function type // Use the reference to get an actual function type
let function_type: &FunctionType = match &type_section.types()[func_type_ref] { let function_type: &FunctionType = match &type_section.types()[func_type_ref] {
@ -61,12 +69,35 @@ fn main() {
}; };
// Parses arguments and constructs runtime values in correspondence of their types // Parses arguments and constructs runtime values in correspondence of their types
function_type.params().iter().enumerate().map(|(i, value)| match value { function_type
&ValueType::I32 => RuntimeValue::I32(program_args[i].parse::<i32>().expect(&format!("Can't parse arg #{} as i32", program_args[i]))), .params()
&ValueType::I64 => RuntimeValue::I64(program_args[i].parse::<i64>().expect(&format!("Can't parse arg #{} as i64", program_args[i]))), .iter()
&ValueType::F32 => RuntimeValue::F32(program_args[i].parse::<f32>().expect(&format!("Can't parse arg #{} as f32", program_args[i]))), .enumerate()
&ValueType::F64 => RuntimeValue::F64(program_args[i].parse::<f64>().expect(&format!("Can't parse arg #{} as f64", program_args[i]))), .map(|(i, value)| match value {
}).collect::<Vec<RuntimeValue>>() &ValueType::I32 => RuntimeValue::I32(
program_args[i]
.parse::<i32>()
.expect(&format!("Can't parse arg #{} as i32", program_args[i])),
),
&ValueType::I64 => RuntimeValue::I64(
program_args[i]
.parse::<i64>()
.expect(&format!("Can't parse arg #{} as i64", program_args[i])),
),
&ValueType::F32 => RuntimeValue::F32(
program_args[i]
.parse::<f32>()
.expect(&format!("Can't parse arg #{} as f32", program_args[i]))
.into(),
),
&ValueType::F64 => RuntimeValue::F64(
program_args[i]
.parse::<f64>()
.expect(&format!("Can't parse arg #{} as f64", program_args[i]))
.into(),
),
})
.collect::<Vec<RuntimeValue>>()
}; };
let loaded_module = wasmi::Module::from_parity_wasm_module(module).expect("Module to be valid"); let loaded_module = wasmi::Module::from_parity_wasm_module(module).expect("Module to be valid");
@ -81,5 +112,9 @@ fn main() {
.run_start(&mut NopExternals) .run_start(&mut NopExternals)
.expect("Failed to run start function in module"); .expect("Failed to run start function in module");
println!("Result: {:?}", main.invoke_export(func_name, &args, &mut NopExternals).expect("")); println!(
"Result: {:?}",
main.invoke_export(func_name, &args, &mut NopExternals)
.expect("")
);
} }

View File

@ -1,256 +1,255 @@
extern crate wasmi;
extern crate parity_wasm; extern crate parity_wasm;
extern crate wasmi;
use std::env; use std::env;
use std::fmt; use std::fmt;
use std::fs::File; use std::fs::File;
use wasmi::{ use wasmi::{
Error as InterpreterError, ModuleInstance, ModuleRef, Error as InterpreterError, Externals, FuncInstance, FuncRef, HostError, ImportsBuilder,
Externals, RuntimeValue, FuncRef, ModuleImportResolver, ModuleImportResolver, ModuleInstance, ModuleRef, RuntimeArgs, RuntimeValue, Signature, Trap,
FuncInstance, HostError, ImportsBuilder, Signature, ValueType, ValueType,
RuntimeArgs, Trap,
}; };
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
OutOfRange, OutOfRange,
AlreadyOccupied, AlreadyOccupied,
Interpreter(InterpreterError), Interpreter(InterpreterError),
} }
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self) write!(f, "{:?}", self)
} }
} }
impl From<InterpreterError> for Error { impl From<InterpreterError> for Error {
fn from(e: InterpreterError) -> Self { fn from(e: InterpreterError) -> Self {
Error::Interpreter(e) Error::Interpreter(e)
} }
} }
impl HostError for Error {} impl HostError for Error {}
mod tictactoe { mod tictactoe {
use super::Error; use super::Error;
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Player { pub enum Player {
X, X,
O, O,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum GameResult { pub enum GameResult {
Draw, Draw,
Won(Player), Won(Player),
} }
impl Player { impl Player {
pub fn into_i32(maybe_player: Option<Player>) -> i32 { pub fn into_i32(maybe_player: Option<Player>) -> i32 {
match maybe_player { match maybe_player {
None => 0, None => 0,
Some(Player::X) => 1, Some(Player::X) => 1,
Some(Player::O) => 2, Some(Player::O) => 2,
} }
} }
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Game { pub struct Game {
board: [Option<Player>; 9], board: [Option<Player>; 9],
} }
impl Game { impl Game {
pub fn new() -> Game { pub fn new() -> Game {
Game { Game { board: [None; 9] }
board: [None; 9], }
}
}
pub fn set(&mut self, idx: i32, player: Player) -> Result<(), Error> { pub fn set(&mut self, idx: i32, player: Player) -> Result<(), Error> {
if idx < 0 || idx > 9 { if idx < 0 || idx > 9 {
return Err(Error::OutOfRange); return Err(Error::OutOfRange);
} }
if self.board[idx as usize] != None { if self.board[idx as usize] != None {
return Err(Error::AlreadyOccupied); return Err(Error::AlreadyOccupied);
} }
self.board[idx as usize] = Some(player); self.board[idx as usize] = Some(player);
Ok(()) Ok(())
} }
pub fn get(&self, idx: i32) -> Result<Option<Player>, Error> { pub fn get(&self, idx: i32) -> Result<Option<Player>, Error> {
if idx < 0 || idx > 9 { if idx < 0 || idx > 9 {
return Err(Error::OutOfRange); return Err(Error::OutOfRange);
} }
Ok(self.board[idx as usize]) Ok(self.board[idx as usize])
} }
pub fn game_result(&self) -> Option<GameResult> { pub fn game_result(&self) -> Option<GameResult> {
// 0, 1, 2 // 0, 1, 2
// 3, 4, 5 // 3, 4, 5
// 6, 7, 8 // 6, 7, 8
let patterns = &[ let patterns = &[
// Rows // Rows
(0, 1, 2), (0, 1, 2),
(3, 4, 5), (3, 4, 5),
(6, 7, 8), (6, 7, 8),
// Columns
(0, 3, 6),
(1, 4, 7),
(2, 5, 8),
// Diagonals
(0, 4, 8),
(2, 4, 6),
];
// Columns // Returns Some(player) if all cells contain same Player.
(0, 3, 6), let all_same = |i1: usize, i2: usize, i3: usize| -> Option<Player> {
(1, 4, 7), if self.board[i1].is_none() {
(2, 5, 8), return None;
}
if self.board[i1] == self.board[i2] && self.board[i2] == self.board[i3] {
return self.board[i1];
}
None
};
// Diagonals for &(i1, i2, i3) in patterns {
(0, 4, 8), if let Some(player) = all_same(i1, i2, i3) {
(2, 4, 6), return Some(GameResult::Won(player));
]; }
}
// Returns Some(player) if all cells contain same Player. // Ok, there is no winner. Check if it's draw.
let all_same = |i1: usize, i2: usize, i3: usize| -> Option<Player> { let all_occupied = self.board.iter().all(|&cell| cell.is_some());
if self.board[i1].is_none() { if all_occupied {
return None; Some(GameResult::Draw)
} } else {
if self.board[i1] == self.board[i2] && self.board[i2] == self.board[i3] { // Nah, there are still empty cells left.
return self.board[i1]; None
} }
None }
}; }
for &(i1, i2, i3) in patterns {
if let Some(player) = all_same(i1, i2, i3) {
return Some(GameResult::Won(player));
}
}
// Ok, there is no winner. Check if it's draw.
let all_occupied = self.board.iter().all(|&cell| cell.is_some());
if all_occupied {
Some(GameResult::Draw)
} else {
// Nah, there are still empty cells left.
None
}
}
}
} }
struct Runtime<'a> { struct Runtime<'a> {
player: tictactoe::Player, player: tictactoe::Player,
game: &'a mut tictactoe::Game, game: &'a mut tictactoe::Game,
} }
const SET_FUNC_INDEX: usize = 0; const SET_FUNC_INDEX: usize = 0;
const GET_FUNC_INDEX: usize = 1; const GET_FUNC_INDEX: usize = 1;
impl<'a> Externals for Runtime<'a> { impl<'a> Externals for Runtime<'a> {
fn invoke_index( fn invoke_index(
&mut self, &mut self,
index: usize, index: usize,
args: RuntimeArgs, args: RuntimeArgs,
) -> Result<Option<RuntimeValue>, Trap> { ) -> Result<Option<RuntimeValue>, Trap> {
match index { match index {
SET_FUNC_INDEX => { SET_FUNC_INDEX => {
let idx: i32 = args.nth(0); let idx: i32 = args.nth(0);
self.game.set(idx, self.player)?; self.game.set(idx, self.player)?;
Ok(None) Ok(None)
} }
GET_FUNC_INDEX => { GET_FUNC_INDEX => {
let idx: i32 = args.nth(0); let idx: i32 = args.nth(0);
let val: i32 = tictactoe::Player::into_i32(self.game.get(idx)?); let val: i32 = tictactoe::Player::into_i32(self.game.get(idx)?);
Ok(Some(val.into())) Ok(Some(val.into()))
} }
_ => panic!("unknown function index") _ => panic!("unknown function index"),
} }
} }
} }
struct RuntimeModuleImportResolver; struct RuntimeModuleImportResolver;
impl<'a> ModuleImportResolver for RuntimeModuleImportResolver { impl<'a> ModuleImportResolver for RuntimeModuleImportResolver {
fn resolve_func( fn resolve_func(
&self, &self,
field_name: &str, field_name: &str,
_signature: &Signature, _signature: &Signature,
) -> Result<FuncRef, InterpreterError> { ) -> Result<FuncRef, InterpreterError> {
let func_ref = match field_name { let func_ref = match field_name {
"set" => { "set" => FuncInstance::alloc_host(
FuncInstance::alloc_host(Signature::new(&[ValueType::I32][..], None), SET_FUNC_INDEX) Signature::new(&[ValueType::I32][..], None),
}, SET_FUNC_INDEX,
"get" => FuncInstance::alloc_host(Signature::new(&[ValueType::I32][..], Some(ValueType::I32)), GET_FUNC_INDEX), ),
_ => return Err( "get" => FuncInstance::alloc_host(
InterpreterError::Function( Signature::new(&[ValueType::I32][..], Some(ValueType::I32)),
format!("host module doesn't export function with name {}", field_name) GET_FUNC_INDEX,
) ),
) _ => {
}; return Err(InterpreterError::Function(format!(
Ok(func_ref) "host module doesn't export function with name {}",
} field_name
)));
}
};
Ok(func_ref)
}
} }
fn instantiate(path: &str) -> Result<ModuleRef, Error> { fn instantiate(path: &str) -> Result<ModuleRef, Error> {
let module = { let module = {
use std::io::prelude::*; use std::io::prelude::*;
let mut file = File::open(path).unwrap(); let mut file = File::open(path).unwrap();
let mut wasm_buf = Vec::new(); let mut wasm_buf = Vec::new();
file.read_to_end(&mut wasm_buf).unwrap(); file.read_to_end(&mut wasm_buf).unwrap();
wasmi::Module::from_buffer(&wasm_buf)? wasmi::Module::from_buffer(&wasm_buf)?
}; };
let mut imports = ImportsBuilder::new(); let mut imports = ImportsBuilder::new();
imports.push_resolver("env", &RuntimeModuleImportResolver); imports.push_resolver("env", &RuntimeModuleImportResolver);
let instance = ModuleInstance::new(&module, &imports)? let instance = ModuleInstance::new(&module, &imports)?.assert_no_start();
.assert_no_start();
Ok(instance) Ok(instance)
} }
fn play( fn play(
x_instance: ModuleRef, x_instance: ModuleRef,
o_instance: ModuleRef, o_instance: ModuleRef,
game: &mut tictactoe::Game, game: &mut tictactoe::Game,
) -> Result<tictactoe::GameResult, Error> { ) -> Result<tictactoe::GameResult, Error> {
let mut turn_of = tictactoe::Player::X; let mut turn_of = tictactoe::Player::X;
let game_result = loop { let game_result = loop {
let (instance, next_turn_of) = match turn_of { let (instance, next_turn_of) = match turn_of {
tictactoe::Player::X => (&x_instance, tictactoe::Player::O), tictactoe::Player::X => (&x_instance, tictactoe::Player::O),
tictactoe::Player::O => (&o_instance, tictactoe::Player::X), tictactoe::Player::O => (&o_instance, tictactoe::Player::X),
}; };
{ {
let mut runtime = Runtime { let mut runtime = Runtime {
player: turn_of, player: turn_of,
game: game, game: game,
}; };
let _ = instance.invoke_export("mk_turn", &[], &mut runtime)?; let _ = instance.invoke_export("mk_turn", &[], &mut runtime)?;
} }
match game.game_result() { match game.game_result() {
Some(game_result) => break game_result, Some(game_result) => break game_result,
None => {} None => {}
} }
turn_of = next_turn_of; turn_of = next_turn_of;
}; };
Ok(game_result) Ok(game_result)
} }
fn main() { fn main() {
let mut game = tictactoe::Game::new(); let mut game = tictactoe::Game::new();
let args: Vec<_> = env::args().collect(); let args: Vec<_> = env::args().collect();
if args.len() < 3 { if args.len() < 3 {
println!("Usage: {} <x player module> <y player module>", args[0]); println!("Usage: {} <x player module> <y player module>", args[0]);
return; return;
} }
// Instantiate modules of X and O players. // Instantiate modules of X and O players.
let x_instance = instantiate(&args[1]).expect("X player module to load"); let x_instance = instantiate(&args[1]).expect("X player module to load");
let o_instance = instantiate(&args[2]).expect("Y player module to load"); let o_instance = instantiate(&args[2]).expect("Y player module to load");
let result = play(x_instance, o_instance, &mut game); let result = play(x_instance, o_instance, &mut game);
println!("result = {:?}, game = {:#?}", result, game); println!("result = {:?}, game = {:#?}", result, game);
} }

View File

@ -10,11 +10,13 @@ cargo-fuzz = true
[dependencies] [dependencies]
wasmi = { path = ".." } wasmi = { path = ".." }
wabt = "0.2.0" wabt = "0.9"
wasmparser = "0.14.1" wasmparser = "0.14.1"
tempdir = "0.3.6"
[dependencies.libfuzzer-sys] [dependencies.libfuzzer-sys]
git = "https://github.com/rust-fuzz/libfuzzer-sys.git" git = "https://github.com/rust-fuzz/libfuzzer-sys.git"
rev = "737524f7de1e85342b8b6cd1c01edc71018183ba"
# Prevent this from interfering with workspaces # Prevent this from interfering with workspaces
[workspace] [workspace]
@ -31,3 +33,7 @@ path = "fuzz_targets/load_wabt.rs"
[[bin]] [[bin]]
name = "load_wasmparser" name = "load_wasmparser"
path = "fuzz_targets/load_wasmparser.rs" path = "fuzz_targets/load_wasmparser.rs"
[[bin]]
name = "load_spec"
path = "fuzz_targets/load_spec.rs"

View File

@ -0,0 +1,53 @@
#![no_main]
#[macro_use]
extern crate libfuzzer_sys;
extern crate wabt;
extern crate wasmi;
extern crate tempdir;
use std::fs::File;
use std::io::Write;
use std::process::{Command, Stdio};
fn run_spec(data: &[u8]) -> Result<(), ()> {
let temp_dir = tempdir::TempDir::new("spec").unwrap();
let mut seed_path = temp_dir.path().to_path_buf();
seed_path.push("test.wasm");
{
let mut seedfile =
File::create(&seed_path).expect("open temporary file for writing to store fuzzer input");
seedfile.write_all(data).expect(
"write fuzzer input to temporary file",
);
seedfile.flush().expect(
"flush fuzzer input to temporary file before starting wasm-opt",
);
}
let exit_status = Command::new("wasm")
.arg("-d")
.arg(&seed_path)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.expect("failed to execute `wasm`");
if exit_status.success() {
Ok(())
} else {
Err(())
}
}
fn run_wasmi(data: &[u8]) -> Result<(), ()> {
let _ = wasmi::Module::from_buffer(data).map_err(|_| ())?;
Ok(())
}
fuzz_target!(|data: &[u8]| {
let wasmi_result = run_wasmi(data);
let wasm_result = run_spec(data);
assert_eq!(wasmi_result.is_ok(), wasm_result.is_ok());
});

2
hfuzz/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
hfuzz_workspace/
hfuzz_target/

10
hfuzz/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "hfuzz"
version = "0.1.0"
authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
[dependencies]
honggfuzz = "=0.5.9" # Strict equal since hfuzz requires dep and cmd versions to match.
wasmi = { path = ".." }
tempdir = "0.3.6"
wabt = "0.9"

70
hfuzz/src/main.rs Normal file
View File

@ -0,0 +1,70 @@
#[macro_use] extern crate honggfuzz;
extern crate wabt;
extern crate wasmi;
extern crate tempdir;
use std::fs::File;
use std::io::Write;
use std::process::{Command, Stdio};
fn dump_all_into_buf(src: &[u8], buf: &mut [u8; 64]) {
let common_len = usize::min(src.len(), buf.len());
buf[0..common_len].copy_from_slice(&src[0..common_len]);
}
fn run_spec(data: &[u8], stdout_msg_buf: &mut [u8; 64], stderr_msg_buf: &mut [u8; 64]) -> Result<(), ()> {
let temp_dir = tempdir::TempDir::new("spec").unwrap();
let mut seed_path = temp_dir.path().to_path_buf();
seed_path.push("test.wasm");
{
let mut seedfile =
File::create(&seed_path).expect("open temporary file for writing to store fuzzer input");
seedfile.write_all(data).expect(
"write fuzzer input to temporary file",
);
seedfile.flush().expect(
"flush fuzzer input to temporary file before starting wasm-opt",
);
}
let output = Command::new("wasm")
.arg("-d")
.arg(&seed_path)
.stdout(Stdio::null())
.stderr(Stdio::null())
.output()
.expect("failed to execute `wasm`");
if output.status.success() {
Ok(())
} else {
dump_all_into_buf(&output.stdout, stdout_msg_buf);
dump_all_into_buf(&output.stderr, stderr_msg_buf);
Err(())
}
}
fn run_wasmi(data: &[u8]) -> Result<(), ()> {
let _ = wasmi::Module::from_buffer(data).map_err(|_| ())?;
Ok(())
}
fn main() {
loop {
fuzz!(|data: &[u8]| {
// Keep messages on stack. This should lead to a different stack hashes for
// different error messages.
let mut stdout_msg_buf: [u8; 64] = [0; 64];
let mut stderr_msg_buf: [u8; 64] = [0; 64];
let wasmi_result = run_wasmi(data);
let wasm_result = run_spec(data, &mut stdout_msg_buf, &mut stderr_msg_buf);
if wasmi_result.is_ok() != wasm_result.is_ok() {
panic!("stdout: {:?}, stderr: {:?}", &stdout_msg_buf[..], &stderr_msg_buf as &[u8]);
}
});
}
}

9
hfuzz/test.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
export HFUZZ_RUN_ARGS="--max_file_size 2048"
die() { echo "$*"; exit 1; }
command -v wasm || die "spec interpreter 'wasm' is not on PATH";
rustup run nightly cargo hfuzz run hfuzz

View File

@ -5,77 +5,79 @@ extern crate wasmi;
use std::env::args; use std::env::args;
use std::fs::File; use std::fs::File;
use wasmi::{
Error, FuncInstance, FuncRef, GlobalDescriptor, GlobalInstance, GlobalRef,
ImportsBuilder, MemoryDescriptor, MemoryInstance, MemoryRef, Module,
ModuleImportResolver, ModuleInstance, NopExternals, RuntimeValue, Signature,
TableDescriptor, TableInstance, TableRef};
use wasmi::memory_units::*; use wasmi::memory_units::*;
use wasmi::{
Error, FuncInstance, FuncRef, GlobalDescriptor, GlobalInstance, GlobalRef, ImportsBuilder,
MemoryDescriptor, MemoryInstance, MemoryRef, Module, ModuleImportResolver, ModuleInstance,
NopExternals, RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef,
};
fn load_from_file(filename: &str) -> Module { fn load_from_file(filename: &str) -> Module {
use std::io::prelude::*; use std::io::prelude::*;
let mut file = File::open(filename).unwrap(); let mut file = File::open(filename).unwrap();
let mut buf = Vec::new(); let mut buf = Vec::new();
file.read_to_end(&mut buf).unwrap(); file.read_to_end(&mut buf).unwrap();
Module::from_buffer(buf).unwrap() Module::from_buffer(buf).unwrap()
} }
struct ResolveAll; struct ResolveAll;
impl ModuleImportResolver for ResolveAll { impl ModuleImportResolver for ResolveAll {
fn resolve_func(&self, _field_name: &str, signature: &Signature) -> Result<FuncRef, Error> { fn resolve_func(&self, _field_name: &str, signature: &Signature) -> Result<FuncRef, Error> {
Ok(FuncInstance::alloc_host(signature.clone(), 0)) Ok(FuncInstance::alloc_host(signature.clone(), 0))
} }
fn resolve_global( fn resolve_global(
&self, &self,
_field_name: &str, _field_name: &str,
global_type: &GlobalDescriptor, global_type: &GlobalDescriptor,
) -> Result<GlobalRef, Error> { ) -> Result<GlobalRef, Error> {
Ok(GlobalInstance::alloc( Ok(GlobalInstance::alloc(
RuntimeValue::default(global_type.value_type()), RuntimeValue::default(global_type.value_type()),
global_type.is_mutable(), global_type.is_mutable(),
)) ))
} }
fn resolve_memory( fn resolve_memory(
&self, &self,
_field_name: &str, _field_name: &str,
memory_type: &MemoryDescriptor, memory_type: &MemoryDescriptor,
) -> Result<MemoryRef, Error> { ) -> Result<MemoryRef, Error> {
Ok(MemoryInstance::alloc( Ok(MemoryInstance::alloc(
Pages(memory_type.initial() as usize), Pages(memory_type.initial() as usize),
memory_type.maximum().map(|m| Pages(m as usize)), memory_type.maximum().map(|m| Pages(m as usize)),
).unwrap()) )
} .unwrap())
}
fn resolve_table( fn resolve_table(
&self, &self,
_field_name: &str, _field_name: &str,
table_type: &TableDescriptor, table_type: &TableDescriptor,
) -> Result<TableRef, Error> { ) -> Result<TableRef, Error> {
Ok(TableInstance::alloc(table_type.initial(), table_type.maximum()).unwrap()) Ok(TableInstance::alloc(table_type.initial(), table_type.maximum()).unwrap())
} }
} }
fn main() { fn main() {
let args: Vec<_> = args().collect(); let args: Vec<_> = args().collect();
if args.len() != 2 { if args.len() != 2 {
println!("Usage: {} <wasm file>", args[0]); println!("Usage: {} <wasm file>", args[0]);
return; return;
} }
let module = load_from_file(&args[1]); let module = load_from_file(&args[1]);
let _ = ModuleInstance::new( let _ = ModuleInstance::new(
&module, &module,
&ImportsBuilder::default() &ImportsBuilder::default()
// Well known imports. // Well known imports.
.with_resolver("env", &ResolveAll) .with_resolver("env", &ResolveAll)
.with_resolver("global", &ResolveAll) .with_resolver("global", &ResolveAll)
.with_resolver("foo", &ResolveAll) .with_resolver("foo", &ResolveAll)
.with_resolver("global.Math", &ResolveAll) .with_resolver("global.Math", &ResolveAll)
.with_resolver("asm2wasm", &ResolveAll) .with_resolver("asm2wasm", &ResolveAll)
.with_resolver("spectest", &ResolveAll), .with_resolver("spectest", &ResolveAll),
).expect("Failed to instantiate module") )
.run_start(&mut NopExternals) .expect("Failed to instantiate module")
.expect("Failed to run start function in module"); .run_start(&mut NopExternals)
.expect("Failed to run start function in module");
} }

View File

@ -1,44 +0,0 @@
use parity_wasm::elements::BlockType;
pub mod stack;
/// Index of default linear memory.
pub const DEFAULT_MEMORY_INDEX: u32 = 0;
/// Index of default table.
pub const DEFAULT_TABLE_INDEX: u32 = 0;
/// Control stack frame.
#[derive(Debug, Clone)]
pub struct BlockFrame {
/// Frame type.
pub frame_type: BlockFrameType,
/// A signature, which is a block signature type indicating the number and types of result values of the region.
pub block_type: BlockType,
/// A label for reference to block instruction.
pub begin_position: usize,
/// A label for reference from branch instructions.
pub branch_position: usize,
/// A label for reference from end instructions.
pub end_position: usize,
/// A limit integer value, which is an index into the value stack indicating where to reset it to on a branch to that label.
pub value_stack_len: usize,
/// Boolean which signals whether value stack became polymorphic. Value stack starts in non-polymorphic state and
/// becomes polymorphic only after an instruction that never passes control further is executed,
/// i.e. `unreachable`, `br` (but not `br_if`!), etc.
pub polymorphic_stack: bool,
}
/// Type of block frame.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BlockFrameType {
/// Function frame.
Function,
/// Usual block frame.
Block,
/// Loop frame (branching to the beginning of block).
Loop,
/// True-subblock of if expression.
IfTrue,
/// False-subblock of if expression.
IfFalse,
}

View File

@ -1,89 +0,0 @@
use std::collections::VecDeque;
use std::error;
use std::fmt;
#[derive(Debug)]
pub struct Error(String);
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl error::Error for Error {
fn description(&self) -> &str {
&self.0
}
}
/// Stack with limit.
#[derive(Debug)]
pub struct StackWithLimit<T> where T: Clone {
/// Stack values.
values: VecDeque<T>,
/// Stack limit (maximal stack len).
limit: usize,
}
impl<T> StackWithLimit<T> where T: Clone {
pub fn with_limit(limit: usize) -> Self {
StackWithLimit {
values: VecDeque::new(),
limit: limit
}
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn limit(&self) -> usize {
self.limit
}
pub fn top(&self) -> Result<&T, Error> {
self.values
.back()
.ok_or_else(|| Error("non-empty stack expected".into()))
}
pub fn top_mut(&mut self) -> Result<&mut T, Error> {
self.values
.back_mut()
.ok_or_else(|| Error("non-empty stack expected".into()))
}
pub fn get(&self, index: usize) -> Result<&T, Error> {
if index >= self.values.len() {
return Err(Error(format!("trying to get value at position {} on stack of size {}", index, self.values.len())));
}
Ok(self.values.get(self.values.len() - 1 - index).expect("checked couple of lines above"))
}
pub fn push(&mut self, value: T) -> Result<(), Error> {
if self.values.len() >= self.limit {
return Err(Error(format!("exceeded stack limit {}", self.limit)));
}
self.values.push_back(value);
Ok(())
}
pub fn pop(&mut self) -> Result<T, Error> {
self.values
.pop_back()
.ok_or_else(|| Error("non-empty stack expected".into()))
}
pub fn resize(&mut self, new_size: usize, dummy: T) {
debug_assert!(new_size <= self.values.len());
self.values.resize(new_size, dummy);
}
}

View File

@ -1,12 +1,17 @@
use std::rc::{Rc, Weak}; use alloc::{
use std::fmt; borrow::Cow,
use std::collections::HashMap; rc::{Rc, Weak},
use parity_wasm::elements::{Local, Opcodes}; vec::Vec,
use {Trap, TrapKind, Signature}; };
use core::fmt;
use host::Externals; use host::Externals;
use runner::{check_function_args, Interpreter}; use isa;
use value::RuntimeValue;
use module::ModuleInstance; use module::ModuleInstance;
use parity_wasm::elements::Local;
use runner::{check_function_args, Interpreter, InterpreterState, StackRecycler};
use types::ValueType;
use value::RuntimeValue;
use {Signature, Trap};
/// Reference to a function (See [`FuncInstance`] for details). /// Reference to a function (See [`FuncInstance`] for details).
/// ///
@ -16,16 +21,16 @@ use module::ModuleInstance;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct FuncRef(Rc<FuncInstance>); pub struct FuncRef(Rc<FuncInstance>);
impl ::std::ops::Deref for FuncRef { impl ::core::ops::Deref for FuncRef {
type Target = FuncInstance; type Target = FuncInstance;
fn deref(&self) -> &FuncInstance { fn deref(&self) -> &FuncInstance {
&self.0 &self.0
} }
} }
/// Runtime representation of a function. /// Runtime representation of a function.
/// ///
/// Functions are the unit of orgianization of code in WebAssembly. Each function takes a sequence of values /// Functions are the unit of organization of code in WebAssembly. Each function takes a sequence of values
/// as parameters and either optionally return a value or trap. /// as parameters and either optionally return a value or trap.
/// Functions can call other function including itself (i.e recursive calls are allowed) and imported functions /// Functions can call other function including itself (i.e recursive calls are allowed) and imported functions
/// (i.e functions defined in another module or by the host environment). /// (i.e functions defined in another module or by the host environment).
@ -41,123 +46,307 @@ pub struct FuncInstance(FuncInstanceInternal);
#[derive(Clone)] #[derive(Clone)]
pub(crate) enum FuncInstanceInternal { pub(crate) enum FuncInstanceInternal {
Internal { Internal {
signature: Rc<Signature>, signature: Rc<Signature>,
module: Weak<ModuleInstance>, module: Weak<ModuleInstance>,
body: Rc<FuncBody>, body: Rc<FuncBody>,
}, },
Host { Host {
signature: Signature, signature: Signature,
host_func_index: usize, host_func_index: usize,
}, },
} }
impl fmt::Debug for FuncInstance { impl fmt::Debug for FuncInstance {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.as_internal() { match self.as_internal() {
&FuncInstanceInternal::Internal { &FuncInstanceInternal::Internal { ref signature, .. } => {
ref signature, // We can't write description of self.module here, because it generate
.. // debug string for function instances and this will lead to infinite loop.
} => { write!(f, "Internal {{ signature={:?} }}", signature,)
// We can't write description of self.module here, because it generate }
// debug string for function instances and this will lead to infinite loop. &FuncInstanceInternal::Host { ref signature, .. } => {
write!( write!(f, "Host {{ signature={:?} }}", signature)
f, }
"Internal {{ signature={:?} }}", }
signature, }
)
}
&FuncInstanceInternal::Host { ref signature, .. } => {
write!(f, "Host {{ signature={:?} }}", signature)
}
}
}
} }
impl FuncInstance { impl FuncInstance {
/// Allocate a function instance for a host function. /// Allocate a function instance for a host function.
/// ///
/// When this function instance will be called by the wasm code, /// When this function instance will be called by the wasm code,
/// the instance of [`Externals`] will be invoked by calling `invoke_index` /// the instance of [`Externals`] will be invoked by calling `invoke_index`
/// with specified `host_func_index` here. /// with specified `host_func_index` here.
/// This call will be made with the `signature` provided here. /// This call will be made with the `signature` provided here.
/// ///
/// [`Externals`]: trait.Externals.html /// [`Externals`]: trait.Externals.html
pub fn alloc_host(signature: Signature, host_func_index: usize) -> FuncRef { pub fn alloc_host(signature: Signature, host_func_index: usize) -> FuncRef {
let func = FuncInstanceInternal::Host { let func = FuncInstanceInternal::Host {
signature, signature,
host_func_index, host_func_index,
}; };
FuncRef(Rc::new(FuncInstance(func))) FuncRef(Rc::new(FuncInstance(func)))
} }
/// Returns [signature] of this function instance. /// Returns [signature] of this function instance.
/// ///
/// This function instance can only be called with matching signatures. /// This function instance can only be called with matching signatures.
/// ///
/// [signature]: struct.Signature.html /// [signature]: struct.Signature.html
pub fn signature(&self) -> &Signature { pub fn signature(&self) -> &Signature {
match *self.as_internal() { match *self.as_internal() {
FuncInstanceInternal::Internal { ref signature, .. } => signature, FuncInstanceInternal::Internal { ref signature, .. } => signature,
FuncInstanceInternal::Host { ref signature, .. } => signature, FuncInstanceInternal::Host { ref signature, .. } => signature,
} }
} }
pub(crate) fn as_internal(&self) -> &FuncInstanceInternal { pub(crate) fn as_internal(&self) -> &FuncInstanceInternal {
&self.0 &self.0
} }
pub(crate) fn alloc_internal( pub(crate) fn alloc_internal(
module: Weak<ModuleInstance>, module: Weak<ModuleInstance>,
signature: Rc<Signature>, signature: Rc<Signature>,
body: FuncBody, body: FuncBody,
) -> FuncRef { ) -> FuncRef {
let func = FuncInstanceInternal::Internal { let func = FuncInstanceInternal::Internal {
signature, signature,
module: module, module: module,
body: Rc::new(body), body: Rc::new(body),
}; };
FuncRef(Rc::new(FuncInstance(func))) FuncRef(Rc::new(FuncInstance(func)))
} }
pub(crate) fn body(&self) -> Option<Rc<FuncBody>> { pub(crate) fn body(&self) -> Option<Rc<FuncBody>> {
match *self.as_internal() { match *self.as_internal() {
FuncInstanceInternal::Internal { ref body, .. } => Some(Rc::clone(body)), FuncInstanceInternal::Internal { ref body, .. } => Some(Rc::clone(body)),
FuncInstanceInternal::Host { .. } => None, FuncInstanceInternal::Host { .. } => None,
} }
} }
/// Invoke this function. /// Invoke this function.
/// ///
/// # Errors /// # Errors
/// ///
/// Returns `Err` if `args` types is not match function [`signature`] or /// Returns `Err` if `args` types is not match function [`signature`] or
/// if [`Trap`] at execution time occured. /// if [`Trap`] at execution time occured.
/// ///
/// [`signature`]: #method.signature /// [`signature`]: #method.signature
/// [`Trap`]: #enum.Trap.html /// [`Trap`]: #enum.Trap.html
pub fn invoke<E: Externals>( pub fn invoke<E: Externals>(
func: &FuncRef, func: &FuncRef,
args: &[RuntimeValue], args: &[RuntimeValue],
externals: &mut E, externals: &mut E,
) -> Result<Option<RuntimeValue>, Trap> { ) -> Result<Option<RuntimeValue>, Trap> {
check_function_args(func.signature(), &args).map_err(|_| TrapKind::UnexpectedSignature)?; check_function_args(func.signature(), &args)?;
match *func.as_internal() { match *func.as_internal() {
FuncInstanceInternal::Internal { .. } => { FuncInstanceInternal::Internal { .. } => {
let mut interpreter = Interpreter::new(externals); let mut interpreter = Interpreter::new(func, args, None)?;
interpreter.start_execution(func, args) interpreter.start_execution(externals)
} }
FuncInstanceInternal::Host { FuncInstanceInternal::Host {
ref host_func_index, ref host_func_index,
.. ..
} => externals.invoke_index(*host_func_index, args.into()), } => externals.invoke_index(*host_func_index, args.into()),
} }
} }
/// Invoke this function using recycled stacks.
///
/// # Errors
///
/// Same as [`invoke`].
///
/// [`invoke`]: #method.invoke
pub fn invoke_with_stack<E: Externals>(
func: &FuncRef,
args: &[RuntimeValue],
externals: &mut E,
stack_recycler: &mut StackRecycler,
) -> Result<Option<RuntimeValue>, Trap> {
check_function_args(func.signature(), &args)?;
match *func.as_internal() {
FuncInstanceInternal::Internal { .. } => {
let mut interpreter = Interpreter::new(func, args, Some(stack_recycler))?;
let return_value = interpreter.start_execution(externals);
stack_recycler.recycle(interpreter);
return_value
}
FuncInstanceInternal::Host {
ref host_func_index,
..
} => externals.invoke_index(*host_func_index, args.into()),
}
}
/// Invoke the function, get a resumable handle. This handle can then be used to [`start_execution`]. If a
/// Host trap happens, caller can use [`resume_execution`] to feed the expected return value back in, and then
/// continue the execution.
///
/// This is an experimental API, and this functionality may not be available in other WebAssembly engines.
///
/// # Errors
///
/// Returns `Err` if `args` types is not match function [`signature`].
///
/// [`signature`]: #method.signature
/// [`Trap`]: #enum.Trap.html
/// [`start_execution`]: struct.FuncInvocation.html#method.start_execution
/// [`resume_execution`]: struct.FuncInvocation.html#method.resume_execution
pub fn invoke_resumable<'args>(
func: &FuncRef,
args: impl Into<Cow<'args, [RuntimeValue]>>,
) -> Result<FuncInvocation<'args>, Trap> {
let args = args.into();
check_function_args(func.signature(), &args)?;
match *func.as_internal() {
FuncInstanceInternal::Internal { .. } => {
let interpreter = Interpreter::new(func, &*args, None)?;
Ok(FuncInvocation {
kind: FuncInvocationKind::Internal(interpreter),
})
}
FuncInstanceInternal::Host {
ref host_func_index,
..
} => Ok(FuncInvocation {
kind: FuncInvocationKind::Host {
args,
host_func_index: *host_func_index,
finished: false,
},
}),
}
}
}
/// A resumable invocation error.
#[derive(Debug)]
pub enum ResumableError {
/// Trap happened.
Trap(Trap),
/// The invocation is not resumable.
///
/// Invocations are only resumable if a host function is called, and the host function returns a trap of `Host` kind. For other cases, this error will be returned. This includes:
/// - The invocation is directly a host function.
/// - The invocation has not been started.
/// - The invocation returns normally or returns any trap other than `Host` kind.
///
/// This error is returned by [`resume_execution`].
///
/// [`resume_execution`]: struct.FuncInvocation.html#method.resume_execution
NotResumable,
/// The invocation has already been started.
///
/// This error is returned by [`start_execution`].
///
/// [`start_execution`]: struct.FuncInvocation.html#method.start_execution
AlreadyStarted,
}
impl From<Trap> for ResumableError {
fn from(trap: Trap) -> Self {
ResumableError::Trap(trap)
}
}
/// A resumable invocation handle. This struct is returned by `FuncInstance::invoke_resumable`.
pub struct FuncInvocation<'args> {
kind: FuncInvocationKind<'args>,
}
enum FuncInvocationKind<'args> {
Internal(Interpreter),
Host {
args: Cow<'args, [RuntimeValue]>,
host_func_index: usize,
finished: bool,
},
}
impl<'args> FuncInvocation<'args> {
/// Whether this invocation is currently resumable.
pub fn is_resumable(&self) -> bool {
match &self.kind {
&FuncInvocationKind::Internal(ref interpreter) => interpreter.state().is_resumable(),
&FuncInvocationKind::Host { .. } => false,
}
}
/// If the invocation is resumable, the expected return value type to be feed back in.
pub fn resumable_value_type(&self) -> Option<ValueType> {
match &self.kind {
&FuncInvocationKind::Internal(ref interpreter) => match interpreter.state() {
&InterpreterState::Resumable(ref value_type) => value_type.clone(),
_ => None,
},
&FuncInvocationKind::Host { .. } => None,
}
}
/// Start the invocation execution.
pub fn start_execution<'externals, E: Externals + 'externals>(
&mut self,
externals: &'externals mut E,
) -> Result<Option<RuntimeValue>, ResumableError> {
match self.kind {
FuncInvocationKind::Internal(ref mut interpreter) => {
if interpreter.state() != &InterpreterState::Initialized {
return Err(ResumableError::AlreadyStarted);
}
Ok(interpreter.start_execution(externals)?)
}
FuncInvocationKind::Host {
ref args,
ref mut finished,
ref host_func_index,
} => {
if *finished {
return Err(ResumableError::AlreadyStarted);
}
*finished = true;
Ok(externals.invoke_index(*host_func_index, args.as_ref().into())?)
}
}
}
/// Resume an execution if a previous trap of Host kind happened.
///
/// `return_val` must be of the value type [`resumable_value_type`], defined by the host function import. Otherwise,
/// `UnexpectedSignature` trap will be returned. The current invocation must also be resumable
/// [`is_resumable`]. Otherwise, a `NotResumable` error will be returned.
///
/// [`resumable_value_type`]: #method.resumable_value_type
/// [`is_resumable`]: #method.is_resumable
pub fn resume_execution<'externals, E: Externals + 'externals>(
&mut self,
return_val: Option<RuntimeValue>,
externals: &'externals mut E,
) -> Result<Option<RuntimeValue>, ResumableError> {
use crate::TrapKind;
if return_val.map(|v| v.value_type()) != self.resumable_value_type() {
return Err(ResumableError::Trap(Trap::new(
TrapKind::UnexpectedSignature,
)));
}
match &mut self.kind {
FuncInvocationKind::Internal(interpreter) => {
if interpreter.state().is_resumable() {
Ok(interpreter.resume_execution(return_val, externals)?)
} else {
Err(ResumableError::AlreadyStarted)
}
}
FuncInvocationKind::Host { .. } => Err(ResumableError::NotResumable),
}
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct FuncBody { pub struct FuncBody {
pub locals: Vec<Local>, pub locals: Vec<Local>,
pub opcodes: Opcodes, pub code: isa::Instructions,
pub labels: HashMap<usize, usize>,
} }

View File

@ -1,9 +1,9 @@
use std::rc::Rc; use alloc::rc::Rc;
use std::cell::Cell; use core::cell::Cell;
use parity_wasm::elements::ValueType as EValueType;
use types::ValueType;
use value::RuntimeValue; use value::RuntimeValue;
use Error; use Error;
use types::ValueType;
use parity_wasm::elements::{ValueType as EValueType};
/// Reference to a global variable (See [`GlobalInstance`] for details). /// Reference to a global variable (See [`GlobalInstance`] for details).
/// ///
@ -13,11 +13,11 @@ use parity_wasm::elements::{ValueType as EValueType};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct GlobalRef(Rc<GlobalInstance>); pub struct GlobalRef(Rc<GlobalInstance>);
impl ::std::ops::Deref for GlobalRef { impl ::core::ops::Deref for GlobalRef {
type Target = GlobalInstance; type Target = GlobalInstance;
fn deref(&self) -> &GlobalInstance { fn deref(&self) -> &GlobalInstance {
&self.0 &self.0
} }
} }
/// Runtime representation of a global variable (or `global` for short). /// Runtime representation of a global variable (or `global` for short).
@ -33,57 +33,59 @@ impl ::std::ops::Deref for GlobalRef {
/// [`I64`]: enum.RuntimeValue.html#variant.I64 /// [`I64`]: enum.RuntimeValue.html#variant.I64
#[derive(Debug)] #[derive(Debug)]
pub struct GlobalInstance { pub struct GlobalInstance {
val: Cell<RuntimeValue>, val: Cell<RuntimeValue>,
mutable: bool, mutable: bool,
} }
impl GlobalInstance { impl GlobalInstance {
/// Allocate a global variable instance. /// Allocate a global variable instance.
/// ///
/// Since it is possible to export only immutable globals, /// Since it is possible to export only immutable globals,
/// users likely want to set `mutable` to `false`. /// users likely want to set `mutable` to `false`.
pub fn alloc(val: RuntimeValue, mutable: bool) -> GlobalRef { pub fn alloc(val: RuntimeValue, mutable: bool) -> GlobalRef {
GlobalRef(Rc::new(GlobalInstance { GlobalRef(Rc::new(GlobalInstance {
val: Cell::new(val), val: Cell::new(val),
mutable, mutable,
})) }))
} }
/// Change the value of this global variable. /// Change the value of this global variable.
/// ///
/// # Errors /// # Errors
/// ///
/// Returns `Err` if this global isn't mutable or if /// Returns `Err` if this global isn't mutable or if
/// type of `val` doesn't match global's type. /// type of `val` doesn't match global's type.
pub fn set(&self, val: RuntimeValue) -> Result<(), Error> { pub fn set(&self, val: RuntimeValue) -> Result<(), Error> {
if !self.mutable { if !self.mutable {
return Err(Error::Global("Attempt to change an immutable variable".into())); return Err(Error::Global(
} "Attempt to change an immutable variable".into(),
if self.value_type() != val.value_type() { ));
return Err(Error::Global("Attempt to change variable type".into())); }
} if self.value_type() != val.value_type() {
self.val.set(val); return Err(Error::Global("Attempt to change variable type".into()));
Ok(()) }
} self.val.set(val);
Ok(())
}
/// Get the value of this global variable. /// Get the value of this global variable.
pub fn get(&self) -> RuntimeValue { pub fn get(&self) -> RuntimeValue {
self.val.get() self.val.get()
} }
/// Returns if this global variable is mutable. /// Returns if this global variable is mutable.
/// ///
/// Note: Imported and/or exported globals are always immutable. /// Note: Imported and/or exported globals are always immutable.
pub fn is_mutable(&self) -> bool { pub fn is_mutable(&self) -> bool {
self.mutable self.mutable
} }
/// Returns value type of this global variable. /// Returns value type of this global variable.
pub fn value_type(&self) -> ValueType { pub fn value_type(&self) -> ValueType {
self.val.get().value_type() self.val.get().value_type()
} }
pub(crate) fn elements_value_type(&self) -> EValueType { pub(crate) fn elements_value_type(&self) -> EValueType {
self.value_type().into_elements() self.value_type().into_elements()
} }
} }

View File

@ -1,53 +1,73 @@
use std::any::TypeId; use core::any::TypeId;
use value::{RuntimeValue, TryInto}; use value::{FromRuntimeValue, RuntimeValue};
use {TrapKind, Trap}; use {Trap, TrapKind};
/// Safe wrapper for list of arguments. /// Wrapper around slice of [`RuntimeValue`] for using it
/// as an argument list conveniently.
///
/// [`RuntimeValue`]: enum.RuntimeValue.html
#[derive(Debug)] #[derive(Debug)]
pub struct RuntimeArgs<'a>(&'a [RuntimeValue]); pub struct RuntimeArgs<'a>(&'a [RuntimeValue]);
impl<'a> From<&'a [RuntimeValue]> for RuntimeArgs<'a> { impl<'a> From<&'a [RuntimeValue]> for RuntimeArgs<'a> {
fn from(inner: &'a [RuntimeValue]) -> Self { fn from(inner: &'a [RuntimeValue]) -> Self {
RuntimeArgs(inner) RuntimeArgs(inner)
} }
}
impl<'a> AsRef<[RuntimeValue]> for RuntimeArgs<'a> {
fn as_ref(&self) -> &[RuntimeValue] {
self.0
}
} }
impl<'a> RuntimeArgs<'a> { impl<'a> RuntimeArgs<'a> {
/// Extract argument by index `idx`. /// Extract argument by index `idx`.
/// ///
/// # Errors /// # Errors
/// ///
/// Returns `Err` if cast is invalid or not enough arguments. /// Returns `Err` if cast is invalid or not enough arguments.
pub fn nth_checked<T>(&self, idx: usize) -> Result<T, Trap> where RuntimeValue: TryInto<T, ::value::Error> { pub fn nth_checked<T>(&self, idx: usize) -> Result<T, Trap>
Ok(self.nth_value_checked(idx)?.try_into().map_err(|_| TrapKind::UnexpectedSignature)?) where
} T: FromRuntimeValue,
{
Ok(self
.nth_value_checked(idx)?
.try_into()
.ok_or_else(|| TrapKind::UnexpectedSignature)?)
}
/// Extract argument as a [`RuntimeValue`] by index `idx`. /// Extract argument as a [`RuntimeValue`] by index `idx`.
/// ///
/// # Errors /// # Errors
/// ///
/// Returns `Err` if this list has not enough arguments. /// Returns `Err` if this list has not enough arguments.
pub fn nth_value_checked(&self, idx: usize) -> Result<RuntimeValue, Trap> { ///
if self.0.len() <= idx { /// [`RuntimeValue`]: enum.RuntimeValue.html
return Err(TrapKind::UnexpectedSignature.into()); pub fn nth_value_checked(&self, idx: usize) -> Result<RuntimeValue, Trap> {
} if self.0.len() <= idx {
Ok(self.0[idx]) return Err(TrapKind::UnexpectedSignature.into());
} }
Ok(self.0[idx])
}
/// Extract argument by index `idx`. /// Extract argument by index `idx`.
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if cast is invalid or not enough arguments. /// Panics if cast is invalid or not enough arguments.
pub fn nth<T>(&self, idx: usize) -> T where RuntimeValue: TryInto<T, ::value::Error> { pub fn nth<T>(&self, idx: usize) -> T
let value = self.nth_value_checked(idx).expect("Invalid argument index"); where
value.try_into().expect("Unexpected argument type") T: FromRuntimeValue,
} {
let value = self.nth_value_checked(idx).expect("Invalid argument index");
value.try_into().expect("Unexpected argument type")
}
/// Total number of arguments /// Total number of arguments
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.0.len() self.0.len()
} }
} }
/// Trait that allows the host to return custom error. /// Trait that allows the host to return custom error.
@ -87,32 +107,32 @@ impl<'a> RuntimeArgs<'a> {
/// _ => panic!(), /// _ => panic!(),
/// } /// }
/// ``` /// ```
pub trait HostError: 'static + ::std::fmt::Display + ::std::fmt::Debug + Send + Sync { pub trait HostError: 'static + ::core::fmt::Display + ::core::fmt::Debug + Send + Sync {
#[doc(hidden)] #[doc(hidden)]
fn __private_get_type_id__(&self) -> TypeId { fn __private_get_type_id__(&self) -> TypeId {
TypeId::of::<Self>() TypeId::of::<Self>()
} }
} }
impl HostError { impl dyn HostError {
/// Attempt to downcast this `HostError` to a concrete type by reference. /// Attempt to downcast this `HostError` to a concrete type by reference.
pub fn downcast_ref<T: HostError>(&self) -> Option<&T> { pub fn downcast_ref<T: HostError>(&self) -> Option<&T> {
if self.__private_get_type_id__() == TypeId::of::<T>() { if self.__private_get_type_id__() == TypeId::of::<T>() {
unsafe { Some(&*(self as *const HostError as *const T)) } unsafe { Some(&*(self as *const dyn HostError as *const T)) }
} else { } else {
None None
} }
} }
/// Attempt to downcast this `HostError` to a concrete type by mutable /// Attempt to downcast this `HostError` to a concrete type by mutable
/// reference. /// reference.
pub fn downcast_mut<T: HostError>(&mut self) -> Option<&mut T> { pub fn downcast_mut<T: HostError>(&mut self) -> Option<&mut T> {
if self.__private_get_type_id__() == TypeId::of::<T>() { if self.__private_get_type_id__() == TypeId::of::<T>() {
unsafe { Some(&mut *(self as *mut HostError as *mut T)) } unsafe { Some(&mut *(self as *mut dyn HostError as *mut T)) }
} else { } else {
None None
} }
} }
} }
/// Trait that allows to implement host functions. /// Trait that allows to implement host functions.
@ -179,20 +199,26 @@ impl HostError {
/// } /// }
/// }; /// };
/// ///
/// if !self.check_signature(index, signature) {
/// return Err(Error::Instantiation(
/// format!("Export {} has a bad signature", field_name)
/// ));
/// }
///
/// Ok(FuncInstance::alloc_host( /// Ok(FuncInstance::alloc_host(
/// Signature::new(&[ValueType::I32, ValueType::I32][..], Some(ValueType::I32)), /// Signature::new(&[ValueType::I32, ValueType::I32][..], Some(ValueType::I32)),
/// ADD_FUNC_INDEX, /// index,
/// )) /// ))
/// } /// }
/// } /// }
/// ``` /// ```
pub trait Externals { pub trait Externals {
/// Perform invoke of a host function by specified `index`. /// Perform invoke of a host function by specified `index`.
fn invoke_index( fn invoke_index(
&mut self, &mut self,
index: usize, index: usize,
args: RuntimeArgs, args: RuntimeArgs,
) -> Result<Option<RuntimeValue>, Trap>; ) -> Result<Option<RuntimeValue>, Trap>;
} }
/// Implementation of [`Externals`] that just traps on [`invoke_index`]. /// Implementation of [`Externals`] that just traps on [`invoke_index`].
@ -202,35 +228,34 @@ pub trait Externals {
pub struct NopExternals; pub struct NopExternals;
impl Externals for NopExternals { impl Externals for NopExternals {
fn invoke_index( fn invoke_index(
&mut self, &mut self,
_index: usize, _index: usize,
_args: RuntimeArgs, _args: RuntimeArgs,
) -> Result<Option<RuntimeValue>, Trap> { ) -> Result<Option<RuntimeValue>, Trap> {
Err(TrapKind::Unreachable.into()) Err(TrapKind::Unreachable.into())
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use value::RuntimeValue; use super::{HostError, RuntimeArgs};
use super::{RuntimeArgs, HostError}; use value::RuntimeValue;
#[test] #[test]
fn i32_runtime_args() { fn i32_runtime_args() {
let args: RuntimeArgs = (&[RuntimeValue::I32(0)][..]).into(); let args: RuntimeArgs = (&[RuntimeValue::I32(0)][..]).into();
let val: i32 = args.nth_checked(0).unwrap(); let val: i32 = args.nth_checked(0).unwrap();
assert_eq!(val, 0); assert_eq!(val, 0);
} }
#[test] #[test]
fn i64_invalid_arg_cast() { fn i64_invalid_arg_cast() {
let args: RuntimeArgs = (&[RuntimeValue::I64(90534534545322)][..]).into(); let args: RuntimeArgs = (&[RuntimeValue::I64(90534534545322)][..]).into();
assert!(args.nth_checked::<i32>(0).is_err()); assert!(args.nth_checked::<i32>(0).is_err());
} }
// Tests that `HostError` trait is object safe. // Tests that `HostError` trait is object safe.
fn _host_error_is_object_safe(_: &HostError) { fn _host_error_is_object_safe(_: &dyn HostError) {}
}
} }

View File

@ -1,13 +1,13 @@
use std::collections::HashMap; use alloc::{collections::BTreeMap, string::String};
use func::FuncRef;
use global::GlobalRef; use global::GlobalRef;
use memory::MemoryRef; use memory::MemoryRef;
use func::FuncRef;
use table::TableRef;
use module::ModuleRef; use module::ModuleRef;
use types::{GlobalDescriptor, TableDescriptor, MemoryDescriptor}; use table::TableRef;
use types::{GlobalDescriptor, MemoryDescriptor, TableDescriptor};
use {Error, Signature}; use {Error, Signature};
/// Resolver of a module's dependencies. /// Resolver of a module's dependencies.
/// ///
/// A module have dependencies in a form of a list of imports (i.e. /// A module have dependencies in a form of a list of imports (i.e.
@ -20,56 +20,55 @@ use {Error, Signature};
/// ///
/// [`ImportsBuilder`]: struct.ImportsBuilder.html /// [`ImportsBuilder`]: struct.ImportsBuilder.html
pub trait ImportResolver { pub trait ImportResolver {
/// Resolve a function.
///
/// Returned function should match given `signature`, i.e. all parameter types and return value should have exact match.
/// Otherwise, link-time error will occur.
fn resolve_func(
&self,
_module_name: &str,
field_name: &str,
_signature: &Signature,
) -> Result<FuncRef, Error>;
/// Resolve a function. /// Resolve a global variable.
/// ///
/// Returned function should match given `signature`, i.e. all parameter types and return value should have exact match. /// Returned global should match given `descriptor`, i.e. type and mutability
/// Otherwise, link-time error will occur. /// should match. Otherwise, link-time error will occur.
fn resolve_func( fn resolve_global(
&self, &self,
_module_name: &str, module_name: &str,
field_name: &str, field_name: &str,
_signature: &Signature, descriptor: &GlobalDescriptor,
) -> Result<FuncRef, Error>; ) -> Result<GlobalRef, Error>;
/// Resolve a global variable. /// Resolve a memory.
/// ///
/// Returned global should match given `descriptor`, i.e. type and mutability /// Returned memory should match requested memory (described by the `descriptor`),
/// should match. Otherwise, link-time error will occur. /// i.e. initial size of a returned memory should be equal or larger than requested memory.
fn resolve_global( /// Furthermore, if requested memory have maximum size, returned memory either should have
&self, /// equal or larger maximum size or have no maximum size at all.
module_name: &str, /// If returned memory doesn't match the requested then link-time error will occur.
field_name: &str, fn resolve_memory(
descriptor: &GlobalDescriptor, &self,
) -> Result<GlobalRef, Error>; module_name: &str,
field_name: &str,
descriptor: &MemoryDescriptor,
) -> Result<MemoryRef, Error>;
/// Resolve a memory. /// Resolve a table.
/// ///
/// Returned memory should match requested memory (described by the `descriptor`), /// Returned table should match requested table (described by the `descriptor`),
/// i.e. initial size of a returned memory should be equal or larger than requested memory. /// i.e. initial size of a returned table should be equal or larger than requested table.
/// Furthermore, if requested memory have maximum size, returned memory either should have /// Furthermore, if requested memory have maximum size, returned memory either should have
/// equal or larger maximum size or have no maximum size at all. /// equal or larger maximum size or have no maximum size at all.
/// If returned memory doesn't match the requested then link-time error will occur. /// If returned table doesn't match the requested then link-time error will occur.
fn resolve_memory( fn resolve_table(
&self, &self,
module_name: &str, module_name: &str,
field_name: &str, field_name: &str,
descriptor: &MemoryDescriptor, descriptor: &TableDescriptor,
) -> Result<MemoryRef, Error>; ) -> Result<TableRef, Error>;
/// Resolve a table.
///
/// Returned table should match requested table (described by the `descriptor`),
/// i.e. initial size of a returned table should be equal or larger than requested table.
/// Furthermore, if requested memory have maximum size, returned memory either should have
/// equal or larger maximum size or have no maximum size at all.
/// If returned table doesn't match the requested then link-time error will occur.
fn resolve_table(
&self,
module_name: &str,
field_name: &str,
descriptor: &TableDescriptor,
) -> Result<TableRef, Error>;
} }
/// Convenience builder of [`ImportResolver`]. /// Convenience builder of [`ImportResolver`].
@ -101,216 +100,212 @@ pub trait ImportResolver {
/// [`ImportResolver`]: trait.ImportResolver.html /// [`ImportResolver`]: trait.ImportResolver.html
/// [`ModuleImportResolver`]: trait.ModuleImportResolver.html /// [`ModuleImportResolver`]: trait.ModuleImportResolver.html
pub struct ImportsBuilder<'a> { pub struct ImportsBuilder<'a> {
modules: HashMap<String, &'a ModuleImportResolver>, modules: BTreeMap<String, &'a dyn ModuleImportResolver>,
} }
impl<'a> Default for ImportsBuilder<'a> { impl<'a> Default for ImportsBuilder<'a> {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
} }
impl<'a> ImportsBuilder<'a> { impl<'a> ImportsBuilder<'a> {
/// Create an empty `ImportsBuilder`. /// Create an empty `ImportsBuilder`.
pub fn new() -> ImportsBuilder<'a> { pub fn new() -> ImportsBuilder<'a> {
ImportsBuilder { modules: HashMap::new() } ImportsBuilder {
} modules: BTreeMap::new(),
}
}
/// Register an resolver by a name. /// Register an resolver by a name.
pub fn with_resolver<N: Into<String>>( pub fn with_resolver<N: Into<String>>(
mut self, mut self,
name: N, name: N,
resolver: &'a ModuleImportResolver, resolver: &'a dyn ModuleImportResolver,
) -> Self { ) -> Self {
self.modules.insert(name.into(), resolver); self.modules.insert(name.into(), resolver);
self self
} }
/// Register an resolver by a name. /// Register an resolver by a name.
/// ///
/// Mutable borrowed version. /// Mutable borrowed version.
pub fn push_resolver<N: Into<String>>(&mut self, name: N, resolver: &'a ModuleImportResolver) { pub fn push_resolver<N: Into<String>>(
self.modules.insert(name.into(), resolver); &mut self,
} name: N,
resolver: &'a dyn ModuleImportResolver,
) {
self.modules.insert(name.into(), resolver);
}
fn resolver(&self, name: &str) -> Option<&ModuleImportResolver> { fn resolver(&self, name: &str) -> Option<&dyn ModuleImportResolver> {
self.modules.get(name).cloned() self.modules.get(name).cloned()
} }
} }
impl<'a> ImportResolver for ImportsBuilder<'a> { impl<'a> ImportResolver for ImportsBuilder<'a> {
fn resolve_func( fn resolve_func(
&self, &self,
module_name: &str, module_name: &str,
field_name: &str, field_name: &str,
signature: &Signature, signature: &Signature,
) -> Result<FuncRef, Error> { ) -> Result<FuncRef, Error> {
self.resolver(module_name).ok_or_else(|| self.resolver(module_name)
Error::Instantiation(format!("Module {} not found", module_name)) .ok_or_else(|| Error::Instantiation(format!("Module {} not found", module_name)))?
)?.resolve_func(field_name, signature) .resolve_func(field_name, signature)
} }
fn resolve_global( fn resolve_global(
&self, &self,
module_name: &str, module_name: &str,
field_name: &str, field_name: &str,
global_type: &GlobalDescriptor, global_type: &GlobalDescriptor,
) -> Result<GlobalRef, Error> { ) -> Result<GlobalRef, Error> {
self.resolver(module_name).ok_or_else(|| self.resolver(module_name)
Error::Instantiation(format!("Module {} not found", module_name)) .ok_or_else(|| Error::Instantiation(format!("Module {} not found", module_name)))?
)?.resolve_global(field_name, global_type) .resolve_global(field_name, global_type)
} }
fn resolve_memory( fn resolve_memory(
&self, &self,
module_name: &str, module_name: &str,
field_name: &str, field_name: &str,
memory_type: &MemoryDescriptor, memory_type: &MemoryDescriptor,
) -> Result<MemoryRef, Error> { ) -> Result<MemoryRef, Error> {
self.resolver(module_name).ok_or_else(|| self.resolver(module_name)
Error::Instantiation(format!("Module {} not found", module_name)) .ok_or_else(|| Error::Instantiation(format!("Module {} not found", module_name)))?
)?.resolve_memory(field_name, memory_type) .resolve_memory(field_name, memory_type)
} }
fn resolve_table( fn resolve_table(
&self, &self,
module_name: &str, module_name: &str,
field_name: &str, field_name: &str,
table_type: &TableDescriptor, table_type: &TableDescriptor,
) -> Result<TableRef, Error> { ) -> Result<TableRef, Error> {
self.resolver(module_name).ok_or_else(|| self.resolver(module_name)
Error::Instantiation(format!("Module {} not found", module_name)) .ok_or_else(|| Error::Instantiation(format!("Module {} not found", module_name)))?
)?.resolve_table(field_name, table_type) .resolve_table(field_name, table_type)
} }
} }
/// Version of [`ImportResolver`] specialized for a single module. /// Version of [`ImportResolver`] specialized for a single module.
/// ///
/// [`ImportResolver`]: trait.ImportResolver.html /// [`ImportResolver`]: trait.ImportResolver.html
pub trait ModuleImportResolver { pub trait ModuleImportResolver {
/// Resolve a function. /// Resolve a function.
/// ///
/// See [`ImportResolver::resolve_func`] for details. /// See [`ImportResolver::resolve_func`] for details.
/// ///
/// [`ImportResolver::resolve_func`]: trait.ImportResolver.html#tymethod.resolve_func /// [`ImportResolver::resolve_func`]: trait.ImportResolver.html#tymethod.resolve_func
fn resolve_func( fn resolve_func(&self, field_name: &str, _signature: &Signature) -> Result<FuncRef, Error> {
&self, Err(Error::Instantiation(format!(
field_name: &str, "Export {} not found",
_signature: &Signature, field_name
) -> Result<FuncRef, Error> { )))
Err(Error::Instantiation( }
format!("Export {} not found", field_name),
))
}
/// Resolve a global variable. /// Resolve a global variable.
/// ///
/// See [`ImportResolver::resolve_global`] for details. /// See [`ImportResolver::resolve_global`] for details.
/// ///
/// [`ImportResolver::resolve_global`]: trait.ImportResolver.html#tymethod.resolve_global /// [`ImportResolver::resolve_global`]: trait.ImportResolver.html#tymethod.resolve_global
fn resolve_global( fn resolve_global(
&self, &self,
field_name: &str, field_name: &str,
_global_type: &GlobalDescriptor, _global_type: &GlobalDescriptor,
) -> Result<GlobalRef, Error> { ) -> Result<GlobalRef, Error> {
Err(Error::Instantiation( Err(Error::Instantiation(format!(
format!("Export {} not found", field_name), "Export {} not found",
)) field_name
} )))
}
/// Resolve a memory. /// Resolve a memory.
/// ///
/// See [`ImportResolver::resolve_memory`] for details. /// See [`ImportResolver::resolve_memory`] for details.
/// ///
/// [`ImportResolver::resolve_memory`]: trait.ImportResolver.html#tymethod.resolve_memory /// [`ImportResolver::resolve_memory`]: trait.ImportResolver.html#tymethod.resolve_memory
fn resolve_memory( fn resolve_memory(
&self, &self,
field_name: &str, field_name: &str,
_memory_type: &MemoryDescriptor, _memory_type: &MemoryDescriptor,
) -> Result<MemoryRef, Error> { ) -> Result<MemoryRef, Error> {
Err(Error::Instantiation( Err(Error::Instantiation(format!(
format!("Export {} not found", field_name), "Export {} not found",
)) field_name
} )))
}
/// Resolve a table. /// Resolve a table.
/// ///
/// See [`ImportResolver::resolve_table`] for details. /// See [`ImportResolver::resolve_table`] for details.
/// ///
/// [`ImportResolver::resolve_table`]: trait.ImportResolver.html#tymethod.resolve_table /// [`ImportResolver::resolve_table`]: trait.ImportResolver.html#tymethod.resolve_table
fn resolve_table( fn resolve_table(
&self, &self,
field_name: &str, field_name: &str,
_table_type: &TableDescriptor, _table_type: &TableDescriptor,
) -> Result<TableRef, Error> { ) -> Result<TableRef, Error> {
Err(Error::Instantiation( Err(Error::Instantiation(format!(
format!("Export {} not found", field_name), "Export {} not found",
)) field_name
} )))
}
} }
impl ModuleImportResolver for ModuleRef { impl ModuleImportResolver for ModuleRef {
fn resolve_func( fn resolve_func(&self, field_name: &str, _signature: &Signature) -> Result<FuncRef, Error> {
&self, Ok(self
field_name: &str, .export_by_name(field_name)
_signature: &Signature, .ok_or_else(|| Error::Instantiation(format!("Export {} not found", field_name)))?
) -> Result<FuncRef, Error> { .as_func()
Ok(self.export_by_name(field_name) .cloned()
.ok_or_else(|| { .ok_or_else(|| {
Error::Instantiation(format!("Export {} not found", field_name)) Error::Instantiation(format!("Export {} is not a function", field_name))
})? })?)
.as_func() }
.cloned()
.ok_or_else(|| {
Error::Instantiation(format!("Export {} is not a function", field_name))
})?)
}
fn resolve_global( fn resolve_global(
&self, &self,
field_name: &str, field_name: &str,
_global_type: &GlobalDescriptor, _global_type: &GlobalDescriptor,
) -> Result<GlobalRef, Error> { ) -> Result<GlobalRef, Error> {
Ok(self.export_by_name(field_name) Ok(self
.ok_or_else(|| { .export_by_name(field_name)
Error::Instantiation(format!("Export {} not found", field_name)) .ok_or_else(|| Error::Instantiation(format!("Export {} not found", field_name)))?
})? .as_global()
.as_global() .cloned()
.cloned() .ok_or_else(|| {
.ok_or_else(|| { Error::Instantiation(format!("Export {} is not a global", field_name))
Error::Instantiation(format!("Export {} is not a global", field_name)) })?)
})?) }
}
fn resolve_memory( fn resolve_memory(
&self, &self,
field_name: &str, field_name: &str,
_memory_type: &MemoryDescriptor, _memory_type: &MemoryDescriptor,
) -> Result<MemoryRef, Error> { ) -> Result<MemoryRef, Error> {
Ok(self.export_by_name(field_name) Ok(self
.ok_or_else(|| { .export_by_name(field_name)
Error::Instantiation(format!("Export {} not found", field_name)) .ok_or_else(|| Error::Instantiation(format!("Export {} not found", field_name)))?
})? .as_memory()
.as_memory() .cloned()
.cloned() .ok_or_else(|| {
.ok_or_else(|| { Error::Instantiation(format!("Export {} is not a memory", field_name))
Error::Instantiation(format!("Export {} is not a memory", field_name)) })?)
})?) }
}
fn resolve_table( fn resolve_table(
&self, &self,
field_name: &str, field_name: &str,
_table_type: &TableDescriptor, _table_type: &TableDescriptor,
) -> Result<TableRef, Error> { ) -> Result<TableRef, Error> {
Ok(self.export_by_name(field_name) Ok(self
.ok_or_else(|| { .export_by_name(field_name)
Error::Instantiation(format!("Export {} not found", field_name)) .ok_or_else(|| Error::Instantiation(format!("Export {} not found", field_name)))?
})? .as_table()
.as_table() .cloned()
.cloned() .ok_or_else(|| Error::Instantiation(format!("Export {} is not a table", field_name)))?)
.ok_or_else(|| { }
Error::Instantiation(format!("Export {} is not a table", field_name))
})?)
}
} }

802
src/isa.rs Normal file
View File

@ -0,0 +1,802 @@
//! An instruction set used by wasmi.
//!
//! The instruction set is mostly derived from Wasm. However,
//! there is a substantial difference.
//!
//! # Structured Stack Machine vs Plain One
//!
//! Wasm is a structured stack machine. Wasm encodes control flow in structures
//! similar to that commonly found in a programming languages
//! such as if, while. That contrasts to a plain stack machine which
//! encodes all control flow with goto-like instructions.
//!
//! Structured stack machine code aligns well with goals of Wasm,
//! namely providing fast validation of Wasm code and compilation to native code.
//!
//! Unfortunately, the downside of structured stack machine code is
//! that it is less convenient to interpret. For example, let's look at
//! the following example in hypothetical structured stack machine:
//!
//! ```plain
//! loop
//! ...
//! if_true_jump_to_end
//! ...
//! end
//! ```
//!
//! To execute `if_true_jump_to_end` , the interpreter needs to skip all instructions
//! until it reaches the *matching* `end`. That's quite inefficient compared
//! to a plain goto to the specific position.
//!
//! Because of this, the translation from the Wasm structured stack machine into a
//! plain one is taking place.
//!
//! # Locals
//!
//! In a plain stack machine local variables and arguments live on the stack. Instead of
//! accessing predefined locals slots in a plain stack machine locals are addressed relative
//! to the current stack pointer. Because of this instead of taking an index of a local
//! in {get,set,tee}_local operations, they take a relative depth as immediate. This works
//! because at each instruction we always know the current stack height.
//!
//! Roughly, the stack layout looks like this
//!
//! | caller arguments |
//! | - arg 1 |
//! | - arg 2 |
//! +------------------+
//! | callee locals |
//! | - var 1 |
//! | - var 2 |
//! +------------------+
//! | operands |
//! | - op 1 |
//! | - op 2 |
//! | | <-- current stack pointer
//! +------------------+
//!
//! # Differences from Wasm
//!
//! - There is no `nop` instruction.
//! - All control flow structures are flattened to plain gotos.
//! - Implicit returns via reaching function scope `End` are replaced with an explicit `return` instruction.
//! - Locals live on the value stack now.
//! - Load/store instructions doesn't take `align` parameter.
//! - *.const store value in straight encoding.
//! - Reserved immediates are ignored for `call_indirect`, `current_memory`, `grow_memory`.
//!
use alloc::vec::Vec;
/// Should we keep a value before "discarding" a stack frame?
///
/// Note that this is a `enum` since Wasm doesn't support multiple return
/// values at the moment.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Keep {
None,
/// Pop one value from the yet-to-be-discarded stack frame to the
/// current stack frame.
Single,
}
impl Keep {
/// Reutrns a number of items that should be kept on the stack.
pub fn count(&self) -> u32 {
match *self {
Keep::None => 0,
Keep::Single => 1,
}
}
}
/// Specifies how many values we should keep and how many we should drop.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct DropKeep {
pub drop: u32,
pub keep: Keep,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Target {
pub dst_pc: u32,
pub drop_keep: DropKeep,
}
/// A relocation entry that specifies.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Reloc {
/// Patch the destination of the branch instruction (br, br_eqz, br_nez)
/// at the specified pc.
Br { pc: u32 },
/// Patch the specified destination index inside of br_table instruction at
/// the specified pc.
BrTable { pc: u32, idx: usize },
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct BrTargets<'a> {
stream: &'a [InstructionInternal],
}
impl<'a> BrTargets<'a> {
pub(crate) fn from_internal(targets: &'a [InstructionInternal]) -> Self {
BrTargets { stream: targets }
}
#[inline]
pub fn get(&self, index: u32) -> Target {
match self.stream[index.min(self.stream.len() as u32 - 1) as usize] {
InstructionInternal::BrTableTarget(target) => target,
_ => panic!("BrTable has incorrect target count"),
}
}
}
/// The main interpreted instruction type. This is what is returned by `InstructionIter`, but
/// it is not what is stored internally. For that, see `InstructionInternal`.
#[derive(Debug, Clone, PartialEq)]
pub enum Instruction<'a> {
/// Push a local variable or an argument from the specified depth.
GetLocal(u32),
/// Pop a value and put it in at the specified depth.
SetLocal(u32),
/// Copy a value to the specified depth.
TeeLocal(u32),
/// Similar to the Wasm ones, but instead of a label depth
/// they specify direct PC.
Br(Target),
BrIfEqz(Target),
BrIfNez(Target),
/// br_table [t1 t2 t3 .. tn] tdefault
///
/// Pops the value from the stack. Then this value is used as an index
/// to the branch table.
///
/// However, the last target represents the default target. So if the index
/// is greater than length of the branch table, then the last index will be used.
///
/// Validation ensures that there should be at least one target.
BrTable(BrTargets<'a>),
Unreachable,
Return(DropKeep),
Call(u32),
CallIndirect(u32),
Drop,
Select,
GetGlobal(u32),
SetGlobal(u32),
I32Load(u32),
I64Load(u32),
F32Load(u32),
F64Load(u32),
I32Load8S(u32),
I32Load8U(u32),
I32Load16S(u32),
I32Load16U(u32),
I64Load8S(u32),
I64Load8U(u32),
I64Load16S(u32),
I64Load16U(u32),
I64Load32S(u32),
I64Load32U(u32),
I32Store(u32),
I64Store(u32),
F32Store(u32),
F64Store(u32),
I32Store8(u32),
I32Store16(u32),
I64Store8(u32),
I64Store16(u32),
I64Store32(u32),
CurrentMemory,
GrowMemory,
I32Const(i32),
I64Const(i64),
F32Const(u32),
F64Const(u64),
I32Eqz,
I32Eq,
I32Ne,
I32LtS,
I32LtU,
I32GtS,
I32GtU,
I32LeS,
I32LeU,
I32GeS,
I32GeU,
I64Eqz,
I64Eq,
I64Ne,
I64LtS,
I64LtU,
I64GtS,
I64GtU,
I64LeS,
I64LeU,
I64GeS,
I64GeU,
F32Eq,
F32Ne,
F32Lt,
F32Gt,
F32Le,
F32Ge,
F64Eq,
F64Ne,
F64Lt,
F64Gt,
F64Le,
F64Ge,
I32Clz,
I32Ctz,
I32Popcnt,
I32Add,
I32Sub,
I32Mul,
I32DivS,
I32DivU,
I32RemS,
I32RemU,
I32And,
I32Or,
I32Xor,
I32Shl,
I32ShrS,
I32ShrU,
I32Rotl,
I32Rotr,
I64Clz,
I64Ctz,
I64Popcnt,
I64Add,
I64Sub,
I64Mul,
I64DivS,
I64DivU,
I64RemS,
I64RemU,
I64And,
I64Or,
I64Xor,
I64Shl,
I64ShrS,
I64ShrU,
I64Rotl,
I64Rotr,
F32Abs,
F32Neg,
F32Ceil,
F32Floor,
F32Trunc,
F32Nearest,
F32Sqrt,
F32Add,
F32Sub,
F32Mul,
F32Div,
F32Min,
F32Max,
F32Copysign,
F64Abs,
F64Neg,
F64Ceil,
F64Floor,
F64Trunc,
F64Nearest,
F64Sqrt,
F64Add,
F64Sub,
F64Mul,
F64Div,
F64Min,
F64Max,
F64Copysign,
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,
}
/// The internally-stored instruction type. This differs from `Instruction` in that the `BrTable`
/// target list is "unrolled" into seperate instructions in order to be able to A) improve cache
/// usage and B) allow this struct to be `Copy` and therefore allow `Instructions::clone` to be
/// a `memcpy`. It also means that `Instructions::drop` is trivial. The overall speedup on some
/// benchmarks is as high as 13%.
///
/// When returning instructions we convert to `Instruction`, whose `BrTable` variant internally
/// borrows the list of instructions and returns targets by reading it.
#[derive(Copy, Debug, Clone, PartialEq, Eq)]
pub(crate) enum InstructionInternal {
GetLocal(u32),
SetLocal(u32),
TeeLocal(u32),
Br(Target),
BrIfEqz(Target),
BrIfNez(Target),
BrTable { count: u32 },
BrTableTarget(Target),
Unreachable,
Return(DropKeep),
Call(u32),
CallIndirect(u32),
Drop,
Select,
GetGlobal(u32),
SetGlobal(u32),
I32Load(u32),
I64Load(u32),
F32Load(u32),
F64Load(u32),
I32Load8S(u32),
I32Load8U(u32),
I32Load16S(u32),
I32Load16U(u32),
I64Load8S(u32),
I64Load8U(u32),
I64Load16S(u32),
I64Load16U(u32),
I64Load32S(u32),
I64Load32U(u32),
I32Store(u32),
I64Store(u32),
F32Store(u32),
F64Store(u32),
I32Store8(u32),
I32Store16(u32),
I64Store8(u32),
I64Store16(u32),
I64Store32(u32),
CurrentMemory,
GrowMemory,
I32Const(i32),
I64Const(i64),
F32Const(u32),
F64Const(u64),
I32Eqz,
I32Eq,
I32Ne,
I32LtS,
I32LtU,
I32GtS,
I32GtU,
I32LeS,
I32LeU,
I32GeS,
I32GeU,
I64Eqz,
I64Eq,
I64Ne,
I64LtS,
I64LtU,
I64GtS,
I64GtU,
I64LeS,
I64LeU,
I64GeS,
I64GeU,
F32Eq,
F32Ne,
F32Lt,
F32Gt,
F32Le,
F32Ge,
F64Eq,
F64Ne,
F64Lt,
F64Gt,
F64Le,
F64Ge,
I32Clz,
I32Ctz,
I32Popcnt,
I32Add,
I32Sub,
I32Mul,
I32DivS,
I32DivU,
I32RemS,
I32RemU,
I32And,
I32Or,
I32Xor,
I32Shl,
I32ShrS,
I32ShrU,
I32Rotl,
I32Rotr,
I64Clz,
I64Ctz,
I64Popcnt,
I64Add,
I64Sub,
I64Mul,
I64DivS,
I64DivU,
I64RemS,
I64RemU,
I64And,
I64Or,
I64Xor,
I64Shl,
I64ShrS,
I64ShrU,
I64Rotl,
I64Rotr,
F32Abs,
F32Neg,
F32Ceil,
F32Floor,
F32Trunc,
F32Nearest,
F32Sqrt,
F32Add,
F32Sub,
F32Mul,
F32Div,
F32Min,
F32Max,
F32Copysign,
F64Abs,
F64Neg,
F64Ceil,
F64Floor,
F64Trunc,
F64Nearest,
F64Sqrt,
F64Add,
F64Sub,
F64Mul,
F64Div,
F64Min,
F64Max,
F64Copysign,
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,
}
#[derive(Debug, Clone)]
pub struct Instructions {
vec: Vec<InstructionInternal>,
}
impl Instructions {
pub fn with_capacity(capacity: usize) -> Self {
Instructions {
vec: Vec::with_capacity(capacity),
}
}
pub fn current_pc(&self) -> u32 {
self.vec.len() as u32
}
pub(crate) fn push(&mut self, instruction: InstructionInternal) {
self.vec.push(instruction);
}
pub fn patch_relocation(&mut self, reloc: Reloc, dst_pc: u32) {
match reloc {
Reloc::Br { pc } => match self.vec[pc as usize] {
InstructionInternal::Br(ref mut target)
| InstructionInternal::BrIfEqz(ref mut target)
| InstructionInternal::BrIfNez(ref mut target) => target.dst_pc = dst_pc,
_ => panic!("branch relocation points to a non-branch instruction"),
},
Reloc::BrTable { pc, idx } => match &mut self.vec[pc as usize + idx + 1] {
InstructionInternal::BrTableTarget(target) => target.dst_pc = dst_pc,
_ => panic!("brtable relocation points to not brtable instruction"),
},
}
}
pub fn iterate_from(&self, position: u32) -> InstructionIter {
InstructionIter {
instructions: &self.vec,
position,
}
}
}
pub struct InstructionIter<'a> {
instructions: &'a [InstructionInternal],
position: u32,
}
impl<'a> InstructionIter<'a> {
#[inline]
pub fn position(&self) -> u32 {
self.position
}
}
impl<'a> Iterator for InstructionIter<'a> {
type Item = Instruction<'a>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let internal = if let Some(i) = self.instructions.get(self.position as usize) {
i
} else {
return None;
};
let out = match *internal {
InstructionInternal::GetLocal(x) => Instruction::GetLocal(x),
InstructionInternal::SetLocal(x) => Instruction::SetLocal(x),
InstructionInternal::TeeLocal(x) => Instruction::TeeLocal(x),
InstructionInternal::Br(x) => Instruction::Br(x),
InstructionInternal::BrIfEqz(x) => Instruction::BrIfEqz(x),
InstructionInternal::BrIfNez(x) => Instruction::BrIfNez(x),
InstructionInternal::BrTable { count } => {
let start = self.position as usize + 1;
self.position += count;
Instruction::BrTable(BrTargets::from_internal(
&self.instructions[start..start + count as usize],
))
}
InstructionInternal::BrTableTarget(_) => panic!("Executed BrTableTarget"),
InstructionInternal::Unreachable => Instruction::Unreachable,
InstructionInternal::Return(x) => Instruction::Return(x),
InstructionInternal::Call(x) => Instruction::Call(x),
InstructionInternal::CallIndirect(x) => Instruction::CallIndirect(x),
InstructionInternal::Drop => Instruction::Drop,
InstructionInternal::Select => Instruction::Select,
InstructionInternal::GetGlobal(x) => Instruction::GetGlobal(x),
InstructionInternal::SetGlobal(x) => Instruction::SetGlobal(x),
InstructionInternal::I32Load(x) => Instruction::I32Load(x),
InstructionInternal::I64Load(x) => Instruction::I64Load(x),
InstructionInternal::F32Load(x) => Instruction::F32Load(x),
InstructionInternal::F64Load(x) => Instruction::F64Load(x),
InstructionInternal::I32Load8S(x) => Instruction::I32Load8S(x),
InstructionInternal::I32Load8U(x) => Instruction::I32Load8U(x),
InstructionInternal::I32Load16S(x) => Instruction::I32Load16S(x),
InstructionInternal::I32Load16U(x) => Instruction::I32Load16U(x),
InstructionInternal::I64Load8S(x) => Instruction::I64Load8S(x),
InstructionInternal::I64Load8U(x) => Instruction::I64Load8U(x),
InstructionInternal::I64Load16S(x) => Instruction::I64Load16S(x),
InstructionInternal::I64Load16U(x) => Instruction::I64Load16U(x),
InstructionInternal::I64Load32S(x) => Instruction::I64Load32S(x),
InstructionInternal::I64Load32U(x) => Instruction::I64Load32U(x),
InstructionInternal::I32Store(x) => Instruction::I32Store(x),
InstructionInternal::I64Store(x) => Instruction::I64Store(x),
InstructionInternal::F32Store(x) => Instruction::F32Store(x),
InstructionInternal::F64Store(x) => Instruction::F64Store(x),
InstructionInternal::I32Store8(x) => Instruction::I32Store8(x),
InstructionInternal::I32Store16(x) => Instruction::I32Store16(x),
InstructionInternal::I64Store8(x) => Instruction::I64Store8(x),
InstructionInternal::I64Store16(x) => Instruction::I64Store16(x),
InstructionInternal::I64Store32(x) => Instruction::I64Store32(x),
InstructionInternal::CurrentMemory => Instruction::CurrentMemory,
InstructionInternal::GrowMemory => Instruction::GrowMemory,
InstructionInternal::I32Const(x) => Instruction::I32Const(x),
InstructionInternal::I64Const(x) => Instruction::I64Const(x),
InstructionInternal::F32Const(x) => Instruction::F32Const(x),
InstructionInternal::F64Const(x) => Instruction::F64Const(x),
InstructionInternal::I32Eqz => Instruction::I32Eqz,
InstructionInternal::I32Eq => Instruction::I32Eq,
InstructionInternal::I32Ne => Instruction::I32Ne,
InstructionInternal::I32LtS => Instruction::I32LtS,
InstructionInternal::I32LtU => Instruction::I32LtU,
InstructionInternal::I32GtS => Instruction::I32GtS,
InstructionInternal::I32GtU => Instruction::I32GtU,
InstructionInternal::I32LeS => Instruction::I32LeS,
InstructionInternal::I32LeU => Instruction::I32LeU,
InstructionInternal::I32GeS => Instruction::I32GeS,
InstructionInternal::I32GeU => Instruction::I32GeU,
InstructionInternal::I64Eqz => Instruction::I64Eqz,
InstructionInternal::I64Eq => Instruction::I64Eq,
InstructionInternal::I64Ne => Instruction::I64Ne,
InstructionInternal::I64LtS => Instruction::I64LtS,
InstructionInternal::I64LtU => Instruction::I64LtU,
InstructionInternal::I64GtS => Instruction::I64GtS,
InstructionInternal::I64GtU => Instruction::I64GtU,
InstructionInternal::I64LeS => Instruction::I64LeS,
InstructionInternal::I64LeU => Instruction::I64LeU,
InstructionInternal::I64GeS => Instruction::I64GeS,
InstructionInternal::I64GeU => Instruction::I64GeU,
InstructionInternal::F32Eq => Instruction::F32Eq,
InstructionInternal::F32Ne => Instruction::F32Ne,
InstructionInternal::F32Lt => Instruction::F32Lt,
InstructionInternal::F32Gt => Instruction::F32Gt,
InstructionInternal::F32Le => Instruction::F32Le,
InstructionInternal::F32Ge => Instruction::F32Ge,
InstructionInternal::F64Eq => Instruction::F64Eq,
InstructionInternal::F64Ne => Instruction::F64Ne,
InstructionInternal::F64Lt => Instruction::F64Lt,
InstructionInternal::F64Gt => Instruction::F64Gt,
InstructionInternal::F64Le => Instruction::F64Le,
InstructionInternal::F64Ge => Instruction::F64Ge,
InstructionInternal::I32Clz => Instruction::I32Clz,
InstructionInternal::I32Ctz => Instruction::I32Ctz,
InstructionInternal::I32Popcnt => Instruction::I32Popcnt,
InstructionInternal::I32Add => Instruction::I32Add,
InstructionInternal::I32Sub => Instruction::I32Sub,
InstructionInternal::I32Mul => Instruction::I32Mul,
InstructionInternal::I32DivS => Instruction::I32DivS,
InstructionInternal::I32DivU => Instruction::I32DivU,
InstructionInternal::I32RemS => Instruction::I32RemS,
InstructionInternal::I32RemU => Instruction::I32RemU,
InstructionInternal::I32And => Instruction::I32And,
InstructionInternal::I32Or => Instruction::I32Or,
InstructionInternal::I32Xor => Instruction::I32Xor,
InstructionInternal::I32Shl => Instruction::I32Shl,
InstructionInternal::I32ShrS => Instruction::I32ShrS,
InstructionInternal::I32ShrU => Instruction::I32ShrU,
InstructionInternal::I32Rotl => Instruction::I32Rotl,
InstructionInternal::I32Rotr => Instruction::I32Rotr,
InstructionInternal::I64Clz => Instruction::I64Clz,
InstructionInternal::I64Ctz => Instruction::I64Ctz,
InstructionInternal::I64Popcnt => Instruction::I64Popcnt,
InstructionInternal::I64Add => Instruction::I64Add,
InstructionInternal::I64Sub => Instruction::I64Sub,
InstructionInternal::I64Mul => Instruction::I64Mul,
InstructionInternal::I64DivS => Instruction::I64DivS,
InstructionInternal::I64DivU => Instruction::I64DivU,
InstructionInternal::I64RemS => Instruction::I64RemS,
InstructionInternal::I64RemU => Instruction::I64RemU,
InstructionInternal::I64And => Instruction::I64And,
InstructionInternal::I64Or => Instruction::I64Or,
InstructionInternal::I64Xor => Instruction::I64Xor,
InstructionInternal::I64Shl => Instruction::I64Shl,
InstructionInternal::I64ShrS => Instruction::I64ShrS,
InstructionInternal::I64ShrU => Instruction::I64ShrU,
InstructionInternal::I64Rotl => Instruction::I64Rotl,
InstructionInternal::I64Rotr => Instruction::I64Rotr,
InstructionInternal::F32Abs => Instruction::F32Abs,
InstructionInternal::F32Neg => Instruction::F32Neg,
InstructionInternal::F32Ceil => Instruction::F32Ceil,
InstructionInternal::F32Floor => Instruction::F32Floor,
InstructionInternal::F32Trunc => Instruction::F32Trunc,
InstructionInternal::F32Nearest => Instruction::F32Nearest,
InstructionInternal::F32Sqrt => Instruction::F32Sqrt,
InstructionInternal::F32Add => Instruction::F32Add,
InstructionInternal::F32Sub => Instruction::F32Sub,
InstructionInternal::F32Mul => Instruction::F32Mul,
InstructionInternal::F32Div => Instruction::F32Div,
InstructionInternal::F32Min => Instruction::F32Min,
InstructionInternal::F32Max => Instruction::F32Max,
InstructionInternal::F32Copysign => Instruction::F32Copysign,
InstructionInternal::F64Abs => Instruction::F64Abs,
InstructionInternal::F64Neg => Instruction::F64Neg,
InstructionInternal::F64Ceil => Instruction::F64Ceil,
InstructionInternal::F64Floor => Instruction::F64Floor,
InstructionInternal::F64Trunc => Instruction::F64Trunc,
InstructionInternal::F64Nearest => Instruction::F64Nearest,
InstructionInternal::F64Sqrt => Instruction::F64Sqrt,
InstructionInternal::F64Add => Instruction::F64Add,
InstructionInternal::F64Sub => Instruction::F64Sub,
InstructionInternal::F64Mul => Instruction::F64Mul,
InstructionInternal::F64Div => Instruction::F64Div,
InstructionInternal::F64Min => Instruction::F64Min,
InstructionInternal::F64Max => Instruction::F64Max,
InstructionInternal::F64Copysign => Instruction::F64Copysign,
InstructionInternal::I32WrapI64 => Instruction::I32WrapI64,
InstructionInternal::I32TruncSF32 => Instruction::I32TruncSF32,
InstructionInternal::I32TruncUF32 => Instruction::I32TruncUF32,
InstructionInternal::I32TruncSF64 => Instruction::I32TruncSF64,
InstructionInternal::I32TruncUF64 => Instruction::I32TruncUF64,
InstructionInternal::I64ExtendSI32 => Instruction::I64ExtendSI32,
InstructionInternal::I64ExtendUI32 => Instruction::I64ExtendUI32,
InstructionInternal::I64TruncSF32 => Instruction::I64TruncSF32,
InstructionInternal::I64TruncUF32 => Instruction::I64TruncUF32,
InstructionInternal::I64TruncSF64 => Instruction::I64TruncSF64,
InstructionInternal::I64TruncUF64 => Instruction::I64TruncUF64,
InstructionInternal::F32ConvertSI32 => Instruction::F32ConvertSI32,
InstructionInternal::F32ConvertUI32 => Instruction::F32ConvertUI32,
InstructionInternal::F32ConvertSI64 => Instruction::F32ConvertSI64,
InstructionInternal::F32ConvertUI64 => Instruction::F32ConvertUI64,
InstructionInternal::F32DemoteF64 => Instruction::F32DemoteF64,
InstructionInternal::F64ConvertSI32 => Instruction::F64ConvertSI32,
InstructionInternal::F64ConvertUI32 => Instruction::F64ConvertUI32,
InstructionInternal::F64ConvertSI64 => Instruction::F64ConvertSI64,
InstructionInternal::F64ConvertUI64 => Instruction::F64ConvertUI64,
InstructionInternal::F64PromoteF32 => Instruction::F64PromoteF32,
InstructionInternal::I32ReinterpretF32 => Instruction::I32ReinterpretF32,
InstructionInternal::I64ReinterpretF64 => Instruction::I64ReinterpretF64,
InstructionInternal::F32ReinterpretI32 => Instruction::F32ReinterpretI32,
InstructionInternal::F64ReinterpretI64 => Instruction::F64ReinterpretI64,
};
self.position += 1;
Some(out)
}
}

View File

@ -1,48 +1,48 @@
//! # wasmi //! # wasmi
//! //!
//! This library allows to load WebAssembly modules in binary format and invoke functions on them. //! This library allows WebAssembly modules to be loaded in binary format and their functions invoked.
//! //!
//! # Introduction //! # Introduction
//! //!
//! WebAssembly (wasm) is a safe, portable, compact format that designed for efficient execution. //! WebAssembly (wasm) is a safe, portable and compact format that is designed for efficient execution.
//! //!
//! Wasm code is distributed in a form of modules, that contains definitions of: //! Wasm code is distributed in the form of modules that contains definitions of:
//! //!
//! - functions, //! - functions,
//! - global variables, //! - global variables,
//! - linear memories, //! - linear memory instances and
//! - tables. //! - tables.
//! //!
//! and this definitions can be imported. Also, each definition can be exported. //! Each of these definitions can be imported and exported.
//! //!
//! In addition to definitions, modules can define initialization data for their memories or tables that takes the //! In addition to these definitions, modules can define initialization data for their memory or tables. This initialization data can take the
//! form of segments copied to given offsets. They can also define a `start` function that is automatically executed. //! form of segments, copied to given offsets. They can also define a `start` function that is automatically executed when the module is loaded.
//! //!
//! ## Loading and Validation //! ## Loading and Validation
//! //!
//! Before execution a module should be validated. This process checks that module is well-formed //! Before execution, a module must be validated. This process checks that the module is well-formed
//! and makes only allowed operations. //! and makes only allowed operations.
//! //!
//! Valid modules can't access memory out of it's sandbox, can't cause stack underflow //! A valid module can't access memory outside its sandbox, can't cause stack underflows
//! and can call functions only with correct signatures. //! and can only call functions with correct signatures.
//! //!
//! ## Instantiatiation //! ## Instantiation
//! //!
//! In order to execute code in wasm module it should be instatiated. //! In order to execute code from a wasm module, it must be instantiated.
//! Instantiation includes the following steps: //! Instantiation includes the following steps:
//! //!
//! 1. Create an empty module instance, //! 1. Creating an empty module instance.
//! 2. Resolve definition instances for each declared import in the module, //! 2. Resolving the definition instances for each declared import in the module.
//! 3. Instantiate definitions declared in the module (e.g. allocate global variables, allocate linear memory, etc), //! 3. Instantiating definitions declared in the module (e.g. allocate global variables, allocate linear memory, etc.).
//! 4. Initialize memory and table contents by copiying segments into them, //! 4. Initializing memory and table contents by copying segments into them.
//! 5. Execute `start` function, if any. //! 5. Executing the `start` function, if any.
//! //!
//! After these steps, module instance are ready to execute functions. //! After these steps, the module instance is ready to execute functions.
//! //!
//! ## Execution //! ## Execution
//! //!
//! It is allowed to only execute functions which are exported by a module. //! It only is allowed to call functions which are exported by the module.
//! Functions can either return a result or trap (e.g. there can't be linking-error at the middle of execution). //! Functions can either return a result or trap (e.g. there can't be linking error in the middle of the function execution).
//! This property is ensured by the validation process. //! This property is ensured by the validation process.
//! //!
//! # Examples //! # Examples
@ -72,7 +72,7 @@
//! .expect("failed to load wasm"); //! .expect("failed to load wasm");
//! //!
//! // Instantiate a module with empty imports and //! // Instantiate a module with empty imports and
//! // asserting that there is no `start` function. //! // assert that there is no `start` function.
//! let instance = //! let instance =
//! ModuleInstance::new( //! ModuleInstance::new(
//! &module, //! &module,
@ -81,7 +81,7 @@
//! .expect("failed to instantiate wasm module") //! .expect("failed to instantiate wasm module")
//! .assert_no_start(); //! .assert_no_start();
//! //!
//! // Finally, invoke exported function "test" with no parameters //! // Finally, invoke the exported function "test" with no parameters
//! // and empty external function executor. //! // and empty external function executor.
//! assert_eq!( //! assert_eq!(
//! instance.invoke_export( //! instance.invoke_export(
@ -95,367 +95,470 @@
//! ``` //! ```
#![warn(missing_docs)] #![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
#[macro_use]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std as alloc;
#[cfg(feature = "std")]
#[macro_use]
extern crate core;
#[cfg(test)]
extern crate assert_matches;
#[cfg(test)] #[cfg(test)]
extern crate wabt; extern crate wabt;
extern crate parity_wasm;
extern crate byteorder;
extern crate memory_units as memory_units_crate; extern crate memory_units as memory_units_crate;
extern crate parity_wasm;
#[cfg(all(not(feature = "32bit_opt_in"), target_pointer_width = "32"))] extern crate wasmi_validation as validation;
compile_error! {
"32-bit targets are not supported at the moment.
You can use '32bit_opt_in' feature.
See https://github.com/pepyakin/wasmi/issues/43"
}
use std::fmt; use alloc::{
boxed::Box,
string::{String, ToString},
vec::Vec,
};
use core::fmt;
#[cfg(feature = "std")]
use std::error; use std::error;
use std::collections::HashMap;
/// Error type which can thrown by wasm code or by host environment. #[cfg(not(feature = "std"))]
extern crate libm;
extern crate num_rational;
extern crate num_traits;
/// Error type which can be thrown by wasm code or by host environment.
/// ///
/// Under some conditions, wasm execution may produce a `Trap`, which immediately aborts execution. /// Under some conditions, wasm execution may produce a `Trap`, which immediately aborts execution.
/// Traps can't be handled by WebAssembly code, but are reported to the embedder. /// Traps can't be handled by WebAssembly code, but are reported to the embedder.
#[derive(Debug)] #[derive(Debug)]
pub struct Trap { pub struct Trap {
kind: TrapKind, kind: TrapKind,
} }
impl Trap { impl Trap {
/// Create new trap. /// Create new trap.
pub fn new(kind: TrapKind) -> Trap { pub fn new(kind: TrapKind) -> Trap {
Trap { kind } Trap { kind }
} }
/// Returns kind of this trap. /// Returns kind of this trap.
pub fn kind(&self) -> &TrapKind { pub fn kind(&self) -> &TrapKind {
&self.kind &self.kind
} }
} }
/// Error type which can thrown by wasm code or by host environment. impl fmt::Display for Trap {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Trap: {:?}", self.kind)
}
}
#[cfg(feature = "std")]
impl error::Error for Trap {
fn description(&self) -> &str {
"runtime trap"
}
}
/// Error type which can be thrown by wasm code or by host environment.
/// ///
/// See [`Trap`] for details. /// See [`Trap`] for details.
/// ///
/// [`Trap`]: struct.Trap.html /// [`Trap`]: struct.Trap.html
#[derive(Debug)] #[derive(Debug)]
pub enum TrapKind { pub enum TrapKind {
/// Wasm code executed `unreachable` opcode. /// Wasm code executed `unreachable` opcode.
/// ///
/// `unreachable` is a special opcode which always traps upon execution. /// `unreachable` is a special opcode which always traps upon execution.
/// This opcode have a similar purpose as `ud2` in x86. /// This opcode have a similar purpose as `ud2` in x86.
Unreachable, Unreachable,
/// Attempt to load or store at the address which /// Attempt to load or store at the address which
/// lies outside of bounds of the memory. /// lies outside of bounds of the memory.
/// ///
/// Since addresses are interpreted as unsigned integers, out of bounds access /// Since addresses are interpreted as unsigned integers, out of bounds access
/// can't happen with negative addresses (i.e. they will always wrap). /// can't happen with negative addresses (i.e. they will always wrap).
MemoryAccessOutOfBounds, MemoryAccessOutOfBounds,
/// Attempt to access table element at index which /// Attempt to access table element at index which
/// lies outside of bounds. /// lies outside of bounds.
/// ///
/// This typically can happen when `call_indirect` is executed /// This typically can happen when `call_indirect` is executed
/// with index that lies out of bounds. /// with index that lies out of bounds.
/// ///
/// Since indexes are interpreted as unsinged integers, out of bounds access /// Since indexes are interpreted as unsinged integers, out of bounds access
/// can't happen with negative indexes (i.e. they will always wrap). /// can't happen with negative indexes (i.e. they will always wrap).
TableAccessOutOfBounds, TableAccessOutOfBounds,
/// Attempt to access table element which is uninitialized (i.e. `None`). /// Attempt to access table element which is uninitialized (i.e. `None`).
/// ///
/// This typically can happen when `call_indirect` is executed. /// This typically can happen when `call_indirect` is executed.
ElemUninitialized, ElemUninitialized,
/// Attempt to divide by zero. /// Attempt to divide by zero.
/// ///
/// This trap typically can happen if `div` or `rem` is executed with /// This trap typically can happen if `div` or `rem` is executed with
/// zero as divider. /// zero as divider.
DivisionByZero, DivisionByZero,
/// Attempt to make a conversion to an int failed. /// Attempt to make a conversion to an int failed.
/// ///
/// This can happen when: /// This can happen when:
/// ///
/// - trying to do signed division (or get the remainder) -2<sup>N-1</sup> over -1. This is /// - trying to do signed division (or get the remainder) -2<sup>N-1</sup> over -1. This is
/// because the result +2<sup>N-1</sup> isn't representable as a N-bit signed integer. /// because the result +2<sup>N-1</sup> isn't representable as a N-bit signed integer.
/// - trying to truncate NaNs, infinity, or value for which the result is out of range into an integer. /// - trying to truncate NaNs, infinity, or value for which the result is out of range into an integer.
InvalidConversionToInt, InvalidConversionToInt,
/// Stack overflow. /// Stack overflow.
/// ///
/// This is likely caused by some infinite or very deep recursion. /// This is likely caused by some infinite or very deep recursion.
/// Extensive inlining might also be the cause of stack overflow. /// Extensive inlining might also be the cause of stack overflow.
StackOverflow, StackOverflow,
/// Attempt to invoke a function with mismatching signature. /// Attempt to invoke a function with mismatching signature.
/// ///
/// This can happen if [`FuncInstance`] was invoked /// This can happen if [`FuncInstance`] was invoked
/// with mismatching [signature][`Signature`]. /// with mismatching [signature][`Signature`].
/// ///
/// This can always happen with indirect calls. `call_indirect` instruction always /// This can always happen with indirect calls. `call_indirect` instruction always
/// specifies the expected signature of function. If `call_indirect` is executed /// specifies the expected signature of function. If `call_indirect` is executed
/// with index that points on function with signature different that is /// with index that points on function with signature different that is
/// expected by this `call_indirect`, this trap is raised. /// expected by this `call_indirect`, this trap is raised.
/// ///
/// [`Signature`]: struct.Signature.html /// [`Signature`]: struct.Signature.html
UnexpectedSignature, UnexpectedSignature,
/// Error specified by the host. /// Error specified by the host.
/// ///
/// Typically returned from an implementation of [`Externals`]. /// Typically returned from an implementation of [`Externals`].
/// ///
/// [`Externals`]: trait.Externals.html /// [`Externals`]: trait.Externals.html
Host(Box<host::HostError>), Host(Box<dyn host::HostError>),
}
impl TrapKind {
/// Whether this trap is specified by the host.
pub fn is_host(&self) -> bool {
match self {
&TrapKind::Host(_) => true,
_ => false,
}
}
} }
/// Internal interpreter error. /// Internal interpreter error.
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// Module validation error. Might occur only at load time. /// Module validation error. Might occur only at load time.
Validation(String), Validation(String),
/// Error while instantiating a module. Might occur when provided /// Error while instantiating a module. Might occur when provided
/// with incorrect exports (i.e. linkage failure). /// with incorrect exports (i.e. linkage failure).
Instantiation(String), Instantiation(String),
/// Function-level error. /// Function-level error.
Function(String), Function(String),
/// Table-level error. /// Table-level error.
Table(String), Table(String),
/// Memory-level error. /// Memory-level error.
Memory(String), Memory(String),
/// Global-level error. /// Global-level error.
Global(String), Global(String),
/// Value-level error. /// Value-level error.
Value(String), Value(String),
/// Trap. /// Trap.
Trap(Trap), Trap(Trap),
/// Custom embedder error. /// Custom embedder error.
Host(Box<host::HostError>), Host(Box<dyn host::HostError>),
} }
impl Error { impl Error {
/// Returns [`HostError`] if this `Error` represents some host error. /// Returns [`HostError`] if this `Error` represents some host error.
/// ///
/// I.e. if this error have variant [`Host`] or [`Trap`][`Trap`] with [host][`TrapKind::Host`] error. /// I.e. if this error have variant [`Host`] or [`Trap`][`Trap`] with [host][`TrapKind::Host`] error.
/// ///
/// [`HostError`]: trait.HostError.html /// [`HostError`]: trait.HostError.html
/// [`Host`]: enum.Error.html#variant.Host /// [`Host`]: enum.Error.html#variant.Host
/// [`Trap`]: enum.Error.html#variant.Trap /// [`Trap`]: enum.Error.html#variant.Trap
/// [`TrapKind::Host`]: enum.TrapKind.html#variant.Host /// [`TrapKind::Host`]: enum.TrapKind.html#variant.Host
pub fn as_host_error(&self) -> Option<&host::HostError> { pub fn as_host_error(&self) -> Option<&dyn host::HostError> {
match *self { match *self {
Error::Host(ref host_err) => Some(&**host_err), Error::Host(ref host_err) => Some(&**host_err),
Error::Trap(ref trap) => match *trap.kind() { Error::Trap(ref trap) => match *trap.kind() {
TrapKind::Host(ref host_err) => Some(&**host_err), TrapKind::Host(ref host_err) => Some(&**host_err),
_ => None, _ => None,
} },
_ => None, _ => None,
} }
} }
} }
impl Into<String> for Error { impl Into<String> for Error {
fn into(self) -> String { fn into(self) -> String {
match self { match self {
Error::Validation(s) => s, Error::Validation(s) => s,
Error::Instantiation(s) => s, Error::Instantiation(s) => s,
Error::Function(s) => s, Error::Function(s) => s,
Error::Table(s) => s, Error::Table(s) => s,
Error::Memory(s) => s, Error::Memory(s) => s,
Error::Global(s) => s, Error::Global(s) => s,
Error::Value(s) => s, Error::Value(s) => s,
Error::Trap(s) => format!("trap: {:?}", s), Error::Trap(s) => format!("trap: {:?}", s),
Error::Host(e) => format!("user: {}", e), Error::Host(e) => format!("user: {}", e),
} }
} }
} }
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
Error::Validation(ref s) => write!(f, "Validation: {}", s), Error::Validation(ref s) => write!(f, "Validation: {}", s),
Error::Instantiation(ref s) => write!(f, "Instantiation: {}", s), Error::Instantiation(ref s) => write!(f, "Instantiation: {}", s),
Error::Function(ref s) => write!(f, "Function: {}", s), Error::Function(ref s) => write!(f, "Function: {}", s),
Error::Table(ref s) => write!(f, "Table: {}", s), Error::Table(ref s) => write!(f, "Table: {}", s),
Error::Memory(ref s) => write!(f, "Memory: {}", s), Error::Memory(ref s) => write!(f, "Memory: {}", s),
Error::Global(ref s) => write!(f, "Global: {}", s), Error::Global(ref s) => write!(f, "Global: {}", s),
Error::Value(ref s) => write!(f, "Value: {}", s), Error::Value(ref s) => write!(f, "Value: {}", s),
Error::Trap(ref s) => write!(f, "Trap: {:?}", s), Error::Trap(ref s) => write!(f, "Trap: {:?}", s),
Error::Host(ref e) => write!(f, "User: {}", e), Error::Host(ref e) => write!(f, "User: {}", e),
} }
} }
} }
#[cfg(feature = "std")]
impl error::Error for Error { impl error::Error for Error {
fn description(&self) -> &str { fn description(&self) -> &str {
match *self { match *self {
Error::Validation(ref s) => s, Error::Validation(ref s) => s,
Error::Instantiation(ref s) => s, Error::Instantiation(ref s) => s,
Error::Function(ref s) => s, Error::Function(ref s) => s,
Error::Table(ref s) => s, Error::Table(ref s) => s,
Error::Memory(ref s) => s, Error::Memory(ref s) => s,
Error::Global(ref s) => s, Error::Global(ref s) => s,
Error::Value(ref s) => s, Error::Value(ref s) => s,
Error::Trap(_) => "Trap", Error::Trap(_) => "Trap",
Error::Host(_) => "Host error", Error::Host(_) => "Host error",
} }
} }
} }
impl<U> From<U> for Error where U: host::HostError + Sized { impl<U> From<U> for Error
fn from(e: U) -> Self { where
Error::Host(Box::new(e)) U: host::HostError + Sized,
} {
fn from(e: U) -> Self {
Error::Host(Box::new(e))
}
} }
impl<U> From<U> for Trap where U: host::HostError + Sized { impl<U> From<U> for Trap
fn from(e: U) -> Self { where
Trap::new(TrapKind::Host(Box::new(e))) U: host::HostError + Sized,
} {
fn from(e: U) -> Self {
Trap::new(TrapKind::Host(Box::new(e)))
}
} }
impl From<Trap> for Error { impl From<Trap> for Error {
fn from(e: Trap) -> Error { fn from(e: Trap) -> Error {
Error::Trap(e) Error::Trap(e)
} }
} }
impl From<TrapKind> for Trap { impl From<TrapKind> for Trap {
fn from(e: TrapKind) -> Trap { fn from(e: TrapKind) -> Trap {
Trap::new(e) Trap::new(e)
} }
} }
impl From<validation::Error> for Error { impl From<validation::Error> for Error {
fn from(e: validation::Error) -> Error { fn from(e: validation::Error) -> Error {
Error::Validation(e.to_string()) Error::Validation(e.to_string())
} }
} }
mod validation; mod func;
mod common; mod global;
mod memory;
mod module;
mod runner;
mod table;
mod value;
mod host; mod host;
mod imports; mod imports;
mod global; mod isa;
mod func; mod memory;
mod module;
pub mod nan_preserving_float;
mod prepare;
mod runner;
mod table;
mod types; mod types;
mod value;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
pub use self::memory::{MemoryInstance, MemoryRef, LINEAR_MEMORY_PAGE_SIZE}; pub use self::func::{FuncInstance, FuncInvocation, FuncRef, ResumableError};
pub use self::table::{TableInstance, TableRef};
pub use self::value::RuntimeValue;
pub use self::host::{Externals, NopExternals, HostError, RuntimeArgs};
pub use self::imports::{ModuleImportResolver, ImportResolver, ImportsBuilder};
pub use self::module::{ModuleInstance, ModuleRef, ExternVal, NotStartedModuleRef};
pub use self::global::{GlobalInstance, GlobalRef}; pub use self::global::{GlobalInstance, GlobalRef};
pub use self::func::{FuncInstance, FuncRef}; pub use self::host::{Externals, HostError, NopExternals, RuntimeArgs};
pub use self::types::{Signature, ValueType, GlobalDescriptor, TableDescriptor, MemoryDescriptor}; pub use self::imports::{ImportResolver, ImportsBuilder, ModuleImportResolver};
pub use self::memory::{MemoryInstance, MemoryRef, LINEAR_MEMORY_PAGE_SIZE};
pub use self::module::{ExternVal, ModuleInstance, ModuleRef, NotStartedModuleRef};
pub use self::runner::{StackRecycler, DEFAULT_CALL_STACK_LIMIT, DEFAULT_VALUE_STACK_LIMIT};
pub use self::table::{TableInstance, TableRef};
pub use self::types::{GlobalDescriptor, MemoryDescriptor, Signature, TableDescriptor, ValueType};
pub use self::value::{Error as ValueError, FromRuntimeValue, LittleEndianConvert, RuntimeValue};
/// WebAssembly-specific sizes and units. /// WebAssembly-specific sizes and units.
pub mod memory_units { pub mod memory_units {
pub use memory_units_crate::wasm32::*; pub use memory_units_crate::wasm32::*;
pub use memory_units_crate::{Bytes, ByteSize, RoundUpTo, size_of}; pub use memory_units_crate::{size_of, ByteSize, Bytes, RoundUpTo};
} }
/// Deserialized module prepared for instantiation. /// Deserialized module prepared for instantiation.
pub struct Module { pub struct Module {
labels: HashMap<usize, HashMap<usize, usize>>, code_map: Vec<isa::Instructions>,
module: parity_wasm::elements::Module, module: parity_wasm::elements::Module,
} }
impl Module { impl Module {
/// Create `Module` from `parity_wasm::elements::Module`.
///
/// This function will load, validate and prepare a `parity_wasm`'s `Module`.
///
/// # Errors
///
/// Returns `Err` if provided `Module` is not valid.
///
/// # Examples
///
/// ```rust
/// extern crate parity_wasm;
/// extern crate wasmi;
///
/// use parity_wasm::builder;
/// use parity_wasm::elements;
///
/// fn main() {
/// let parity_module =
/// builder::module()
/// .function()
/// .signature().with_param(elements::ValueType::I32).build()
/// .body().build()
/// .build()
/// .build();
///
/// let module = wasmi::Module::from_parity_wasm_module(parity_module)
/// .expect("parity-wasm builder generated invalid module!");
///
/// // Instantiate `module`, etc...
/// }
/// ```
pub fn from_parity_wasm_module(module: parity_wasm::elements::Module) -> Result<Module, Error> {
let prepare::CompiledModule { code_map, module } = prepare::compile_module(module)?;
/// Create `Module` from `parity_wasm::elements::Module`. Ok(Module { code_map, module })
/// }
/// This function will load, validate and prepare a `parity_wasm`'s `Module`.
///
/// # Errors
///
/// Returns `Err` if provided `Module` is not valid.
///
/// # Examples
///
/// ```rust
/// extern crate parity_wasm;
/// extern crate wasmi;
///
/// use parity_wasm::builder;
/// use parity_wasm::elements;
///
/// fn main() {
/// let parity_module =
/// builder::module()
/// .function()
/// .signature().with_param(elements::ValueType::I32).build()
/// .body().build()
/// .build()
/// .build();
///
/// let module = wasmi::Module::from_parity_wasm_module(parity_module)
/// .expect("parity-wasm builder generated invalid module!");
///
/// // Instantiate `module`, etc...
/// }
/// ```
pub fn from_parity_wasm_module(module: parity_wasm::elements::Module) -> Result<Module, Error> {
use validation::{validate_module, ValidatedModule};
let ValidatedModule {
labels,
module,
} = validate_module(module)?;
Ok(Module { /// Fail if the module contains any floating-point operations
labels, ///
module, /// # Errors
}) ///
} /// Returns `Err` if provided `Module` is not valid.
///
/// # Examples
///
/// ```rust
/// # extern crate wasmi;
/// # extern crate wabt;
///
/// let wasm_binary: Vec<u8> =
/// wabt::wat2wasm(
/// r#"
/// (module
/// (func $add (param $lhs i32) (param $rhs i32) (result i32)
/// get_local $lhs
/// get_local $rhs
/// i32.add))
/// "#,
/// )
/// .expect("failed to parse wat");
///
/// // Load wasm binary and prepare it for instantiation.
/// let module = wasmi::Module::from_buffer(&wasm_binary).expect("Parsing failed");
/// assert!(module.deny_floating_point().is_ok());
///
/// let wasm_binary: Vec<u8> =
/// wabt::wat2wasm(
/// r#"
/// (module
/// (func $add (param $lhs f32) (param $rhs f32) (result f32)
/// get_local $lhs
/// get_local $rhs
/// f32.add))
/// "#,
/// )
/// .expect("failed to parse wat");
///
/// let module = wasmi::Module::from_buffer(&wasm_binary).expect("Parsing failed");
/// assert!(module.deny_floating_point().is_err());
///
/// let wasm_binary: Vec<u8> =
/// wabt::wat2wasm(
/// r#"
/// (module
/// (func $add (param $lhs f32) (param $rhs f32) (result f32)
/// get_local $lhs))
/// "#,
/// )
/// .expect("failed to parse wat");
///
/// let module = wasmi::Module::from_buffer(&wasm_binary).expect("Parsing failed");
/// assert!(module.deny_floating_point().is_err());
/// ```
pub fn deny_floating_point(&self) -> Result<(), Error> {
prepare::deny_floating_point(&self.module).map_err(Into::into)
}
/// Create `Module` from a given buffer. /// Create `Module` from a given buffer.
/// ///
/// This function will deserialize wasm module from a given module, /// This function will deserialize wasm module from a given module,
/// validate and prepare it for instantiation. /// validate and prepare it for instantiation.
/// ///
/// # Errors /// # Errors
/// ///
/// Returns `Err` if wasm binary in provided `buffer` is not valid wasm binary. /// Returns `Err` if wasm binary in provided `buffer` is not valid wasm binary.
/// ///
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
/// extern crate wasmi; /// extern crate wasmi;
/// ///
/// fn main() { /// fn main() {
/// let module = /// let module =
/// wasmi::Module::from_buffer( /// wasmi::Module::from_buffer(
/// // Minimal module: /// // Minimal module:
/// // \0asm - magic /// // \0asm - magic
/// // 0x01 - version (in little-endian) /// // 0x01 - version (in little-endian)
/// &[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00] /// &[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]
/// ).expect("Failed to load minimal module"); /// ).expect("Failed to load minimal module");
/// ///
/// // Instantiate `module`, etc... /// // Instantiate `module`, etc...
/// } /// }
/// ``` /// ```
pub fn from_buffer<B: AsRef<[u8]>>(buffer: B) -> Result<Module, Error> { pub fn from_buffer<B: AsRef<[u8]>>(buffer: B) -> Result<Module, Error> {
let module = parity_wasm::elements::deserialize_buffer(buffer.as_ref()) let module = parity_wasm::elements::deserialize_buffer(buffer.as_ref())
.map_err(|e: parity_wasm::elements::Error| Error::Validation(e.to_string()))?; .map_err(|e: parity_wasm::elements::Error| Error::Validation(e.to_string()))?;
Module::from_parity_wasm_module(module) Module::from_parity_wasm_module(module)
} }
pub(crate) fn module(&self) -> &parity_wasm::elements::Module { pub(crate) fn module(&self) -> &parity_wasm::elements::Module {
&self.module &self.module
} }
pub(crate) fn labels(&self) -> &HashMap<usize, HashMap<usize, usize>> { pub(crate) fn code(&self) -> &Vec<isa::Instructions> {
&self.labels &self.code_map
} }
} }

View File

@ -1,476 +0,0 @@
use std::u32;
use std::ops::Range;
use std::cmp;
use std::fmt;
use std::rc::Rc;
use std::cell::RefCell;
use parity_wasm::elements::ResizableLimits;
use Error;
use memory_units::{RoundUpTo, Pages, Bytes};
/// Size of a page of [linear memory][`MemoryInstance`] - 64KiB.
///
/// The size of a memory is always a integer multiple of a page size.
///
/// [`MemoryInstance`]: struct.MemoryInstance.html
pub const LINEAR_MEMORY_PAGE_SIZE: Bytes = Bytes(65536);
/// Maximal number of pages.
const LINEAR_MEMORY_MAX_PAGES: Pages = Pages(65536);
/// Reference to a memory (See [`MemoryInstance`] for details).
///
/// This reference has a reference-counting semantics.
///
/// [`MemoryInstance`]: struct.MemoryInstance.html
///
#[derive(Clone, Debug)]
pub struct MemoryRef(Rc<MemoryInstance>);
impl ::std::ops::Deref for MemoryRef {
type Target = MemoryInstance;
fn deref(&self) -> &MemoryInstance {
&self.0
}
}
/// Runtime representation of a linear memory (or `memory` for short).
///
/// A memory is a contiguous, mutable array of raw bytes. Wasm code can load and store values
/// from/to a linear memory at any byte address.
/// A trap occurs if an access is not within the bounds of the current memory size.
///
/// A memory is created with an initial size but can be grown dynamically.
/// The growth can be limited by specifying maximum size.
/// The size of a memory is always a integer multiple of a [page size][`LINEAR_MEMORY_PAGE_SIZE`] - 64KiB.
///
/// At the moment, wasm doesn't provide any way to shrink the memory.
///
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
pub struct MemoryInstance {
/// Memofy limits.
limits: ResizableLimits,
/// Linear memory buffer.
buffer: RefCell<Vec<u8>>,
initial: Pages,
maximum: Option<Pages>,
}
impl fmt::Debug for MemoryInstance {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("MemoryInstance")
.field("limits", &self.limits)
.field("buffer.len", &self.buffer.borrow().len())
.field("maximum", &self.maximum)
.field("initial", &self.initial)
.finish()
}
}
struct CheckedRegion<'a, B: 'a> where B: ::std::ops::Deref<Target=Vec<u8>> {
buffer: &'a B,
offset: usize,
size: usize,
}
impl<'a, B: 'a> CheckedRegion<'a, B> where B: ::std::ops::Deref<Target=Vec<u8>> {
fn range(&self) -> Range<usize> {
self.offset..self.offset+self.size
}
fn slice(&self) -> &[u8] {
&self.buffer[self.range()]
}
fn intersects(&self, other: &Self) -> bool {
let low = cmp::max(self.offset, other.offset);
let high = cmp::min(self.offset + self.size, other.offset + other.size);
low < high
}
}
impl MemoryInstance {
/// Allocate a memory instance.
///
/// The memory allocated with initial number of pages specified by `initial`.
/// Minimal possible value for `initial` is 0 and maximum possible is `65536`.
/// (Since maximum addressible memory is 2<sup>32</sup> = 4GiB = 65536 * [64KiB][`LINEAR_MEMORY_PAGE_SIZE`]).
///
/// It is possible to limit maximum number of pages this memory instance can have by specifying
/// `maximum`. If not specified, this memory instance would be able to allocate up to 4GiB.
///
/// Allocated memory is always zeroed.
///
/// # Errors
///
/// Returns `Err` if:
///
/// - `initial` is greater than `maximum`
/// - either `initial` or `maximum` is greater than `65536`.
///
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
pub fn alloc(initial: Pages, maximum: Option<Pages>) -> Result<MemoryRef, Error> {
validate_memory(initial, maximum).map_err(Error::Memory)?;
let memory = MemoryInstance::new(initial, maximum);
Ok(MemoryRef(Rc::new(memory)))
}
/// Create new linear memory instance.
fn new(initial: Pages, maximum: Option<Pages>) -> Self {
let limits = ResizableLimits::new(initial.0 as u32, maximum.map(|p| p.0 as u32));
let initial_size: Bytes = initial.into();
MemoryInstance {
limits: limits,
buffer: RefCell::new(vec![0; initial_size.0]),
initial: initial,
maximum: maximum,
}
}
/// Return linear memory limits.
pub(crate) fn limits(&self) -> &ResizableLimits {
&self.limits
}
/// Returns number of pages this `MemoryInstance` was created with.
pub fn initial(&self) -> Pages {
self.initial
}
/// Returns maximum amount of pages this `MemoryInstance` can grow to.
///
/// Returns `None` if there is no limit set.
/// Maximum memory size cannot exceed `65536` pages or 4GiB.
pub fn maximum(&self) -> Option<Pages> {
self.maximum
}
/// Returns current linear memory size.
///
/// Maximum memory size cannot exceed `65536` pages or 4GiB.
///
/// # Example
///
/// To convert number of pages to number of bytes you can use the following code:
///
/// ```rust
/// use wasmi::MemoryInstance;
/// use wasmi::memory_units::*;
///
/// let memory = MemoryInstance::alloc(Pages(1), None).unwrap();
/// let byte_size: Bytes = memory.current_size().into();
/// assert_eq!(
/// byte_size,
/// Bytes(65536),
/// );
/// ```
pub fn current_size(&self) -> Pages {
Bytes(self.buffer.borrow().len()).round_up_to()
}
/// Copy data from memory at given offset.
///
/// This will allocate vector for you.
/// If you can provide a mutable slice you can use [`get_into`].
///
/// [`get_into`]: #method.get_into
pub fn get(&self, offset: u32, size: usize) -> Result<Vec<u8>, Error> {
let buffer = self.buffer.borrow();
let region = self.checked_region(&buffer, offset as usize, size)?;
Ok(region.slice().to_vec())
}
/// Copy data from given offset in the memory into `target` slice.
///
/// # Errors
///
/// Returns `Err` if the specified region is out of bounds.
pub fn get_into(&self, offset: u32, target: &mut [u8]) -> Result<(), Error> {
let buffer = self.buffer.borrow();
let region = self.checked_region(&buffer, offset as usize, target.len())?;
target.copy_from_slice(region.slice());
Ok(())
}
/// Copy data in the memory at given offset.
pub fn set(&self, offset: u32, value: &[u8]) -> Result<(), Error> {
let mut buffer = self.buffer.borrow_mut();
let range = self.checked_region(&buffer, offset as usize, value.len())?.range();
buffer[range].copy_from_slice(value);
Ok(())
}
/// Increases the size of the linear memory by given number of pages.
/// Returns previous memory size if succeeds.
///
/// # Errors
///
/// Returns `Err` if attempted to allocate more memory than permited by the limit.
pub fn grow(&self, additional: Pages) -> Result<Pages, Error> {
let size_before_grow: Pages = self.current_size();
println!("grow({:?}) = {:?}", additional, size_before_grow);
if additional == Pages(0) {
return Ok(size_before_grow);
}
if additional > Pages(65536) {
return Err(Error::Memory(format!(
"Trying to grow memory by more than 65536 pages"
)));
}
let new_size: Pages = size_before_grow + additional;
let maximum = self.maximum.unwrap_or(LINEAR_MEMORY_MAX_PAGES);
if new_size > maximum {
return Err(Error::Memory(format!(
"Trying to grow memory by {} pages when already have {}",
additional.0, size_before_grow.0,
)));
}
// Resize underlying buffer up to a new size filling newly allocated space with zeroes.
// This size is guaranteed to be larger than current size.
let new_buffer_length: Bytes = new_size.into();
{
let mut buffer = self.buffer.borrow_mut();
debug_assert!(new_buffer_length.0 > buffer.len());
buffer.resize(new_buffer_length.0, 0);
}
Ok(size_before_grow)
}
fn checked_region<'a, B>(&self, buffer: &'a B, offset: usize, size: usize) -> Result<CheckedRegion<'a, B>, Error>
where B: ::std::ops::Deref<Target=Vec<u8>>
{
let end = offset.checked_add(size)
.ok_or_else(|| Error::Memory(format!("trying to access memory block of size {} from offset {}", size, offset)))?;
if end > buffer.len() {
return Err(Error::Memory(format!("trying to access region [{}..{}] in memory [0..{}]", offset, end, buffer.len())));
}
Ok(CheckedRegion {
buffer: buffer,
offset: offset,
size: size,
})
}
/// Copy contents of one memory region to another.
///
/// Semantically equivalent to `memmove`.
///
/// # Errors
///
/// Returns `Err` if either of specified regions is out of bounds.
pub fn copy(&self, src_offset: usize, dst_offset: usize, len: usize) -> Result<(), Error> {
let buffer = self.buffer.borrow_mut();
let read_region = self.checked_region(&buffer, src_offset, len)?;
let write_region = self.checked_region(&buffer, dst_offset, len)?;
unsafe { ::std::ptr::copy(
buffer[read_region.range()].as_ptr(),
buffer[write_region.range()].as_ptr() as *mut _,
len,
)}
Ok(())
}
/// Copy contents of one memory region to another (non-overlapping version).
///
/// Semantically equivalent to `memcpy`.
/// but returns Error if source overlaping with destination.
///
/// # Errors
///
/// Returns `Err` if:
///
/// - either of specified regions is out of bounds,
/// - these regions overlaps.
pub fn copy_nonoverlapping(&self, src_offset: usize, dst_offset: usize, len: usize) -> Result<(), Error> {
let buffer = self.buffer.borrow_mut();
let read_region = self.checked_region(&buffer, src_offset, len)?;
let write_region = self.checked_region(&buffer, dst_offset, len)?;
if read_region.intersects(&write_region) {
return Err(Error::Memory(format!("non-overlapping copy is used for overlapping regions")))
}
unsafe { ::std::ptr::copy_nonoverlapping(
buffer[read_region.range()].as_ptr(),
buffer[write_region.range()].as_ptr() as *mut _,
len,
)}
Ok(())
}
/// Fill memory region with a specified value.
///
/// Semantically equivalent to `memset`.
///
/// # Errors
///
/// Returns `Err` if the specified region is out of bounds.
pub fn clear(&self, offset: usize, new_val: u8, len: usize) -> Result<(), Error> {
let mut buffer = self.buffer.borrow_mut();
let range = self.checked_region(&buffer, offset, len)?.range();
for val in &mut buffer[range] { *val = new_val }
Ok(())
}
/// Fill specified memory region with zeroes.
///
/// # Errors
///
/// Returns `Err` if the specified region is out of bounds.
pub fn zero(&self, offset: usize, len: usize) -> Result<(), Error> {
self.clear(offset, 0, len)
}
}
pub fn validate_memory(initial: Pages, maximum: Option<Pages>) -> Result<(), String> {
if initial > LINEAR_MEMORY_MAX_PAGES {
return Err(format!("initial memory size must be at most {} pages", LINEAR_MEMORY_MAX_PAGES.0));
}
if let Some(maximum) = maximum {
if initial > maximum {
return Err(format!(
"maximum limit {} is less than minimum {}",
maximum.0,
initial.0,
));
}
if maximum > LINEAR_MEMORY_MAX_PAGES {
return Err(format!("maximum memory size must be at most {} pages", LINEAR_MEMORY_MAX_PAGES.0));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::{MemoryInstance, LINEAR_MEMORY_PAGE_SIZE};
use Error;
use memory_units::Pages;
#[test]
fn alloc() {
let fixtures = &[
(0, None, true),
(0, Some(0), true),
(1, None, true),
(1, Some(1), true),
(0, Some(1), true),
(1, Some(0), false),
(0, Some(65536), true),
(65536, Some(65536), true),
(65536, Some(0), false),
(65536, None, true),
];
for (index, &(initial, maybe_max, expected_ok)) in fixtures.iter().enumerate() {
let initial: Pages = Pages(initial);
let maximum: Option<Pages> = maybe_max.map(|m| Pages(m));
let result = MemoryInstance::alloc(initial, maximum);
if result.is_ok() != expected_ok {
panic!(
"unexpected error at {}, initial={:?}, max={:?}, expected={}, result={:?}",
index,
initial,
maybe_max,
expected_ok,
result,
);
}
}
}
#[test]
fn ensure_page_size() {
use memory_units::ByteSize;
assert_eq!(LINEAR_MEMORY_PAGE_SIZE, Pages::byte_size());
}
fn create_memory(initial_content: &[u8]) -> MemoryInstance {
let mem = MemoryInstance::new(Pages(1), Some(Pages(1)));
mem.set(0, initial_content).expect("Successful initialize the memory");
mem
}
#[test]
fn copy_overlaps_1() {
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
mem.copy(0, 4, 6).expect("Successfully copy the elements");
let result = mem.get(0, 10).expect("Successfully retrieve the result");
assert_eq!(result, &[0, 1, 2, 3, 0, 1, 2, 3, 4, 5]);
}
#[test]
fn copy_overlaps_2() {
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
mem.copy(4, 0, 6).expect("Successfully copy the elements");
let result = mem.get(0, 10).expect("Successfully retrieve the result");
assert_eq!(result, &[4, 5, 6, 7, 8, 9, 6, 7, 8, 9]);
}
#[test]
fn copy_nonoverlapping() {
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
mem.copy_nonoverlapping(0, 10, 10).expect("Successfully copy the elements");
let result = mem.get(10, 10).expect("Successfully retrieve the result");
assert_eq!(result, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
}
#[test]
fn copy_nonoverlapping_overlaps_1() {
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
let result = mem.copy_nonoverlapping(0, 4, 6);
match result {
Err(Error::Memory(_)) => {},
_ => panic!("Expected Error::Memory(_) result, but got {:?}", result),
}
}
#[test]
fn copy_nonoverlapping_overlaps_2() {
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
let result = mem.copy_nonoverlapping(4, 0, 6);
match result {
Err(Error::Memory(_)) => {},
_ => panic!("Expected Error::Memory(_), but got {:?}", result),
}
}
#[test]
fn clear() {
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
mem.clear(0, 0x4A, 10).expect("To successfully clear the memory");
let result = mem.get(0, 10).expect("To successfully retrieve the result");
assert_eq!(result, &[0x4A; 10]);
}
#[test]
fn get_into() {
let mem = MemoryInstance::new(Pages(1), None);
mem.set(6, &[13, 17, 129]).expect("memory set should not fail");
let mut data = [0u8; 2];
mem.get_into(7, &mut data[..]).expect("get_into should not fail");
assert_eq!(data, [17, 129]);
}
}

189
src/memory/mmap_bytebuf.rs Normal file
View File

@ -0,0 +1,189 @@
//! An implementation of a `ByteBuf` based on virtual memory.
//!
//! This implementation uses `mmap` on POSIX systems (and should use `VirtualAlloc` on windows).
//! There are possibilities to improve the performance for the reallocating case by reserving
//! memory up to maximum. This might be a problem for systems that don't have a lot of virtual
//! memory (i.e. 32-bit platforms).
use std::ptr::{self, NonNull};
use std::slice;
struct Mmap {
/// The pointer that points to the start of the mapping.
///
/// This value doesn't change after creation.
ptr: NonNull<u8>,
/// The length of this mapping.
///
/// Cannot be more than `isize::max_value()`. This value doesn't change after creation.
len: usize,
}
impl Mmap {
/// Create a new mmap mapping
///
/// Returns `Err` if:
/// - `len` should not exceed `isize::max_value()`
/// - `len` should be greater than 0.
/// - `mmap` returns an error (almost certainly means out of memory).
fn new(len: usize) -> Result<Self, &'static str> {
if len > isize::max_value() as usize {
return Err("`len` should not exceed `isize::max_value()`");
}
if len == 0 {
return Err("`len` should be greater than 0");
}
let ptr_or_err = unsafe {
// Safety Proof:
// There are not specific safety proofs are required for this call, since the call
// by itself can't invoke any safety problems (however, misusing its result can).
libc::mmap(
// `addr` - let the system to choose the address at which to create the mapping.
ptr::null_mut(),
// the length of the mapping in bytes.
len,
// `prot` - protection flags: READ WRITE !EXECUTE
libc::PROT_READ | libc::PROT_WRITE,
// `flags`
// `MAP_ANON` - mapping is not backed by any file and initial contents are
// initialized to zero.
// `MAP_PRIVATE` - the mapping is private to this process.
libc::MAP_ANON | libc::MAP_PRIVATE,
// `fildes` - a file descriptor. Pass -1 as this is required for some platforms
// when the `MAP_ANON` is passed.
-1,
// `offset` - offset from the file.
0,
)
};
match ptr_or_err {
// With the current parameters, the error can only be returned in case of insufficient
// memory.
libc::MAP_FAILED => Err("mmap returned an error"),
_ => {
let ptr = NonNull::new(ptr_or_err as *mut u8).ok_or("mmap returned 0")?;
Ok(Self { ptr, len })
}
}
}
fn as_slice(&self) -> &[u8] {
unsafe {
// Safety Proof:
// - Aliasing guarantees of `self.ptr` are not violated since `self` is the only owner.
// - This pointer was allocated for `self.len` bytes and thus is a valid slice.
// - `self.len` doesn't change throughout the lifetime of `self`.
// - The value is returned valid for the duration of lifetime of `self`.
// `self` cannot be destroyed while the returned slice is alive.
// - `self.ptr` is of `NonNull` type and thus `.as_ptr()` can never return NULL.
// - `self.len` cannot be larger than `isize::max_value()`.
slice::from_raw_parts(self.ptr.as_ptr(), self.len)
}
}
fn as_slice_mut(&mut self) -> &mut [u8] {
unsafe {
// Safety Proof:
// - See the proof for `Self::as_slice`
// - Additionally, it is not possible to obtain two mutable references for `self.ptr`
slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len)
}
}
}
impl Drop for Mmap {
fn drop(&mut self) {
let ret_val = unsafe {
// Safety proof:
// - `self.ptr` was allocated by a call to `mmap`.
// - `self.len` was saved at the same time and it doesn't change throughout the lifetime
// of `self`.
libc::munmap(self.ptr.as_ptr() as *mut libc::c_void, self.len)
};
// There is no reason for `munmap` to fail to deallocate a private annonymous mapping
// allocated by `mmap`.
// However, for the cases when it actually fails prefer to fail, in order to not leak
// and exhaust the virtual memory.
assert_eq!(ret_val, 0, "munmap failed");
}
}
pub struct ByteBuf {
mmap: Option<Mmap>,
}
impl ByteBuf {
pub fn new(len: usize) -> Result<Self, &'static str> {
let mmap = if len == 0 {
None
} else {
Some(Mmap::new(len)?)
};
Ok(Self { mmap })
}
pub fn realloc(&mut self, new_len: usize) -> Result<(), &'static str> {
let new_mmap = if new_len == 0 {
None
} else {
let mut new_mmap = Mmap::new(new_len)?;
if let Some(cur_mmap) = self.mmap.take() {
let src = cur_mmap.as_slice();
let dst = new_mmap.as_slice_mut();
let amount = src.len().min(dst.len());
dst[..amount].copy_from_slice(&src[..amount]);
}
Some(new_mmap)
};
self.mmap = new_mmap;
Ok(())
}
pub fn len(&self) -> usize {
self.mmap.as_ref().map(|m| m.len).unwrap_or(0)
}
pub fn as_slice(&self) -> &[u8] {
self.mmap.as_ref().map(|m| m.as_slice()).unwrap_or(&[])
}
pub fn as_slice_mut(&mut self) -> &mut [u8] {
self.mmap
.as_mut()
.map(|m| m.as_slice_mut())
.unwrap_or(&mut [])
}
pub fn erase(&mut self) -> Result<(), &'static str> {
let len = self.len();
if len > 0 {
// The order is important.
//
// 1. First we clear, and thus drop, the current mmap if any.
// 2. And then we create a new one.
//
// Otherwise we double the peak memory consumption.
self.mmap = None;
self.mmap = Some(Mmap::new(len)?);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::ByteBuf;
const PAGE_SIZE: usize = 4096;
// This is not required since wasm memories can only grow but nice to have.
#[test]
fn byte_buf_shrink() {
let mut byte_buf = ByteBuf::new(PAGE_SIZE * 3).unwrap();
byte_buf.realloc(PAGE_SIZE * 2).unwrap();
}
}

740
src/memory/mod.rs Normal file
View File

@ -0,0 +1,740 @@
use alloc::{rc::Rc, string::ToString, vec::Vec};
use core::{
cell::{Cell, RefCell},
cmp, fmt,
ops::Range,
u32,
};
use memory_units::{Bytes, Pages, RoundUpTo};
use parity_wasm::elements::ResizableLimits;
use value::LittleEndianConvert;
use Error;
#[cfg(all(unix, not(feature = "vec_memory")))]
#[path = "mmap_bytebuf.rs"]
mod bytebuf;
#[cfg(any(not(unix), feature = "vec_memory"))]
#[path = "vec_bytebuf.rs"]
mod bytebuf;
use self::bytebuf::ByteBuf;
/// Size of a page of [linear memory][`MemoryInstance`] - 64KiB.
///
/// The size of a memory is always a integer multiple of a page size.
///
/// [`MemoryInstance`]: struct.MemoryInstance.html
pub const LINEAR_MEMORY_PAGE_SIZE: Bytes = Bytes(65536);
/// Reference to a memory (See [`MemoryInstance`] for details).
///
/// This reference has a reference-counting semantics.
///
/// [`MemoryInstance`]: struct.MemoryInstance.html
///
#[derive(Clone, Debug)]
pub struct MemoryRef(Rc<MemoryInstance>);
impl ::core::ops::Deref for MemoryRef {
type Target = MemoryInstance;
fn deref(&self) -> &MemoryInstance {
&self.0
}
}
/// Runtime representation of a linear memory (or `memory` for short).
///
/// A memory is a contiguous, mutable array of raw bytes. Wasm code can load and store values
/// from/to a linear memory at any byte address.
/// A trap occurs if an access is not within the bounds of the current memory size.
///
/// A memory is created with an initial size but can be grown dynamically.
/// The growth can be limited by specifying maximum size.
/// The size of a memory is always a integer multiple of a [page size][`LINEAR_MEMORY_PAGE_SIZE`] - 64KiB.
///
/// At the moment, wasm doesn't provide any way to shrink the memory.
///
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
pub struct MemoryInstance {
/// Memory limits.
limits: ResizableLimits,
/// Linear memory buffer with lazy allocation.
buffer: RefCell<ByteBuf>,
initial: Pages,
current_size: Cell<usize>,
maximum: Option<Pages>,
}
impl fmt::Debug for MemoryInstance {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("MemoryInstance")
.field("limits", &self.limits)
.field("buffer.len", &self.buffer.borrow().len())
.field("maximum", &self.maximum)
.field("initial", &self.initial)
.finish()
}
}
struct CheckedRegion {
offset: usize,
size: usize,
}
impl CheckedRegion {
fn range(&self) -> Range<usize> {
self.offset..self.offset + self.size
}
fn intersects(&self, other: &Self) -> bool {
let low = cmp::max(self.offset, other.offset);
let high = cmp::min(self.offset + self.size, other.offset + other.size);
low < high
}
}
impl MemoryInstance {
/// Allocate a memory instance.
///
/// The memory allocated with initial number of pages specified by `initial`.
/// Minimal possible value for `initial` is 0 and maximum possible is `65536`.
/// (Since maximum addressible memory is 2<sup>32</sup> = 4GiB = 65536 * [64KiB][`LINEAR_MEMORY_PAGE_SIZE`]).
///
/// It is possible to limit maximum number of pages this memory instance can have by specifying
/// `maximum`. If not specified, this memory instance would be able to allocate up to 4GiB.
///
/// Allocated memory is always zeroed.
///
/// # Errors
///
/// Returns `Err` if:
///
/// - `initial` is greater than `maximum`
/// - either `initial` or `maximum` is greater than `65536`.
///
/// [`LINEAR_MEMORY_PAGE_SIZE`]: constant.LINEAR_MEMORY_PAGE_SIZE.html
pub fn alloc(initial: Pages, maximum: Option<Pages>) -> Result<MemoryRef, Error> {
{
use core::convert::TryInto;
let initial_u32: u32 = initial.0.try_into().map_err(|_| {
Error::Memory(format!("initial ({}) can't be coerced to u32", initial.0))
})?;
let maximum_u32: Option<u32> = match maximum {
Some(maximum_pages) => Some(maximum_pages.0.try_into().map_err(|_| {
Error::Memory(format!(
"maximum ({}) can't be coerced to u32",
maximum_pages.0
))
})?),
None => None,
};
validation::validate_memory(initial_u32, maximum_u32).map_err(Error::Memory)?;
}
let memory = MemoryInstance::new(initial, maximum)?;
Ok(MemoryRef(Rc::new(memory)))
}
/// Create new linear memory instance.
fn new(initial: Pages, maximum: Option<Pages>) -> Result<Self, Error> {
let limits = ResizableLimits::new(initial.0 as u32, maximum.map(|p| p.0 as u32));
let initial_size: Bytes = initial.into();
Ok(MemoryInstance {
limits: limits,
buffer: RefCell::new(
ByteBuf::new(initial_size.0).map_err(|err| Error::Memory(err.to_string()))?,
),
initial: initial,
current_size: Cell::new(initial_size.0),
maximum: maximum,
})
}
/// Return linear memory limits.
pub(crate) fn limits(&self) -> &ResizableLimits {
&self.limits
}
/// Returns number of pages this `MemoryInstance` was created with.
pub fn initial(&self) -> Pages {
self.initial
}
/// Returns maximum amount of pages this `MemoryInstance` can grow to.
///
/// Returns `None` if there is no limit set.
/// Maximum memory size cannot exceed `65536` pages or 4GiB.
pub fn maximum(&self) -> Option<Pages> {
self.maximum
}
/// Returns current linear memory size.
///
/// Maximum memory size cannot exceed `65536` pages or 4GiB.
///
/// # Example
///
/// To convert number of pages to number of bytes you can use the following code:
///
/// ```rust
/// use wasmi::MemoryInstance;
/// use wasmi::memory_units::*;
///
/// let memory = MemoryInstance::alloc(Pages(1), None).unwrap();
/// let byte_size: Bytes = memory.current_size().into();
/// assert_eq!(
/// byte_size,
/// Bytes(65536),
/// );
/// ```
pub fn current_size(&self) -> Pages {
Bytes(self.buffer.borrow().len()).round_up_to()
}
/// Get value from memory at given offset.
pub fn get_value<T: LittleEndianConvert>(&self, offset: u32) -> Result<T, Error> {
let mut buffer = self.buffer.borrow_mut();
let region =
self.checked_region(&mut buffer, offset as usize, ::core::mem::size_of::<T>())?;
Ok(
T::from_little_endian(&buffer.as_slice_mut()[region.range()])
.expect("Slice size is checked"),
)
}
/// Copy data from memory at given offset.
///
/// This will allocate vector for you.
/// If you can provide a mutable slice you can use [`get_into`].
///
/// [`get_into`]: #method.get_into
pub fn get(&self, offset: u32, size: usize) -> Result<Vec<u8>, Error> {
let mut buffer = self.buffer.borrow_mut();
let region = self.checked_region(&mut buffer, offset as usize, size)?;
Ok(buffer.as_slice_mut()[region.range()].to_vec())
}
/// Copy data from given offset in the memory into `target` slice.
///
/// # Errors
///
/// Returns `Err` if the specified region is out of bounds.
pub fn get_into(&self, offset: u32, target: &mut [u8]) -> Result<(), Error> {
let mut buffer = self.buffer.borrow_mut();
let region = self.checked_region(&mut buffer, offset as usize, target.len())?;
target.copy_from_slice(&buffer.as_slice_mut()[region.range()]);
Ok(())
}
/// Copy data in the memory at given offset.
pub fn set(&self, offset: u32, value: &[u8]) -> Result<(), Error> {
let mut buffer = self.buffer.borrow_mut();
let range = self
.checked_region(&mut buffer, offset as usize, value.len())?
.range();
buffer.as_slice_mut()[range].copy_from_slice(value);
Ok(())
}
/// Copy value in the memory at given offset.
pub fn set_value<T: LittleEndianConvert>(&self, offset: u32, value: T) -> Result<(), Error> {
let mut buffer = self.buffer.borrow_mut();
let range = self
.checked_region(&mut buffer, offset as usize, ::core::mem::size_of::<T>())?
.range();
value.into_little_endian(&mut buffer.as_slice_mut()[range]);
Ok(())
}
/// Increases the size of the linear memory by given number of pages.
/// Returns previous memory size if succeeds.
///
/// # Errors
///
/// Returns `Err` if attempted to allocate more memory than permited by the limit.
pub fn grow(&self, additional: Pages) -> Result<Pages, Error> {
let size_before_grow: Pages = self.current_size();
if additional == Pages(0) {
return Ok(size_before_grow);
}
if additional > Pages(65536) {
return Err(Error::Memory(format!(
"Trying to grow memory by more than 65536 pages"
)));
}
let new_size: Pages = size_before_grow + additional;
let maximum = self
.maximum
.unwrap_or(Pages(validation::LINEAR_MEMORY_MAX_PAGES as usize));
if new_size > maximum {
return Err(Error::Memory(format!(
"Trying to grow memory by {} pages when already have {}",
additional.0, size_before_grow.0,
)));
}
let new_buffer_length: Bytes = new_size.into();
self.buffer
.borrow_mut()
.realloc(new_buffer_length.0)
.map_err(|err| Error::Memory(err.to_string()))?;
self.current_size.set(new_buffer_length.0);
Ok(size_before_grow)
}
fn checked_region(
&self,
buffer: &mut ByteBuf,
offset: usize,
size: usize,
) -> Result<CheckedRegion, Error> {
let end = offset.checked_add(size).ok_or_else(|| {
Error::Memory(format!(
"trying to access memory block of size {} from offset {}",
size, offset
))
})?;
if end > buffer.len() {
return Err(Error::Memory(format!(
"trying to access region [{}..{}] in memory [0..{}]",
offset,
end,
buffer.len()
)));
}
Ok(CheckedRegion {
offset: offset,
size: size,
})
}
fn checked_region_pair(
&self,
buffer: &mut ByteBuf,
offset1: usize,
size1: usize,
offset2: usize,
size2: usize,
) -> Result<(CheckedRegion, CheckedRegion), Error> {
let end1 = offset1.checked_add(size1).ok_or_else(|| {
Error::Memory(format!(
"trying to access memory block of size {} from offset {}",
size1, offset1
))
})?;
let end2 = offset2.checked_add(size2).ok_or_else(|| {
Error::Memory(format!(
"trying to access memory block of size {} from offset {}",
size2, offset2
))
})?;
if end1 > buffer.len() {
return Err(Error::Memory(format!(
"trying to access region [{}..{}] in memory [0..{}]",
offset1,
end1,
buffer.len()
)));
}
if end2 > buffer.len() {
return Err(Error::Memory(format!(
"trying to access region [{}..{}] in memory [0..{}]",
offset2,
end2,
buffer.len()
)));
}
Ok((
CheckedRegion {
offset: offset1,
size: size1,
},
CheckedRegion {
offset: offset2,
size: size2,
},
))
}
/// Copy contents of one memory region to another.
///
/// Semantically equivalent to `memmove`.
///
/// # Errors
///
/// Returns `Err` if either of specified regions is out of bounds.
pub fn copy(&self, src_offset: usize, dst_offset: usize, len: usize) -> Result<(), Error> {
let mut buffer = self.buffer.borrow_mut();
let (read_region, write_region) =
self.checked_region_pair(&mut buffer, src_offset, len, dst_offset, len)?;
unsafe {
::core::ptr::copy(
buffer.as_slice()[read_region.range()].as_ptr(),
buffer.as_slice_mut()[write_region.range()].as_mut_ptr(),
len,
)
}
Ok(())
}
/// Copy contents of one memory region to another (non-overlapping version).
///
/// Semantically equivalent to `memcpy`.
/// but returns Error if source overlaping with destination.
///
/// # Errors
///
/// Returns `Err` if:
///
/// - either of specified regions is out of bounds,
/// - these regions overlaps.
pub fn copy_nonoverlapping(
&self,
src_offset: usize,
dst_offset: usize,
len: usize,
) -> Result<(), Error> {
let mut buffer = self.buffer.borrow_mut();
let (read_region, write_region) =
self.checked_region_pair(&mut buffer, src_offset, len, dst_offset, len)?;
if read_region.intersects(&write_region) {
return Err(Error::Memory(format!(
"non-overlapping copy is used for overlapping regions"
)));
}
unsafe {
::core::ptr::copy_nonoverlapping(
buffer.as_slice()[read_region.range()].as_ptr(),
buffer.as_slice_mut()[write_region.range()].as_mut_ptr(),
len,
)
}
Ok(())
}
/// Copy memory between two (possibly distinct) memory instances.
///
/// If the same memory instance passed as `src` and `dst` then usual `copy` will be used.
pub fn transfer(
src: &MemoryRef,
src_offset: usize,
dst: &MemoryRef,
dst_offset: usize,
len: usize,
) -> Result<(), Error> {
if Rc::ptr_eq(&src.0, &dst.0) {
// `transfer` is invoked with with same source and destination. Let's assume that regions may
// overlap and use `copy`.
return src.copy(src_offset, dst_offset, len);
}
// Because memory references point to different memory instances, it is safe to `borrow_mut`
// both buffers at once (modulo `with_direct_access_mut`).
let mut src_buffer = src.buffer.borrow_mut();
let mut dst_buffer = dst.buffer.borrow_mut();
let src_range = src
.checked_region(&mut src_buffer, src_offset, len)?
.range();
let dst_range = dst
.checked_region(&mut dst_buffer, dst_offset, len)?
.range();
dst_buffer.as_slice_mut()[dst_range].copy_from_slice(&src_buffer.as_slice()[src_range]);
Ok(())
}
/// Fill the memory region with the specified value.
///
/// Semantically equivalent to `memset`.
///
/// # Errors
///
/// Returns `Err` if the specified region is out of bounds.
pub fn clear(&self, offset: usize, new_val: u8, len: usize) -> Result<(), Error> {
let mut buffer = self.buffer.borrow_mut();
let range = self.checked_region(&mut buffer, offset, len)?.range();
for val in &mut buffer.as_slice_mut()[range] {
*val = new_val
}
Ok(())
}
/// Fill the specified memory region with zeroes.
///
/// # Errors
///
/// Returns `Err` if the specified region is out of bounds.
pub fn zero(&self, offset: usize, len: usize) -> Result<(), Error> {
self.clear(offset, 0, len)
}
/// Set every byte in the entire linear memory to 0, preserving its size.
///
/// Might be useful for some optimization shenanigans.
pub fn erase(&self) -> Result<(), Error> {
self.buffer
.borrow_mut()
.erase()
.map_err(|err| Error::Memory(err.to_string()))
}
/// Provides direct access to the underlying memory buffer.
///
/// # Panics
///
/// Any call that requires write access to memory (such as [`set`], [`clear`], etc) made within
/// the closure will panic.
///
/// [`set`]: #method.get
/// [`clear`]: #method.set
pub fn with_direct_access<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
let buf = self.buffer.borrow();
f(buf.as_slice())
}
/// Provides direct mutable access to the underlying memory buffer.
///
/// # Panics
///
/// Any calls that requires either read or write access to memory (such as [`get`], [`set`], [`copy`], etc) made
/// within the closure will panic. Proceed with caution.
///
/// [`get`]: #method.get
/// [`set`]: #method.set
pub fn with_direct_access_mut<R, F: FnOnce(&mut [u8]) -> R>(&self, f: F) -> R {
let mut buf = self.buffer.borrow_mut();
f(buf.as_slice_mut())
}
}
#[cfg(test)]
mod tests {
use super::{MemoryInstance, MemoryRef, LINEAR_MEMORY_PAGE_SIZE};
use memory_units::Pages;
use std::rc::Rc;
use Error;
#[test]
fn alloc() {
let mut fixtures = vec![
(0, None, true),
(0, Some(0), true),
(1, None, true),
(1, Some(1), true),
(0, Some(1), true),
(1, Some(0), false),
];
#[cfg(target_pointer_width = "64")]
fixtures.extend(&[
(65536, Some(65536), true),
(65536, Some(0), false),
(65536, None, true),
]);
for (index, &(initial, maybe_max, expected_ok)) in fixtures.iter().enumerate() {
let initial: Pages = Pages(initial);
let maximum: Option<Pages> = maybe_max.map(|m| Pages(m));
let result = MemoryInstance::alloc(initial, maximum);
if result.is_ok() != expected_ok {
panic!(
"unexpected error at {}, initial={:?}, max={:?}, expected={}, result={:?}",
index, initial, maybe_max, expected_ok, result,
);
}
}
}
#[test]
fn ensure_page_size() {
use memory_units::ByteSize;
assert_eq!(LINEAR_MEMORY_PAGE_SIZE, Pages::byte_size());
}
fn create_memory(initial_content: &[u8]) -> MemoryInstance {
let mem = MemoryInstance::new(Pages(1), Some(Pages(1))).unwrap();
mem.set(0, initial_content)
.expect("Successful initialize the memory");
mem
}
#[test]
fn copy_overlaps_1() {
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
mem.copy(0, 4, 6).expect("Successfully copy the elements");
let result = mem.get(0, 10).expect("Successfully retrieve the result");
assert_eq!(result, &[0, 1, 2, 3, 0, 1, 2, 3, 4, 5]);
}
#[test]
fn copy_overlaps_2() {
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
mem.copy(4, 0, 6).expect("Successfully copy the elements");
let result = mem.get(0, 10).expect("Successfully retrieve the result");
assert_eq!(result, &[4, 5, 6, 7, 8, 9, 6, 7, 8, 9]);
}
#[test]
fn copy_nonoverlapping() {
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
mem.copy_nonoverlapping(0, 10, 10)
.expect("Successfully copy the elements");
let result = mem.get(10, 10).expect("Successfully retrieve the result");
assert_eq!(result, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
}
#[test]
fn copy_nonoverlapping_overlaps_1() {
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
let result = mem.copy_nonoverlapping(0, 4, 6);
match result {
Err(Error::Memory(_)) => {}
_ => panic!("Expected Error::Memory(_) result, but got {:?}", result),
}
}
#[test]
fn copy_nonoverlapping_overlaps_2() {
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
let result = mem.copy_nonoverlapping(4, 0, 6);
match result {
Err(Error::Memory(_)) => {}
_ => panic!("Expected Error::Memory(_), but got {:?}", result),
}
}
#[test]
fn transfer_works() {
let src = MemoryRef(Rc::new(create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])));
let dst = MemoryRef(Rc::new(create_memory(&[
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
])));
MemoryInstance::transfer(&src, 4, &dst, 0, 3).unwrap();
assert_eq!(src.get(0, 10).unwrap(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
assert_eq!(
dst.get(0, 10).unwrap(),
&[4, 5, 6, 13, 14, 15, 16, 17, 18, 19]
);
}
#[test]
fn transfer_still_works_with_same_memory() {
let src = MemoryRef(Rc::new(create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])));
MemoryInstance::transfer(&src, 4, &src, 0, 3).unwrap();
assert_eq!(src.get(0, 10).unwrap(), &[4, 5, 6, 3, 4, 5, 6, 7, 8, 9]);
}
#[test]
fn transfer_oob_with_same_memory_errors() {
let src = MemoryRef(Rc::new(create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])));
assert!(MemoryInstance::transfer(&src, 65535, &src, 0, 3).is_err());
// Check that memories content left untouched
assert_eq!(src.get(0, 10).unwrap(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
}
#[test]
fn transfer_oob_errors() {
let src = MemoryRef(Rc::new(create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])));
let dst = MemoryRef(Rc::new(create_memory(&[
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
])));
assert!(MemoryInstance::transfer(&src, 65535, &dst, 0, 3).is_err());
// Check that memories content left untouched
assert_eq!(src.get(0, 10).unwrap(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
assert_eq!(
dst.get(0, 10).unwrap(),
&[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
);
}
#[test]
fn clear() {
let mem = create_memory(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
mem.clear(0, 0x4A, 10)
.expect("To successfully clear the memory");
let result = mem.get(0, 10).expect("To successfully retrieve the result");
assert_eq!(result, &[0x4A; 10]);
}
#[test]
fn get_into() {
let mem = MemoryInstance::new(Pages(1), None).unwrap();
mem.set(6, &[13, 17, 129])
.expect("memory set should not fail");
let mut data = [0u8; 2];
mem.get_into(7, &mut data[..])
.expect("get_into should not fail");
assert_eq!(data, [17, 129]);
}
#[test]
fn zero_copy() {
let mem = MemoryInstance::alloc(Pages(1), None).unwrap();
mem.set(100, &[0]).expect("memory set should not fail");
mem.with_direct_access_mut(|buf| {
assert_eq!(
buf.len(),
65536,
"the buffer length is expected to be 1 page long"
);
buf[..10].copy_from_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
});
mem.with_direct_access(|buf| {
assert_eq!(
buf.len(),
65536,
"the buffer length is expected to be 1 page long"
);
assert_eq!(&buf[..10], &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
});
}
#[should_panic]
#[test]
fn zero_copy_panics_on_nested_access() {
let mem = MemoryInstance::alloc(Pages(1), None).unwrap();
let mem_inner = mem.clone();
mem.with_direct_access(move |_| {
let _ = mem_inner.set(0, &[11, 12, 13]);
});
}
}

39
src/memory/vec_bytebuf.rs Normal file
View File

@ -0,0 +1,39 @@
//! An implementation of `ByteBuf` based on a plain `Vec`.
use alloc::vec::Vec;
pub struct ByteBuf {
buf: Vec<u8>,
}
impl ByteBuf {
pub fn new(len: usize) -> Result<Self, &'static str> {
let mut buf = Vec::new();
buf.resize(len, 0u8);
Ok(Self { buf })
}
pub fn realloc(&mut self, new_len: usize) -> Result<(), &'static str> {
self.buf.resize(new_len, 0u8);
Ok(())
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn as_slice(&self) -> &[u8] {
self.buf.as_ref()
}
pub fn as_slice_mut(&mut self) -> &mut [u8] {
self.buf.as_mut()
}
pub fn erase(&mut self) -> Result<(), &'static str> {
for v in &mut self.buf {
*v = 0;
}
Ok(())
}
}

File diff suppressed because it is too large Load Diff

217
src/nan_preserving_float.rs Normal file
View File

@ -0,0 +1,217 @@
#![allow(missing_docs)]
#[cfg(not(feature = "std"))]
use libm::{F32Ext, F64Ext};
use core::cmp::{Ordering, PartialEq, PartialOrd};
use core::ops::{Add, Div, Mul, Neg, Rem, Sub};
macro_rules! impl_binop {
($for:ident, $is:ident, $op:ident, $func_name:ident) => {
impl<T: Into<$for>> $op<T> for $for {
type Output = Self;
fn $func_name(self, other: T) -> Self {
$for(
$op::$func_name($is::from_bits(self.0), $is::from_bits(other.into().0))
.to_bits(),
)
}
}
};
}
macro_rules! float {
($for:ident, $rep:ident, $is:ident) => {
float!(
$for,
$rep,
$is,
1 << (::core::mem::size_of::<$is>() * 8 - 1)
);
};
($for:ident, $rep:ident, $is:ident, $sign_bit:expr) => {
#[derive(Copy, Clone)]
pub struct $for($rep);
impl_binop!($for, $is, Add, add);
impl_binop!($for, $is, Sub, sub);
impl_binop!($for, $is, Mul, mul);
impl_binop!($for, $is, Div, div);
impl_binop!($for, $is, Rem, rem);
impl $for {
pub fn from_bits(other: $rep) -> Self {
$for(other)
}
pub fn to_bits(self) -> $rep {
self.0
}
pub fn from_float(fl: $is) -> Self {
fl.into()
}
pub fn to_float(self) -> $is {
self.into()
}
pub fn is_nan(self) -> bool {
self.to_float().is_nan()
}
pub fn abs(self) -> Self {
$for(self.0 & !$sign_bit)
}
pub fn fract(self) -> Self {
self.to_float().fract().into()
}
pub fn min(self, other: Self) -> Self {
Self::from(self.to_float().min(other.to_float()))
}
pub fn max(self, other: Self) -> Self {
Self::from(self.to_float().max(other.to_float()))
}
}
impl From<$is> for $for {
fn from(other: $is) -> $for {
$for(other.to_bits())
}
}
impl From<$for> for $is {
fn from(other: $for) -> $is {
<$is>::from_bits(other.0)
}
}
impl Neg for $for {
type Output = Self;
fn neg(self) -> Self {
$for(self.0 ^ $sign_bit)
}
}
impl<T: Into<$for> + Copy> PartialEq<T> for $for {
fn eq(&self, other: &T) -> bool {
$is::from(*self) == $is::from((*other).into())
}
}
impl<T: Into<$for> + Copy> PartialOrd<T> for $for {
fn partial_cmp(&self, other: &T) -> Option<Ordering> {
$is::from(*self).partial_cmp(&$is::from((*other).into()))
}
}
impl ::core::fmt::Debug for $for {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
$is::from(*self).fmt(f)
}
}
};
}
float!(F32, u32, f32);
float!(F64, u64, f64);
impl From<u32> for F32 {
fn from(other: u32) -> Self {
Self::from_bits(other)
}
}
impl From<F32> for u32 {
fn from(other: F32) -> Self {
other.to_bits()
}
}
impl From<u64> for F64 {
fn from(other: u64) -> Self {
Self::from_bits(other)
}
}
impl From<F64> for u64 {
fn from(other: F64) -> Self {
other.to_bits()
}
}
#[cfg(test)]
mod tests {
extern crate rand;
use self::rand::Rng;
use super::{F32, F64};
use core::{
fmt::Debug,
iter,
ops::{Add, Div, Mul, Neg, Sub},
};
fn test_ops<T, F, I>(iter: I)
where
T: Add<Output = T>
+ Div<Output = T>
+ Mul<Output = T>
+ Sub<Output = T>
+ Neg<Output = T>
+ Copy
+ Debug
+ PartialEq,
F: Into<T>
+ Add<Output = F>
+ Div<Output = F>
+ Mul<Output = F>
+ Sub<Output = F>
+ Neg<Output = F>
+ Copy
+ Debug,
I: IntoIterator<Item = (F, F)>,
{
for (a, b) in iter {
assert_eq!((a + b).into(), a.into() + b.into());
assert_eq!((a - b).into(), a.into() - b.into());
assert_eq!((a * b).into(), a.into() * b.into());
assert_eq!((a / b).into(), a.into() / b.into());
assert_eq!((-a).into(), -a.into());
assert_eq!((-b).into(), -b.into());
}
}
#[test]
fn test_ops_f32() {
let mut rng = rand::thread_rng();
let iter = iter::repeat(()).map(|_| rng.gen());
test_ops::<F32, f32, _>(iter.take(1000));
}
#[test]
fn test_ops_f64() {
let mut rng = rand::thread_rng();
let iter = iter::repeat(()).map(|_| rng.gen());
test_ops::<F64, f64, _>(iter.take(1000));
}
#[test]
fn test_neg_nan_f32() {
assert_eq!((-F32(0xff80_3210)).0, 0x7f80_3210);
}
#[test]
fn test_neg_nan_f64() {
assert_eq!((-F64(0xff80_3210_0000_0000)).0, 0x7f80_3210_0000_0000);
}
}

1276
src/prepare/compile.rs Normal file

File diff suppressed because it is too large Load Diff

169
src/prepare/mod.rs Normal file
View File

@ -0,0 +1,169 @@
use alloc::vec::Vec;
use crate::{
isa,
validation::{validate_module, Error, Validator},
};
use parity_wasm::elements::Module;
mod compile;
#[cfg(test)]
mod tests;
#[derive(Clone)]
pub struct CompiledModule {
pub code_map: Vec<isa::Instructions>,
pub module: Module,
}
pub struct WasmiValidation {
code_map: Vec<isa::Instructions>,
}
// This implementation of `Validation` is compiling wasm code at the
// validation time.
impl Validator for WasmiValidation {
type Output = Vec<isa::Instructions>;
type FuncValidator = compile::Compiler;
fn new(_module: &Module) -> Self {
WasmiValidation {
// TODO: with capacity?
code_map: Vec::new(),
}
}
fn on_function_validated(&mut self, _index: u32, output: isa::Instructions) {
self.code_map.push(output);
}
fn finish(self) -> Vec<isa::Instructions> {
self.code_map
}
}
/// Validate a module and compile it to the internal representation.
pub fn compile_module(module: Module) -> Result<CompiledModule, Error> {
let code_map = validate_module::<WasmiValidation>(&module)?;
Ok(CompiledModule { module, code_map })
}
/// Verify that the module doesn't use floating point instructions or types.
///
/// Returns `Err` if
///
/// - Any of function bodies uses a floating pointer instruction (an instruction that
/// consumes or produces a value of a floating point type)
/// - If a floating point type used in a definition of a function.
pub fn deny_floating_point(module: &Module) -> Result<(), Error> {
use parity_wasm::elements::{
Instruction::{self, *},
Type, ValueType,
};
if let Some(code) = module.code_section() {
for op in code.bodies().iter().flat_map(|body| body.code().elements()) {
macro_rules! match_eq {
($pattern:pat) => {
|val| if let $pattern = *val { true } else { false }
};
}
const DENIED: &[fn(&Instruction) -> bool] = &[
match_eq!(F32Load(_, _)),
match_eq!(F64Load(_, _)),
match_eq!(F32Store(_, _)),
match_eq!(F64Store(_, _)),
match_eq!(F32Const(_)),
match_eq!(F64Const(_)),
match_eq!(F32Eq),
match_eq!(F32Ne),
match_eq!(F32Lt),
match_eq!(F32Gt),
match_eq!(F32Le),
match_eq!(F32Ge),
match_eq!(F64Eq),
match_eq!(F64Ne),
match_eq!(F64Lt),
match_eq!(F64Gt),
match_eq!(F64Le),
match_eq!(F64Ge),
match_eq!(F32Abs),
match_eq!(F32Neg),
match_eq!(F32Ceil),
match_eq!(F32Floor),
match_eq!(F32Trunc),
match_eq!(F32Nearest),
match_eq!(F32Sqrt),
match_eq!(F32Add),
match_eq!(F32Sub),
match_eq!(F32Mul),
match_eq!(F32Div),
match_eq!(F32Min),
match_eq!(F32Max),
match_eq!(F32Copysign),
match_eq!(F64Abs),
match_eq!(F64Neg),
match_eq!(F64Ceil),
match_eq!(F64Floor),
match_eq!(F64Trunc),
match_eq!(F64Nearest),
match_eq!(F64Sqrt),
match_eq!(F64Add),
match_eq!(F64Sub),
match_eq!(F64Mul),
match_eq!(F64Div),
match_eq!(F64Min),
match_eq!(F64Max),
match_eq!(F64Copysign),
match_eq!(F32ConvertSI32),
match_eq!(F32ConvertUI32),
match_eq!(F32ConvertSI64),
match_eq!(F32ConvertUI64),
match_eq!(F32DemoteF64),
match_eq!(F64ConvertSI32),
match_eq!(F64ConvertUI32),
match_eq!(F64ConvertSI64),
match_eq!(F64ConvertUI64),
match_eq!(F64PromoteF32),
match_eq!(F32ReinterpretI32),
match_eq!(F64ReinterpretI64),
match_eq!(I32TruncSF32),
match_eq!(I32TruncUF32),
match_eq!(I32TruncSF64),
match_eq!(I32TruncUF64),
match_eq!(I64TruncSF32),
match_eq!(I64TruncUF32),
match_eq!(I64TruncSF64),
match_eq!(I64TruncUF64),
match_eq!(I32ReinterpretF32),
match_eq!(I64ReinterpretF64),
];
if DENIED.iter().any(|is_denied| is_denied(op)) {
return Err(Error(format!("Floating point operation denied: {:?}", op)));
}
}
}
if let (Some(sec), Some(types)) = (module.function_section(), module.type_section()) {
let types = types.types();
for sig in sec.entries() {
if let Some(typ) = types.get(sig.type_ref() as usize) {
match *typ {
Type::Function(ref func) => {
if func
.params()
.iter()
.chain(func.return_type().as_ref())
.any(|&typ| typ == ValueType::F32 || typ == ValueType::F64)
{
return Err(Error(format!("Use of floating point types denied")));
}
}
}
}
}
}
Ok(())
}

746
src/prepare/tests.rs Normal file
View File

@ -0,0 +1,746 @@
use super::{compile_module, CompiledModule};
use parity_wasm::{deserialize_buffer, elements::Module};
use isa;
use wabt;
fn validate(wat: &str) -> CompiledModule {
let wasm = wabt::wat2wasm(wat).unwrap();
let module = deserialize_buffer::<Module>(&wasm).unwrap();
let compiled_module = compile_module(module).unwrap();
compiled_module
}
fn compile(module: &CompiledModule) -> (Vec<isa::Instruction>, Vec<u32>) {
let code = &module.code_map[0];
let mut instructions = Vec::new();
let mut pcs = Vec::new();
let mut iter = code.iterate_from(0);
loop {
let pc = iter.position();
if let Some(instruction) = iter.next() {
instructions.push(instruction.clone());
pcs.push(pc);
} else {
break;
}
}
(instructions, pcs)
}
macro_rules! targets {
($($target:expr),*) => {
::isa::BrTargets::from_internal(
&[$($target,)*]
.iter()
.map(|&target| ::isa::InstructionInternal::BrTableTarget(target))
.collect::<Vec<_>>()[..]
)
};
}
#[test]
fn implicit_return_no_value() {
let module = validate(
r#"
(module
(func (export "call")
)
)
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![isa::Instruction::Return(isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
})]
)
}
#[test]
fn implicit_return_with_value() {
let module = validate(
r#"
(module
(func (export "call") (result i32)
i32.const 0
)
)
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(0),
isa::Instruction::Return(isa::DropKeep {
drop: 0,
keep: isa::Keep::Single,
}),
]
)
}
#[test]
fn implicit_return_param() {
let module = validate(
r#"
(module
(func (export "call") (param i32)
)
)
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![isa::Instruction::Return(isa::DropKeep {
drop: 1,
keep: isa::Keep::None,
}),]
)
}
#[test]
fn get_local() {
let module = validate(
r#"
(module
(func (export "call") (param i32) (result i32)
get_local 0
)
)
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![
isa::Instruction::GetLocal(1),
isa::Instruction::Return(isa::DropKeep {
drop: 1,
keep: isa::Keep::Single,
}),
]
)
}
#[test]
fn explicit_return() {
let module = validate(
r#"
(module
(func (export "call") (param i32) (result i32)
get_local 0
return
)
)
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![
isa::Instruction::GetLocal(1),
isa::Instruction::Return(isa::DropKeep {
drop: 1,
keep: isa::Keep::Single,
}),
isa::Instruction::Return(isa::DropKeep {
drop: 1,
keep: isa::Keep::Single,
}),
]
)
}
#[test]
fn add_params() {
let module = validate(
r#"
(module
(func (export "call") (param i32) (param i32) (result i32)
get_local 0
get_local 1
i32.add
)
)
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![
// This is tricky. Locals are now loaded from the stack. The load
// happens from address relative of the current stack pointer. The first load
// takes the value below the previous one (i.e the second argument) and then, it increments
// the stack pointer. And then the same thing hapens with the value below the previous one
// (which happens to be the value loaded by the first get_local).
isa::Instruction::GetLocal(2),
isa::Instruction::GetLocal(2),
isa::Instruction::I32Add,
isa::Instruction::Return(isa::DropKeep {
drop: 2,
keep: isa::Keep::Single,
}),
]
)
}
#[test]
fn drop_locals() {
let module = validate(
r#"
(module
(func (export "call") (param i32)
(local i32)
get_local 0
set_local 1
)
)
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![
isa::Instruction::GetLocal(2),
isa::Instruction::SetLocal(1),
isa::Instruction::Return(isa::DropKeep {
drop: 2,
keep: isa::Keep::None,
}),
]
)
}
#[test]
fn if_without_else() {
let module = validate(
r#"
(module
(func (export "call") (param i32) (result i32)
i32.const 1
if
i32.const 2
return
end
i32.const 3
)
)
"#,
);
let (code, pcs) = compile(&module);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfEqz(isa::Target {
dst_pc: pcs[4],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
},
}),
isa::Instruction::I32Const(2),
isa::Instruction::Return(isa::DropKeep {
drop: 1, // 1 param
keep: isa::Keep::Single, // 1 result
}),
isa::Instruction::I32Const(3),
isa::Instruction::Return(isa::DropKeep {
drop: 1,
keep: isa::Keep::Single,
}),
]
)
}
#[test]
fn if_else() {
let module = validate(
r#"
(module
(func (export "call")
(local i32)
i32.const 1
if
i32.const 2
set_local 0
else
i32.const 3
set_local 0
end
)
)
"#,
);
let (code, pcs) = compile(&module);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfEqz(isa::Target {
dst_pc: pcs[5],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
},
}),
isa::Instruction::I32Const(2),
isa::Instruction::SetLocal(1),
isa::Instruction::Br(isa::Target {
dst_pc: pcs[7],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
},
}),
isa::Instruction::I32Const(3),
isa::Instruction::SetLocal(1),
isa::Instruction::Return(isa::DropKeep {
drop: 1,
keep: isa::Keep::None,
}),
]
)
}
#[test]
fn if_else_returns_result() {
let module = validate(
r#"
(module
(func (export "call")
i32.const 1
if (result i32)
i32.const 2
else
i32.const 3
end
drop
)
)
"#,
);
let (code, pcs) = compile(&module);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfEqz(isa::Target {
dst_pc: pcs[4],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
},
}),
isa::Instruction::I32Const(2),
isa::Instruction::Br(isa::Target {
dst_pc: pcs[5],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
},
}),
isa::Instruction::I32Const(3),
isa::Instruction::Drop,
isa::Instruction::Return(isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
}),
]
)
}
#[test]
fn if_else_branch_from_true_branch() {
let module = validate(
r#"
(module
(func (export "call")
i32.const 1
if (result i32)
i32.const 1
i32.const 1
br_if 0
drop
i32.const 2
else
i32.const 3
end
drop
)
)
"#,
);
let (code, pcs) = compile(&module);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfEqz(isa::Target {
dst_pc: pcs[8],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
},
}),
isa::Instruction::I32Const(1),
isa::Instruction::I32Const(1),
isa::Instruction::BrIfNez(isa::Target {
dst_pc: pcs[9],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::Single,
},
}),
isa::Instruction::Drop,
isa::Instruction::I32Const(2),
isa::Instruction::Br(isa::Target {
dst_pc: pcs[9],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
},
}),
isa::Instruction::I32Const(3),
isa::Instruction::Drop,
isa::Instruction::Return(isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
}),
]
)
}
#[test]
fn if_else_branch_from_false_branch() {
let module = validate(
r#"
(module
(func (export "call")
i32.const 1
if (result i32)
i32.const 1
else
i32.const 2
i32.const 1
br_if 0
drop
i32.const 3
end
drop
)
)
"#,
);
let (code, pcs) = compile(&module);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfEqz(isa::Target {
dst_pc: pcs[4],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
},
}),
isa::Instruction::I32Const(1),
isa::Instruction::Br(isa::Target {
dst_pc: pcs[9],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
},
}),
isa::Instruction::I32Const(2),
isa::Instruction::I32Const(1),
isa::Instruction::BrIfNez(isa::Target {
dst_pc: pcs[9],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::Single,
},
}),
isa::Instruction::Drop,
isa::Instruction::I32Const(3),
isa::Instruction::Drop,
isa::Instruction::Return(isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
}),
]
)
}
#[test]
fn loop_() {
let module = validate(
r#"
(module
(func (export "call")
loop (result i32)
i32.const 1
br_if 0
i32.const 2
end
drop
)
)
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfNez(isa::Target {
dst_pc: 0,
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
},
}),
isa::Instruction::I32Const(2),
isa::Instruction::Drop,
isa::Instruction::Return(isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
}),
]
)
}
#[test]
fn loop_empty() {
let module = validate(
r#"
(module
(func (export "call")
loop
end
)
)
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![isa::Instruction::Return(isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
}),]
)
}
#[test]
fn spec_as_br_if_value_cond() {
use self::isa::Instruction::*;
let module = validate(
r#"
(func (export "as-br_if-value-cond") (result i32)
(block (result i32)
(drop
(br_if 0
(i32.const 6)
(br_table 0 0
(i32.const 9)
(i32.const 0)
)
)
)
(i32.const 7)
)
)
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![
I32Const(6),
I32Const(9),
I32Const(0),
isa::Instruction::BrTable(targets![
isa::Target {
dst_pc: 9,
drop_keep: isa::DropKeep {
drop: 1,
keep: isa::Keep::Single
}
},
isa::Target {
dst_pc: 9,
drop_keep: isa::DropKeep {
drop: 1,
keep: isa::Keep::Single
}
}
]),
BrIfNez(isa::Target {
dst_pc: 9,
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::Single
}
}),
Drop,
I32Const(7),
Return(isa::DropKeep {
drop: 0,
keep: isa::Keep::Single
})
]
);
}
#[test]
fn brtable() {
let module = validate(
r#"
(module
(func (export "call")
block $1
loop $2
i32.const 0
br_table $2 $1
end
end
)
)
"#,
);
let (code, pcs) = compile(&module);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(0),
isa::Instruction::BrTable(targets![
isa::Target {
dst_pc: 0,
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
},
},
isa::Target {
dst_pc: pcs[2],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
},
}
]),
isa::Instruction::Return(isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
}),
]
)
}
#[test]
fn brtable_returns_result() {
let module = validate(
r#"
(module
(func (export "call")
block $1 (result i32)
block $2 (result i32)
i32.const 0
i32.const 1
br_table $2 $1
end
unreachable
end
drop
)
)
"#,
);
let (code, pcs) = compile(&module);
println!("{:?}", (&code, &pcs));
assert_eq!(
code,
vec![
isa::Instruction::I32Const(0),
isa::Instruction::I32Const(1),
isa::Instruction::BrTable(targets![
isa::Target {
dst_pc: pcs[3],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::Single,
},
},
isa::Target {
dst_pc: pcs[4],
drop_keep: isa::DropKeep {
keep: isa::Keep::Single,
drop: 0,
},
}
]),
isa::Instruction::Unreachable,
isa::Instruction::Drop,
isa::Instruction::Return(isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
}),
]
)
}
#[test]
fn wabt_example() {
let module = validate(
r#"
(module
(func (export "call") (param i32) (result i32)
block $exit
get_local 0
br_if $exit
i32.const 1
return
end
i32.const 2
return
)
)
"#,
);
let (code, pcs) = compile(&module);
assert_eq!(
code,
vec![
isa::Instruction::GetLocal(1),
isa::Instruction::BrIfNez(isa::Target {
dst_pc: pcs[4],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
},
}),
isa::Instruction::I32Const(1),
isa::Instruction::Return(isa::DropKeep {
drop: 1, // 1 parameter
keep: isa::Keep::Single,
}),
isa::Instruction::I32Const(2),
isa::Instruction::Return(isa::DropKeep {
drop: 1,
keep: isa::Keep::Single,
}),
isa::Instruction::Return(isa::DropKeep {
drop: 1,
keep: isa::Keep::Single,
}),
]
)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,11 @@
use std::u32; use alloc::{rc::Rc, vec::Vec};
use std::fmt; use core::cell::RefCell;
use std::cell::RefCell; use core::fmt;
use std::rc::Rc; use core::u32;
use parity_wasm::elements::ResizableLimits;
use Error;
use func::FuncRef; use func::FuncRef;
use module::check_limits; use module::check_limits;
use parity_wasm::elements::ResizableLimits;
use Error;
/// Reference to a table (See [`TableInstance`] for details). /// Reference to a table (See [`TableInstance`] for details).
/// ///
@ -16,11 +16,11 @@ use module::check_limits;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct TableRef(Rc<TableInstance>); pub struct TableRef(Rc<TableInstance>);
impl ::std::ops::Deref for TableRef { impl ::core::ops::Deref for TableRef {
type Target = TableInstance; type Target = TableInstance;
fn deref(&self) -> &TableInstance { fn deref(&self) -> &TableInstance {
&self.0 &self.0
} }
} }
/// Runtime representation of a table. /// Runtime representation of a table.
@ -37,118 +37,118 @@ impl ::std::ops::Deref for TableRef {
/// [`grow`]: #method.grow /// [`grow`]: #method.grow
/// ///
pub struct TableInstance { pub struct TableInstance {
/// Table limits. /// Table limits.
limits: ResizableLimits, limits: ResizableLimits,
/// Table memory buffer. /// Table memory buffer.
buffer: RefCell<Vec<Option<FuncRef>>>, buffer: RefCell<Vec<Option<FuncRef>>>,
} }
impl fmt::Debug for TableInstance { impl fmt::Debug for TableInstance {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("TableInstance") f.debug_struct("TableInstance")
.field("limits", &self.limits) .field("limits", &self.limits)
.field("buffer.len", &self.buffer.borrow().len()) .field("buffer.len", &self.buffer.borrow().len())
.finish() .finish()
} }
} }
impl TableInstance { impl TableInstance {
/// Allocate a table instance. /// Allocate a table instance.
/// ///
/// The table allocated with initial size, specified by `initial_size`. /// The table allocated with initial size, specified by `initial_size`.
/// Maximum size can be specified by `maximum_size`. /// Maximum size can be specified by `maximum_size`.
/// ///
/// All table elements are allocated uninitialized. /// All table elements are allocated uninitialized.
/// ///
/// # Errors /// # Errors
/// ///
/// Returns `Err` if `initial_size` is greater than `maximum_size`. /// Returns `Err` if `initial_size` is greater than `maximum_size`.
pub fn alloc(initial_size: u32, maximum_size: Option<u32>) -> Result<TableRef, Error> { pub fn alloc(initial_size: u32, maximum_size: Option<u32>) -> Result<TableRef, Error> {
let table = TableInstance::new(ResizableLimits::new(initial_size, maximum_size))?; let table = TableInstance::new(ResizableLimits::new(initial_size, maximum_size))?;
Ok(TableRef(Rc::new(table))) Ok(TableRef(Rc::new(table)))
} }
fn new(limits: ResizableLimits) -> Result<TableInstance, Error> { fn new(limits: ResizableLimits) -> Result<TableInstance, Error> {
check_limits(&limits)?; check_limits(&limits)?;
Ok(TableInstance { Ok(TableInstance {
buffer: RefCell::new(vec![None; limits.initial() as usize]), buffer: RefCell::new(vec![None; limits.initial() as usize]),
limits: limits, limits: limits,
}) })
} }
/// Return table limits. /// Return table limits.
pub(crate) fn limits(&self) -> &ResizableLimits { pub(crate) fn limits(&self) -> &ResizableLimits {
&self.limits &self.limits
} }
/// Returns size this table was created with. /// Returns size this table was created with.
pub fn initial_size(&self) -> u32 { pub fn initial_size(&self) -> u32 {
self.limits.initial() self.limits.initial()
} }
/// Returns maximum size `TableInstance` can grow to. /// Returns maximum size `TableInstance` can grow to.
pub fn maximum_size(&self) -> Option<u32> { pub fn maximum_size(&self) -> Option<u32> {
self.limits.maximum() self.limits.maximum()
} }
/// Returns current size of the table. /// Returns current size of the table.
pub fn current_size(&self) -> u32 { pub fn current_size(&self) -> u32 {
self.buffer.borrow().len() as u32 self.buffer.borrow().len() as u32
} }
/// Increases the size of the table by given number of elements. /// Increases the size of the table by given number of elements.
/// ///
/// # Errors /// # Errors
/// ///
/// Returns `Err` if tried to allocate more elements than permited by limit. /// Returns `Err` if tried to allocate more elements than permited by limit.
pub fn grow(&self, by: u32) -> Result<(), Error> { pub fn grow(&self, by: u32) -> Result<(), Error> {
let mut buffer = self.buffer.borrow_mut(); let mut buffer = self.buffer.borrow_mut();
let maximum_size = self.maximum_size().unwrap_or(u32::MAX); let maximum_size = self.maximum_size().unwrap_or(u32::MAX);
let new_size = self.current_size().checked_add(by) let new_size = self
.and_then(|new_size| { .current_size()
if maximum_size < new_size { .checked_add(by)
None .and_then(|new_size| {
} else { if maximum_size < new_size {
Some(new_size) None
} } else {
}) Some(new_size)
.ok_or_else(|| }
Error::Table(format!( })
"Trying to grow table by {} items when there are already {} items", .ok_or_else(|| {
by, Error::Table(format!(
self.current_size(), "Trying to grow table by {} items when there are already {} items",
)) by,
)?; self.current_size(),
buffer.resize(new_size as usize, None); ))
Ok(()) })?;
} buffer.resize(new_size as usize, None);
Ok(())
}
/// Get the specific value in the table /// Get the specific value in the table
pub fn get(&self, offset: u32) -> Result<Option<FuncRef>, Error> { pub fn get(&self, offset: u32) -> Result<Option<FuncRef>, Error> {
let buffer = self.buffer.borrow(); let buffer = self.buffer.borrow();
let buffer_len = buffer.len(); let buffer_len = buffer.len();
let table_elem = buffer.get(offset as usize).cloned().ok_or_else(|| let table_elem = buffer.get(offset as usize).cloned().ok_or_else(|| {
Error::Table(format!( Error::Table(format!(
"trying to read table item with index {} when there are only {} items", "trying to read table item with index {} when there are only {} items",
offset, offset, buffer_len
buffer_len ))
)), })?;
)?; Ok(table_elem)
Ok(table_elem) }
}
/// Set the table element to the specified function. /// Set the table element to the specified function.
pub fn set(&self, offset: u32, value: Option<FuncRef>) -> Result<(), Error> { pub fn set(&self, offset: u32, value: Option<FuncRef>) -> Result<(), Error> {
let mut buffer = self.buffer.borrow_mut(); let mut buffer = self.buffer.borrow_mut();
let buffer_len = buffer.len(); let buffer_len = buffer.len();
let table_elem = buffer.get_mut(offset as usize).ok_or_else(|| let table_elem = buffer.get_mut(offset as usize).ok_or_else(|| {
Error::Table(format!( Error::Table(format!(
"trying to update table item with index {} when there are only {} items", "trying to update table item with index {} when there are only {} items",
offset, offset, buffer_len
buffer_len ))
)) })?;
)?; *table_elem = value;
*table_elem = value; Ok(())
Ok(()) }
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
use wabt; use wabt;
use {Module}; use Module;
mod host; mod host;
mod wasm; mod wasm;
@ -12,12 +12,31 @@ fn assert_std_err_impl<T: ::std::error::Error>() {}
#[test] #[test]
fn assert_error_properties() { fn assert_error_properties() {
assert_send::<Error>(); assert_send::<Error>();
assert_sync::<Error>(); assert_sync::<Error>();
assert_std_err_impl::<Error>(); assert_std_err_impl::<Error>();
}
/// Test that converting an u32 (u64) that does not fit in an i32 (i64)
/// to a RuntimeValue and back works as expected and the number remains unchanged.
#[test]
fn unsigned_to_runtime_value() {
use super::RuntimeValue;
let overflow_i32: u32 = ::core::i32::MAX as u32 + 1;
assert_eq!(
RuntimeValue::from(overflow_i32).try_into::<u32>().unwrap(),
overflow_i32
);
let overflow_i64: u64 = ::core::i64::MAX as u64 + 1;
assert_eq!(
RuntimeValue::from(overflow_i64).try_into::<u64>().unwrap(),
overflow_i64
);
} }
pub fn parse_wat(source: &str) -> Module { pub fn parse_wat(source: &str) -> Module {
let wasm_binary = wabt::wat2wasm(source).expect("Failed to parse wat source"); let wasm_binary = wabt::wat2wasm(source).expect("Failed to parse wat source");
Module::from_buffer(wasm_binary).expect("Failed to load parsed module") Module::from_buffer(wasm_binary).expect("Failed to load parsed module")
} }

View File

@ -1,110 +1,113 @@
use {
Error, Signature, FuncRef, GlobalInstance, GlobalRef, ImportsBuilder, MemoryInstance,
MemoryRef, ModuleImportResolver, ModuleInstance, NopExternals, RuntimeValue,
TableInstance, TableRef, Module, GlobalDescriptor, TableDescriptor, MemoryDescriptor,
};
use memory_units::Pages; use memory_units::Pages;
use std::fs::File; use std::fs::File;
use {
Error, FuncRef, GlobalDescriptor, GlobalInstance, GlobalRef, ImportsBuilder, MemoryDescriptor,
MemoryInstance, MemoryRef, Module, ModuleImportResolver, ModuleInstance, NopExternals,
RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef,
};
struct Env { struct Env {
table_base: GlobalRef, table_base: GlobalRef,
memory_base: GlobalRef, memory_base: GlobalRef,
memory: MemoryRef, memory: MemoryRef,
table: TableRef, table: TableRef,
} }
impl Env { impl Env {
fn new() -> Env { fn new() -> Env {
Env { Env {
table_base: GlobalInstance::alloc(RuntimeValue::I32(0), false), table_base: GlobalInstance::alloc(RuntimeValue::I32(0), false),
memory_base: GlobalInstance::alloc(RuntimeValue::I32(0), false), memory_base: GlobalInstance::alloc(RuntimeValue::I32(0), false),
memory: MemoryInstance::alloc(Pages(256), None).unwrap(), memory: MemoryInstance::alloc(Pages(256), None).unwrap(),
table: TableInstance::alloc(64, None).unwrap(), table: TableInstance::alloc(64, None).unwrap(),
} }
} }
} }
impl ModuleImportResolver for Env { impl ModuleImportResolver for Env {
fn resolve_func(&self, _field_name: &str, _func_type: &Signature) -> Result<FuncRef, Error> { fn resolve_func(&self, _field_name: &str, _func_type: &Signature) -> Result<FuncRef, Error> {
Err(Error::Instantiation( Err(Error::Instantiation(
"env module doesn't provide any functions".into(), "env module doesn't provide any functions".into(),
)) ))
} }
fn resolve_global( fn resolve_global(
&self, &self,
field_name: &str, field_name: &str,
_global_type: &GlobalDescriptor, _global_type: &GlobalDescriptor,
) -> Result<GlobalRef, Error> { ) -> Result<GlobalRef, Error> {
match field_name { match field_name {
"tableBase" => Ok(self.table_base.clone()), "tableBase" => Ok(self.table_base.clone()),
"memoryBase" => Ok(self.memory_base.clone()), "memoryBase" => Ok(self.memory_base.clone()),
_ => Err(Error::Instantiation(format!( _ => Err(Error::Instantiation(format!(
"env module doesn't provide global '{}'", "env module doesn't provide global '{}'",
field_name field_name
))), ))),
} }
} }
fn resolve_memory( fn resolve_memory(
&self, &self,
field_name: &str, field_name: &str,
_memory_type: &MemoryDescriptor, _memory_type: &MemoryDescriptor,
) -> Result<MemoryRef, Error> { ) -> Result<MemoryRef, Error> {
match field_name { match field_name {
"memory" => Ok(self.memory.clone()), "memory" => Ok(self.memory.clone()),
_ => Err(Error::Instantiation(format!( _ => Err(Error::Instantiation(format!(
"env module doesn't provide memory '{}'", "env module doesn't provide memory '{}'",
field_name field_name
))), ))),
} }
} }
fn resolve_table(&self, field_name: &str, _table_type: &TableDescriptor) -> Result<TableRef, Error> { fn resolve_table(
match field_name { &self,
"table" => Ok(self.table.clone()), field_name: &str,
_ => Err(Error::Instantiation( _table_type: &TableDescriptor,
format!("env module doesn't provide table '{}'", field_name), ) -> Result<TableRef, Error> {
)), match field_name {
} "table" => Ok(self.table.clone()),
} _ => Err(Error::Instantiation(format!(
"env module doesn't provide table '{}'",
field_name
))),
}
}
} }
fn load_from_file(filename: &str) -> Module { fn load_from_file(filename: &str) -> Module {
use std::io::prelude::*; use std::io::prelude::*;
let mut file = File::open(filename).unwrap(); let mut file = File::open(filename).unwrap();
let mut buf = Vec::new(); let mut buf = Vec::new();
file.read_to_end(&mut buf).unwrap(); file.read_to_end(&mut buf).unwrap();
let wasm_buf = ::wabt::wat2wasm(&buf).unwrap(); let wasm_buf = ::wabt::wat2wasm(&buf).unwrap();
Module::from_buffer(wasm_buf).unwrap() Module::from_buffer(wasm_buf).unwrap()
} }
#[test] #[test]
fn interpreter_inc_i32() { fn interpreter_inc_i32() {
// Name of function contained in WASM file (note the leading underline) // Name of function contained in WASM file (note the leading underline)
const FUNCTION_NAME: &'static str = "_inc_i32"; const FUNCTION_NAME: &'static str = "_inc_i32";
// The WASM file containing the module and function // The WASM file containing the module and function
const WASM_FILE: &str = &"res/fixtures/inc_i32.wast"; const WASM_FILE: &str = &"res/fixtures/inc_i32.wast";
let module = load_from_file(WASM_FILE); let module = load_from_file(WASM_FILE);
let env = Env::new(); let env = Env::new();
let instance = ModuleInstance::new( let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env))
&module, .expect("Failed to instantiate module")
&ImportsBuilder::new().with_resolver("env", &env), .assert_no_start();
).expect("Failed to instantiate module")
.assert_no_start();
let i32_val = 42; let i32_val = 42;
// the functions expects a single i32 parameter // the functions expects a single i32 parameter
let args = &[RuntimeValue::I32(i32_val)]; let args = &[RuntimeValue::I32(i32_val)];
let exp_retval = Some(RuntimeValue::I32(i32_val + 1)); let exp_retval = Some(RuntimeValue::I32(i32_val + 1));
let retval = instance let retval = instance
.invoke_export(FUNCTION_NAME, args, &mut NopExternals) .invoke_export(FUNCTION_NAME, args, &mut NopExternals)
.expect(""); .expect("");
assert_eq!(exp_retval, retval); assert_eq!(exp_retval, retval);
} }
#[test] #[test]
@ -114,18 +117,15 @@ fn interpreter_accumulate_u8() {
// The WASM file containing the module and function // The WASM file containing the module and function
const WASM_FILE: &str = &"res/fixtures/accumulate_u8.wast"; const WASM_FILE: &str = &"res/fixtures/accumulate_u8.wast";
// The octet sequence being accumulated // The octet sequence being accumulated
const BUF: &[u8] = &[9,8,7,6,5,4,3,2,1]; const BUF: &[u8] = &[9, 8, 7, 6, 5, 4, 3, 2, 1];
// Load the module-structure from wasm-file and add to program // Load the module-structure from wasm-file and add to program
let module = load_from_file(WASM_FILE); let module = load_from_file(WASM_FILE);
let env = Env::new(); let env = Env::new();
let instance = ModuleInstance::new( let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env))
&module, .expect("Failed to instantiate module")
&ImportsBuilder::new().with_resolver("env", &env), .assert_no_start();
).expect("Failed to instantiate module")
.assert_no_start();
let env_memory = env.memory.clone(); let env_memory = env.memory.clone();
@ -134,7 +134,10 @@ fn interpreter_accumulate_u8() {
let _ = env_memory.set(offset, BUF); let _ = env_memory.set(offset, BUF);
// Set up the function argument list and invoke the function // Set up the function argument list and invoke the function
let args = &[RuntimeValue::I32(BUF.len() as i32), RuntimeValue::I32(offset as i32)]; let args = &[
RuntimeValue::I32(BUF.len() as i32),
RuntimeValue::I32(offset as i32),
];
let retval = instance let retval = instance
.invoke_export(FUNCTION_NAME, args, &mut NopExternals) .invoke_export(FUNCTION_NAME, args, &mut NopExternals)
.expect("Failed to execute function"); .expect("Failed to execute function");

View File

@ -1,7 +1,8 @@
use std::borrow::Cow; use alloc::borrow::Cow;
use parity_wasm::elements::{ use parity_wasm::elements::{
FunctionType, ValueType as EValueType, GlobalType, TableType, MemoryType}; FunctionType, GlobalType, MemoryType, TableType, ValueType as EValueType,
};
/// Signature of a [function]. /// Signature of a [function].
/// ///
@ -13,39 +14,60 @@ use parity_wasm::elements::{
/// [function]: struct.FuncInstance.html /// [function]: struct.FuncInstance.html
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Signature { pub struct Signature {
params: Cow<'static, [ValueType]>, params: Cow<'static, [ValueType]>,
return_type: Option<ValueType>, return_type: Option<ValueType>,
} }
impl Signature { impl Signature {
/// Creates new signature with givens /// Creates new signature with givens
/// parameter types and optional return type. /// parameter types and optional return type.
pub fn new<C: Into<Cow<'static, [ValueType]>>>( ///
params: C, /// # Examples
return_type: Option<ValueType> ///
) -> Signature { /// ```rust
Signature { /// use wasmi::{Signature, ValueType};
params: params.into(), ///
return_type: return_type, /// // s1: (i32) -> ()
} /// let s1 = Signature::new(&[ValueType::I32][..], None);
} ///
/// // s2: () -> i32
/// let s2 = Signature::new(&[][..], Some(ValueType::I32));
///
/// // s3: (I64) -> ()
/// let dynamic_params = vec![ValueType::I64];
/// let s3 = Signature::new(dynamic_params, None);
/// ```
pub fn new<C: Into<Cow<'static, [ValueType]>>>(
params: C,
return_type: Option<ValueType>,
) -> Signature {
Signature {
params: params.into(),
return_type: return_type,
}
}
/// Returns parameter types of this signature. /// Returns parameter types of this signature.
pub fn params(&self) -> &[ValueType] { pub fn params(&self) -> &[ValueType] {
&self.params.as_ref() &self.params.as_ref()
} }
/// Returns return type of this signature. /// Returns return type of this signature.
pub fn return_type(&self) -> Option<ValueType> { pub fn return_type(&self) -> Option<ValueType> {
self.return_type self.return_type
} }
pub(crate) fn from_elements(func_type: &FunctionType) -> Signature { pub(crate) fn from_elements(func_type: &FunctionType) -> Signature {
Signature { Signature {
params: func_type.params().iter().cloned().map(ValueType::from_elements).collect(), params: func_type
return_type: func_type.return_type().map(ValueType::from_elements), .params()
} .iter()
} .cloned()
.map(ValueType::from_elements)
.collect(),
return_type: func_type.return_type().map(ValueType::from_elements),
}
}
} }
/// Type of a value. /// Type of a value.
@ -55,34 +77,34 @@ impl Signature {
/// [`RuntimeValue`]: enum.RuntimeValue.html /// [`RuntimeValue`]: enum.RuntimeValue.html
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ValueType { pub enum ValueType {
/// 32-bit signed or unsigned integer. /// 32-bit signed or unsigned integer.
I32, I32,
/// 64-bit signed or unsigned integer. /// 64-bit signed or unsigned integer.
I64, I64,
/// 32-bit IEEE 754-2008 floating point number. /// 32-bit IEEE 754-2008 floating point number.
F32, F32,
/// 64-bit IEEE 754-2008 floating point number. /// 64-bit IEEE 754-2008 floating point number.
F64, F64,
} }
impl ValueType { impl ValueType {
pub(crate) fn from_elements(value_type: EValueType) -> ValueType { pub(crate) fn from_elements(value_type: EValueType) -> ValueType {
match value_type { match value_type {
EValueType::I32 => ValueType::I32, EValueType::I32 => ValueType::I32,
EValueType::I64 => ValueType::I64, EValueType::I64 => ValueType::I64,
EValueType::F32 => ValueType::F32, EValueType::F32 => ValueType::F32,
EValueType::F64 => ValueType::F64, EValueType::F64 => ValueType::F64,
} }
} }
pub(crate) fn into_elements(self) -> EValueType { pub(crate) fn into_elements(self) -> EValueType {
match self { match self {
ValueType::I32 => EValueType::I32, ValueType::I32 => EValueType::I32,
ValueType::I64 => EValueType::I64, ValueType::I64 => EValueType::I64,
ValueType::F32 => EValueType::F32, ValueType::F32 => EValueType::F32,
ValueType::F64 => EValueType::F64, ValueType::F64 => EValueType::F64,
} }
} }
} }
/// Description of a global variable. /// Description of a global variable.
@ -92,29 +114,29 @@ impl ValueType {
/// ///
/// [`ImportResolver`]: trait.ImportResolver.html /// [`ImportResolver`]: trait.ImportResolver.html
pub struct GlobalDescriptor { pub struct GlobalDescriptor {
value_type: ValueType, value_type: ValueType,
mutable: bool, mutable: bool,
} }
impl GlobalDescriptor { impl GlobalDescriptor {
pub(crate) fn from_elements(global_type: &GlobalType) -> GlobalDescriptor { pub(crate) fn from_elements(global_type: &GlobalType) -> GlobalDescriptor {
GlobalDescriptor { GlobalDescriptor {
value_type: ValueType::from_elements(global_type.content_type()), value_type: ValueType::from_elements(global_type.content_type()),
mutable: global_type.is_mutable(), mutable: global_type.is_mutable(),
} }
} }
/// Returns [`ValueType`] of the requested global. /// Returns [`ValueType`] of the requested global.
/// ///
/// [`ValueType`]: enum.ValueType.html /// [`ValueType`]: enum.ValueType.html
pub fn value_type(&self) -> ValueType { pub fn value_type(&self) -> ValueType {
self.value_type self.value_type
} }
/// Returns whether the requested global mutable. /// Returns whether the requested global mutable.
pub fn is_mutable(&self) -> bool { pub fn is_mutable(&self) -> bool {
self.mutable self.mutable
} }
} }
/// Description of a table. /// Description of a table.
@ -124,27 +146,27 @@ impl GlobalDescriptor {
/// ///
/// [`ImportResolver`]: trait.ImportResolver.html /// [`ImportResolver`]: trait.ImportResolver.html
pub struct TableDescriptor { pub struct TableDescriptor {
initial: u32, initial: u32,
maximum: Option<u32>, maximum: Option<u32>,
} }
impl TableDescriptor { impl TableDescriptor {
pub(crate) fn from_elements(table_type: &TableType) -> TableDescriptor { pub(crate) fn from_elements(table_type: &TableType) -> TableDescriptor {
TableDescriptor { TableDescriptor {
initial: table_type.limits().initial(), initial: table_type.limits().initial(),
maximum: table_type.limits().maximum(), maximum: table_type.limits().maximum(),
} }
} }
/// Returns initial size of the requested table. /// Returns initial size of the requested table.
pub fn initial(&self) -> u32 { pub fn initial(&self) -> u32 {
self.initial self.initial
} }
/// Returns maximum size of the requested table. /// Returns maximum size of the requested table.
pub fn maximum(&self) -> Option<u32> { pub fn maximum(&self) -> Option<u32> {
self.maximum self.maximum
} }
} }
/// Description of a linear memory. /// Description of a linear memory.
@ -154,25 +176,25 @@ impl TableDescriptor {
/// ///
/// [`ImportResolver`]: trait.ImportResolver.html /// [`ImportResolver`]: trait.ImportResolver.html
pub struct MemoryDescriptor { pub struct MemoryDescriptor {
initial: u32, initial: u32,
maximum: Option<u32>, maximum: Option<u32>,
} }
impl MemoryDescriptor { impl MemoryDescriptor {
pub(crate) fn from_elements(memory_type: &MemoryType) -> MemoryDescriptor { pub(crate) fn from_elements(memory_type: &MemoryType) -> MemoryDescriptor {
MemoryDescriptor { MemoryDescriptor {
initial: memory_type.limits().initial(), initial: memory_type.limits().initial(),
maximum: memory_type.limits().maximum(), maximum: memory_type.limits().maximum(),
} }
} }
/// Returns initial size (in pages) of the requested memory. /// Returns initial size (in pages) of the requested memory.
pub fn initial(&self) -> u32 { pub fn initial(&self) -> u32 {
self.initial self.initial
} }
/// Returns maximum size (in pages) of the requested memory. /// Returns maximum size (in pages) of the requested memory.
pub fn maximum(&self) -> Option<u32> { pub fn maximum(&self) -> Option<u32> {
self.maximum self.maximum
} }
} }

View File

@ -1,134 +0,0 @@
use parity_wasm::elements::{MemoryType, TableType, GlobalType, BlockType, ValueType, FunctionType};
use validation::Error;
#[derive(Default, Debug)]
pub struct ModuleContext {
pub memories: Vec<MemoryType>,
pub tables: Vec<TableType>,
pub globals: Vec<GlobalType>,
pub types: Vec<FunctionType>,
pub func_type_indexes: Vec<u32>,
}
impl ModuleContext {
pub fn memories(&self) -> &[MemoryType] {
&self.memories
}
pub fn tables(&self) -> &[TableType] {
&self.tables
}
pub fn globals(&self) -> &[GlobalType] {
&self.globals
}
pub fn types(&self) -> &[FunctionType] {
&self.types
}
pub fn func_type_indexes(&self) -> &[u32] {
&self.func_type_indexes
}
pub fn require_memory(&self, idx: u32) -> Result<(), Error> {
if self.memories().get(idx as usize).is_none() {
return Err(Error(format!("Memory at index {} doesn't exists", idx)));
}
Ok(())
}
pub fn require_table(&self, idx: u32) -> Result<&TableType, Error> {
self.tables()
.get(idx as usize)
.ok_or_else(|| Error(format!("Table at index {} doesn't exists", idx)))
}
pub fn require_function(&self, idx: u32) -> Result<(&[ValueType], BlockType), Error> {
let ty_idx = self.func_type_indexes()
.get(idx as usize)
.ok_or_else(|| Error(format!("Function at index {} doesn't exists", idx)))?;
self.require_function_type(*ty_idx)
}
pub fn require_function_type(&self, idx: u32) -> Result<(&[ValueType], BlockType), Error> {
let ty = self.types()
.get(idx as usize)
.ok_or_else(|| Error(format!("Type at index {} doesn't exists", idx)))?;
let params = ty.params();
let return_ty = ty.return_type()
.map(BlockType::Value)
.unwrap_or(BlockType::NoResult);
Ok((params, return_ty))
}
pub fn require_global(&self, idx: u32, mutability: Option<bool>) -> Result<&GlobalType, Error> {
let global = self.globals()
.get(idx as usize)
.ok_or_else(|| Error(format!("Global at index {} doesn't exists", idx)))?;
if let Some(expected_mutable) = mutability {
if expected_mutable && !global.is_mutable() {
return Err(Error(format!("Expected global {} to be mutable", idx)));
}
if !expected_mutable && global.is_mutable() {
return Err(Error(format!("Expected global {} to be immutable", idx)));
}
}
Ok(global)
}
}
#[derive(Default)]
pub struct ModuleContextBuilder {
memories: Vec<MemoryType>,
tables: Vec<TableType>,
globals: Vec<GlobalType>,
types: Vec<FunctionType>,
func_type_indexes: Vec<u32>,
}
impl ModuleContextBuilder {
pub fn new() -> ModuleContextBuilder {
ModuleContextBuilder::default()
}
pub fn push_memory(&mut self, memory: MemoryType) {
self.memories.push(memory);
}
pub fn push_table(&mut self, table: TableType) {
self.tables.push(table);
}
pub fn push_global(&mut self, global: GlobalType) {
self.globals.push(global);
}
pub fn set_types(&mut self, types: Vec<FunctionType>) {
self.types = types;
}
pub fn push_func_type_index(&mut self, func_type_index: u32) {
self.func_type_indexes.push(func_type_index);
}
pub fn build(self) -> ModuleContext {
let ModuleContextBuilder {
memories,
tables,
globals,
types,
func_type_indexes,
} = self;
ModuleContext {
memories,
tables,
globals,
types,
func_type_indexes,
}
}
}

View File

@ -1,763 +0,0 @@
use std::u32;
use std::iter::repeat;
use std::collections::HashMap;
use parity_wasm::elements::{Opcode, BlockType, ValueType, TableElementType, Func, FuncBody};
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
use validation::context::ModuleContext;
use validation::Error;
use common::stack::StackWithLimit;
use common::{BlockFrame, BlockFrameType};
/// Maximum number of entries in value stack per function.
const DEFAULT_VALUE_STACK_LIMIT: usize = 16384;
/// Maximum number of entries in frame stack per function.
const DEFAULT_FRAME_STACK_LIMIT: usize = 16384;
/// Function validation context.
struct FunctionValidationContext<'a> {
/// Wasm module
module: &'a ModuleContext,
/// Current instruction position.
position: usize,
/// Local variables.
locals: &'a [ValueType],
/// Value stack.
value_stack: StackWithLimit<StackValueType>,
/// Frame stack.
frame_stack: StackWithLimit<BlockFrame>,
/// Function return type. None if validating expression.
return_type: Option<BlockType>,
/// Labels positions.
labels: HashMap<usize, usize>,
}
/// Value type on the stack.
#[derive(Debug, Clone, Copy)]
enum StackValueType {
/// Any value type.
Any,
/// Concrete value type.
Specific(ValueType),
}
/// Function validator.
pub struct Validator;
/// Instruction outcome.
#[derive(Debug, Clone)]
enum InstructionOutcome {
/// Continue with next instruction.
ValidateNextInstruction,
/// Unreachable instruction reached.
Unreachable,
}
impl Validator {
pub fn validate_function(
module: &ModuleContext,
func: &Func,
body: &FuncBody,
) -> Result<HashMap<usize, usize>, Error> {
let (params, result_ty) = module.require_function_type(func.type_ref())?;
// locals = (params + vars)
let mut locals = params.to_vec();
locals.extend(
body.locals()
.iter()
.flat_map(|l| repeat(l.value_type())
.take(l.count() as usize)
),
);
let mut context = FunctionValidationContext::new(
&module,
&locals,
DEFAULT_VALUE_STACK_LIMIT,
DEFAULT_FRAME_STACK_LIMIT,
result_ty,
);
context.push_label(BlockFrameType::Function, result_ty)?;
Validator::validate_function_block(&mut context, body.code().elements())?;
while !context.frame_stack.is_empty() {
context.pop_label()?;
}
Ok(context.into_labels())
}
fn validate_function_block(context: &mut FunctionValidationContext, body: &[Opcode]) -> Result<(), Error> {
let body_len = body.len();
if body_len == 0 {
return Err(Error("Non-empty function body expected".into()));
}
loop {
let opcode = &body[context.position];
match Validator::validate_instruction(context, opcode)? {
InstructionOutcome::ValidateNextInstruction => (),
InstructionOutcome::Unreachable => context.unreachable()?,
}
context.position += 1;
if context.position == body_len {
return Ok(());
}
}
}
fn validate_instruction(context: &mut FunctionValidationContext, opcode: &Opcode) -> Result<InstructionOutcome, Error> {
use self::Opcode::*;
match *opcode {
Unreachable => Ok(InstructionOutcome::Unreachable),
Nop => Ok(InstructionOutcome::ValidateNextInstruction),
Block(block_type) => Validator::validate_block(context, block_type),
Loop(block_type) => Validator::validate_loop(context, block_type),
If(block_type) => Validator::validate_if(context, block_type),
Else => Validator::validate_else(context),
End => Validator::validate_end(context),
Br(idx) => Validator::validate_br(context, idx),
BrIf(idx) => Validator::validate_br_if(context, idx),
BrTable(ref table, default) => Validator::validate_br_table(context, table, default),
Return => Validator::validate_return(context),
Call(index) => Validator::validate_call(context, index),
CallIndirect(index, _reserved) => Validator::validate_call_indirect(context, index),
Drop => Validator::validate_drop(context),
Select => Validator::validate_select(context),
GetLocal(index) => Validator::validate_get_local(context, index),
SetLocal(index) => Validator::validate_set_local(context, index),
TeeLocal(index) => Validator::validate_tee_local(context, index),
GetGlobal(index) => Validator::validate_get_global(context, index),
SetGlobal(index) => Validator::validate_set_global(context, index),
I32Load(align, _) => Validator::validate_load(context, align, 4, ValueType::I32),
I64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::I64),
F32Load(align, _) => Validator::validate_load(context, align, 4, ValueType::F32),
F64Load(align, _) => Validator::validate_load(context, align, 8, ValueType::F64),
I32Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I32),
I32Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I32),
I32Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I32),
I32Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I32),
I64Load8S(align, _) => Validator::validate_load(context, align, 1, ValueType::I64),
I64Load8U(align, _) => Validator::validate_load(context, align, 1, ValueType::I64),
I64Load16S(align, _) => Validator::validate_load(context, align, 2, ValueType::I64),
I64Load16U(align, _) => Validator::validate_load(context, align, 2, ValueType::I64),
I64Load32S(align, _) => Validator::validate_load(context, align, 4, ValueType::I64),
I64Load32U(align, _) => Validator::validate_load(context, align, 4, ValueType::I64),
I32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::I32),
I64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::I64),
F32Store(align, _) => Validator::validate_store(context, align, 4, ValueType::F32),
F64Store(align, _) => Validator::validate_store(context, align, 8, ValueType::F64),
I32Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I32),
I32Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I32),
I64Store8(align, _) => Validator::validate_store(context, align, 1, ValueType::I64),
I64Store16(align, _) => Validator::validate_store(context, align, 2, ValueType::I64),
I64Store32(align, _) => Validator::validate_store(context, align, 4, ValueType::I64),
CurrentMemory(_) => Validator::validate_current_memory(context),
GrowMemory(_) => Validator::validate_grow_memory(context),
I32Const(_) => Validator::validate_const(context, ValueType::I32),
I64Const(_) => Validator::validate_const(context, ValueType::I64),
F32Const(_) => Validator::validate_const(context, ValueType::F32),
F64Const(_) => Validator::validate_const(context, ValueType::F64),
I32Eqz => Validator::validate_testop(context, ValueType::I32),
I32Eq => Validator::validate_relop(context, ValueType::I32),
I32Ne => Validator::validate_relop(context, ValueType::I32),
I32LtS => Validator::validate_relop(context, ValueType::I32),
I32LtU => Validator::validate_relop(context, ValueType::I32),
I32GtS => Validator::validate_relop(context, ValueType::I32),
I32GtU => Validator::validate_relop(context, ValueType::I32),
I32LeS => Validator::validate_relop(context, ValueType::I32),
I32LeU => Validator::validate_relop(context, ValueType::I32),
I32GeS => Validator::validate_relop(context, ValueType::I32),
I32GeU => Validator::validate_relop(context, ValueType::I32),
I64Eqz => Validator::validate_testop(context, ValueType::I64),
I64Eq => Validator::validate_relop(context, ValueType::I64),
I64Ne => Validator::validate_relop(context, ValueType::I64),
I64LtS => Validator::validate_relop(context, ValueType::I64),
I64LtU => Validator::validate_relop(context, ValueType::I64),
I64GtS => Validator::validate_relop(context, ValueType::I64),
I64GtU => Validator::validate_relop(context, ValueType::I64),
I64LeS => Validator::validate_relop(context, ValueType::I64),
I64LeU => Validator::validate_relop(context, ValueType::I64),
I64GeS => Validator::validate_relop(context, ValueType::I64),
I64GeU => Validator::validate_relop(context, ValueType::I64),
F32Eq => Validator::validate_relop(context, ValueType::F32),
F32Ne => Validator::validate_relop(context, ValueType::F32),
F32Lt => Validator::validate_relop(context, ValueType::F32),
F32Gt => Validator::validate_relop(context, ValueType::F32),
F32Le => Validator::validate_relop(context, ValueType::F32),
F32Ge => Validator::validate_relop(context, ValueType::F32),
F64Eq => Validator::validate_relop(context, ValueType::F64),
F64Ne => Validator::validate_relop(context, ValueType::F64),
F64Lt => Validator::validate_relop(context, ValueType::F64),
F64Gt => Validator::validate_relop(context, ValueType::F64),
F64Le => Validator::validate_relop(context, ValueType::F64),
F64Ge => Validator::validate_relop(context, ValueType::F64),
I32Clz => Validator::validate_unop(context, ValueType::I32),
I32Ctz => Validator::validate_unop(context, ValueType::I32),
I32Popcnt => Validator::validate_unop(context, ValueType::I32),
I32Add => Validator::validate_binop(context, ValueType::I32),
I32Sub => Validator::validate_binop(context, ValueType::I32),
I32Mul => Validator::validate_binop(context, ValueType::I32),
I32DivS => Validator::validate_binop(context, ValueType::I32),
I32DivU => Validator::validate_binop(context, ValueType::I32),
I32RemS => Validator::validate_binop(context, ValueType::I32),
I32RemU => Validator::validate_binop(context, ValueType::I32),
I32And => Validator::validate_binop(context, ValueType::I32),
I32Or => Validator::validate_binop(context, ValueType::I32),
I32Xor => Validator::validate_binop(context, ValueType::I32),
I32Shl => Validator::validate_binop(context, ValueType::I32),
I32ShrS => Validator::validate_binop(context, ValueType::I32),
I32ShrU => Validator::validate_binop(context, ValueType::I32),
I32Rotl => Validator::validate_binop(context, ValueType::I32),
I32Rotr => Validator::validate_binop(context, ValueType::I32),
I64Clz => Validator::validate_unop(context, ValueType::I64),
I64Ctz => Validator::validate_unop(context, ValueType::I64),
I64Popcnt => Validator::validate_unop(context, ValueType::I64),
I64Add => Validator::validate_binop(context, ValueType::I64),
I64Sub => Validator::validate_binop(context, ValueType::I64),
I64Mul => Validator::validate_binop(context, ValueType::I64),
I64DivS => Validator::validate_binop(context, ValueType::I64),
I64DivU => Validator::validate_binop(context, ValueType::I64),
I64RemS => Validator::validate_binop(context, ValueType::I64),
I64RemU => Validator::validate_binop(context, ValueType::I64),
I64And => Validator::validate_binop(context, ValueType::I64),
I64Or => Validator::validate_binop(context, ValueType::I64),
I64Xor => Validator::validate_binop(context, ValueType::I64),
I64Shl => Validator::validate_binop(context, ValueType::I64),
I64ShrS => Validator::validate_binop(context, ValueType::I64),
I64ShrU => Validator::validate_binop(context, ValueType::I64),
I64Rotl => Validator::validate_binop(context, ValueType::I64),
I64Rotr => Validator::validate_binop(context, ValueType::I64),
F32Abs => Validator::validate_unop(context, ValueType::F32),
F32Neg => Validator::validate_unop(context, ValueType::F32),
F32Ceil => Validator::validate_unop(context, ValueType::F32),
F32Floor => Validator::validate_unop(context, ValueType::F32),
F32Trunc => Validator::validate_unop(context, ValueType::F32),
F32Nearest => Validator::validate_unop(context, ValueType::F32),
F32Sqrt => Validator::validate_unop(context, ValueType::F32),
F32Add => Validator::validate_binop(context, ValueType::F32),
F32Sub => Validator::validate_binop(context, ValueType::F32),
F32Mul => Validator::validate_binop(context, ValueType::F32),
F32Div => Validator::validate_binop(context, ValueType::F32),
F32Min => Validator::validate_binop(context, ValueType::F32),
F32Max => Validator::validate_binop(context, ValueType::F32),
F32Copysign => Validator::validate_binop(context, ValueType::F32),
F64Abs => Validator::validate_unop(context, ValueType::F64),
F64Neg => Validator::validate_unop(context, ValueType::F64),
F64Ceil => Validator::validate_unop(context, ValueType::F64),
F64Floor => Validator::validate_unop(context, ValueType::F64),
F64Trunc => Validator::validate_unop(context, ValueType::F64),
F64Nearest => Validator::validate_unop(context, ValueType::F64),
F64Sqrt => Validator::validate_unop(context, ValueType::F64),
F64Add => Validator::validate_binop(context, ValueType::F64),
F64Sub => Validator::validate_binop(context, ValueType::F64),
F64Mul => Validator::validate_binop(context, ValueType::F64),
F64Div => Validator::validate_binop(context, ValueType::F64),
F64Min => Validator::validate_binop(context, ValueType::F64),
F64Max => Validator::validate_binop(context, ValueType::F64),
F64Copysign => Validator::validate_binop(context, ValueType::F64),
I32WrapI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::I32),
I32TruncSF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32),
I32TruncUF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32),
I32TruncSF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I32),
I32TruncUF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I32),
I64ExtendSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::I64),
I64ExtendUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::I64),
I64TruncSF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I64),
I64TruncUF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I64),
I64TruncSF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64),
I64TruncUF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64),
F32ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32),
F32ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32),
F32ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F32),
F32ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F32),
F32DemoteF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::F32),
F64ConvertSI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F64),
F64ConvertUI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F64),
F64ConvertSI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64),
F64ConvertUI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64),
F64PromoteF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::F64),
I32ReinterpretF32 => Validator::validate_cvtop(context, ValueType::F32, ValueType::I32),
I64ReinterpretF64 => Validator::validate_cvtop(context, ValueType::F64, ValueType::I64),
F32ReinterpretI32 => Validator::validate_cvtop(context, ValueType::I32, ValueType::F32),
F64ReinterpretI64 => Validator::validate_cvtop(context, ValueType::I64, ValueType::F64),
}
}
fn validate_const(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
context.push_value(value_type.into())?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_unop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
context.pop_value(value_type.into())?;
context.push_value(value_type.into())?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_binop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
context.pop_value(value_type.into())?;
context.pop_value(value_type.into())?;
context.push_value(value_type.into())?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_testop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
context.pop_value(value_type.into())?;
context.push_value(ValueType::I32.into())?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_relop(context: &mut FunctionValidationContext, value_type: ValueType) -> Result<InstructionOutcome, Error> {
context.pop_value(value_type.into())?;
context.pop_value(value_type.into())?;
context.push_value(ValueType::I32.into())?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_cvtop(context: &mut FunctionValidationContext, value_type1: ValueType, value_type2: ValueType) -> Result<InstructionOutcome, Error> {
context.pop_value(value_type1.into())?;
context.push_value(value_type2.into())?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_drop(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
context.pop_value(StackValueType::Any).map(|_| ())?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_select(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
context.pop_value(ValueType::I32.into())?;
let select_type = context.pop_value(StackValueType::Any)?;
context.pop_value(select_type)?;
context.push_value(select_type)?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_get_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
let local_type = context.require_local(index)?;
context.push_value(local_type)?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_set_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
let local_type = context.require_local(index)?;
let value_type = context.pop_value(StackValueType::Any)?;
if local_type != value_type {
return Err(Error(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type)));
}
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_tee_local(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
let local_type = context.require_local(index)?;
let value_type = context.tee_value(StackValueType::Any)?;
if local_type != value_type {
return Err(Error(format!("Trying to update local {} of type {:?} with value of type {:?}", index, local_type, value_type)));
}
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_get_global(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
let global_type: StackValueType = {
let global = context.module.require_global(index, None)?;
global.content_type().into()
};
context.push_value(global_type)?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_set_global(context: &mut FunctionValidationContext, index: u32) -> Result<InstructionOutcome, Error> {
let global_type: StackValueType = {
let global = context.module.require_global(index, Some(true))?;
global.content_type().into()
};
let value_type = context.pop_value(StackValueType::Any)?;
if global_type != value_type {
return Err(Error(format!("Trying to update global {} of type {:?} with value of type {:?}", index, global_type, value_type)));
}
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_load(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: ValueType) -> Result<InstructionOutcome, Error> {
if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align {
return Err(Error(format!("Too large memory alignment 2^{} (expected at most {})", align, max_align)));
}
context.pop_value(ValueType::I32.into())?;
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
context.push_value(value_type.into())?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_store(context: &mut FunctionValidationContext, align: u32, max_align: u32, value_type: ValueType) -> Result<InstructionOutcome, Error> {
if 1u32.checked_shl(align).unwrap_or(u32::MAX) > max_align {
return Err(Error(format!("Too large memory alignment 2^{} (expected at most {})", align, max_align)));
}
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
context.pop_value(value_type.into())?;
context.pop_value(ValueType::I32.into())?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_block(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
context.push_label(BlockFrameType::Block, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
}
fn validate_loop(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
context.push_label(BlockFrameType::Loop, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
}
fn validate_if(context: &mut FunctionValidationContext, block_type: BlockType) -> Result<InstructionOutcome, Error> {
context.pop_value(ValueType::I32.into())?;
context.push_label(BlockFrameType::IfTrue, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
}
fn validate_else(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
let block_type = {
let top_frame = context.top_label()?;
if top_frame.frame_type != BlockFrameType::IfTrue {
return Err(Error("Misplaced else instruction".into()));
}
top_frame.block_type
};
context.pop_label()?;
if let BlockType::Value(value_type) = block_type {
context.pop_value(value_type.into())?;
}
context.push_label(BlockFrameType::IfFalse, block_type).map(|_| InstructionOutcome::ValidateNextInstruction)
}
fn validate_end(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
{
let top_frame = context.top_label()?;
if top_frame.frame_type == BlockFrameType::IfTrue {
if top_frame.block_type != BlockType::NoResult {
return Err(Error(format!("If block without else required to have NoResult block type. But it have {:?} type", top_frame.block_type)));
}
}
}
context.pop_label().map(|_| InstructionOutcome::ValidateNextInstruction)
}
fn validate_br(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
let (frame_type, frame_block_type) = {
let frame = context.require_label(idx)?;
(frame.frame_type, frame.block_type)
};
if frame_type != BlockFrameType::Loop {
if let BlockType::Value(value_type) = frame_block_type {
context.tee_value(value_type.into())?;
}
}
Ok(InstructionOutcome::Unreachable)
}
fn validate_br_if(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
context.pop_value(ValueType::I32.into())?;
let (frame_type, frame_block_type) = {
let frame = context.require_label(idx)?;
(frame.frame_type, frame.block_type)
};
if frame_type != BlockFrameType::Loop {
if let BlockType::Value(value_type) = frame_block_type {
context.tee_value(value_type.into())?;
}
}
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_br_table(context: &mut FunctionValidationContext, table: &[u32], default: u32) -> Result<InstructionOutcome, Error> {
let mut required_block_type = None;
{
let default_block = context.require_label(default)?;
if default_block.frame_type != BlockFrameType::Loop {
required_block_type = Some(default_block.block_type);
}
for label in table {
let label_block = context.require_label(*label)?;
if label_block.frame_type != BlockFrameType::Loop {
if let Some(required_block_type) = required_block_type {
if required_block_type != label_block.block_type {
return Err(Error(format!("Labels in br_table points to block of different types: {:?} and {:?}", required_block_type, label_block.block_type)));
}
}
required_block_type = Some(label_block.block_type);
}
}
}
context.pop_value(ValueType::I32.into())?;
if let Some(required_block_type) = required_block_type {
if let BlockType::Value(value_type) = required_block_type {
context.tee_value(value_type.into())?;
}
}
Ok(InstructionOutcome::Unreachable)
}
fn validate_return(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
if let BlockType::Value(value_type) = context.return_type()? {
context.tee_value(value_type.into())?;
}
Ok(InstructionOutcome::Unreachable)
}
fn validate_call(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
let (argument_types, return_type) = context.module.require_function(idx)?;
for argument_type in argument_types.iter().rev() {
context.pop_value((*argument_type).into())?;
}
if let BlockType::Value(value_type) = return_type {
context.push_value(value_type.into())?;
}
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_call_indirect(context: &mut FunctionValidationContext, idx: u32) -> Result<InstructionOutcome, Error> {
{
let table = context.module.require_table(DEFAULT_TABLE_INDEX)?;
if table.elem_type() != TableElementType::AnyFunc {
return Err(Error(format!(
"Table {} has element type {:?} while `anyfunc` expected",
idx,
table.elem_type()
)));
}
}
context.pop_value(ValueType::I32.into())?;
let (argument_types, return_type) = context.module.require_function_type(idx)?;
for argument_type in argument_types.iter().rev() {
context.pop_value((*argument_type).into())?;
}
if let BlockType::Value(value_type) = return_type {
context.push_value(value_type.into())?;
}
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_current_memory(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
context.push_value(ValueType::I32.into())?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn validate_grow_memory(context: &mut FunctionValidationContext) -> Result<InstructionOutcome, Error> {
context.module.require_memory(DEFAULT_MEMORY_INDEX)?;
context.pop_value(ValueType::I32.into())?;
context.push_value(ValueType::I32.into())?;
Ok(InstructionOutcome::ValidateNextInstruction)
}
}
impl<'a> FunctionValidationContext<'a> {
fn new(
module: &'a ModuleContext,
locals: &'a [ValueType],
value_stack_limit: usize,
frame_stack_limit: usize,
return_type: BlockType,
) -> Self {
FunctionValidationContext {
module: module,
position: 0,
locals: locals,
value_stack: StackWithLimit::with_limit(value_stack_limit),
frame_stack: StackWithLimit::with_limit(frame_stack_limit),
return_type: Some(return_type),
labels: HashMap::new(),
}
}
fn push_value(&mut self, value_type: StackValueType) -> Result<(), Error> {
Ok(self.value_stack.push(value_type.into())?)
}
fn pop_value(&mut self, value_type: StackValueType) -> Result<StackValueType, Error> {
let (is_stack_polymorphic, label_value_stack_len) = {
let frame = self.top_label()?;
(frame.polymorphic_stack, frame.value_stack_len)
};
let stack_is_empty = self.value_stack.len() == label_value_stack_len;
let actual_value = if stack_is_empty && is_stack_polymorphic {
StackValueType::Any
} else {
self.check_stack_access()?;
self.value_stack.pop()?
};
match actual_value {
StackValueType::Specific(stack_value_type) if stack_value_type == value_type => {
Ok(actual_value)
}
StackValueType::Any => Ok(actual_value),
stack_value_type @ _ => Err(Error(format!(
"Expected value of type {:?} on top of stack. Got {:?}",
value_type, stack_value_type
))),
}
}
fn check_stack_access(&self) -> Result<(), Error> {
let value_stack_min = self.frame_stack.top().expect("at least 1 topmost block").value_stack_len;
if self.value_stack.len() > value_stack_min {
Ok(())
} else {
Err(Error("Trying to access parent frame stack values.".into()))
}
}
fn tee_value(&mut self, value_type: StackValueType) -> Result<StackValueType, Error> {
let value = self.pop_value(value_type)?;
self.push_value(value)?;
Ok(value)
}
fn unreachable(&mut self) -> Result<(), Error> {
let frame = self.frame_stack.top_mut()?;
self.value_stack.resize(frame.value_stack_len, StackValueType::Any);
frame.polymorphic_stack = true;
Ok(())
}
fn top_label(&self) -> Result<&BlockFrame, Error> {
Ok(self.frame_stack.top()?)
}
fn push_label(&mut self, frame_type: BlockFrameType, block_type: BlockType) -> Result<(), Error> {
Ok(self.frame_stack.push(BlockFrame {
frame_type: frame_type,
block_type: block_type,
begin_position: self.position,
branch_position: self.position,
end_position: self.position,
value_stack_len: self.value_stack.len(),
polymorphic_stack: false,
})?)
}
fn pop_label(&mut self) -> Result<InstructionOutcome, Error> {
// Don't pop frame yet. This is essential since we still might pop values from the value stack
// and this in turn requires current frame to check whether or not we've reached
// unreachable.
let block_type = self.frame_stack.top()?.block_type;
match block_type {
BlockType::NoResult => (),
BlockType::Value(required_value_type) => {
self.pop_value(StackValueType::Specific(required_value_type))?;
}
}
let frame = self.frame_stack.pop()?;
if self.value_stack.len() != frame.value_stack_len {
return Err(Error(format!(
"Unexpected stack height {}, expected {}",
self.value_stack.len(),
frame.value_stack_len
)));
}
if !self.frame_stack.is_empty() {
self.labels.insert(frame.begin_position, self.position);
}
if let BlockType::Value(value_type) = frame.block_type {
self.push_value(value_type.into())?;
}
Ok(InstructionOutcome::ValidateNextInstruction)
}
fn require_label(&self, idx: u32) -> Result<&BlockFrame, Error> {
Ok(self.frame_stack.get(idx as usize)?)
}
fn return_type(&self) -> Result<BlockType, Error> {
self.return_type.ok_or(Error("Trying to return from expression".into()))
}
fn require_local(&self, idx: u32) -> Result<StackValueType, Error> {
self.locals.get(idx as usize)
.cloned()
.map(Into::into)
.ok_or(Error(format!("Trying to access local with index {} when there are only {} locals", idx, self.locals.len())))
}
fn into_labels(self) -> HashMap<usize, usize> {
self.labels
}
}
impl StackValueType {
fn is_any(&self) -> bool {
match self {
&StackValueType::Any => true,
_ => false,
}
}
fn value_type(&self) -> ValueType {
match self {
&StackValueType::Any => unreachable!("must be checked by caller"),
&StackValueType::Specific(value_type) => value_type,
}
}
}
impl From<ValueType> for StackValueType {
fn from(value_type: ValueType) -> Self {
StackValueType::Specific(value_type)
}
}
impl PartialEq<StackValueType> for StackValueType {
fn eq(&self, other: &StackValueType) -> bool {
if self.is_any() || other.is_any() {
true
} else {
self.value_type() == other.value_type()
}
}
}
impl PartialEq<ValueType> for StackValueType {
fn eq(&self, other: &ValueType) -> bool {
if self.is_any() {
true
} else {
self.value_type() == *other
}
}
}
impl PartialEq<StackValueType> for ValueType {
fn eq(&self, other: &StackValueType) -> bool {
other == self
}
}

View File

@ -1,337 +0,0 @@
use std::error;
use std::fmt;
use std::collections::{HashMap, HashSet};
use parity_wasm::elements::{
BlockType, External, GlobalEntry, GlobalType, Internal, MemoryType, Module, Opcode,
ResizableLimits, TableType, ValueType, InitExpr, Type
};
use common::stack;
use self::context::ModuleContextBuilder;
use self::func::Validator;
use memory_units::Pages;
mod context;
mod func;
#[cfg(test)]
mod tests;
#[derive(Debug)]
pub struct Error(String);
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl error::Error for Error {
fn description(&self) -> &str {
&self.0
}
}
impl From<stack::Error> for Error {
fn from(e: stack::Error) -> Error {
Error(format!("Stack: {}", e))
}
}
#[derive(Clone)]
pub struct ValidatedModule {
pub labels: HashMap<usize, HashMap<usize, usize>>,
pub module: Module,
}
impl ::std::ops::Deref for ValidatedModule {
type Target = Module;
fn deref(&self) -> &Module {
&self.module
}
}
pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
let mut context_builder = ModuleContextBuilder::new();
let mut imported_globals = Vec::new();
let mut labels = HashMap::new();
// Copy types from module as is.
context_builder.set_types(
module
.type_section()
.map(|ts| {
ts.types()
.into_iter()
.map(|&Type::Function(ref ty)| ty)
.cloned()
.collect()
})
.unwrap_or_default(),
);
// Fill elements with imported values.
for import_entry in module
.import_section()
.map(|i| i.entries())
.unwrap_or_default()
{
match *import_entry.external() {
External::Function(idx) => context_builder.push_func_type_index(idx),
External::Table(ref table) => context_builder.push_table(table.clone()),
External::Memory(ref memory) => context_builder.push_memory(memory.clone()),
External::Global(ref global) => {
context_builder.push_global(global.clone());
imported_globals.push(global.clone());
}
}
}
// Concatenate elements with defined in the module.
if let Some(function_section) = module.function_section() {
for func_entry in function_section.entries() {
context_builder.push_func_type_index(func_entry.type_ref())
}
}
if let Some(table_section) = module.table_section() {
for table_entry in table_section.entries() {
validate_table_type(table_entry)?;
context_builder.push_table(table_entry.clone());
}
}
if let Some(mem_section) = module.memory_section() {
for mem_entry in mem_section.entries() {
validate_memory_type(mem_entry)?;
context_builder.push_memory(mem_entry.clone());
}
}
if let Some(global_section) = module.global_section() {
for global_entry in global_section.entries() {
validate_global_entry(global_entry, &imported_globals)?;
context_builder.push_global(global_entry.global_type().clone());
}
}
let context = context_builder.build();
let function_section_len = module
.function_section()
.map(|s| s.entries().len())
.unwrap_or(0);
let code_section_len = module.code_section().map(|s| s.bodies().len()).unwrap_or(0);
if function_section_len != code_section_len {
return Err(Error(format!(
"length of function section is {}, while len of code section is {}",
function_section_len,
code_section_len
)));
}
// validate every function body in user modules
if function_section_len != 0 {
// tests use invalid code
let function_section = module.function_section().expect(
"function_section_len != 0; qed",
);
let code_section = module.code_section().expect(
"function_section_len != 0; function_section_len == code_section_len; qed",
);
// check every function body
for (index, function) in function_section.entries().iter().enumerate() {
let function_body = code_section.bodies().get(index as usize).ok_or(
Error(format!(
"Missing body for function {}",
index
)),
)?;
let func_labels = Validator::validate_function(&context, function, function_body)
.map_err(|e| {
let Error(ref msg) = e;
Error(format!("Function #{} validation error: {}", index, msg))
})?;
labels.insert(index, func_labels);
}
}
// validate start section
if let Some(start_fn_idx) = module.start_section() {
let (params, return_ty) = context.require_function(start_fn_idx)?;
if return_ty != BlockType::NoResult || params.len() != 0 {
return Err(Error(
"start function expected to have type [] -> []".into(),
));
}
}
// validate export section
if let Some(export_section) = module.export_section() {
let mut export_names = HashSet::with_capacity(export_section.entries().len());
for export in export_section.entries() {
// HashSet::insert returns false if item already in set.
let duplicate = export_names.insert(export.field()) == false;
if duplicate {
return Err(Error(
format!("duplicate export {}", export.field()),
));
}
match *export.internal() {
Internal::Function(function_index) => {
context.require_function(function_index)?;
}
Internal::Global(global_index) => {
context.require_global(global_index, Some(false))?;
}
Internal::Memory(memory_index) => {
context.require_memory(memory_index)?;
}
Internal::Table(table_index) => {
context.require_table(table_index)?;
}
}
}
}
// validate import section
if let Some(import_section) = module.import_section() {
for import in import_section.entries() {
match *import.external() {
External::Function(function_type_index) => {
context.require_function(function_type_index)?;
}
External::Global(ref global_type) => {
if global_type.is_mutable() {
return Err(Error(format!(
"trying to import mutable global {}",
import.field()
)));
}
}
External::Memory(ref memory_type) => {
validate_memory_type(memory_type)?;
}
External::Table(ref table_type) => {
validate_table_type(table_type)?;
}
}
}
}
// there must be no greater than 1 table in tables index space
if context.tables().len() > 1 {
return Err(Error(format!(
"too many tables in index space: {}",
context.tables().len()
)));
}
// there must be no greater than 1 linear memory in memory index space
if context.memories().len() > 1 {
return Err(Error(format!(
"too many memory regions in index space: {}",
context.memories().len()
)));
}
// use data section to initialize linear memory regions
if let Some(data_section) = module.data_section() {
for data_segment in data_section.entries() {
context.require_memory(data_segment.index())?;
let init_ty = expr_const_type(data_segment.offset(), context.globals())?;
if init_ty != ValueType::I32 {
return Err(Error("segment offset should return I32".into()));
}
}
}
// use element section to fill tables
if let Some(element_section) = module.elements_section() {
for element_segment in element_section.entries() {
context.require_table(element_segment.index())?;
let init_ty = expr_const_type(element_segment.offset(), context.globals())?;
if init_ty != ValueType::I32 {
return Err(Error("segment offset should return I32".into()));
}
for function_index in element_segment.members() {
context.require_function(*function_index)?;
}
}
}
Ok(ValidatedModule {
module,
labels
})
}
fn validate_limits(limits: &ResizableLimits) -> Result<(), Error> {
if let Some(maximum) = limits.maximum() {
if limits.initial() > maximum {
return Err(Error(format!(
"maximum limit {} is less than minimum {}",
maximum,
limits.initial()
)));
}
}
Ok(())
}
fn validate_memory_type(memory_type: &MemoryType) -> Result<(), Error> {
let initial: Pages = Pages(memory_type.limits().initial() as usize);
let maximum: Option<Pages> = memory_type.limits().maximum().map(|m| Pages(m as usize));
::memory::validate_memory(initial, maximum).map_err(Error)
}
fn validate_table_type(table_type: &TableType) -> Result<(), Error> {
validate_limits(table_type.limits())
}
fn validate_global_entry(global_entry: &GlobalEntry, globals: &[GlobalType]) -> Result<(), Error> {
let init = global_entry.init_expr();
let init_expr_ty = expr_const_type(init, globals)?;
if init_expr_ty != global_entry.global_type().content_type() {
return Err(Error(format!(
"Trying to initialize variable of type {:?} with value of type {:?}",
global_entry.global_type().content_type(),
init_expr_ty
)));
}
Ok(())
}
/// Returns type of this constant expression.
fn expr_const_type(init_expr: &InitExpr, globals: &[GlobalType]) -> Result<ValueType, Error> {
let code = init_expr.code();
if code.len() != 2 {
return Err(Error(
"Init expression should always be with length 2".into(),
));
}
let expr_ty: ValueType = match code[0] {
Opcode::I32Const(_) => ValueType::I32,
Opcode::I64Const(_) => ValueType::I64,
Opcode::F32Const(_) => ValueType::F32,
Opcode::F64Const(_) => ValueType::F64,
Opcode::GetGlobal(idx) => {
match globals.get(idx as usize) {
Some(target_global) => {
if target_global.is_mutable() {
return Err(Error(format!("Global {} is mutable", idx)));
}
target_global.content_type()
}
None => {
return Err(Error(
format!("Global {} doesn't exists or not yet defined", idx),
))
}
}
}
_ => return Err(Error("Non constant opcode in init expr".into())),
};
if code[1] != Opcode::End {
return Err(Error("Expression doesn't ends with `end` opcode".into()));
}
Ok(expr_ty)
}

View File

@ -1,301 +0,0 @@
use super::validate_module;
use parity_wasm::builder::module;
use parity_wasm::elements::{
External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType,
Opcode, Opcodes, TableType, ValueType, BlockType
};
#[test]
fn empty_is_valid() {
let module = module().build();
assert!(validate_module(module).is_ok());
}
#[test]
fn limits() {
let test_cases = vec![
// min > max
(10, Some(9), false),
// min = max
(10, Some(10), true),
// table/memory is always valid without max
(10, None, true),
];
for (min, max, is_valid) in test_cases {
// defined table
let m = module()
.table()
.with_min(min)
.with_max(max)
.build()
.build();
assert_eq!(validate_module(m).is_ok(), is_valid);
// imported table
let m = module()
.with_import(
ImportEntry::new(
"core".into(),
"table".into(),
External::Table(TableType::new(min, max))
)
)
.build();
assert_eq!(validate_module(m).is_ok(), is_valid);
// defined memory
let m = module()
.memory()
.with_min(min)
.with_max(max)
.build()
.build();
assert_eq!(validate_module(m).is_ok(), is_valid);
// imported table
let m = module()
.with_import(
ImportEntry::new(
"core".into(),
"memory".into(),
External::Memory(MemoryType::new(min, max))
)
)
.build();
assert_eq!(validate_module(m).is_ok(), is_valid);
}
}
#[test]
fn global_init_const() {
let m = module()
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(
vec![Opcode::I32Const(42), Opcode::End]
)
)
)
.build();
assert!(validate_module(m).is_ok());
// init expr type differs from declared global type
let m = module()
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I64, true),
InitExpr::new(vec![Opcode::I32Const(42), Opcode::End])
)
)
.build();
assert!(validate_module(m).is_err());
}
#[test]
fn global_init_global() {
let m = module()
.with_import(
ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, false))
)
)
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End])
)
)
.build();
assert!(validate_module(m).is_ok());
// get_global can reference only previously defined globals
let m = module()
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End])
)
)
.build();
assert!(validate_module(m).is_err());
// get_global can reference only const globals
let m = module()
.with_import(
ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, true))
)
)
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End])
)
)
.build();
assert!(validate_module(m).is_err());
// get_global in init_expr can only refer to imported globals.
let m = module()
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, false),
InitExpr::new(vec![Opcode::I32Const(0), Opcode::End])
)
)
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End])
)
)
.build();
assert!(validate_module(m).is_err());
}
#[test]
fn global_init_misc() {
// without delimiting End opcode
let m = module()
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Opcode::I32Const(42)])
)
)
.build();
assert!(validate_module(m).is_err());
// empty init expr
let m = module()
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Opcode::End])
)
)
.build();
assert!(validate_module(m).is_err());
// not an constant opcode used
let m = module()
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Opcode::Unreachable, Opcode::End])
)
)
.build();
assert!(validate_module(m).is_err());
}
#[test]
fn module_limits_validity() {
// module cannot contain more than 1 memory atm.
let m = module()
.with_import(
ImportEntry::new(
"core".into(),
"memory".into(),
External::Memory(MemoryType::new(10, None))
)
)
.memory()
.with_min(10)
.build()
.build();
assert!(validate_module(m).is_err());
// module cannot contain more than 1 table atm.
let m = module()
.with_import(
ImportEntry::new(
"core".into(),
"table".into(),
External::Table(TableType::new(10, None))
)
)
.table()
.with_min(10)
.build()
.build();
assert!(validate_module(m).is_err());
}
#[test]
fn funcs() {
// recursive function calls is legal.
let m = module()
.function()
.signature().return_type().i32().build()
.body().with_opcodes(Opcodes::new(vec![
Opcode::Call(1),
Opcode::End,
])).build()
.build()
.function()
.signature().return_type().i32().build()
.body().with_opcodes(Opcodes::new(vec![
Opcode::Call(0),
Opcode::End,
])).build()
.build()
.build();
assert!(validate_module(m).is_ok());
}
#[test]
fn globals() {
// import immutable global is legal.
let m = module()
.with_import(
ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, false))
)
)
.build();
assert!(validate_module(m).is_ok());
// import mutable global is invalid.
let m = module()
.with_import(
ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, true))
)
)
.build();
assert!(validate_module(m).is_err());
}
#[test]
fn if_else_with_return_type_validation() {
let m = module()
.function()
.signature().build()
.body().with_opcodes(Opcodes::new(vec![
Opcode::I32Const(1),
Opcode::If(BlockType::NoResult),
Opcode::I32Const(1),
Opcode::If(BlockType::Value(ValueType::I32)),
Opcode::I32Const(1),
Opcode::Else,
Opcode::I32Const(2),
Opcode::End,
Opcode::Drop,
Opcode::End,
Opcode::End,
])).build()
.build()
.build();
validate_module(m).unwrap();
}

File diff suppressed because it is too large Load Diff

12
test.sh
View File

@ -2,8 +2,18 @@
set -eux set -eux
EXTRA_ARGS=""
if [ -n "${TARGET-}" ]; then
# Tests build in debug mode are prohibitively
# slow when ran under emulation so that
# e.g. Travis CI will hit timeouts.
EXTRA_ARGS="--release --target=${TARGET}"
export RUSTFLAGS="--cfg debug_assertions"
fi
cd $(dirname $0) cd $(dirname $0)
time cargo test time cargo test --all ${EXTRA_ARGS}
cd - cd -

View File

@ -1,12 +1,12 @@
mod run; mod run;
macro_rules! run_test { macro_rules! run_test {
($label: expr, $test_name: ident) => ( ($label: expr, $test_name: ident) => {
#[test] #[test]
fn $test_name() { fn $test_name() {
self::run::spec($label) self::run::spec($label)
} }
); };
} }
run_test!("address", wasm_address); run_test!("address", wasm_address);
@ -21,10 +21,10 @@ run_test!("call", wasm_call);
run_test!("call_indirect", wasm_call_indirect); run_test!("call_indirect", wasm_call_indirect);
run_test!("comments", wasm_comments); run_test!("comments", wasm_comments);
run_test!("const", wasm_const); run_test!("const", wasm_const);
// TODO: commented out until sNaN issue is resolved: run_test!("conversions", wasm_conversions);
// https://github.com/NikVolf/parity-wasm/blob/b5aaf103cf28f1e36df832f4883f55043e67894b/src/interpreter/value.rs#L510 run_test!("custom", wasm_custom);
// run_test!("conversions", wasm_conversions);
run_test!("custom_section", wasm_custom_section); run_test!("custom_section", wasm_custom_section);
run_test!("data", wasm_data);
run_test!("elem", wasm_elem); run_test!("elem", wasm_elem);
run_test!("endianness", wasm_endianness); run_test!("endianness", wasm_endianness);
run_test!("exports", wasm_exports); run_test!("exports", wasm_exports);
@ -35,11 +35,9 @@ run_test!("f64", wasm_f64);
run_test!("f64_bitwise", wasm_f64_bitwise); run_test!("f64_bitwise", wasm_f64_bitwise);
run_test!("f64_cmp", wasm_f64_cmp); run_test!("f64_cmp", wasm_f64_cmp);
run_test!("fac", wasm_fac); run_test!("fac", wasm_fac);
// TODO: commented out until sNaN issue is resolved: run_test!("float_exprs", wasm_float_exprs);
// https://github.com/NikVolf/parity-wasm/blob/b5aaf103cf28f1e36df832f4883f55043e67894b/src/interpreter/value.rs#L510 run_test!("float_literals", wasm_float_literals);
// run_test!("float_exprs", wasm_float_exprs); run_test!("float_memory", wasm_float_memory);
// run_test!("float_literals", wasm_float_literals);
// run_test!("float_memory", wasm_float_memory);
run_test!("float_misc", wasm_float_misc); run_test!("float_misc", wasm_float_misc);
run_test!("forward", wasm_forward); run_test!("forward", wasm_forward);
run_test!("func", wasm_func); run_test!("func", wasm_func);

View File

@ -1,22 +1,24 @@
#![cfg(test)] #![cfg(test)]
use std::collections::HashMap; use std::collections::HashMap;
use std::fs::File;
use wasmi::{
Error as InterpreterError, Externals, FuncInstance, FuncRef, GlobalDescriptor,
GlobalInstance, GlobalRef, ImportResolver, ImportsBuilder, MemoryDescriptor,
MemoryInstance, MemoryRef, Module, ModuleImportResolver, ModuleInstance, ModuleRef,
RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef, Trap,
};
use wasmi::memory_units::Pages;
use wabt::script::{self, Action, Command, CommandKind, ScriptParser, Value}; use wabt::script::{self, Action, Command, CommandKind, ScriptParser, Value};
use wasmi::memory_units::Pages;
use wasmi::{
Error as InterpreterError, Externals, FuncInstance, FuncRef, GlobalDescriptor, GlobalInstance,
GlobalRef, ImportResolver, ImportsBuilder, MemoryDescriptor, MemoryInstance, MemoryRef, Module,
ModuleImportResolver, ModuleInstance, ModuleRef, RuntimeArgs, RuntimeValue, Signature,
TableDescriptor, TableInstance, TableRef, Trap,
};
fn spec_to_runtime_value(value: Value) -> RuntimeValue { fn spec_to_runtime_value(val: Value<u32, u64>) -> RuntimeValue {
match value { match val {
Value::I32(v) => RuntimeValue::I32(v), Value::I32(v) => RuntimeValue::I32(v),
Value::I64(v) => RuntimeValue::I64(v), Value::I64(v) => RuntimeValue::I64(v),
Value::F32(v) => RuntimeValue::F32(v), Value::F32(v) => RuntimeValue::F32(v.into()),
Value::F64(v) => RuntimeValue::F64(v), Value::F64(v) => RuntimeValue::F64(v.into()),
Value::V128(_) => panic!("v128 is not supported"),
} }
} }
@ -54,8 +56,8 @@ impl SpecModule {
table: TableInstance::alloc(10, Some(20)).unwrap(), table: TableInstance::alloc(10, Some(20)).unwrap(),
memory: MemoryInstance::alloc(Pages(1), Some(Pages(2))).unwrap(), memory: MemoryInstance::alloc(Pages(1), Some(Pages(2))).unwrap(),
global_i32: GlobalInstance::alloc(RuntimeValue::I32(666), false), global_i32: GlobalInstance::alloc(RuntimeValue::I32(666), false),
global_f32: GlobalInstance::alloc(RuntimeValue::F32(666.0), false), global_f32: GlobalInstance::alloc(RuntimeValue::F32(666.0.into()), false),
global_f64: GlobalInstance::alloc(RuntimeValue::F64(666.0), false), global_f64: GlobalInstance::alloc(RuntimeValue::F64(666.0.into()), false),
} }
} }
} }
@ -84,29 +86,29 @@ impl ModuleImportResolver for SpecModule {
field_name: &str, field_name: &str,
func_type: &Signature, func_type: &Signature,
) -> Result<FuncRef, InterpreterError> { ) -> Result<FuncRef, InterpreterError> {
let index = match field_name { let index = match field_name {
"print" => PRINT_FUNC_INDEX, "print" => PRINT_FUNC_INDEX,
"print_i32" => PRINT_FUNC_INDEX, "print_i32" => PRINT_FUNC_INDEX,
"print_i32_f32" => PRINT_FUNC_INDEX, "print_i32_f32" => PRINT_FUNC_INDEX,
"print_f64_f64" => PRINT_FUNC_INDEX, "print_f64_f64" => PRINT_FUNC_INDEX,
"print_f32" => PRINT_FUNC_INDEX, "print_f32" => PRINT_FUNC_INDEX,
"print_f64" => PRINT_FUNC_INDEX, "print_f64" => PRINT_FUNC_INDEX,
_ => { _ => {
return Err(InterpreterError::Instantiation(format!( return Err(InterpreterError::Instantiation(format!(
"Unknown host func import {}", "Unknown host func import {}",
field_name field_name
))); )));
} }
}; };
if func_type.return_type().is_some() { if func_type.return_type().is_some() {
return Err(InterpreterError::Instantiation( return Err(InterpreterError::Instantiation(
"Function `print_` have unit return type".into(), "Function `print_` have unit return type".into(),
)); ));
} }
let func = FuncInstance::alloc_host(func_type.clone(), index); let func = FuncInstance::alloc_host(func_type.clone(), index);
return Ok(func); return Ok(func);
} }
fn resolve_global( fn resolve_global(
@ -114,15 +116,15 @@ impl ModuleImportResolver for SpecModule {
field_name: &str, field_name: &str,
_global_type: &GlobalDescriptor, _global_type: &GlobalDescriptor,
) -> Result<GlobalRef, InterpreterError> { ) -> Result<GlobalRef, InterpreterError> {
match field_name { match field_name {
"global_i32" => Ok(self.global_i32.clone()), "global_i32" => Ok(self.global_i32.clone()),
"global_f32" => Ok(self.global_f32.clone()), "global_f32" => Ok(self.global_f32.clone()),
"global_f64" => Ok(self.global_f64.clone()), "global_f64" => Ok(self.global_f64.clone()),
_ => Err(InterpreterError::Instantiation(format!( _ => Err(InterpreterError::Instantiation(format!(
"Unknown host global import {}", "Unknown host global import {}",
field_name field_name
))) ))),
} }
} }
fn resolve_memory( fn resolve_memory(
@ -191,7 +193,8 @@ impl SpecDriver {
fn module_or_last(&self, name: Option<&str>) -> Result<ModuleRef, InterpreterError> { fn module_or_last(&self, name: Option<&str>) -> Result<ModuleRef, InterpreterError> {
match name { match name {
Some(name) => self.module(name), Some(name) => self.module(name),
None => self.last_module None => self
.last_module
.clone() .clone()
.ok_or_else(|| InterpreterError::Instantiation("No modules registered".into())), .ok_or_else(|| InterpreterError::Instantiation("No modules registered".into())),
} }
@ -269,22 +272,26 @@ fn try_load(wasm: &[u8], spec_driver: &mut SpecDriver) -> Result<(), Error> {
Ok(()) Ok(())
} }
fn load_module(wasm: &[u8], name: &Option<String>, spec_driver: &mut SpecDriver) -> ModuleRef { fn load_module(
let module = try_load_module(wasm).expect(&format!("Wasm failed to load")); wasm: &[u8],
name: &Option<String>,
spec_driver: &mut SpecDriver,
) -> Result<ModuleRef, Error> {
let module = try_load_module(wasm)?;
let instance = ModuleInstance::new(&module, spec_driver) let instance = ModuleInstance::new(&module, spec_driver)
.expect("Instantiation failed") .map_err(|e| Error::Load(e.to_string()))?
.run_start(spec_driver.spec_module()) .run_start(spec_driver.spec_module())
.expect("Run start failed"); .map_err(|trap| Error::Start(trap))?;
let module_name = name.clone(); let module_name = name.clone();
spec_driver.add_module(module_name, instance.clone()); spec_driver.add_module(module_name, instance.clone());
instance Ok(instance)
} }
fn run_action( fn run_action(
program: &mut SpecDriver, program: &mut SpecDriver,
action: &Action, action: &Action<u32, u64>,
) -> Result<Option<RuntimeValue>, InterpreterError> { ) -> Result<Option<RuntimeValue>, InterpreterError> {
match *action { match *action {
Action::Invoke { Action::Invoke {
@ -298,14 +305,12 @@ fn run_action(
"Expected program to have loaded module {:?}", "Expected program to have loaded module {:?}",
module module
)); ));
module.invoke_export( let vec_args = args
field, .iter()
&args.iter() .cloned()
.cloned() .map(spec_to_runtime_value)
.map(spec_to_runtime_value) .collect::<Vec<_>>();
.collect::<Vec<_>>(), module.invoke_export(field, &vec_args, program.spec_module())
program.spec_module(),
)
} }
Action::Get { Action::Get {
ref module, ref module,
@ -341,11 +346,41 @@ pub fn spec(name: &str) {
fn try_spec(name: &str) -> Result<(), Error> { fn try_spec(name: &str) -> Result<(), Error> {
let mut spec_driver = SpecDriver::new(); let mut spec_driver = SpecDriver::new();
let spec_script_path = format!("tests/spec/testsuite/{}.wast", name); let spec_script_path = format!("tests/spec/testsuite/{}.wast", name);
let mut parser = ScriptParser::from_file(spec_script_path).expect("Can't read spec script");
use std::io::Read;
let mut spec_source = Vec::new();
let mut spec_file = File::open(&spec_script_path).expect("Can't open file");
spec_file
.read_to_end(&mut spec_source)
.expect("Can't read file");
let mut parser = ScriptParser::from_source_and_name(&spec_source, &format!("{}.wast", name))
.expect("Can't read spec script");
let mut errors = vec![];
while let Some(Command { kind, line }) = parser.next()? { while let Some(Command { kind, line }) = parser.next()? {
macro_rules! assert_eq {
($a:expr, $b:expr) => {{
let (a, b) = ($a, $b);
if a != b {
errors.push(format!(
r#"ERROR (line {}):
expected: {:?}
got: {:?}
"#,
line, b, a,
));
}
}};
}
println!("Running spec cmd {}: {:?}", line, kind);
match kind { match kind {
CommandKind::Module { name, module, .. } => { CommandKind::Module { name, module, .. } => {
load_module(&module.into_vec()?, &name, &mut spec_driver); load_module(&module.into_vec(), &name, &mut spec_driver)
.expect("Failed to load module");
} }
CommandKind::AssertReturn { action, expected } => { CommandKind::AssertReturn { action, expected } => {
let result = run_action(&mut spec_driver, &action); let result = run_action(&mut spec_driver, &action);
@ -374,7 +409,6 @@ fn try_spec(name: &str) -> Result<(), Error> {
spec_expected @ _ => assert_eq!(actual_result, spec_expected), spec_expected @ _ => assert_eq!(actual_result, spec_expected),
} }
} }
println!("assert_return at line {} - success", line);
} }
Err(e) => { Err(e) => {
panic!("Expected action to return value, got error: {:?}", e); panic!("Expected action to return value, got error: {:?}", e);
@ -388,18 +422,21 @@ fn try_spec(name: &str) -> Result<(), Error> {
Ok(result) => { Ok(result) => {
for actual_result in result.into_iter().collect::<Vec<RuntimeValue>>() { for actual_result in result.into_iter().collect::<Vec<RuntimeValue>>() {
match actual_result { match actual_result {
RuntimeValue::F32(val) => if !val.is_nan() { RuntimeValue::F32(val) => {
panic!("Expected nan value, got {:?}", val) if !val.is_nan() {
}, panic!("Expected nan value, got {:?}", val)
RuntimeValue::F64(val) => if !val.is_nan() { }
panic!("Expected nan value, got {:?}", val) }
}, RuntimeValue::F64(val) => {
if !val.is_nan() {
panic!("Expected nan value, got {:?}", val)
}
}
val @ _ => { val @ _ => {
panic!("Expected action to return float value, got {:?}", val) panic!("Expected action to return float value, got {:?}", val)
} }
} }
} }
println!("assert_return_nan at line {} - success", line);
} }
Err(e) => { Err(e) => {
panic!("Expected action to return value, got error: {:?}", e); panic!("Expected action to return value, got error: {:?}", e);
@ -410,7 +447,7 @@ fn try_spec(name: &str) -> Result<(), Error> {
let result = run_action(&mut spec_driver, &action); let result = run_action(&mut spec_driver, &action);
match result { match result {
Ok(result) => panic!("Expected exhaustion, got result: {:?}", result), Ok(result) => panic!("Expected exhaustion, got result: {:?}", result),
Err(e) => println!("assert_exhaustion at line {} - success ({:?})", line, e), Err(_e) => {}
} }
} }
CommandKind::AssertTrap { action, .. } => { CommandKind::AssertTrap { action, .. } => {
@ -422,24 +459,22 @@ fn try_spec(name: &str) -> Result<(), Error> {
result result
); );
} }
Err(e) => { Err(_e) => {}
println!("assert_trap at line {} - success ({:?})", line, e);
}
} }
} }
CommandKind::AssertInvalid { module, .. } CommandKind::AssertInvalid { module, .. }
| CommandKind::AssertMalformed { module, .. } | CommandKind::AssertMalformed { module, .. }
| CommandKind::AssertUnlinkable { module, .. } => { | CommandKind::AssertUnlinkable { module, .. } => {
let module_load = try_load(&module.into_vec()?, &mut spec_driver); let module_load = try_load(&module.into_vec(), &mut spec_driver);
match module_load { match module_load {
Ok(_) => panic!("Expected invalid module definition, got some module!"), Ok(_) => panic!("Expected invalid module definition, got some module!"),
Err(e) => println!("assert_invalid at line {} - success ({:?})", line, e), Err(_e) => {}
} }
} }
CommandKind::AssertUninstantiable { module, .. } => { CommandKind::AssertUninstantiable { module, .. } => {
match try_load(&module.into_vec()?, &mut spec_driver) { match try_load(&module.into_vec(), &mut spec_driver) {
Ok(_) => panic!("Expected error running start function at line {}", line), Ok(_) => panic!("Expected error running start function at line {}", line),
Err(e) => println!("assert_uninstantiable - success ({:?})", e), Err(_e) => {}
} }
} }
CommandKind::Register { name, as_name, .. } => { CommandKind::Register { name, as_name, .. } => {
@ -456,5 +491,14 @@ fn try_spec(name: &str) -> Result<(), Error> {
} }
} }
if !errors.is_empty() {
use std::fmt::Write;
let mut out = "\n".to_owned();
for err in errors {
write!(out, "{}", err).expect("Error formatting errors");
}
panic!(out);
}
Ok(()) Ok(())
} }

@ -1 +1 @@
Subproject commit c538faa43217146f458b9bc2d4b704d0a4d80963 Subproject commit c6a690f89a0dda3c79700aa6377d8b5d8a970eba

View File

@ -1,6 +1,6 @@
//! Official spec testsuite. //! Official spec testsuite.
extern crate wasmi;
extern crate wabt; extern crate wabt;
extern crate wasmi;
mod spec; mod spec;

19
validation/Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[package]
name = "wasmi-validation"
version = "0.2.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "MIT/Apache-2.0"
repository = "https://github.com/paritytech/wasmi"
description = "Wasm code validator"
[dependencies]
parity-wasm = { version = "0.40.1", default-features = false }
[dev-dependencies]
assert_matches = "1.1"
[features]
default = ["std"]
std = ["parity-wasm/std"]
core = []

141
validation/src/context.rs Normal file
View File

@ -0,0 +1,141 @@
use crate::Error;
use alloc::vec::Vec;
use parity_wasm::elements::{
BlockType, FunctionType, GlobalType, MemoryType, TableType, ValueType,
};
#[derive(Default, Debug)]
pub struct ModuleContext {
pub memories: Vec<MemoryType>,
pub tables: Vec<TableType>,
pub globals: Vec<GlobalType>,
pub types: Vec<FunctionType>,
pub func_type_indexes: Vec<u32>,
}
impl ModuleContext {
pub fn memories(&self) -> &[MemoryType] {
&self.memories
}
pub fn tables(&self) -> &[TableType] {
&self.tables
}
pub fn globals(&self) -> &[GlobalType] {
&self.globals
}
pub fn types(&self) -> &[FunctionType] {
&self.types
}
pub fn func_type_indexes(&self) -> &[u32] {
&self.func_type_indexes
}
pub fn require_memory(&self, idx: u32) -> Result<(), Error> {
if self.memories().get(idx as usize).is_none() {
return Err(Error(format!("Memory at index {} doesn't exists", idx)));
}
Ok(())
}
pub fn require_table(&self, idx: u32) -> Result<&TableType, Error> {
self.tables()
.get(idx as usize)
.ok_or_else(|| Error(format!("Table at index {} doesn't exists", idx)))
}
pub fn require_function(&self, idx: u32) -> Result<(&[ValueType], BlockType), Error> {
let ty_idx = self
.func_type_indexes()
.get(idx as usize)
.ok_or_else(|| Error(format!("Function at index {} doesn't exists", idx)))?;
self.require_function_type(*ty_idx)
}
pub fn require_function_type(&self, idx: u32) -> Result<(&[ValueType], BlockType), Error> {
let ty = self
.types()
.get(idx as usize)
.ok_or_else(|| Error(format!("Type at index {} doesn't exists", idx)))?;
let params = ty.params();
let return_ty = ty
.return_type()
.map(BlockType::Value)
.unwrap_or(BlockType::NoResult);
Ok((params, return_ty))
}
pub fn require_global(&self, idx: u32, mutability: Option<bool>) -> Result<&GlobalType, Error> {
let global = self
.globals()
.get(idx as usize)
.ok_or_else(|| Error(format!("Global at index {} doesn't exists", idx)))?;
if let Some(expected_mutable) = mutability {
if expected_mutable && !global.is_mutable() {
return Err(Error(format!("Expected global {} to be mutable", idx)));
}
if !expected_mutable && global.is_mutable() {
return Err(Error(format!("Expected global {} to be immutable", idx)));
}
}
Ok(global)
}
}
#[derive(Default)]
pub struct ModuleContextBuilder {
memories: Vec<MemoryType>,
tables: Vec<TableType>,
globals: Vec<GlobalType>,
types: Vec<FunctionType>,
func_type_indexes: Vec<u32>,
}
impl ModuleContextBuilder {
pub fn new() -> ModuleContextBuilder {
ModuleContextBuilder::default()
}
pub fn push_memory(&mut self, memory: MemoryType) {
self.memories.push(memory);
}
pub fn push_table(&mut self, table: TableType) {
self.tables.push(table);
}
pub fn push_global(&mut self, global: GlobalType) {
self.globals.push(global);
}
pub fn set_types(&mut self, types: Vec<FunctionType>) {
self.types = types;
}
pub fn push_func_type_index(&mut self, func_type_index: u32) {
self.func_type_indexes.push(func_type_index);
}
pub fn build(self) -> ModuleContext {
let ModuleContextBuilder {
memories,
tables,
globals,
types,
func_type_indexes,
} = self;
ModuleContext {
memories,
tables,
globals,
types,
func_type_indexes,
}
}
}

1216
validation/src/func.rs Normal file

File diff suppressed because it is too large Load Diff

449
validation/src/lib.rs Normal file
View File

@ -0,0 +1,449 @@
// TODO: Uncomment
// #![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
#[macro_use]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std as alloc;
pub mod stack;
/// Index of default linear memory.
pub const DEFAULT_MEMORY_INDEX: u32 = 0;
/// Index of default table.
pub const DEFAULT_TABLE_INDEX: u32 = 0;
/// Maximal number of pages that a wasm instance supports.
pub const LINEAR_MEMORY_MAX_PAGES: u32 = 65536;
use alloc::{string::String, vec::Vec};
use core::fmt;
#[cfg(feature = "std")]
use std::error;
use self::context::ModuleContextBuilder;
use parity_wasm::elements::{
BlockType, ExportEntry, External, FuncBody, GlobalEntry, GlobalType, InitExpr, Instruction,
Internal, MemoryType, Module, ResizableLimits, TableType, Type, ValueType,
};
pub mod context;
pub mod func;
pub mod util;
#[cfg(test)]
mod tests;
// TODO: Consider using a type other than String, because
// of formatting machinary is not welcomed in substrate runtimes.
#[derive(Debug)]
pub struct Error(pub String);
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(feature = "std")]
impl error::Error for Error {
fn description(&self) -> &str {
&self.0
}
}
impl From<stack::Error> for Error {
fn from(e: stack::Error) -> Error {
Error(format!("Stack: {}", e))
}
}
pub trait Validator {
type Output;
type FuncValidator: FuncValidator;
fn new(module: &Module) -> Self;
fn on_function_validated(
&mut self,
index: u32,
output: <<Self as Validator>::FuncValidator as FuncValidator>::Output,
);
fn finish(self) -> Self::Output;
}
pub trait FuncValidator {
type Output;
fn new(ctx: &func::FunctionValidationContext, body: &FuncBody) -> Self;
fn next_instruction(
&mut self,
ctx: &mut func::FunctionValidationContext,
instruction: &Instruction,
) -> Result<(), Error>;
fn finish(self) -> Self::Output;
}
/// A module validator that just validates modules and produces no result.
pub struct PlainValidator;
impl Validator for PlainValidator {
type Output = ();
type FuncValidator = PlainFuncValidator;
fn new(_module: &Module) -> PlainValidator {
PlainValidator
}
fn on_function_validated(
&mut self,
_index: u32,
_output: <<Self as Validator>::FuncValidator as FuncValidator>::Output,
) -> () {
()
}
fn finish(self) -> () {
()
}
}
/// A function validator that just validates modules and produces no result.
pub struct PlainFuncValidator;
impl FuncValidator for PlainFuncValidator {
type Output = ();
fn new(_ctx: &func::FunctionValidationContext, _body: &FuncBody) -> PlainFuncValidator {
PlainFuncValidator
}
fn next_instruction(
&mut self,
ctx: &mut func::FunctionValidationContext,
instruction: &Instruction,
) -> Result<(), Error> {
ctx.step(instruction)
}
fn finish(self) -> () {
()
}
}
pub fn validate_module<V: Validator>(module: &Module) -> Result<V::Output, Error> {
let mut context_builder = ModuleContextBuilder::new();
let mut imported_globals = Vec::new();
let mut validation = V::new(&module);
// Copy types from module as is.
context_builder.set_types(
module
.type_section()
.map(|ts| {
ts.types()
.into_iter()
.map(|&Type::Function(ref ty)| ty)
.cloned()
.collect()
})
.unwrap_or_default(),
);
// Fill elements with imported values.
for import_entry in module
.import_section()
.map(|i| i.entries())
.unwrap_or_default()
{
match *import_entry.external() {
External::Function(idx) => context_builder.push_func_type_index(idx),
External::Table(ref table) => context_builder.push_table(table.clone()),
External::Memory(ref memory) => context_builder.push_memory(memory.clone()),
External::Global(ref global) => {
context_builder.push_global(global.clone());
imported_globals.push(global.clone());
}
}
}
// Concatenate elements with defined in the module.
if let Some(function_section) = module.function_section() {
for func_entry in function_section.entries() {
context_builder.push_func_type_index(func_entry.type_ref())
}
}
if let Some(table_section) = module.table_section() {
for table_entry in table_section.entries() {
validate_table_type(table_entry)?;
context_builder.push_table(table_entry.clone());
}
}
if let Some(mem_section) = module.memory_section() {
for mem_entry in mem_section.entries() {
validate_memory_type(mem_entry)?;
context_builder.push_memory(mem_entry.clone());
}
}
if let Some(global_section) = module.global_section() {
for global_entry in global_section.entries() {
validate_global_entry(global_entry, &imported_globals)?;
context_builder.push_global(global_entry.global_type().clone());
}
}
let context = context_builder.build();
let function_section_len = module
.function_section()
.map(|s| s.entries().len())
.unwrap_or(0);
let code_section_len = module.code_section().map(|s| s.bodies().len()).unwrap_or(0);
if function_section_len != code_section_len {
return Err(Error(format!(
"length of function section is {}, while len of code section is {}",
function_section_len, code_section_len
)));
}
// validate every function body in user modules
if function_section_len != 0 {
// tests use invalid code
let function_section = module
.function_section()
.expect("function_section_len != 0; qed");
let code_section = module
.code_section()
.expect("function_section_len != 0; function_section_len == code_section_len; qed");
// check every function body
for (index, function) in function_section.entries().iter().enumerate() {
let function_body = code_section
.bodies()
.get(index as usize)
.ok_or(Error(format!("Missing body for function {}", index)))?;
let output = func::drive::<V::FuncValidator>(&context, function, function_body)
.map_err(|Error(ref msg)| {
Error(format!(
"Function #{} reading/validation error: {}",
index, msg
))
})?;
validation.on_function_validated(index as u32, output);
}
}
// validate start section
if let Some(start_fn_idx) = module.start_section() {
let (params, return_ty) = context.require_function(start_fn_idx)?;
if return_ty != BlockType::NoResult || params.len() != 0 {
return Err(Error(
"start function expected to have type [] -> []".into(),
));
}
}
// validate export section
if let Some(export_section) = module.export_section() {
let mut export_names = export_section
.entries()
.iter()
.map(ExportEntry::field)
.collect::<Vec<_>>();
export_names.sort_unstable();
for (fst, snd) in export_names.iter().zip(export_names.iter().skip(1)) {
if fst == snd {
return Err(Error(format!("duplicate export {}", fst)));
}
}
for export in export_section.entries() {
match *export.internal() {
Internal::Function(function_index) => {
context.require_function(function_index)?;
}
Internal::Global(global_index) => {
context.require_global(global_index, Some(false))?;
}
Internal::Memory(memory_index) => {
context.require_memory(memory_index)?;
}
Internal::Table(table_index) => {
context.require_table(table_index)?;
}
}
}
}
// validate import section
if let Some(import_section) = module.import_section() {
for import in import_section.entries() {
match *import.external() {
External::Function(function_type_index) => {
context.require_function_type(function_type_index)?;
}
External::Global(ref global_type) => {
if global_type.is_mutable() {
return Err(Error(format!(
"trying to import mutable global {}",
import.field()
)));
}
}
External::Memory(ref memory_type) => {
validate_memory_type(memory_type)?;
}
External::Table(ref table_type) => {
validate_table_type(table_type)?;
}
}
}
}
// there must be no greater than 1 table in tables index space
if context.tables().len() > 1 {
return Err(Error(format!(
"too many tables in index space: {}",
context.tables().len()
)));
}
// there must be no greater than 1 linear memory in memory index space
if context.memories().len() > 1 {
return Err(Error(format!(
"too many memory regions in index space: {}",
context.memories().len()
)));
}
// use data section to initialize linear memory regions
if let Some(data_section) = module.data_section() {
for data_segment in data_section.entries() {
context.require_memory(data_segment.index())?;
let offset = data_segment
.offset()
.as_ref()
.ok_or_else(|| Error("passive memory segments are not supported".into()))?;
let init_ty = expr_const_type(&offset, context.globals())?;
if init_ty != ValueType::I32 {
return Err(Error("segment offset should return I32".into()));
}
}
}
// use element section to fill tables
if let Some(element_section) = module.elements_section() {
for element_segment in element_section.entries() {
context.require_table(element_segment.index())?;
let offset = element_segment
.offset()
.as_ref()
.ok_or_else(|| Error("passive element segments are not supported".into()))?;
let init_ty = expr_const_type(&offset, context.globals())?;
if init_ty != ValueType::I32 {
return Err(Error("segment offset should return I32".into()));
}
for function_index in element_segment.members() {
context.require_function(*function_index)?;
}
}
}
Ok(validation.finish())
}
fn validate_limits(limits: &ResizableLimits) -> Result<(), Error> {
if let Some(maximum) = limits.maximum() {
if limits.initial() > maximum {
return Err(Error(format!(
"maximum limit {} is less than minimum {}",
maximum,
limits.initial()
)));
}
}
Ok(())
}
fn validate_memory_type(memory_type: &MemoryType) -> Result<(), Error> {
let initial = memory_type.limits().initial();
let maximum: Option<u32> = memory_type.limits().maximum();
validate_memory(initial, maximum).map_err(Error)
}
pub fn validate_memory(initial: u32, maximum: Option<u32>) -> Result<(), String> {
if initial > LINEAR_MEMORY_MAX_PAGES {
return Err(format!(
"initial memory size must be at most {} pages",
LINEAR_MEMORY_MAX_PAGES
));
}
if let Some(maximum) = maximum {
if initial > maximum {
return Err(format!(
"maximum limit {} is less than minimum {}",
maximum, initial,
));
}
if maximum > LINEAR_MEMORY_MAX_PAGES {
return Err(format!(
"maximum memory size must be at most {} pages",
LINEAR_MEMORY_MAX_PAGES
));
}
}
Ok(())
}
fn validate_table_type(table_type: &TableType) -> Result<(), Error> {
validate_limits(table_type.limits())
}
fn validate_global_entry(global_entry: &GlobalEntry, globals: &[GlobalType]) -> Result<(), Error> {
let init = global_entry.init_expr();
let init_expr_ty = expr_const_type(init, globals)?;
if init_expr_ty != global_entry.global_type().content_type() {
return Err(Error(format!(
"Trying to initialize variable of type {:?} with value of type {:?}",
global_entry.global_type().content_type(),
init_expr_ty
)));
}
Ok(())
}
/// Returns type of this constant expression.
fn expr_const_type(init_expr: &InitExpr, globals: &[GlobalType]) -> Result<ValueType, Error> {
let code = init_expr.code();
if code.len() != 2 {
return Err(Error(
"Init expression should always be with length 2".into(),
));
}
let expr_ty: ValueType = match code[0] {
Instruction::I32Const(_) => ValueType::I32,
Instruction::I64Const(_) => ValueType::I64,
Instruction::F32Const(_) => ValueType::F32,
Instruction::F64Const(_) => ValueType::F64,
Instruction::GetGlobal(idx) => match globals.get(idx as usize) {
Some(target_global) => {
if target_global.is_mutable() {
return Err(Error(format!("Global {} is mutable", idx)));
}
target_global.content_type()
}
None => {
return Err(Error(format!(
"Global {} doesn't exists or not yet defined",
idx
)));
}
},
_ => return Err(Error("Non constant opcode in init expr".into())),
};
if code[1] != Instruction::End {
return Err(Error("Expression doesn't ends with `end` opcode".into()));
}
Ok(expr_ty)
}

100
validation/src/stack.rs Normal file
View File

@ -0,0 +1,100 @@
use alloc::{string::String, vec::Vec};
use core::fmt;
#[cfg(feature = "std")]
use std::error;
#[derive(Debug)]
pub struct Error(String);
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(feature = "std")]
impl error::Error for Error {
fn description(&self) -> &str {
&self.0
}
}
/// Stack with limit.
#[derive(Debug)]
pub struct StackWithLimit<T>
where
T: Clone,
{
/// Stack values.
values: Vec<T>,
/// Stack limit (maximal stack len).
limit: usize,
}
impl<T> StackWithLimit<T>
where
T: Clone,
{
pub fn with_limit(limit: usize) -> Self {
StackWithLimit {
values: Vec::new(),
limit: limit,
}
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn top(&self) -> Result<&T, Error> {
self.values
.last()
.ok_or_else(|| Error("non-empty stack expected".into()))
}
pub fn top_mut(&mut self) -> Result<&mut T, Error> {
self.values
.last_mut()
.ok_or_else(|| Error("non-empty stack expected".into()))
}
pub fn get(&self, index: usize) -> Result<&T, Error> {
if index >= self.values.len() {
return Err(Error(format!(
"trying to get value at position {} on stack of size {}",
index,
self.values.len()
)));
}
Ok(self
.values
.get(self.values.len() - 1 - index)
.expect("checked couple of lines above"))
}
pub fn push(&mut self, value: T) -> Result<(), Error> {
if self.values.len() >= self.limit {
return Err(Error(format!("exceeded stack limit {}", self.limit)));
}
self.values.push(value);
Ok(())
}
pub fn pop(&mut self) -> Result<T, Error> {
self.values
.pop()
.ok_or_else(|| Error("non-empty stack expected".into()))
}
pub fn resize(&mut self, new_size: usize, dummy: T) {
debug_assert!(new_size <= self.values.len());
self.values.resize(new_size, dummy);
}
}

277
validation/src/tests.rs Normal file
View File

@ -0,0 +1,277 @@
use crate::{Error, PlainValidator};
use parity_wasm::{
builder::module,
elements::{
BlockType, External, GlobalEntry, GlobalType, ImportEntry, InitExpr, Instruction,
Instructions, MemoryType, Module, TableType, ValueType,
},
};
fn validate_module(module: &Module) -> Result<(), Error> {
super::validate_module::<PlainValidator>(module)
}
#[test]
fn empty_is_valid() {
let module = module().build();
assert!(validate_module(&module).is_ok());
}
#[test]
fn limits() {
let test_cases = vec![
// min > max
(10, Some(9), false),
// min = max
(10, Some(10), true),
// table/memory is always valid without max
(10, None, true),
];
for (min, max, is_valid) in test_cases {
// defined table
let m = module().table().with_min(min).with_max(max).build().build();
assert_eq!(validate_module(&m).is_ok(), is_valid);
// imported table
let m = module()
.with_import(ImportEntry::new(
"core".into(),
"table".into(),
External::Table(TableType::new(min, max)),
))
.build();
assert_eq!(validate_module(&m).is_ok(), is_valid);
// defined memory
let m = module()
.memory()
.with_min(min)
.with_max(max)
.build()
.build();
assert_eq!(validate_module(&m).is_ok(), is_valid);
// imported table
let m = module()
.with_import(ImportEntry::new(
"core".into(),
"memory".into(),
External::Memory(MemoryType::new(min, max)),
))
.build();
assert_eq!(validate_module(&m).is_ok(), is_valid);
}
}
#[test]
fn global_init_const() {
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]),
))
.build();
assert!(validate_module(&m).is_ok());
// init expr type differs from declared global type
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I64, true),
InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]),
))
.build();
assert!(validate_module(&m).is_err());
}
#[test]
fn global_init_global() {
let m = module()
.with_import(ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, false)),
))
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
))
.build();
assert!(validate_module(&m).is_ok());
// get_global can reference only previously defined globals
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
))
.build();
assert!(validate_module(&m).is_err());
// get_global can reference only const globals
let m = module()
.with_import(ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, true)),
))
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
))
.build();
assert!(validate_module(&m).is_err());
// get_global in init_expr can only refer to imported globals.
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, false),
InitExpr::new(vec![Instruction::I32Const(0), Instruction::End]),
))
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
))
.build();
assert!(validate_module(&m).is_err());
}
#[test]
fn global_init_misc() {
// without delimiting End opcode
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::I32Const(42)]),
))
.build();
assert!(validate_module(&m).is_err());
// empty init expr
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::End]),
))
.build();
assert!(validate_module(&m).is_err());
// not an constant opcode used
let m = module()
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::Unreachable, Instruction::End]),
))
.build();
assert!(validate_module(&m).is_err());
}
#[test]
fn module_limits_validity() {
// module cannot contain more than 1 memory atm.
let m = module()
.with_import(ImportEntry::new(
"core".into(),
"memory".into(),
External::Memory(MemoryType::new(10, None)),
))
.memory()
.with_min(10)
.build()
.build();
assert!(validate_module(&m).is_err());
// module cannot contain more than 1 table atm.
let m = module()
.with_import(ImportEntry::new(
"core".into(),
"table".into(),
External::Table(TableType::new(10, None)),
))
.table()
.with_min(10)
.build()
.build();
assert!(validate_module(&m).is_err());
}
#[test]
fn funcs() {
// recursive function calls is legal.
let m = module()
.function()
.signature()
.return_type()
.i32()
.build()
.body()
.with_instructions(Instructions::new(vec![
Instruction::Call(1),
Instruction::End,
]))
.build()
.build()
.function()
.signature()
.return_type()
.i32()
.build()
.body()
.with_instructions(Instructions::new(vec![
Instruction::Call(0),
Instruction::End,
]))
.build()
.build()
.build();
assert!(validate_module(&m).is_ok());
}
#[test]
fn globals() {
// import immutable global is legal.
let m = module()
.with_import(ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, false)),
))
.build();
assert!(validate_module(&m).is_ok());
// import mutable global is invalid.
let m = module()
.with_import(ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, true)),
))
.build();
assert!(validate_module(&m).is_err());
}
#[test]
fn if_else_with_return_type_validation() {
let m = module()
.function()
.signature()
.build()
.body()
.with_instructions(Instructions::new(vec![
Instruction::I32Const(1),
Instruction::If(BlockType::NoResult),
Instruction::I32Const(1),
Instruction::If(BlockType::Value(ValueType::I32)),
Instruction::I32Const(1),
Instruction::Else,
Instruction::I32Const(2),
Instruction::End,
Instruction::Drop,
Instruction::End,
Instruction::End,
]))
.build()
.build()
.build();
validate_module(&m).unwrap();
}

130
validation/src/util.rs Normal file
View File

@ -0,0 +1,130 @@
use crate::Error;
use alloc::string::String;
use parity_wasm::elements::{Local, ValueType};
#[cfg(test)]
use assert_matches::assert_matches;
/// Locals are the concatenation of a slice of function parameters
/// with function declared local variables.
///
/// Local variables are given in the form of groups represented by pairs
/// of a value_type and a count.
#[derive(Debug)]
pub struct Locals<'a> {
params: &'a [ValueType],
local_groups: &'a [Local],
count: u32,
}
impl<'a> Locals<'a> {
/// Create a new wrapper around declared variables and parameters.
pub fn new(params: &'a [ValueType], local_groups: &'a [Local]) -> Result<Locals<'a>, Error> {
let mut acc = params.len() as u32;
for locals_group in local_groups {
acc = acc
.checked_add(locals_group.count())
.ok_or_else(|| Error(String::from("Locals range not in 32-bit range")))?;
}
Ok(Locals {
params,
local_groups,
count: acc,
})
}
/// Returns parameter count.
pub fn param_count(&self) -> u32 {
self.params.len() as u32
}
/// Returns total count of all declared locals and paramaterers.
pub fn count(&self) -> u32 {
self.count
}
/// Returns the type of a local variable (either a declared local or a param).
///
/// Returns `Err` in the case of overflow or when idx falls out of range.
pub fn type_of_local(&self, idx: u32) -> Result<ValueType, Error> {
if let Some(param) = self.params.get(idx as usize) {
return Ok(*param);
}
// If an index doesn't point to a param, then we have to look into local declarations.
let mut start_idx = self.param_count();
for locals_group in self.local_groups {
let end_idx = start_idx
.checked_add(locals_group.count())
.ok_or_else(|| Error(String::from("Locals range not in 32-bit range")))?;
if idx >= start_idx && idx < end_idx {
return Ok(locals_group.value_type());
}
start_idx = end_idx;
}
// We didn't find anything, that's an error.
// At this moment `start_idx` should hold the count of all locals
// (since it's either set to the `end_idx` or equal to `params.len()`)
let total_count = start_idx;
Err(Error(format!(
"Trying to access local with index {} when there are only {} locals",
idx, total_count
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn locals_it_works() {
let params = vec![ValueType::I32, ValueType::I64];
let local_groups = vec![Local::new(2, ValueType::F32), Local::new(2, ValueType::F64)];
let locals = Locals::new(&params, &local_groups).unwrap();
assert_matches!(locals.type_of_local(0), Ok(ValueType::I32));
assert_matches!(locals.type_of_local(1), Ok(ValueType::I64));
assert_matches!(locals.type_of_local(2), Ok(ValueType::F32));
assert_matches!(locals.type_of_local(3), Ok(ValueType::F32));
assert_matches!(locals.type_of_local(4), Ok(ValueType::F64));
assert_matches!(locals.type_of_local(5), Ok(ValueType::F64));
assert_matches!(locals.type_of_local(6), Err(_));
}
#[test]
fn locals_no_declared_locals() {
let params = vec![ValueType::I32];
let locals = Locals::new(&params, &[]).unwrap();
assert_matches!(locals.type_of_local(0), Ok(ValueType::I32));
assert_matches!(locals.type_of_local(1), Err(_));
}
#[test]
fn locals_no_params() {
let local_groups = vec![Local::new(2, ValueType::I32), Local::new(3, ValueType::I64)];
let locals = Locals::new(&[], &local_groups).unwrap();
assert_matches!(locals.type_of_local(0), Ok(ValueType::I32));
assert_matches!(locals.type_of_local(1), Ok(ValueType::I32));
assert_matches!(locals.type_of_local(2), Ok(ValueType::I64));
assert_matches!(locals.type_of_local(3), Ok(ValueType::I64));
assert_matches!(locals.type_of_local(4), Ok(ValueType::I64));
assert_matches!(locals.type_of_local(5), Err(_));
}
#[test]
fn locals_u32_overflow() {
let local_groups = vec![
Local::new(u32::max_value(), ValueType::I32),
Local::new(1, ValueType::I64),
];
assert_matches!(Locals::new(&[], &local_groups), Err(_));
}
}