mirror of
https://git.proxmox.com/git/rustc
synced 2025-05-02 19:21:32 +00:00
New upstream version 1.26.0+dfsg1
This commit is contained in:
parent
2c00a5a8d9
commit
0531ce1d5e
@ -26,10 +26,10 @@ As a reminder, all contributors are expected to follow our [Code of Conduct][coc
|
||||
## Feature Requests
|
||||
[feature-requests]: #feature-requests
|
||||
|
||||
To request a change to the way that the Rust language works, please open an
|
||||
issue in the [RFCs repository](https://github.com/rust-lang/rfcs/issues/new)
|
||||
rather than this one. New features and other significant language changes
|
||||
must go through the RFC process.
|
||||
To request a change to the way the Rust language works, please head over
|
||||
to the [RFCs repository](https://github.com/rust-lang/rfcs) and view the
|
||||
[README](https://github.com/rust-lang/rfcs/blob/master/README.md)
|
||||
for instructions.
|
||||
|
||||
## Bug Reports
|
||||
[bug-reports]: #bug-reports
|
||||
@ -594,7 +594,7 @@ If you're looking for somewhere to start, check out the [E-easy][eeasy] tag.
|
||||
[inom]: https://github.com/rust-lang/rust/issues?q=is%3Aopen+is%3Aissue+label%3AI-nominated
|
||||
[eeasy]: https://github.com/rust-lang/rust/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy
|
||||
[lru]: https://github.com/rust-lang/rust/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-asc
|
||||
[rfcbot]: https://github.com/dikaiosune/rust-dashboard/blob/master/RFCBOT.md
|
||||
[rfcbot]: https://github.com/anp/rfcbot-rs/
|
||||
|
||||
## Out-of-tree Contributions
|
||||
[out-of-tree-contributions]: #out-of-tree-contributions
|
||||
@ -623,6 +623,7 @@ For people new to Rust, and just starting to contribute, or even for
|
||||
more seasoned developers, some useful places to look for information
|
||||
are:
|
||||
|
||||
* The [rustc guide] contains information about how various parts of the compiler work
|
||||
* [Rust Forge][rustforge] contains additional documentation, including write-ups of how to achieve common tasks
|
||||
* The [Rust Internals forum][rif], a place to ask questions and
|
||||
discuss Rust's internals
|
||||
@ -635,6 +636,7 @@ are:
|
||||
* **Google!** ([search only in Rust Documentation][gsearchdocs] to find types, traits, etc. quickly)
|
||||
* Don't be afraid to ask! The Rust community is friendly and helpful.
|
||||
|
||||
[rustc guide]: https://rust-lang-nursery.github.io/rustc-guide/about-this-guide.html
|
||||
[gdfrustc]: http://manishearth.github.io/rust-internals-docs/rustc/
|
||||
[gsearchdocs]: https://www.google.com/search?q=site:doc.rust-lang.org+your+query+here
|
||||
[rif]: http://internals.rust-lang.org
|
||||
|
22
COPYRIGHT
22
COPYRIGHT
@ -192,28 +192,6 @@ their own copyright notices and license terms:
|
||||
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
||||
OF SUCH DAMAGE.
|
||||
|
||||
* Hoedown, the markdown parser, under src/rt/hoedown, is
|
||||
licensed as follows.
|
||||
|
||||
Copyright (c) 2008, Natacha Porté
|
||||
Copyright (c) 2011, Vicent Martí
|
||||
Copyright (c) 2013, Devin Torres and the Hoedown authors
|
||||
|
||||
Permission to use, copy, modify, and distribute this
|
||||
software for any purpose with or without fee is hereby
|
||||
granted, provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR
|
||||
DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
|
||||
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR
|
||||
ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
|
||||
OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
* libbacktrace, under src/libbacktrace:
|
||||
|
||||
Copyright (C) 2012-2014 Free Software Foundation, Inc.
|
||||
|
@ -129,9 +129,6 @@ CALL "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\vcvars64.
|
||||
python x.py build
|
||||
```
|
||||
|
||||
If you are seeing build failure when compiling `rustc_binaryen`, make sure the path
|
||||
length of the rust folder is not longer than 22 characters.
|
||||
|
||||
#### Specifying an ABI
|
||||
[specifying-an-abi]: #specifying-an-abi
|
||||
|
||||
@ -227,9 +224,13 @@ variety of channels on Mozilla's IRC network, irc.mozilla.org. The
|
||||
most popular channel is [#rust], a venue for general discussion about
|
||||
Rust. And a good place to ask for help would be [#rust-beginners].
|
||||
|
||||
Also, the [rustc guide] might be a good place to start if you want to
|
||||
find out how various parts of the compiler work.
|
||||
|
||||
[IRC]: https://en.wikipedia.org/wiki/Internet_Relay_Chat
|
||||
[#rust]: irc://irc.mozilla.org/rust
|
||||
[#rust-beginners]: irc://irc.mozilla.org/rust-beginners
|
||||
[rustc guide]: https://rust-lang-nursery.github.io/rustc-guide/about-this-guide.html
|
||||
|
||||
## License
|
||||
[license]: #license
|
||||
|
116
RELEASES.md
116
RELEASES.md
@ -1,3 +1,115 @@
|
||||
Version 1.25.0 (2018-03-29)
|
||||
==========================
|
||||
|
||||
Language
|
||||
--------
|
||||
- [Stabilised `#[repr(align(x))]`.][47006] [RFC 1358]
|
||||
- [You can now use nested groups of imports.][47948]
|
||||
e.g. `use std::{fs::File, io::Read, path::{Path, PathBuf}};`
|
||||
- [You can now have `|` at the start of a match arm.][47947] e.g.
|
||||
```rust
|
||||
enum Foo { A, B, C }
|
||||
|
||||
fn main() {
|
||||
let x = Foo::A;
|
||||
match x {
|
||||
| Foo::A
|
||||
| Foo::B => println!("AB"),
|
||||
| Foo::C => println!("C"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Compiler
|
||||
--------
|
||||
- [Upgraded to LLVM 6.][47828]
|
||||
- [Added `-C lto=val` option.][47521]
|
||||
- [Added `i586-unknown-linux-musl` target][47282]
|
||||
|
||||
Libraries
|
||||
---------
|
||||
- [Impl Send for `process::Command` on Unix.][47760]
|
||||
- [Impl PartialEq and Eq for `ParseCharError`.][47790]
|
||||
- [`UnsafeCell::into_inner` is now safe.][47204]
|
||||
- [Implement libstd for CloudABI.][47268]
|
||||
- [`Float::{from_bits, to_bits}` is now available in libcore.][46931]
|
||||
- [Implement `AsRef<Path>` for Component][46985]
|
||||
- [Implemented `Write` for `Cursor<&mut Vec<u8>>`][46830]
|
||||
- [Moved `Duration` to libcore.][46666]
|
||||
|
||||
Stabilized APIs
|
||||
---------------
|
||||
- [`Location::column`]
|
||||
- [`ptr::NonNull`]
|
||||
|
||||
The following functions can now be used in a constant expression.
|
||||
eg. `static MINUTE: Duration = Duration::from_secs(60);`
|
||||
- [`Duration::new`][47300]
|
||||
- [`Duration::from_secs`][47300]
|
||||
- [`Duration::from_millis`][47300]
|
||||
|
||||
Cargo
|
||||
-----
|
||||
- [`cargo new` no longer removes `rust` or `rs` prefixs/suffixs.][cargo/5013]
|
||||
- [`cargo new` now defaults to creating a binary crate, instead of a
|
||||
library crate.][cargo/5029]
|
||||
|
||||
Misc
|
||||
----
|
||||
- [Rust by example is now shipped with new releases][46196]
|
||||
|
||||
Compatibility Notes
|
||||
-------------------
|
||||
- [Deprecated `net::lookup_host`.][47510]
|
||||
- [`rustdoc` has switched to pulldown as the default markdown renderer.][47398]
|
||||
- The borrow checker was sometimes incorrectly permitting overlapping borrows
|
||||
around indexing operations (see [#47349][47349]). This has been fixed (which also
|
||||
enabled some correct code that used to cause errors (e.g. [#33903][33903] and [#46095][46095]).
|
||||
- [Removed deprecated unstable attribute `#[simd]`.][47251]
|
||||
|
||||
[33903]: https://github.com/rust-lang/rust/pull/33903
|
||||
[47947]: https://github.com/rust-lang/rust/pull/47947
|
||||
[47948]: https://github.com/rust-lang/rust/pull/47948
|
||||
[47760]: https://github.com/rust-lang/rust/pull/47760
|
||||
[47790]: https://github.com/rust-lang/rust/pull/47790
|
||||
[47828]: https://github.com/rust-lang/rust/pull/47828
|
||||
[47398]: https://github.com/rust-lang/rust/pull/47398
|
||||
[47510]: https://github.com/rust-lang/rust/pull/47510
|
||||
[47521]: https://github.com/rust-lang/rust/pull/47521
|
||||
[47204]: https://github.com/rust-lang/rust/pull/47204
|
||||
[47251]: https://github.com/rust-lang/rust/pull/47251
|
||||
[47268]: https://github.com/rust-lang/rust/pull/47268
|
||||
[47282]: https://github.com/rust-lang/rust/pull/47282
|
||||
[47300]: https://github.com/rust-lang/rust/pull/47300
|
||||
[47349]: https://github.com/rust-lang/rust/pull/47349
|
||||
[46931]: https://github.com/rust-lang/rust/pull/46931
|
||||
[46985]: https://github.com/rust-lang/rust/pull/46985
|
||||
[47006]: https://github.com/rust-lang/rust/pull/47006
|
||||
[46830]: https://github.com/rust-lang/rust/pull/46830
|
||||
[46095]: https://github.com/rust-lang/rust/pull/46095
|
||||
[46666]: https://github.com/rust-lang/rust/pull/46666
|
||||
[46196]: https://github.com/rust-lang/rust/pull/46196
|
||||
[cargo/5013]: https://github.com/rust-lang/cargo/pull/5013
|
||||
[cargo/5029]: https://github.com/rust-lang/cargo/pull/5029
|
||||
[RFC 1358]: https://github.com/rust-lang/rfcs/pull/1358
|
||||
[`Location::column`]: https://doc.rust-lang.org/std/panic/struct.Location.html#method.column
|
||||
[`ptr::NonNull`]: https://doc.rust-lang.org/std/ptr/struct.NonNull.html
|
||||
|
||||
|
||||
Version 1.24.1 (2018-03-01)
|
||||
==========================
|
||||
|
||||
- [Do not abort when unwinding through FFI][48251]
|
||||
- [Emit UTF-16 files for linker arguments on Windows][48318]
|
||||
- [Make the error index generator work again][48308]
|
||||
- [Cargo will warn on Windows 7 if an update is needed][cargo/5069].
|
||||
|
||||
[48251]: https://github.com/rust-lang/rust/issues/48251
|
||||
[48308]: https://github.com/rust-lang/rust/issues/48308
|
||||
[48318]: https://github.com/rust-lang/rust/issues/48318
|
||||
[cargo/5069]: https://github.com/rust-lang/cargo/pull/5069
|
||||
|
||||
|
||||
Version 1.24.0 (2018-02-15)
|
||||
==========================
|
||||
|
||||
@ -29,7 +141,7 @@ Libraries
|
||||
- [Copied `AsciiExt` methods onto `char`][46077]
|
||||
- [Remove `T: Sized` requirement on `ptr::is_null()`][46094]
|
||||
- [impl `From<RecvError>` for `{TryRecvError, RecvTimeoutError}`][45506]
|
||||
- [Optimised `f32::{min, max}` to generate more efficent x86 assembly][47080]
|
||||
- [Optimised `f32::{min, max}` to generate more efficient x86 assembly][47080]
|
||||
- [`[u8]::contains` now uses memchr which provides a 3x speed improvement][46713]
|
||||
|
||||
Stabilized APIs
|
||||
@ -78,6 +190,7 @@ Compatibility Notes
|
||||
- [`column!()` macro is one-based instead of zero-based][46977]
|
||||
- [`fmt::Arguments` can no longer be shared across threads][45198]
|
||||
- [Access to `#[repr(packed)]` struct fields is now unsafe][44884]
|
||||
- [Cargo sets a different working directory for the compiler][cargo/4788]
|
||||
|
||||
[44884]: https://github.com/rust-lang/rust/pull/44884
|
||||
[45198]: https://github.com/rust-lang/rust/pull/45198
|
||||
@ -106,6 +219,7 @@ Compatibility Notes
|
||||
[47080]: https://github.com/rust-lang/rust/pull/47080
|
||||
[47084]: https://github.com/rust-lang/rust/pull/47084
|
||||
[cargo/4743]: https://github.com/rust-lang/cargo/pull/4743
|
||||
[cargo/4788]: https://github.com/rust-lang/cargo/pull/4788
|
||||
[cargo/4817]: https://github.com/rust-lang/cargo/pull/4817
|
||||
[`RefCell::replace`]: https://doc.rust-lang.org/std/cell/struct.RefCell.html#method.replace
|
||||
[`RefCell::swap`]: https://doc.rust-lang.org/std/cell/struct.RefCell.html#method.swap
|
||||
|
@ -118,6 +118,10 @@
|
||||
# Indicate whether submodules are managed and updated automatically.
|
||||
#submodules = true
|
||||
|
||||
# Update submodules only when the checked out commit in the submodules differs
|
||||
# from what is committed in the main rustc repo.
|
||||
#fast-submodules = true
|
||||
|
||||
# The path to (or name of) the GDB executable to use. This is only used for
|
||||
# executing the debuginfo test suite.
|
||||
#gdb = "gdb"
|
||||
@ -151,8 +155,8 @@
|
||||
# default.
|
||||
#extended = false
|
||||
|
||||
# Installs choosen set of extended tools if enables. By default builds all.
|
||||
# If choosen tool failed to build the installation fails.
|
||||
# Installs chosen set of extended tools if enables. By default builds all.
|
||||
# If chosen tool failed to build the installation fails.
|
||||
#tools = ["cargo", "rls", "rustfmt", "analysis", "src"]
|
||||
|
||||
# Verbosity level: 0 == not verbose, 1 == verbose, 2 == very verbose
|
||||
@ -182,6 +186,10 @@
|
||||
# essentially skipping stage0 as the local compiler is recompiling itself again.
|
||||
#local-rebuild = false
|
||||
|
||||
# Print out how long each rustbuild step took (mostly intended for CI and
|
||||
# tracking over time)
|
||||
#print-step-timings = false
|
||||
|
||||
# =============================================================================
|
||||
# General install configuration options
|
||||
# =============================================================================
|
||||
@ -239,11 +247,6 @@
|
||||
# compiler.
|
||||
#codegen-units = 1
|
||||
|
||||
# Whether to enable ThinLTO (and increase the codegen units to either a default
|
||||
# or the configured value). On by default. If we want the fastest possible
|
||||
# compiler, we should disable this.
|
||||
#thinlto = true
|
||||
|
||||
# Whether or not debug assertions are enabled for the compiler and standard
|
||||
# library. Also enables compilation of debug! and trace! logging macros.
|
||||
#debug-assertions = false
|
||||
@ -268,6 +271,9 @@
|
||||
# Whether or not `panic!`s generate backtraces (RUST_BACKTRACE)
|
||||
#backtrace = true
|
||||
|
||||
# Build rustc with experimental parallelization
|
||||
#experimental-parallel-queries = false
|
||||
|
||||
# The default linker that will be hard-coded into the generated compiler for
|
||||
# targets that don't specify linker explicitly in their target specifications.
|
||||
# Note that this is not the linker used to link said compiler.
|
||||
@ -321,11 +327,18 @@
|
||||
# bootstrap)
|
||||
#codegen-backends = ["llvm"]
|
||||
|
||||
# This is the name of the directory in which codegen backends will get installed
|
||||
#codegen-backends-dir = "codegen-backends"
|
||||
|
||||
# Flag indicating whether `libstd` calls an imported function to handle basic IO
|
||||
# when targeting WebAssembly. Enable this to debug tests for the `wasm32-unknown-unknown`
|
||||
# target, as without this option the test output will not be captured.
|
||||
#wasm-syscall = false
|
||||
|
||||
# Indicates whether LLD will be compiled and made available in the sysroot for
|
||||
# rustc to execute.
|
||||
#lld = false
|
||||
|
||||
# =============================================================================
|
||||
# Options for specific targets
|
||||
#
|
||||
|
@ -1 +1 @@
|
||||
84203cac67e65ca8640b8392348411098c856985
|
||||
a7756804103447ea4e68a71ccf071e7ad8f7a03e
|
1499
src/Cargo.lock
generated
1499
src/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -72,3 +72,4 @@ cargo = { path = "tools/cargo" }
|
||||
# RLS depends on `rustfmt` from crates.io, so we put this in a `[patch]` section
|
||||
# for crates.io
|
||||
rustfmt-nightly = { path = "tools/rustfmt" }
|
||||
clippy_lints = { path = "tools/clippy/clippy_lints" }
|
||||
|
15
src/README.md
Normal file
15
src/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
This directory contains the source code of the rust project, including:
|
||||
- `rustc` and its tests
|
||||
- `libstd`
|
||||
- Various submodules for tools, like rustdoc, rls, etc.
|
||||
|
||||
For more information on how various parts of the compiler work, see the [rustc guide].
|
||||
|
||||
Their is also useful content in the following READMEs, which are gradually being moved over to the guide:
|
||||
- https://github.com/rust-lang/rust/tree/master/src/librustc/ty/maps
|
||||
- https://github.com/rust-lang/rust/tree/master/src/librustc/dep_graph
|
||||
- https://github.com/rust-lang/rust/blob/master/src/librustc/infer/region_constraints
|
||||
- https://github.com/rust-lang/rust/tree/master/src/librustc/infer/higher_ranked
|
||||
- https://github.com/rust-lang/rust/tree/master/src/librustc/infer/lexical_region_resolve
|
||||
|
||||
[rustc guide]: https://rust-lang-nursery.github.io/rustc-guide/about-this-guide.html
|
@ -1,165 +0,0 @@
|
||||
sudo: false
|
||||
dist: trusty
|
||||
language: cpp
|
||||
|
||||
jobs:
|
||||
include:
|
||||
# Build with clang and run tests on the host system (Ubuntu).
|
||||
- &test-ubuntu
|
||||
stage: test
|
||||
compiler: clang
|
||||
python: 2.7
|
||||
node_js: 7
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test']
|
||||
packages: ['cmake', 'g++-5']
|
||||
before_install:
|
||||
- export CC="${CC_COMPILER}"
|
||||
- export CXX="${CXX_COMPILER}"
|
||||
- export ASAN_OPTIONS="symbolize=1"
|
||||
install:
|
||||
- pip install --user flake8==3.4.1
|
||||
before_script:
|
||||
# Check the style of a subset of Python code until the other code is updated.
|
||||
- flake8 ./scripts/
|
||||
- ./check.py --test-waterfall --only-prepare
|
||||
script:
|
||||
- cmake . -DCMAKE_C_FLAGS="$COMPILER_FLAGS" -DCMAKE_CXX_FLAGS="$COMPILER_FLAGS"
|
||||
- make -j2
|
||||
- ./check.py --test-waterfall
|
||||
env: |
|
||||
CC_COMPILER="./test/wasm-install/wasm-install/bin/clang"
|
||||
CXX_COMPILER="./test/wasm-install/wasm-install/bin/clang++"
|
||||
|
||||
- <<: *test-ubuntu
|
||||
env: |
|
||||
CC_COMPILER="./test/wasm-install/wasm-install/bin/clang"
|
||||
CXX_COMPILER="./test/wasm-install/wasm-install/bin/clang++"
|
||||
COMPILER_FLAGS="-fsanitize=undefined -fno-sanitize-recover=all -fsanitize-blacklist=$(pwd)/ubsan.blacklist"
|
||||
|
||||
- <<: *test-ubuntu
|
||||
env: |
|
||||
CC_COMPILER="./test/wasm-install/wasm-install/bin/clang"
|
||||
CXX_COMPILER="./test/wasm-install/wasm-install/bin/clang++"
|
||||
COMPILER_FLAGS="-fsanitize=address"
|
||||
|
||||
- <<: *test-ubuntu
|
||||
env: |
|
||||
CC_COMPILER="./test/wasm-install/wasm-install/bin/clang"
|
||||
CXX_COMPILER="./test/wasm-install/wasm-install/bin/clang++"
|
||||
COMPILER_FLAGS="-fsanitize=thread"
|
||||
|
||||
# Build with gcc 5 and run tests on the host system (Ubuntu).
|
||||
- <<: *test-ubuntu
|
||||
compiler: gcc
|
||||
env: |
|
||||
CC_COMPILER="gcc-5"
|
||||
CXX_COMPILER="g++-5"
|
||||
|
||||
# Build the .js outputs using emcc
|
||||
- &test-emcc
|
||||
stage: test
|
||||
compiler: clang
|
||||
python: 2.7
|
||||
node_js: 7
|
||||
language: node_js
|
||||
node_js:
|
||||
- node
|
||||
sudo: required
|
||||
services:
|
||||
- docker
|
||||
before_install:
|
||||
- docker run -dit --name emscripten -v $(pwd):/src trzeci/emscripten:sdk-incoming-64bit bash
|
||||
script:
|
||||
# run binaryen.js and wasm.js tests before and after building, so we see if the bundled
|
||||
# version is good too
|
||||
- docker exec -it emscripten bash ./travis-emcc-tests.sh
|
||||
|
||||
# Build with gcc 6.3 and run tests on Alpine Linux (inside chroot).
|
||||
# Note: Alpine uses musl libc.
|
||||
- &test-alpine
|
||||
stage: test
|
||||
sudo: true
|
||||
language: minimal
|
||||
compiler: gcc
|
||||
env: ARCH=x86_64
|
||||
before_install:
|
||||
- &download-alpine-script
|
||||
"wget 'https://raw.githubusercontent.com/alpinelinux/alpine-chroot-install/v0.6.0/alpine-chroot-install' \
|
||||
&& echo 'a827a4ba3d0817e7c88bae17fe34e50204983d1e alpine-chroot-install' | sha1sum -c || travis_terminate 1"
|
||||
- &define-alpine-func
|
||||
alpine() { /alpine/enter-chroot -u "$USER" "$@"; }
|
||||
install:
|
||||
- sudo sh alpine-chroot-install -a "$ARCH" -p 'build-base cmake git nodejs python2'
|
||||
before_script:
|
||||
- alpine ./check.py --test-waterfall --only-prepare
|
||||
script:
|
||||
- alpine cmake .
|
||||
- alpine make -j2
|
||||
- alpine ./check.py --test-waterfall
|
||||
|
||||
|
||||
# Build statically linked release binaries with gcc 6.3 on Alpine Linux
|
||||
# (inside chroot). If building a tagged commit, then deploy release tarball
|
||||
# to GitHub Releases.
|
||||
- &build-alpine
|
||||
<<: *test-alpine
|
||||
stage: build
|
||||
env: ARCH=x86_64
|
||||
before_install:
|
||||
# XXX: This is ugly hack to skip this job (and all derived) for pull
|
||||
# requests, run it only on master branch and tags (to cut down build
|
||||
# time). Replace it after Travis finally implement proper support for
|
||||
# conditional jobs or stages.
|
||||
- if [[ "$TRAVIS_PULL_REQUEST" != "false" || ( "$TRAVIS_BRANCH" != "master" && -z "$TRAVIS_TAG" ) ]]; then
|
||||
travis_terminate 0;
|
||||
fi
|
||||
- *download-alpine-script
|
||||
- *define-alpine-func
|
||||
# Don't run before_script inherited from *test-alpine.
|
||||
before_script: skip
|
||||
script:
|
||||
- alpine cmake -DCMAKE_BUILD_TYPE=Release
|
||||
-DCMAKE_VERBOSE_MAKEFILE=ON
|
||||
-DCMAKE_CXX_FLAGS="-static -no-pie"
|
||||
-DCMAKE_C_FLAGS="-static -no-pie" .
|
||||
- alpine make -j2
|
||||
- alpine find bin/ -type f -perm -u=x -exec strip {} +
|
||||
- alpine ls -lh bin/
|
||||
# Check if the built executables are really statically linked.
|
||||
- if [ -n "$(find bin/ -type f -perm -u=x -exec file {} + | grep -Fvw 'statically linked')" ]; then
|
||||
file bin/*; false;
|
||||
fi
|
||||
before_deploy:
|
||||
- PKGNAME="binaryen-$TRAVIS_TAG-$ARCH-linux"
|
||||
- mv bin binaryen-$TRAVIS_TAG
|
||||
- tar -czf $PKGNAME.tar.gz binaryen-$TRAVIS_TAG
|
||||
- sha256sum $PKGNAME.tar.gz > $PKGNAME.tar.gz.sha256
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: "cu6CD5BaycXdCylvcs+Fho5+OVTkh9mZwH8RTnNpXo9hAQzLJDFgcNBHeXHEHtcp4IWf/YZSMu48UKnpU9sP5iF0AS4rtuEBJk5gOKkgB8GWnuIOePFkfANAZMN+EncuUwhAdN56iOAESXqnlHYgmJjyRVCHOxiezuWTOYui4lxoIAdxvOMJc3E9yfzUq4Epm2GDszSDN7ObmRIJpVgDXD9Sze1Xv4IkbIwc0biCmduGGLp3ow2KM+RZ4tOF0c8P0ki49vOFHr6n2Vmqg0QCiVNd4JJBRBCGn6Tzip2jsTQewnUUvpYCZafLeRV//v//voNA6ZUz91yXR23GIhkfdlyuqnz3/7l335Sa749M1lpYfSRWvwg9mJEqP66mxqTrWzj1xSItr9T+p0WhSmRN/4UEJPuItYPSma6kfv+H7qhLa3ZYKECH8hHW79grYmUWtiX0vQVIgnctJGgboPNLfG/1mNtmCI241wK0S3zvL2okdZH8/PqxfllYHMBTUp9lUrop8eoLKPgHZPm6+V20dgTUgOuGTZzTWwQ7Uk/Pg8JMUgkre5y0eo6pP3z0vDW1NNFNhouJ5oGkAeK/HAznr8Q0zWWF1vGFhoyC8ok/IJ7yKxK9scJVPBDe4oox6tr1zlsxzNEYE0/mY3JjuWV0z8RgjrIAbRe8IpGTkYz5VOM="
|
||||
file: binaryen-$TRAVIS_TAG-*.tar.gz*
|
||||
file_glob: true
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
|
||||
# Build binaries for other architectures using QEMU user-mode emulation.
|
||||
# Note: We don't run tests for these architectures, because some fail under
|
||||
# QEMU/binfmt and it takes too long time (hits time limit on Travis).
|
||||
- <<: *build-alpine
|
||||
env: ARCH=x86
|
||||
|
||||
- <<: *build-alpine
|
||||
env: ARCH=aarch64
|
||||
|
||||
- <<: *build-alpine
|
||||
env: ARCH=armhf
|
||||
|
||||
- <<: *build-alpine
|
||||
env: ARCH=ppc64le
|
||||
|
||||
notifications:
|
||||
email: false
|
@ -1,312 +0,0 @@
|
||||
PROJECT(binaryen C CXX)
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 2.8.7)
|
||||
INCLUDE(GNUInstallDirs)
|
||||
|
||||
IF(NOT CMAKE_BUILD_TYPE)
|
||||
MESSAGE(STATUS "No build type selected, default to Release")
|
||||
SET(CMAKE_BUILD_TYPE "Release")
|
||||
ENDIF()
|
||||
|
||||
OPTION(BUILD_STATIC_LIB "Build as a static library" OFF)
|
||||
|
||||
# Support functionality.
|
||||
|
||||
FUNCTION(ADD_COMPILE_FLAG value)
|
||||
MESSAGE(STATUS "Building with ${value}")
|
||||
FOREACH(variable CMAKE_C_FLAGS CMAKE_CXX_FLAGS)
|
||||
SET(${variable} "${${variable}} ${value}" PARENT_SCOPE)
|
||||
ENDFOREACH(variable)
|
||||
ENDFUNCTION()
|
||||
|
||||
FUNCTION(ADD_CXX_FLAG value)
|
||||
MESSAGE(STATUS "Building with ${value}")
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${value}" PARENT_SCOPE)
|
||||
ENDFUNCTION()
|
||||
|
||||
FUNCTION(ADD_DEBUG_COMPILE_FLAG value)
|
||||
IF("${CMAKE_BUILD_TYPE}" MATCHES "Debug")
|
||||
MESSAGE(STATUS "Building with ${value}")
|
||||
ENDIF()
|
||||
FOREACH(variable CMAKE_C_FLAGS_DEBUG CMAKE_CXX_FLAGS_DEBUG)
|
||||
SET(${variable} "${${variable}} ${value}" PARENT_SCOPE)
|
||||
ENDFOREACH(variable)
|
||||
ENDFUNCTION()
|
||||
|
||||
FUNCTION(ADD_NONDEBUG_COMPILE_FLAG value)
|
||||
IF(NOT "${CMAKE_BUILD_TYPE}" MATCHES "Debug")
|
||||
MESSAGE(STATUS "Building with ${value}")
|
||||
ENDIF()
|
||||
FOREACH(variable CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELEASE CMAKE_C_FLAGS_RELWITHDEBINFO CMAKE_CXX_FLAGS_RELWITHDEBINFO CMAKE_C_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_MINSIZEREL)
|
||||
SET(${variable} "${${variable}} ${value}" PARENT_SCOPE)
|
||||
ENDFOREACH(variable)
|
||||
ENDFUNCTION()
|
||||
|
||||
FUNCTION(ADD_LINK_FLAG value)
|
||||
MESSAGE(STATUS "Linking with ${value}")
|
||||
FOREACH(variable CMAKE_EXE_LINKER_FLAGS)
|
||||
SET(${variable} "${${variable}} ${value}" PARENT_SCOPE)
|
||||
ENDFOREACH(variable)
|
||||
ENDFUNCTION()
|
||||
|
||||
# Compiler setup.
|
||||
|
||||
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
|
||||
# Force output to bin/ and lib/. This is to suppress CMake multigenerator output paths and avoid bin/Debug, bin/Release/ and so on, which is CMake default.
|
||||
FOREACH(SUFFIX "_DEBUG" "_RELEASE" "_RELWITHDEBINFO" "_MINSIZEREL" "")
|
||||
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY${SUFFIX} "${PROJECT_BINARY_DIR}/bin")
|
||||
SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY${SUFFIX} "${PROJECT_BINARY_DIR}/lib")
|
||||
SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY${SUFFIX} "${PROJECT_BINARY_DIR}/lib")
|
||||
ENDFOREACH()
|
||||
|
||||
IF(MSVC)
|
||||
IF(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "19.0") # VS2013 and older explicitly need /arch:sse2 set, VS2015 no longer has that option, but always enabled.
|
||||
ADD_COMPILE_FLAG("/arch:sse2")
|
||||
ENDIF()
|
||||
ADD_COMPILE_FLAG("/wd4146") # Ignore warning "warning C4146: unary minus operator applied to unsigned type, result still unsigned", this pattern is used somewhat commonly in the code.
|
||||
# 4267 and 4244 are conversion/truncation warnings. We might want to fix these but they are currently pervasive.
|
||||
ADD_COMPILE_FLAG("/wd4267")
|
||||
ADD_COMPILE_FLAG("/wd4244")
|
||||
ADD_COMPILE_FLAG("/WX-")
|
||||
ADD_DEBUG_COMPILE_FLAG("/Od")
|
||||
ADD_NONDEBUG_COMPILE_FLAG("/O2")
|
||||
ADD_COMPILE_FLAG("/D_CRT_SECURE_NO_WARNINGS")
|
||||
ADD_COMPILE_FLAG("/D_SCL_SECURE_NO_WARNINGS")
|
||||
|
||||
ADD_NONDEBUG_COMPILE_FLAG("/UNDEBUG") # Keep asserts.
|
||||
# Also remove /D NDEBUG to avoid MSVC warnings about conflicting defines.
|
||||
if( NOT CMAKE_BUILD_TYPE MATCHES "Debug" )
|
||||
foreach (flags_var_to_scrub
|
||||
CMAKE_CXX_FLAGS_RELEASE
|
||||
CMAKE_CXX_FLAGS_RELWITHDEBINFO
|
||||
CMAKE_CXX_FLAGS_MINSIZEREL
|
||||
CMAKE_C_FLAGS_RELEASE
|
||||
CMAKE_C_FLAGS_RELWITHDEBINFO
|
||||
CMAKE_C_FLAGS_MINSIZEREL)
|
||||
string (REGEX REPLACE "(^| )[/-]D *NDEBUG($| )" " "
|
||||
"${flags_var_to_scrub}" "${${flags_var_to_scrub}}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
ADD_LINK_FLAG("/STACK:8388608")
|
||||
|
||||
IF(RUN_STATIC_ANALYZER)
|
||||
ADD_DEFINITIONS(/analyze)
|
||||
ENDIF()
|
||||
ELSE()
|
||||
SET(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
SET(CMAKE_THREAD_PREFER_PTHREAD ON)
|
||||
FIND_PACKAGE(Threads REQUIRED)
|
||||
ADD_CXX_FLAG("-std=c++11")
|
||||
if (NOT EMSCRIPTEN)
|
||||
# try to get the target architecture by compiling a dummy.c file and
|
||||
# checking the architecture using the file command.
|
||||
file(WRITE ${PROJECT_BINARY_DIR}/dummy.c "main(){}")
|
||||
try_compile(
|
||||
COMPILE_OK
|
||||
${PROJECT_BINARY_DIR}
|
||||
${PROJECT_BINARY_DIR}/dummy.c
|
||||
OUTPUT_VARIABLE COMPILE_OUTPUT
|
||||
COPY_FILE ${PROJECT_BINARY_DIR}/dummy
|
||||
)
|
||||
if (COMPILE_OK)
|
||||
execute_process(
|
||||
COMMAND file ${PROJECT_BINARY_DIR}/dummy
|
||||
RESULT_VARIABLE FILE_RESULT
|
||||
OUTPUT_VARIABLE FILE_OUTPUT
|
||||
ERROR_QUIET
|
||||
)
|
||||
|
||||
if (FILE_RESULT EQUAL 0)
|
||||
if (${FILE_OUTPUT} MATCHES "x86[-_]64")
|
||||
set(TARGET_ARCH "x86-64")
|
||||
elseif (${FILE_OUTPUT} MATCHES "Intel 80386")
|
||||
set(TARGET_ARCH "i386")
|
||||
elseif (${FILE_OUTPUT} MATCHES "ARM")
|
||||
set(TARGET_ARCH "ARM")
|
||||
else ()
|
||||
message(WARNING "Unknown target architecture!")
|
||||
endif ()
|
||||
if(TARGET_ARCH)
|
||||
MESSAGE(STATUS "Building for platform ${TARGET_ARCH}")
|
||||
endif ()
|
||||
else ()
|
||||
message(WARNING "Error running file on dummy executable")
|
||||
endif ()
|
||||
else ()
|
||||
message(WARNING "Error compiling dummy.c file: ${COMPILE_OUTPUT}")
|
||||
endif ()
|
||||
|
||||
if (TARGET_ARCH STREQUAL "i386")
|
||||
# wasm doesn't allow for x87 floating point math
|
||||
ADD_COMPILE_FLAG("-msse2")
|
||||
ADD_COMPILE_FLAG("-mfpmath=sse")
|
||||
elseif(TARGET_ARCH STREQUAL "ARM")
|
||||
# stub for ARM-specific instructions. GCC6 adds NEON with the below flags
|
||||
ADD_COMPILE_FLAG("-march=native")
|
||||
endif ()
|
||||
endif ()
|
||||
ADD_COMPILE_FLAG("-Wall")
|
||||
ADD_COMPILE_FLAG("-Werror")
|
||||
ADD_COMPILE_FLAG("-Wextra")
|
||||
ADD_COMPILE_FLAG("-Wno-unused-parameter")
|
||||
ADD_COMPILE_FLAG("-fno-omit-frame-pointer")
|
||||
IF(WIN32)
|
||||
ADD_COMPILE_FLAG("-D_GNU_SOURCE")
|
||||
ADD_LINK_FLAG("-Wl,--stack,8388608")
|
||||
ELSE()
|
||||
ADD_COMPILE_FLAG("-fPIC")
|
||||
ENDIF()
|
||||
ADD_DEBUG_COMPILE_FLAG("-O0")
|
||||
ADD_DEBUG_COMPILE_FLAG("-g3")
|
||||
ADD_NONDEBUG_COMPILE_FLAG("-O2")
|
||||
ADD_NONDEBUG_COMPILE_FLAG("-UNDEBUG") # Keep asserts.
|
||||
ENDIF()
|
||||
|
||||
# clang doesn't print colored diagnostics when invoked from Ninja
|
||||
IF (UNIX AND
|
||||
CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND
|
||||
CMAKE_GENERATOR STREQUAL "Ninja")
|
||||
ADD_COMPILE_FLAG("-fcolor-diagnostics")
|
||||
ENDIF()
|
||||
|
||||
# Static libraries
|
||||
# Current (partial) dependency structure is as follows:
|
||||
# passes -> wasm -> asmjs -> support
|
||||
# TODO: It's odd that wasm should depend on asmjs, maybe we should fix that.
|
||||
ADD_SUBDIRECTORY(src/ir)
|
||||
ADD_SUBDIRECTORY(src/asmjs)
|
||||
ADD_SUBDIRECTORY(src/cfg)
|
||||
ADD_SUBDIRECTORY(src/emscripten-optimizer)
|
||||
ADD_SUBDIRECTORY(src/passes)
|
||||
ADD_SUBDIRECTORY(src/support)
|
||||
ADD_SUBDIRECTORY(src/wasm)
|
||||
|
||||
# Sources.
|
||||
|
||||
|
||||
SET(binaryen_SOURCES
|
||||
src/binaryen-c.cpp
|
||||
)
|
||||
IF(BUILD_STATIC_LIB)
|
||||
ADD_LIBRARY(binaryen STATIC ${binaryen_SOURCES})
|
||||
ELSE()
|
||||
ADD_LIBRARY(binaryen SHARED ${binaryen_SOURCES})
|
||||
ENDIF()
|
||||
TARGET_LINK_LIBRARIES(binaryen passes wasm asmjs emscripten-optimizer ir cfg support)
|
||||
INSTALL(TARGETS binaryen DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
|
||||
INSTALL(FILES src/binaryen-c.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
INSTALL(FILES bin/wasm.js DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME})
|
||||
INSTALL(FILES bin/binaryen.js DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME})
|
||||
|
||||
SET(wasm-shell_SOURCES
|
||||
src/tools/wasm-shell.cpp
|
||||
src/wasm-interpreter.cpp
|
||||
)
|
||||
ADD_EXECUTABLE(wasm-shell
|
||||
${wasm-shell_SOURCES})
|
||||
TARGET_LINK_LIBRARIES(wasm-shell wasm asmjs emscripten-optimizer passes ir cfg support)
|
||||
SET_PROPERTY(TARGET wasm-shell PROPERTY CXX_STANDARD 11)
|
||||
SET_PROPERTY(TARGET wasm-shell PROPERTY CXX_STANDARD_REQUIRED ON)
|
||||
INSTALL(TARGETS wasm-shell DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
SET(wasm-opt_SOURCES
|
||||
src/tools/wasm-opt.cpp
|
||||
src/wasm-interpreter.cpp
|
||||
)
|
||||
ADD_EXECUTABLE(wasm-opt
|
||||
${wasm-opt_SOURCES})
|
||||
TARGET_LINK_LIBRARIES(wasm-opt wasm asmjs emscripten-optimizer passes ir cfg support)
|
||||
SET_PROPERTY(TARGET wasm-opt PROPERTY CXX_STANDARD 11)
|
||||
SET_PROPERTY(TARGET wasm-opt PROPERTY CXX_STANDARD_REQUIRED ON)
|
||||
INSTALL(TARGETS wasm-opt DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
SET(wasm-merge_SOURCES
|
||||
src/tools/wasm-merge.cpp
|
||||
)
|
||||
ADD_EXECUTABLE(wasm-merge
|
||||
${wasm-merge_SOURCES})
|
||||
TARGET_LINK_LIBRARIES(wasm-merge wasm asmjs emscripten-optimizer passes ir cfg support)
|
||||
SET_PROPERTY(TARGET wasm-merge PROPERTY CXX_STANDARD 11)
|
||||
SET_PROPERTY(TARGET wasm-merge PROPERTY CXX_STANDARD_REQUIRED ON)
|
||||
INSTALL(TARGETS wasm-merge DESTINATION bin)
|
||||
|
||||
SET(asm2wasm_SOURCES
|
||||
src/tools/asm2wasm.cpp
|
||||
src/wasm-emscripten.cpp
|
||||
)
|
||||
ADD_EXECUTABLE(asm2wasm
|
||||
${asm2wasm_SOURCES})
|
||||
TARGET_LINK_LIBRARIES(asm2wasm emscripten-optimizer passes wasm asmjs ir cfg support)
|
||||
SET_PROPERTY(TARGET asm2wasm PROPERTY CXX_STANDARD 11)
|
||||
SET_PROPERTY(TARGET asm2wasm PROPERTY CXX_STANDARD_REQUIRED ON)
|
||||
INSTALL(TARGETS asm2wasm DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
SET(wasm2asm_SOURCES
|
||||
src/tools/wasm2asm.cpp
|
||||
)
|
||||
ADD_EXECUTABLE(wasm2asm
|
||||
${wasm2asm_SOURCES})
|
||||
TARGET_LINK_LIBRARIES(wasm2asm passes wasm asmjs emscripten-optimizer ir cfg support)
|
||||
SET_PROPERTY(TARGET wasm2asm PROPERTY CXX_STANDARD 11)
|
||||
SET_PROPERTY(TARGET wasm2asm PROPERTY CXX_STANDARD_REQUIRED ON)
|
||||
INSTALL(TARGETS wasm2asm DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
SET(s2wasm_SOURCES
|
||||
src/tools/s2wasm.cpp
|
||||
src/wasm-emscripten.cpp
|
||||
src/wasm-linker.cpp
|
||||
)
|
||||
ADD_EXECUTABLE(s2wasm
|
||||
${s2wasm_SOURCES})
|
||||
TARGET_LINK_LIBRARIES(s2wasm passes wasm asmjs ir cfg support)
|
||||
SET_PROPERTY(TARGET s2wasm PROPERTY CXX_STANDARD 11)
|
||||
SET_PROPERTY(TARGET s2wasm PROPERTY CXX_STANDARD_REQUIRED ON)
|
||||
INSTALL(TARGETS s2wasm DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
SET(wasm_as_SOURCES
|
||||
src/tools/wasm-as.cpp
|
||||
)
|
||||
ADD_EXECUTABLE(wasm-as
|
||||
${wasm_as_SOURCES})
|
||||
TARGET_LINK_LIBRARIES(wasm-as wasm asmjs passes ir cfg support)
|
||||
SET_PROPERTY(TARGET wasm-as PROPERTY CXX_STANDARD 11)
|
||||
SET_PROPERTY(TARGET wasm-as PROPERTY CXX_STANDARD_REQUIRED ON)
|
||||
INSTALL(TARGETS wasm-as DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
SET(wasm_dis_SOURCES
|
||||
src/tools/wasm-dis.cpp
|
||||
)
|
||||
ADD_EXECUTABLE(wasm-dis
|
||||
${wasm_dis_SOURCES})
|
||||
TARGET_LINK_LIBRARIES(wasm-dis passes wasm asmjs ir cfg support)
|
||||
SET_PROPERTY(TARGET wasm-dis PROPERTY CXX_STANDARD 11)
|
||||
SET_PROPERTY(TARGET wasm-dis PROPERTY CXX_STANDARD_REQUIRED ON)
|
||||
INSTALL(TARGETS wasm-dis DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
SET(wasm-ctor-eval_SOURCES
|
||||
src/tools/wasm-ctor-eval.cpp
|
||||
)
|
||||
ADD_EXECUTABLE(wasm-ctor-eval
|
||||
${wasm-ctor-eval_SOURCES})
|
||||
TARGET_LINK_LIBRARIES(wasm-ctor-eval emscripten-optimizer passes wasm asmjs ir cfg support)
|
||||
SET_PROPERTY(TARGET wasm-ctor-eval PROPERTY CXX_STANDARD 11)
|
||||
SET_PROPERTY(TARGET wasm-ctor-eval PROPERTY CXX_STANDARD_REQUIRED ON)
|
||||
INSTALL(TARGETS wasm-ctor-eval DESTINATION bin)
|
||||
|
||||
IF (UNIX) # TODO: port to windows
|
||||
|
||||
SET(wasm-reduce_SOURCES
|
||||
src/tools/wasm-reduce.cpp
|
||||
src/wasm-interpreter.cpp
|
||||
)
|
||||
ADD_EXECUTABLE(wasm-reduce
|
||||
${wasm-reduce_SOURCES})
|
||||
TARGET_LINK_LIBRARIES(wasm-reduce wasm asmjs passes wasm ir cfg support)
|
||||
SET_PROPERTY(TARGET wasm-reduce PROPERTY CXX_STANDARD 11)
|
||||
SET_PROPERTY(TARGET wasm-reduce PROPERTY CXX_STANDARD_REQUIRED ON)
|
||||
INSTALL(TARGETS wasm-reduce DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
ENDIF()
|
||||
|
@ -1,8 +0,0 @@
|
||||
# Contributing to WebAssembly
|
||||
|
||||
Interested in participating? Please follow
|
||||
[the same contributing guidelines as the design repository][].
|
||||
|
||||
[the same contributing guidelines as the design repository]: https://github.com/WebAssembly/design/blob/master/Contributing.md
|
||||
|
||||
Also, please be sure to read [the README.md](README.md) for this repository.
|
@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -1,241 +0,0 @@
|
||||
[](https://travis-ci.org/WebAssembly/binaryen) [](https://ci.appveyor.com/project/WebAssembly/binaryen/branch/master)
|
||||
|
||||
# Binaryen
|
||||
|
||||
Binaryen is a compiler and toolchain infrastructure library for WebAssembly, written in C++. It aims to make [compiling to WebAssembly](https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen) **easy, fast, and effective**:
|
||||
|
||||
* **Easy**: Binaryen has a simple [C API](https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen#c-api-1) in a single header, and can also be [used from JavaScript](https://github.com/WebAssembly/binaryen/blob/master/docs/binaryen.js.Markdown). It accepts input in [WebAssembly-like form](https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen#what-do-i-need-to-have-in-order-to-use-binaryen-to-compile-to-webassembly) but also accepts a general [control flow graph](https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen#cfg-api) for compilers that prefer that.
|
||||
* **Fast**: Binaryen's internal IR uses compact data structures and is designed for completely parallel codegen and optimization, using all available CPU cores. Binaryen's IR also compiles down to WebAssembly extremely easily and quickly because it is essentially a subset of WebAssembly.
|
||||
* **Effective**: Binaryen's optimizer has [many passes](https://github.com/WebAssembly/binaryen/tree/master/src/passes) that can improve code very significantly (e.g. local coloring to coalesce local variables; dead code elimination; precomputing expressions when possible at compile time; etc.). These optimizations aim to make Binaryen powerful enough to be [used as a compiler backend by itself](https://kripken.github.io/talks/binaryen.html#/9). One specific area of focus is on WebAssembly-specific optimizations (that general-purpose compilers might not do), which you can think of as [wasm minification](https://kripken.github.io/talks/binaryen.html#/2), similar to minification for JavaScript, CSS, etc., all of which are language-specific (an example of such an optimization is block return value generation in `SimplifyLocals`).
|
||||
|
||||
Compilers built using Binaryen include
|
||||
|
||||
* [`asm2wasm`](https://github.com/WebAssembly/binaryen/blob/master/src/asm2wasm.h) which compiles asm.js to WebAssembly
|
||||
* [`s2wasm`](https://github.com/WebAssembly/binaryen/blob/master/src/s2wasm.h) which compiles the LLVM WebAssembly's backend `.s` output format
|
||||
* [`AssemblyScript`](https://github.com/AssemblyScript/assemblyscript)
|
||||
* [`wasm2asm`](https://github.com/WebAssembly/binaryen/blob/master/src/wasm2asm.h) which compiles WebAssembly to asm.js
|
||||
* [`mir2wasm`](https://github.com/brson/mir2wasm/) which compiles Rust MIR
|
||||
|
||||
Binaryen also provides a set of **toolchain utilities** that can
|
||||
|
||||
* **Parse** and **emit** WebAssembly. In particular this lets you load WebAssembly, optimize it using Binaryen, and re-emit it, thus implementing a wasm-to-wasm optimizer in a single command.
|
||||
* **Interpret** WebAssembly as well as run the WebAssembly spec tests.
|
||||
* Integrate with **[Emscripten](http://emscripten.org)** in order to provide a complete compiler toolchain from C and C++ to WebAssembly.
|
||||
* **Polyfill** WebAssembly by running it in the interpreter compiled to JavaScript, if the browser does not yet have native support (useful for testing).
|
||||
|
||||
Consult the [contributing instructions](Contributing.md) if you're interested in participating.
|
||||
|
||||
## Binaryen IR
|
||||
|
||||
Binaryen's internal IR is designed to be
|
||||
|
||||
* **Flexible and fast** for optimization.
|
||||
* **As close as possible to WebAssembly** so it is simple and fast to convert it to and from WebAssembly.
|
||||
|
||||
There are a few differences between Binaryen IR and the WebAssembly language:
|
||||
|
||||
* Tree structure
|
||||
* Binaryen IR [is a tree](https://github.com/WebAssembly/binaryen/issues/663), i.e., it has hierarchical structure, for convenience of optimization. This differs from the WebAssembly binary format which is a stack machine.
|
||||
* Consequently Binaryen's text format allows only s-expressions. WebAssembly's official text format is primarily a linear instruction list (with s-expression extensions). Binaryen can't read the linear style, but it can read a wasm text file if it contains only s-expressions.
|
||||
* Types and unreachable code
|
||||
* WebAssembly limits block/if/loop types to none and the concrete value types (i32, i64, f32, f64). Binaryen IR has an unreachable type, and it allows block/if/loop to take it, allowing [local transforms that don't need to know the global context](https://github.com/WebAssembly/binaryen/issues/903).
|
||||
* Binaryen ignores unreachable code when reading WebAssembly binaries. That means that if you read a wasm file with unreachable code, that code will be discarded as if it were optimized out (often this is what you want anyhow, and optimized programs have no unreachable code anyway, but if you write an unoptimized file and then read it, it may look different). The reason for this behavior is that unreachable code in WebAssembly has corner cases that are tricky to handle in Binaryen IR (it can be very unstructured, and Binaryen IR is more structured than WebAssembly as noted earlier). Note that Binaryen does support unreachable code in wast text files, since as we saw Binaryen only supports s-expressions there, which are structured.
|
||||
* Blocks
|
||||
* Binaryen IR has only one node that contains a variable-length list of operands: the block. WebAssembly on the other hand allows lists in loops, if arms, and the top level of a function. Binaryen's IR has a single operand for all non-block nodes; this operand may of course be a block. The motivation for this property is that many passes need special code for iterating on lists, so having a single IR node with a list simplifies them.
|
||||
* As in wasm, blocks and loops may have names. Branch targets in the IR are resolved by name (as opposed to nesting depth). This has 2 consequences:
|
||||
* Blocks without names may not be branch targets.
|
||||
* Names are required to be unique. (Reading wast files with duplicate names is supported; the names are modified when the IR is constructed).
|
||||
* As an optimization, a block that is the child of a loop (or if arm, or function toplevel) and which has no branches targeting it will not be emitted when generating wasm. Instead its list of operands will be directly used in the containing node. Such a block is sometimes called an "implicit block".
|
||||
|
||||
As a result, you might notice that round-trip conversions (wasm => Binaryen IR => wasm) change code a little in some corner cases.
|
||||
|
||||
Notes when working with Binaryen IR:
|
||||
|
||||
* As mentioned above, Binaryen IR has a tree structure. As a result, each expression should have exactly one parent - you should not "reuse" a node by having it appear more than once in the tree. The motivation for this limitation is that when we optimize we modify nodes, so if they appear more than once in the tree, a change in one place can appear in another incorrectly.
|
||||
* For similar reasons, nodes should not appear in more than one functions.
|
||||
|
||||
## Tools
|
||||
|
||||
This repository contains code that builds the following tools in `bin/`:
|
||||
|
||||
* **wasm-shell**: A shell that can load and interpret WebAssembly code. It can also run the spec test suite.
|
||||
* **wasm-as**: Assembles WebAssembly in text format (currently S-Expression format) into binary format (going through Binaryen IR).
|
||||
* **wasm-dis**: Un-assembles WebAssembly in binary format into text format (going through Binaryen IR).
|
||||
* **wasm-opt**: Loads WebAssembly and runs Binaryen IR passes on it.
|
||||
* **asm2wasm**: An asm.js-to-WebAssembly compiler, using Emscripten's asm optimizer infrastructure. This is used by Emscripten in Binaryen mode when it uses Emscripten's fastcomp asm.js backend.
|
||||
* **wasm2asm**: A WebAssembly-to-asm.js compiler (still experimental).
|
||||
* **s2wasm**: A compiler from the `.s` format emitted by the new WebAssembly backend being developed in LLVM. This is used by Emscripten in Binaryen mode when it integrates with the new LLVM backend.
|
||||
* **wasm-merge**: Combines wasm files into a single big wasm file (without sophisticated linking).
|
||||
* **wasm-ctor-eval**: A tool that can execute C++ global constructors ahead of time. Used by Emscripten.
|
||||
* **wasm.js**: wasm.js contains Binaryen components compiled to JavaScript, including the interpreter, `asm2wasm`, the S-Expression parser, etc., which allow you to use Binaryen with Emscripten and execute code compiled to WASM even if the browser doesn't have native support yet. This can be useful as a (slow) polyfill.
|
||||
* **binaryen.js**: A standalone JavaScript library that exposes Binaryen methods for [creating and optimizing WASM modules](https://github.com/WebAssembly/binaryen/blob/master/test/binaryen.js/hello-world.js).
|
||||
|
||||
Usage instructions for each are below.
|
||||
|
||||
## Building
|
||||
|
||||
```
|
||||
cmake . && make
|
||||
```
|
||||
Note that you can also use `ninja` as your generator: `cmake -G Ninja . && ninja`
|
||||
|
||||
* A C++11 compiler is required.
|
||||
* The JavaScript components can be built using `build-js.sh`, see notes inside. Normally this is not needed as builds are provided in this repo already.
|
||||
|
||||
If you also want to compile C/C++ to WebAssembly (and not just asm.js to WebAssembly), you'll need Emscripten. You'll need the `incoming` branch there (which you can get via [the SDK](http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html)), for more details see [the wiki](https://github.com/kripken/emscripten/wiki/WebAssembly).
|
||||
|
||||
## Running
|
||||
|
||||
### wasm-opt
|
||||
|
||||
Run
|
||||
|
||||
````
|
||||
bin/wasm-opt [.wasm or .wast file] [options] [passes, see --help] [--help]
|
||||
````
|
||||
|
||||
The wasm optimizer receives WebAssembly as input, and can run transformation passes on it, as well as print it (before and/or after the transformations). For example, try
|
||||
|
||||
````
|
||||
bin/wasm-opt test/passes/lower-if-else.wast --print
|
||||
````
|
||||
|
||||
That will pretty-print out one of the test cases in the test suite. To run a transformation pass on it, try
|
||||
|
||||
````
|
||||
bin/wasm-opt test/passes/lower-if-else.wast --print --lower-if-else
|
||||
````
|
||||
|
||||
The `lower-if-else` pass lowers if-else into a block and a break. You can see the change the transformation causes by comparing the output of the two print commands.
|
||||
|
||||
It's easy to add your own transformation passes to the shell, just add `.cpp` files into `src/passes`, and rebuild the shell. For example code, take a look at the [`lower-if-else` pass](https://github.com/WebAssembly/binaryen/blob/master/src/passes/LowerIfElse.cpp).
|
||||
|
||||
Some more notes:
|
||||
|
||||
* See `bin/wasm-opt --help` for the full list of options and passes.
|
||||
* Passing `--debug` will emit some debugging info.
|
||||
|
||||
### asm2wasm
|
||||
|
||||
run
|
||||
|
||||
```
|
||||
bin/asm2wasm [input.asm.js file]
|
||||
```
|
||||
|
||||
This will print out a WebAssembly module in s-expression format to the console.
|
||||
|
||||
For example, try
|
||||
|
||||
```
|
||||
$ bin/asm2wasm test/hello_world.asm.js
|
||||
```
|
||||
|
||||
That input file contains
|
||||
|
||||
```javascript
|
||||
function () {
|
||||
"use asm";
|
||||
function add(x, y) {
|
||||
x = x | 0;
|
||||
y = y | 0;
|
||||
return x + y | 0;
|
||||
}
|
||||
return { add: add };
|
||||
}
|
||||
```
|
||||
|
||||
You should see something like this:
|
||||
|
||||

|
||||
|
||||
By default you should see pretty colors as in that image. Set `COLORS=0` in the env to disable colors if you prefer that. On Linux and Mac, you can set `COLORS=1` in the env to force colors (useful when piping to `more`, for example). For Windows, pretty colors are only available when `stdout/stderr` are not redirected/piped.
|
||||
|
||||
Pass `--debug` on the command line to see debug info, about asm.js functions as they are parsed, etc.
|
||||
|
||||
### C/C++ Source ⇒ asm2wasm ⇒ WebAssembly
|
||||
|
||||
When using `emcc` with the `BINARYEN` option, it will use Binaryen to build to WebAssembly. This lets you compile C and C++ to WebAssembly, with emscripten using asm.js internally as a build step. Since emscripten's asm.js generation is very stable, and asm2wasm is a fairly simple process, this method of compiling C and C++ to WebAssembly is usable already. See the [emscripten wiki](https://github.com/kripken/emscripten/wiki/WebAssembly) for more details about how to use it.
|
||||
|
||||
### C/C++ Source ⇒ WebAssembly LLVM backend ⇒ s2wasm ⇒ WebAssembly
|
||||
|
||||
Binaryen's `s2wasm` tool can translate the `.s` output from the LLVM WebAssembly backend into WebAssembly. You can receive `.s` output from `llc`, and then run `s2wasm` on that:
|
||||
|
||||
```
|
||||
llc code.ll -march=wasm32 -filetype=asm -o code.s
|
||||
s2wasm code.s > code.wast
|
||||
```
|
||||
|
||||
You can also use Emscripten, which will do those steps for you (as well as link to system libraries, etc.). You can use either normal Emscripten, including it's "fastcomp" fork of LLVM, or you can use "vanilla" LLVM, that is, pure upstream LLVM without Emscripten's additions. With Vanilla LLVM, you can build with
|
||||
|
||||
```
|
||||
./emcc input.cpp -s BINARYEN=1
|
||||
```
|
||||
|
||||
With normal Emscripten, you will need to tell it to use the WebAssembly backend, since its default is asm.js, by setting an env var,
|
||||
|
||||
```
|
||||
EMCC_WASM_BACKEND=1 ./emcc input.cpp -s BINARYEN=1
|
||||
```
|
||||
|
||||
(without the env var, the `BINARYEN` option will make it use the asm.js backend, then `asm2wasm`).
|
||||
|
||||
For more details, see the [emscripten wiki](https://github.com/kripken/emscripten/wiki/WebAssembly).
|
||||
|
||||
## Testing
|
||||
|
||||
```
|
||||
./check.py
|
||||
```
|
||||
|
||||
(or `python check.py`) will run `wasm-shell`, `wasm-opt`, `asm2wasm`, `wasm.js`, etc. on the testcases in `test/`, and verify their outputs.
|
||||
|
||||
It will also run `s2wasm` through the last known good LLVM output from the [build waterfall][].
|
||||
|
||||
[build waterfall]: https://build.chromium.org/p/client.wasm.llvm/console
|
||||
|
||||
The `check.py` script supports some options:
|
||||
|
||||
```
|
||||
./check.py [--interpreter=/path/to/interpreter] [TEST1] [TEST2]..
|
||||
```
|
||||
|
||||
* If an interpreter is provided, we run the output through it, checking for parse errors.
|
||||
* If tests are provided, we run exactly those. If none are provided, we run them all.
|
||||
* Some tests require `emcc` or `nodejs` in the path. They will not run if the tool cannot be found, and you'll see a warning.
|
||||
* We have tests from upstream in `tests/spec` and `tests/waterfall`, in git submodules. Running `./check.py` should update those.
|
||||
|
||||
## Design Principles
|
||||
|
||||
* **Interned strings for names**: It's very convenient to have names on nodes, instead of just numeric indices etc. To avoid most of the performance difference between strings and numeric indices, all strings are interned, which means there is a single copy of each string in memory, string comparisons are just a pointer comparison, etc.
|
||||
* **Allocate in arenas**: Based on experience with other optimizing/transformating toolchains, it's not worth the overhead to carefully track memory of individual nodes. Instead, we allocate all elements of a module in an arena, and the entire arena can be freed when the module is no longer needed.
|
||||
|
||||
## FAQ
|
||||
|
||||
* How does `asm2wasm` relate to the new WebAssembly backend which is being developed in upstream LLVM?
|
||||
|
||||
This is separate from that. `asm2wasm` focuses on compiling asm.js to WebAssembly, as emitted by Emscripten's asm.js backend. This is useful because while in the long term Emscripten hopes to use the new WebAssembly backend, the `asm2wasm` route is a very quick and easy way to generate WebAssembly output. It will also be useful for benchmarking the new backend as it progresses.
|
||||
|
||||
* How about compiling WebAssembly to asm.js (the opposite direction of `asm2wasm`)? Wouldn't that be useful for polyfilling?
|
||||
|
||||
Experimentation with this is happening, in `wasm2asm`.
|
||||
|
||||
This would be useful, but it is a much harder task, due to some decisions made in WebAssembly. For example, WebAssembly can have control flow nested inside expressions, which can't directly map to asm.js. It could be supported by outlining the code to another function, or to compiling it down into new basic blocks and control-flow-free instructions, but it is hard to do so in a way that is both fast to do and emits code that is fast to execute. On the other hand, compiling asm.js to WebAssembly is almost straightforward.
|
||||
|
||||
We just have to do more work on `wasm2asm` and see how efficient we can make it.
|
||||
|
||||
* Can `asm2wasm` compile any asm.js code?
|
||||
|
||||
Almost. Some decisions made in WebAssembly preclude that, for example, there are no global variables. That means that `asm2wasm` has to map asm.js global variables onto locations in memory, but then it must know of a safe zone in memory in which to do so, and that information is not directly available in asm.js.
|
||||
|
||||
`asm2wasm` and `emcc_to_wasm.js.sh` do some integration with Emscripten in order to work around these issues, like asking Emscripten to reserve same space for the globals, etc.
|
||||
|
||||
* Why the weird name for the project?
|
||||
|
||||
"Binaryen" is a combination of **binary** - since WebAssembly is a binary format for the web - and **Emscripten** - with which it can integrate in order to compile C and C++ all the way to WebAssembly, via asm.js. Binaryen began as Emscripten's WebAssembly processing library (`wasm-emscripten`).
|
||||
|
||||
"Binaryen" is pronounced [in the same manner](http://www.makinggameofthrones.com/production-diary/2011/2/11/official-pronunciation-guide-for-game-of-thrones.html) as "[Targaryen](https://en.wikipedia.org/wiki/List_of_A_Song_of_Ice_and_Fire_characters#House_Targaryen)": *bi-NAIR-ee-in*. Or something like that? Anyhow, however Targaryen is correctly pronounced, they should rhyme. Aside from pronunciation, the Targaryen house words, "Fire and Blood", have also inspired Binaryen's: "Code and Bugs."
|
||||
|
||||
* Does it compile under Windows and/or Visual Studio?
|
||||
|
||||
Yes, it does. Here's a step-by-step [tutorial](https://github.com/brakmic/brakmic/blob/master/webassembly/COMPILING_WIN32.md "Compiling under Win32") on how to compile it under **Windows 10 x64** with **CMake** and **Visual Studio 2015**. Help would be appreciated on Windows and OS X as most of the core devs are on Linux.
|
@ -1,35 +0,0 @@
|
||||
---
|
||||
|
||||
init:
|
||||
- set PATH=C:\Python27\Scripts;%PATH% # while python's bin is already in PATH, but pip.exe in Scripts\ dir isn't
|
||||
- set PATH=C:\msys64\mingw64\bin;C:\msys64\usr\bin;%PATH%
|
||||
|
||||
environment:
|
||||
DEGREE_OF_PARALLELISM: 3
|
||||
matrix:
|
||||
- GENERATOR: MSYS Makefiles
|
||||
CONFIG: Release
|
||||
PARALLEL_FLAG: -j
|
||||
- GENERATOR: Visual Studio 14 2015
|
||||
CONFIG: Release
|
||||
PARALLEL_FLAG: "/m:"
|
||||
- GENERATOR: Visual Studio 14 2015 Win64
|
||||
CONFIG: Debug
|
||||
PARALLEL_FLAG: "/m:"
|
||||
- GENERATOR: Visual Studio 14 2015 Win64
|
||||
CONFIG: Release
|
||||
PARALLEL_FLAG: "/m:"
|
||||
|
||||
install:
|
||||
- pip install flake8==3.4.1
|
||||
|
||||
before_build:
|
||||
# Check the style of a subset of Python code until the other code is updated.
|
||||
- flake8 ./scripts/
|
||||
|
||||
build_script:
|
||||
- cmake . -DCMAKE_BUILD_TYPE=%CONFIG% -G "%GENERATOR%"
|
||||
- cmake --build . --config %CONFIG% -- %PARALLEL_FLAG%%DEGREE_OF_PARALLELISM%
|
||||
|
||||
test_script:
|
||||
- ctest --output-on-failure --timeout 10 -j 5
|
@ -1,309 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os, sys, subprocess, difflib
|
||||
|
||||
from scripts.test.support import run_command, split_wast
|
||||
from scripts.test.shared import (
|
||||
ASM2WASM, MOZJS, S2WASM, WASM_SHELL, WASM_OPT, WASM_AS, WASM_DIS,
|
||||
WASM_CTOR_EVAL, WASM_MERGE, WASM_REDUCE, WASM2ASM,
|
||||
BINARYEN_INSTALL_DIR, has_shell_timeout)
|
||||
from scripts.test.wasm2asm import tests, spec_tests, extra_tests, assert_tests
|
||||
|
||||
|
||||
print '[ processing and updating testcases... ]\n'
|
||||
|
||||
for asm in sorted(os.listdir('test')):
|
||||
if asm.endswith('.asm.js'):
|
||||
for precise in [0, 1, 2]:
|
||||
for opts in [1, 0]:
|
||||
cmd = ASM2WASM + [os.path.join('test', asm), '--enable-threads']
|
||||
wasm = asm.replace('.asm.js', '.fromasm')
|
||||
if not precise:
|
||||
cmd += ['--trap-mode=allow', '--ignore-implicit-traps']
|
||||
wasm += '.imprecise'
|
||||
elif precise == 2:
|
||||
cmd += ['--trap-mode=clamp']
|
||||
wasm += '.clamp'
|
||||
if not opts:
|
||||
wasm += '.no-opts'
|
||||
if precise:
|
||||
cmd += ['-O0'] # test that -O0 does nothing
|
||||
else:
|
||||
cmd += ['-O']
|
||||
if 'debugInfo' in asm:
|
||||
cmd += ['-g']
|
||||
if 'noffi' in asm:
|
||||
cmd += ['--no-legalize-javascript-ffi']
|
||||
if precise and opts:
|
||||
# test mem init importing
|
||||
open('a.mem', 'wb').write(asm)
|
||||
cmd += ['--mem-init=a.mem']
|
||||
if asm[0] == 'e':
|
||||
cmd += ['--mem-base=1024']
|
||||
if 'i64' in asm or 'wasm-only' in asm or 'noffi' in asm:
|
||||
cmd += ['--wasm-only']
|
||||
print ' '.join(cmd)
|
||||
actual = run_command(cmd)
|
||||
with open(os.path.join('test', wasm), 'w') as o: o.write(actual)
|
||||
if 'debugInfo' in asm:
|
||||
cmd += ['--source-map', os.path.join('test', wasm + '.map'), '-o', 'a.wasm']
|
||||
run_command(cmd)
|
||||
|
||||
extension_arg_map = {
|
||||
'.wast': [],
|
||||
'.clamp.wast': ['--trap-mode=clamp'],
|
||||
'.js.wast': ['--trap-mode=js'],
|
||||
}
|
||||
for dot_s_dir in ['dot_s', 'llvm_autogenerated']:
|
||||
for s in sorted(os.listdir(os.path.join('test', dot_s_dir))):
|
||||
if not s.endswith('.s'): continue
|
||||
print '..', s
|
||||
for ext, ext_args in extension_arg_map.iteritems():
|
||||
wasm = s.replace('.s', ext)
|
||||
expected_file = os.path.join('test', dot_s_dir, wasm)
|
||||
if ext != '.wast' and not os.path.exists(expected_file):
|
||||
continue
|
||||
|
||||
full = os.path.join('test', dot_s_dir, s)
|
||||
stack_alloc = ['--allocate-stack=1024'] if dot_s_dir == 'llvm_autogenerated' else []
|
||||
cmd = S2WASM + [full, '--emscripten-glue'] + stack_alloc + ext_args
|
||||
if s.startswith('start_'):
|
||||
cmd.append('--start')
|
||||
actual = run_command(cmd, stderr=subprocess.PIPE, expected_err='')
|
||||
|
||||
with open(expected_file, 'w') as o: o.write(actual)
|
||||
|
||||
for t in sorted(os.listdir(os.path.join('test', 'print'))):
|
||||
if t.endswith('.wast'):
|
||||
print '..', t
|
||||
wasm = os.path.basename(t).replace('.wast', '')
|
||||
cmd = WASM_OPT + [os.path.join('test', 'print', t), '--print']
|
||||
print ' ', ' '.join(cmd)
|
||||
actual = subprocess.check_output(cmd)
|
||||
print cmd, actual
|
||||
with open(os.path.join('test', 'print', wasm + '.txt'), 'w') as o: o.write(actual)
|
||||
cmd = WASM_OPT + [os.path.join('test', 'print', t), '--print-minified']
|
||||
print ' ', ' '.join(cmd)
|
||||
actual = subprocess.check_output(cmd)
|
||||
with open(os.path.join('test', 'print', wasm + '.minified.txt'), 'w') as o: o.write(actual)
|
||||
|
||||
for t in sorted(os.listdir(os.path.join('test', 'passes'))):
|
||||
if t.endswith(('.wast', '.wasm')):
|
||||
print '..', t
|
||||
binary = '.wasm' in t
|
||||
passname = os.path.basename(t).replace('.wast', '').replace('.wasm', '')
|
||||
opts = [('--' + p if not p.startswith('O') else '-' + p) for p in passname.split('_')]
|
||||
t = os.path.join('test', 'passes', t)
|
||||
actual = ''
|
||||
for module, asserts in split_wast(t):
|
||||
assert len(asserts) == 0
|
||||
with open('split.wast', 'w') as o: o.write(module)
|
||||
cmd = WASM_OPT + opts + ['split.wast', '--print']
|
||||
actual += run_command(cmd)
|
||||
with open(os.path.join('test', 'passes', passname + ('.bin' if binary else '') + '.txt'), 'w') as o: o.write(actual)
|
||||
if 'emit-js-wrapper' in t:
|
||||
with open('a.js') as i:
|
||||
with open(t + '.js', 'w') as o:
|
||||
o.write(i.read())
|
||||
if 'emit-spec-wrapper' in t:
|
||||
with open('a.wat') as i:
|
||||
with open(t + '.wat', 'w') as o:
|
||||
o.write(i.read())
|
||||
|
||||
print '\n[ checking wasm-opt -o notation... ]\n'
|
||||
|
||||
wast = os.path.join('test', 'hello_world.wast')
|
||||
cmd = WASM_OPT + [wast, '-o', 'a.wast', '-S']
|
||||
run_command(cmd)
|
||||
open(wast, 'w').write(open('a.wast').read())
|
||||
|
||||
print '\n[ checking binary format testcases... ]\n'
|
||||
|
||||
for wast in sorted(os.listdir('test')):
|
||||
if wast.endswith('.wast') and not wast in []: # blacklist some known failures
|
||||
for debug_info in [0, 1]:
|
||||
cmd = WASM_AS + [os.path.join('test', wast), '-o', 'a.wasm']
|
||||
if debug_info: cmd += ['-g']
|
||||
print ' '.join(cmd)
|
||||
if os.path.exists('a.wasm'): os.unlink('a.wasm')
|
||||
subprocess.check_call(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
assert os.path.exists('a.wasm')
|
||||
|
||||
cmd = WASM_DIS + ['a.wasm', '-o', 'a.wast']
|
||||
print ' '.join(cmd)
|
||||
if os.path.exists('a.wast'): os.unlink('a.wast')
|
||||
subprocess.check_call(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
assert os.path.exists('a.wast')
|
||||
actual = open('a.wast').read()
|
||||
binary_name = wast + '.fromBinary'
|
||||
if not debug_info: binary_name += '.noDebugInfo'
|
||||
with open(os.path.join('test', binary_name), 'w') as o: o.write(actual)
|
||||
|
||||
print '\n[ checking example testcases... ]\n'
|
||||
|
||||
for t in sorted(os.listdir(os.path.join('test', 'example'))):
|
||||
output_file = os.path.join('bin', 'example')
|
||||
libdir = os.path.join(BINARYEN_INSTALL_DIR, 'lib')
|
||||
cmd = ['-Isrc', '-g', '-lasmjs', '-lsupport', '-L' + libdir, '-pthread', '-o', output_file]
|
||||
if t.endswith('.txt'):
|
||||
# check if there is a trace in the file, if so, we should build it
|
||||
out = subprocess.Popen([os.path.join('scripts', 'clean_c_api_trace.py'), os.path.join('test', 'example', t)], stdout=subprocess.PIPE).communicate()[0]
|
||||
if len(out) == 0:
|
||||
print ' (no trace in ', t, ')'
|
||||
continue
|
||||
print ' (will check trace in ', t, ')'
|
||||
src = 'trace.cpp'
|
||||
with open(src, 'w') as o: o.write(out)
|
||||
expected = os.path.join('test', 'example', t + '.txt')
|
||||
else:
|
||||
src = os.path.join('test', 'example', t)
|
||||
expected = os.path.join('test', 'example', '.'.join(t.split('.')[:-1]) + '.txt')
|
||||
if not src.endswith(('.c', '.cpp')):
|
||||
continue
|
||||
# build the C file separately
|
||||
extra = [os.environ.get('CC') or 'gcc',
|
||||
src, '-c', '-o', 'example.o',
|
||||
'-Isrc', '-g', '-L' + libdir, '-pthread']
|
||||
print 'build: ', ' '.join(extra)
|
||||
print os.getcwd()
|
||||
subprocess.check_call(extra)
|
||||
# Link against the binaryen C library DSO, using rpath
|
||||
cmd = ['example.o', '-lbinaryen', '-Wl,-rpath=' + os.path.abspath(libdir)] + cmd
|
||||
print ' ', t, src, expected
|
||||
if os.environ.get('COMPILER_FLAGS'):
|
||||
for f in os.environ.get('COMPILER_FLAGS').split(' '):
|
||||
cmd.append(f)
|
||||
cmd = [os.environ.get('CXX') or 'g++', '-std=c++11'] + cmd
|
||||
try:
|
||||
print 'link: ', ' '.join(cmd)
|
||||
subprocess.check_call(cmd)
|
||||
print 'run...', output_file
|
||||
proc = subprocess.Popen([output_file], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
actual, err = proc.communicate()
|
||||
assert proc.returncode == 0, [proc.returncode, actual, err]
|
||||
with open(expected, 'w') as o: o.write(actual)
|
||||
finally:
|
||||
os.remove(output_file)
|
||||
if sys.platform == 'darwin':
|
||||
# Also removes debug directory produced on Mac OS
|
||||
shutil.rmtree(output_file + '.dSYM')
|
||||
|
||||
print '\n[ checking wasm-opt testcases... ]\n'
|
||||
|
||||
for t in os.listdir('test'):
|
||||
if t.endswith('.wast') and not t.startswith('spec'):
|
||||
print '..', t
|
||||
t = os.path.join('test', t)
|
||||
f = t + '.from-wast'
|
||||
cmd = WASM_OPT + [t, '--print']
|
||||
actual = run_command(cmd)
|
||||
actual = actual.replace('printing before:\n', '')
|
||||
open(f, 'w').write(actual)
|
||||
|
||||
print '\n[ checking wasm-dis on provided binaries... ]\n'
|
||||
|
||||
for t in os.listdir('test'):
|
||||
if t.endswith('.wasm') and not t.startswith('spec'):
|
||||
print '..', t
|
||||
t = os.path.join('test', t)
|
||||
cmd = WASM_DIS + [t]
|
||||
if os.path.isfile(t + '.map'): cmd += ['--source-map', t + '.map']
|
||||
actual = run_command(cmd)
|
||||
|
||||
open(t + '.fromBinary', 'w').write(actual)
|
||||
|
||||
print '\n[ checking wasm-merge... ]\n'
|
||||
|
||||
for t in os.listdir(os.path.join('test', 'merge')):
|
||||
if t.endswith(('.wast', '.wasm')):
|
||||
print '..', t
|
||||
t = os.path.join('test', 'merge', t)
|
||||
u = t + '.toMerge'
|
||||
for finalize in [0, 1]:
|
||||
for opt in [0, 1]:
|
||||
cmd = WASM_MERGE + [t, u, '-o', 'a.wast', '-S', '--verbose']
|
||||
if finalize: cmd += ['--finalize-memory-base=1024', '--finalize-table-base=8']
|
||||
if opt: cmd += ['-O']
|
||||
stdout = run_command(cmd)
|
||||
actual = open('a.wast').read()
|
||||
out = t + '.combined'
|
||||
if finalize: out += '.finalized'
|
||||
if opt: out += '.opt'
|
||||
with open(out, 'w') as o: o.write(actual)
|
||||
with open(out + '.stdout', 'w') as o: o.write(stdout)
|
||||
|
||||
if MOZJS:
|
||||
print '\n[ checking binaryen.js testcases... ]\n'
|
||||
|
||||
for s in sorted(os.listdir(os.path.join('test', 'binaryen.js'))):
|
||||
if not s.endswith('.js'): continue
|
||||
print s
|
||||
f = open('a.js', 'w')
|
||||
f.write(open(os.path.join('bin', 'binaryen.js')).read())
|
||||
f.write(open(os.path.join('test', 'binaryen.js', s)).read())
|
||||
f.close()
|
||||
cmd = [MOZJS, 'a.js']
|
||||
out = run_command(cmd, stderr=subprocess.STDOUT)
|
||||
with open(os.path.join('test', 'binaryen.js', s + '.txt'), 'w') as o: o.write(out)
|
||||
|
||||
print '\n[ checking wasm-ctor-eval... ]\n'
|
||||
|
||||
for t in os.listdir(os.path.join('test', 'ctor-eval')):
|
||||
if t.endswith(('.wast', '.wasm')):
|
||||
print '..', t
|
||||
t = os.path.join('test', 'ctor-eval', t)
|
||||
ctors = open(t + '.ctors').read().strip()
|
||||
cmd = WASM_CTOR_EVAL + [t, '-o', 'a.wast', '-S', '--ctors', ctors]
|
||||
stdout = run_command(cmd)
|
||||
actual = open('a.wast').read()
|
||||
out = t + '.out'
|
||||
with open(out, 'w') as o: o.write(actual)
|
||||
|
||||
print '\n[ checking wasm2asm ]\n'
|
||||
|
||||
for wasm in tests + spec_tests + extra_tests:
|
||||
if not wasm.endswith('.wast'):
|
||||
continue
|
||||
|
||||
asm = os.path.basename(wasm).replace('.wast', '.2asm.js')
|
||||
expected_file = os.path.join('test', asm)
|
||||
|
||||
if not os.path.exists(expected_file):
|
||||
continue
|
||||
|
||||
print '..', wasm
|
||||
|
||||
cmd = WASM2ASM + [os.path.join('test', wasm)]
|
||||
out = run_command(cmd)
|
||||
with open(expected_file, 'w') as o: o.write(out)
|
||||
|
||||
for wasm in assert_tests:
|
||||
print '..', wasm
|
||||
|
||||
asserts = os.path.basename(wasm).replace('.wast.asserts', '.asserts.js')
|
||||
traps = os.path.basename(wasm).replace('.wast.asserts', '.traps.js')
|
||||
asserts_expected_file = os.path.join('test', asserts)
|
||||
traps_expected_file = os.path.join('test', traps)
|
||||
|
||||
cmd = WASM2ASM + [os.path.join('test', wasm), '--allow-asserts']
|
||||
out = run_command(cmd)
|
||||
with open(asserts_expected_file, 'w') as o: o.write(out)
|
||||
|
||||
cmd += ['--pedantic']
|
||||
out = run_command(cmd)
|
||||
with open(traps_expected_file, 'w') as o: o.write(out)
|
||||
|
||||
if has_shell_timeout():
|
||||
print '\n[ checking wasm-reduce ]\n'
|
||||
|
||||
for t in os.listdir(os.path.join('test', 'reduce')):
|
||||
if t.endswith('.wast'):
|
||||
print '..', t
|
||||
t = os.path.join('test', 'reduce', t)
|
||||
# convert to wasm
|
||||
run_command(WASM_AS + [t, '-o', 'a.wasm'])
|
||||
print run_command(WASM_REDUCE + ['a.wasm', '--command=bin/wasm-opt b.wasm --fuzz-exec', '-t', 'b.wasm', '-w', 'c.wasm'])
|
||||
expected = t + '.txt'
|
||||
run_command(WASM_DIS + ['c.wasm', '-o', expected])
|
||||
|
||||
print '\n[ success! ]'
|
@ -1,393 +0,0 @@
|
||||
#
|
||||
# This file builds the js components using emscripten. You normally don't need
|
||||
# to run this, as the builds are bundled in the repo in bin/. Running this is
|
||||
# useful if you are a developer and want to update those builds.
|
||||
#
|
||||
# Usage: build-js.sh
|
||||
# Usage: EMSCRIPTEN=path/to/emscripten build-js.sh # explicit emscripten dir
|
||||
#
|
||||
# Emscripten's em++ and tools/webidl_binder.py will be accessed through the
|
||||
# env var EMSCRIPTEN, e.g. ${EMSCRIPTEN}/em++
|
||||
#
|
||||
# You can get emscripten from
|
||||
# http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html
|
||||
#
|
||||
set -e
|
||||
|
||||
if [ "$1" == "-h" ] || [ "$1" == "--help" ] || [ "$1" == "-help" ]; then
|
||||
echo "usage: $0 [-g]" >&2
|
||||
echo " -g produce debug build" >&2
|
||||
echo ""
|
||||
echo "If EMSCRIPTEN is set in the envionment, emscripten will be loaded"
|
||||
echo "from that directory. Otherwise the location of emscripten is resolved"
|
||||
echo "through PATH."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z $EMSCRIPTEN ]; then
|
||||
if (which emcc >/dev/null); then
|
||||
# Found emcc in PATH -- set EMSCRIPTEN (we need this to access webidl_binder.py)
|
||||
EMSCRIPTEN=$(dirname "$(which emcc)")
|
||||
else
|
||||
echo "$0: EMSCRIPTEN environment variable is not set and emcc was not found in PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
elif [ ! -d "$EMSCRIPTEN" ]; then
|
||||
echo "$0: \"$EMSCRIPTEN\" (\$EMSCRIPTEN) is not a directory" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
EMCC_ARGS="-std=c++11 --memory-init-file 0"
|
||||
EMCC_ARGS="$EMCC_ARGS -s ALLOW_MEMORY_GROWTH=1"
|
||||
EMCC_ARGS="$EMCC_ARGS -s DEMANGLE_SUPPORT=1"
|
||||
EMCC_ARGS="$EMCC_ARGS -s DISABLE_EXCEPTION_CATCHING=0" # Exceptions are thrown and caught when optimizing endless loops
|
||||
OUT_FILE_SUFFIX=
|
||||
|
||||
if [ "$1" == "-g" ]; then
|
||||
EMCC_ARGS="$EMCC_ARGS -O2" # need emcc js opts to be decently fast
|
||||
EMCC_ARGS="$EMCC_ARGS --llvm-opts 0 --llvm-lto 0"
|
||||
EMCC_ARGS="$EMCC_ARGS -profiling"
|
||||
OUT_FILE_SUFFIX=-g
|
||||
else
|
||||
EMCC_ARGS="$EMCC_ARGS -Oz"
|
||||
EMCC_ARGS="$EMCC_ARGS --llvm-lto 1"
|
||||
EMCC_ARGS="$EMCC_ARGS -s ELIMINATE_DUPLICATE_FUNCTIONS=1"
|
||||
# Why these settings?
|
||||
# See https://gist.github.com/rsms/e33c61a25a31c08260161a087be03169
|
||||
fi
|
||||
|
||||
if [ "$1" != "-g" ]; then
|
||||
EMCC_ARGS="$EMCC_ARGS --closure 1"
|
||||
fi
|
||||
|
||||
echo "building shared bitcode"
|
||||
|
||||
"$EMSCRIPTEN/em++" \
|
||||
$EMCC_ARGS \
|
||||
src/asmjs/asm_v_wasm.cpp \
|
||||
src/asmjs/shared-constants.cpp \
|
||||
src/cfg/Relooper.cpp \
|
||||
src/emscripten-optimizer/optimizer-shared.cpp \
|
||||
src/emscripten-optimizer/parser.cpp \
|
||||
src/emscripten-optimizer/simple_ast.cpp \
|
||||
src/ir/ExpressionAnalyzer.cpp \
|
||||
src/ir/ExpressionManipulator.cpp \
|
||||
src/ir/LocalGraph.cpp \
|
||||
src/passes/pass.cpp \
|
||||
src/passes/CoalesceLocals.cpp \
|
||||
src/passes/CodeFolding.cpp \
|
||||
src/passes/CodePushing.cpp \
|
||||
src/passes/ConstHoisting.cpp \
|
||||
src/passes/DeadCodeElimination.cpp \
|
||||
src/passes/DuplicateFunctionElimination.cpp \
|
||||
src/passes/ExtractFunction.cpp \
|
||||
src/passes/Flatten.cpp \
|
||||
src/passes/I64ToI32Lowering.cpp \
|
||||
src/passes/Inlining.cpp \
|
||||
src/passes/InstrumentLocals.cpp \
|
||||
src/passes/InstrumentMemory.cpp \
|
||||
src/passes/LegalizeJSInterface.cpp \
|
||||
src/passes/LocalCSE.cpp \
|
||||
src/passes/LogExecution.cpp \
|
||||
src/passes/MemoryPacking.cpp \
|
||||
src/passes/MergeBlocks.cpp \
|
||||
src/passes/Metrics.cpp \
|
||||
src/passes/NameList.cpp \
|
||||
src/passes/OptimizeInstructions.cpp \
|
||||
src/passes/PickLoadSigns.cpp \
|
||||
src/passes/PostEmscripten.cpp \
|
||||
src/passes/Precompute.cpp \
|
||||
src/passes/Print.cpp \
|
||||
src/passes/PrintCallGraph.cpp \
|
||||
src/passes/RelooperJumpThreading.cpp \
|
||||
src/passes/RemoveImports.cpp \
|
||||
src/passes/RemoveMemory.cpp \
|
||||
src/passes/RemoveUnusedBrs.cpp \
|
||||
src/passes/RemoveUnusedModuleElements.cpp \
|
||||
src/passes/RemoveUnusedNames.cpp \
|
||||
src/passes/ReorderFunctions.cpp \
|
||||
src/passes/ReorderLocals.cpp \
|
||||
src/passes/ReReloop.cpp \
|
||||
src/passes/SafeHeap.cpp \
|
||||
src/passes/SimplifyLocals.cpp \
|
||||
src/passes/SSAify.cpp \
|
||||
src/passes/TrapMode.cpp \
|
||||
src/passes/Untee.cpp \
|
||||
src/passes/Vacuum.cpp \
|
||||
src/support/bits.cpp \
|
||||
src/support/colors.cpp \
|
||||
src/support/safe_integer.cpp \
|
||||
src/support/threads.cpp \
|
||||
src/wasm/literal.cpp \
|
||||
src/wasm/wasm-binary.cpp \
|
||||
src/wasm/wasm-s-parser.cpp \
|
||||
src/wasm/wasm-type.cpp \
|
||||
src/wasm/wasm-validator.cpp \
|
||||
src/wasm/wasm.cpp \
|
||||
src/wasm-emscripten.cpp \
|
||||
-Isrc/ \
|
||||
-o shared.bc
|
||||
|
||||
echo "building wasm.js"
|
||||
|
||||
"$EMSCRIPTEN/em++" \
|
||||
$EMCC_ARGS \
|
||||
src/wasm-js.cpp \
|
||||
shared.bc \
|
||||
-Isrc/ \
|
||||
-o bin/wasm${OUT_FILE_SUFFIX}.js \
|
||||
-s MODULARIZE=1 \
|
||||
-s 'EXPORT_NAME="WasmJS"'
|
||||
|
||||
echo "building binaryen.js"
|
||||
|
||||
function export_function { if [ -z ${EXPORTED_FUNCTIONS} ]; then EXPORTED_FUNCTIONS='"'$1'"'; else EXPORTED_FUNCTIONS=${EXPORTED_FUNCTIONS}',"'$1'"'; fi }
|
||||
export_function "_BinaryenNone"
|
||||
export_function "_BinaryenInt32"
|
||||
export_function "_BinaryenInt64"
|
||||
export_function "_BinaryenFloat32"
|
||||
export_function "_BinaryenFloat64"
|
||||
export_function "_BinaryenUndefined"
|
||||
export_function "_BinaryenInvalidId"
|
||||
export_function "_BinaryenBlockId"
|
||||
export_function "_BinaryenIfId"
|
||||
export_function "_BinaryenLoopId"
|
||||
export_function "_BinaryenBreakId"
|
||||
export_function "_BinaryenSwitchId"
|
||||
export_function "_BinaryenCallId"
|
||||
export_function "_BinaryenCallImportId"
|
||||
export_function "_BinaryenCallIndirectId"
|
||||
export_function "_BinaryenGetLocalId"
|
||||
export_function "_BinaryenSetLocalId"
|
||||
export_function "_BinaryenGetGlobalId"
|
||||
export_function "_BinaryenSetGlobalId"
|
||||
export_function "_BinaryenLoadId"
|
||||
export_function "_BinaryenStoreId"
|
||||
export_function "_BinaryenConstId"
|
||||
export_function "_BinaryenUnaryId"
|
||||
export_function "_BinaryenBinaryId"
|
||||
export_function "_BinaryenSelectId"
|
||||
export_function "_BinaryenDropId"
|
||||
export_function "_BinaryenReturnId"
|
||||
export_function "_BinaryenHostId"
|
||||
export_function "_BinaryenNopId"
|
||||
export_function "_BinaryenUnreachableId"
|
||||
export_function "_BinaryenAtomicCmpxchgId"
|
||||
export_function "_BinaryenAtomicRMWId"
|
||||
export_function "_BinaryenAtomicWaitId"
|
||||
export_function "_BinaryenAtomicWakeId"
|
||||
export_function "_BinaryenModuleCreate"
|
||||
export_function "_BinaryenModuleDispose"
|
||||
export_function "_BinaryenAddFunctionType"
|
||||
export_function "_BinaryenGetFunctionTypeBySignature"
|
||||
export_function "_BinaryenLiteralInt32"
|
||||
export_function "_BinaryenLiteralInt64"
|
||||
export_function "_BinaryenLiteralFloat32"
|
||||
export_function "_BinaryenLiteralFloat64"
|
||||
export_function "_BinaryenLiteralFloat32Bits"
|
||||
export_function "_BinaryenLiteralFloat64Bits"
|
||||
export_function "_BinaryenClzInt32"
|
||||
export_function "_BinaryenCtzInt32"
|
||||
export_function "_BinaryenPopcntInt32"
|
||||
export_function "_BinaryenNegFloat32"
|
||||
export_function "_BinaryenAbsFloat32"
|
||||
export_function "_BinaryenCeilFloat32"
|
||||
export_function "_BinaryenFloorFloat32"
|
||||
export_function "_BinaryenTruncFloat32"
|
||||
export_function "_BinaryenNearestFloat32"
|
||||
export_function "_BinaryenSqrtFloat32"
|
||||
export_function "_BinaryenEqZInt32"
|
||||
export_function "_BinaryenClzInt64"
|
||||
export_function "_BinaryenCtzInt64"
|
||||
export_function "_BinaryenPopcntInt64"
|
||||
export_function "_BinaryenNegFloat64"
|
||||
export_function "_BinaryenAbsFloat64"
|
||||
export_function "_BinaryenCeilFloat64"
|
||||
export_function "_BinaryenFloorFloat64"
|
||||
export_function "_BinaryenTruncFloat64"
|
||||
export_function "_BinaryenNearestFloat64"
|
||||
export_function "_BinaryenSqrtFloat64"
|
||||
export_function "_BinaryenEqZInt64"
|
||||
export_function "_BinaryenExtendSInt32"
|
||||
export_function "_BinaryenExtendUInt32"
|
||||
export_function "_BinaryenWrapInt64"
|
||||
export_function "_BinaryenTruncSFloat32ToInt32"
|
||||
export_function "_BinaryenTruncSFloat32ToInt64"
|
||||
export_function "_BinaryenTruncUFloat32ToInt32"
|
||||
export_function "_BinaryenTruncUFloat32ToInt64"
|
||||
export_function "_BinaryenTruncSFloat64ToInt32"
|
||||
export_function "_BinaryenTruncSFloat64ToInt64"
|
||||
export_function "_BinaryenTruncUFloat64ToInt32"
|
||||
export_function "_BinaryenTruncUFloat64ToInt64"
|
||||
export_function "_BinaryenReinterpretFloat32"
|
||||
export_function "_BinaryenReinterpretFloat64"
|
||||
export_function "_BinaryenConvertSInt32ToFloat32"
|
||||
export_function "_BinaryenConvertSInt32ToFloat64"
|
||||
export_function "_BinaryenConvertUInt32ToFloat32"
|
||||
export_function "_BinaryenConvertUInt32ToFloat64"
|
||||
export_function "_BinaryenConvertSInt64ToFloat32"
|
||||
export_function "_BinaryenConvertSInt64ToFloat64"
|
||||
export_function "_BinaryenConvertUInt64ToFloat32"
|
||||
export_function "_BinaryenConvertUInt64ToFloat64"
|
||||
export_function "_BinaryenPromoteFloat32"
|
||||
export_function "_BinaryenDemoteFloat64"
|
||||
export_function "_BinaryenReinterpretInt32"
|
||||
export_function "_BinaryenReinterpretInt64"
|
||||
export_function "_BinaryenAddInt32"
|
||||
export_function "_BinaryenSubInt32"
|
||||
export_function "_BinaryenMulInt32"
|
||||
export_function "_BinaryenDivSInt32"
|
||||
export_function "_BinaryenDivUInt32"
|
||||
export_function "_BinaryenRemSInt32"
|
||||
export_function "_BinaryenRemUInt32"
|
||||
export_function "_BinaryenAndInt32"
|
||||
export_function "_BinaryenOrInt32"
|
||||
export_function "_BinaryenXorInt32"
|
||||
export_function "_BinaryenShlInt32"
|
||||
export_function "_BinaryenShrUInt32"
|
||||
export_function "_BinaryenShrSInt32"
|
||||
export_function "_BinaryenRotLInt32"
|
||||
export_function "_BinaryenRotRInt32"
|
||||
export_function "_BinaryenEqInt32"
|
||||
export_function "_BinaryenNeInt32"
|
||||
export_function "_BinaryenLtSInt32"
|
||||
export_function "_BinaryenLtUInt32"
|
||||
export_function "_BinaryenLeSInt32"
|
||||
export_function "_BinaryenLeUInt32"
|
||||
export_function "_BinaryenGtSInt32"
|
||||
export_function "_BinaryenGtUInt32"
|
||||
export_function "_BinaryenGeSInt32"
|
||||
export_function "_BinaryenGeUInt32"
|
||||
export_function "_BinaryenAddInt64"
|
||||
export_function "_BinaryenSubInt64"
|
||||
export_function "_BinaryenMulInt64"
|
||||
export_function "_BinaryenDivSInt64"
|
||||
export_function "_BinaryenDivUInt64"
|
||||
export_function "_BinaryenRemSInt64"
|
||||
export_function "_BinaryenRemUInt64"
|
||||
export_function "_BinaryenAndInt64"
|
||||
export_function "_BinaryenOrInt64"
|
||||
export_function "_BinaryenXorInt64"
|
||||
export_function "_BinaryenShlInt64"
|
||||
export_function "_BinaryenShrUInt64"
|
||||
export_function "_BinaryenShrSInt64"
|
||||
export_function "_BinaryenRotLInt64"
|
||||
export_function "_BinaryenRotRInt64"
|
||||
export_function "_BinaryenEqInt64"
|
||||
export_function "_BinaryenNeInt64"
|
||||
export_function "_BinaryenLtSInt64"
|
||||
export_function "_BinaryenLtUInt64"
|
||||
export_function "_BinaryenLeSInt64"
|
||||
export_function "_BinaryenLeUInt64"
|
||||
export_function "_BinaryenGtSInt64"
|
||||
export_function "_BinaryenGtUInt64"
|
||||
export_function "_BinaryenGeSInt64"
|
||||
export_function "_BinaryenGeUInt64"
|
||||
export_function "_BinaryenAddFloat32"
|
||||
export_function "_BinaryenSubFloat32"
|
||||
export_function "_BinaryenMulFloat32"
|
||||
export_function "_BinaryenDivFloat32"
|
||||
export_function "_BinaryenCopySignFloat32"
|
||||
export_function "_BinaryenMinFloat32"
|
||||
export_function "_BinaryenMaxFloat32"
|
||||
export_function "_BinaryenEqFloat32"
|
||||
export_function "_BinaryenNeFloat32"
|
||||
export_function "_BinaryenLtFloat32"
|
||||
export_function "_BinaryenLeFloat32"
|
||||
export_function "_BinaryenGtFloat32"
|
||||
export_function "_BinaryenGeFloat32"
|
||||
export_function "_BinaryenAddFloat64"
|
||||
export_function "_BinaryenSubFloat64"
|
||||
export_function "_BinaryenMulFloat64"
|
||||
export_function "_BinaryenDivFloat64"
|
||||
export_function "_BinaryenCopySignFloat64"
|
||||
export_function "_BinaryenMinFloat64"
|
||||
export_function "_BinaryenMaxFloat64"
|
||||
export_function "_BinaryenEqFloat64"
|
||||
export_function "_BinaryenNeFloat64"
|
||||
export_function "_BinaryenLtFloat64"
|
||||
export_function "_BinaryenLeFloat64"
|
||||
export_function "_BinaryenGtFloat64"
|
||||
export_function "_BinaryenGeFloat64"
|
||||
export_function "_BinaryenPageSize"
|
||||
export_function "_BinaryenCurrentMemory"
|
||||
export_function "_BinaryenGrowMemory"
|
||||
export_function "_BinaryenHasFeature"
|
||||
export_function "_BinaryenAtomicRMWAdd"
|
||||
export_function "_BinaryenAtomicRMWSub"
|
||||
export_function "_BinaryenAtomicRMWAnd"
|
||||
export_function "_BinaryenAtomicRMWOr"
|
||||
export_function "_BinaryenAtomicRMWXor"
|
||||
export_function "_BinaryenAtomicRMWXchg"
|
||||
export_function "_BinaryenBlock"
|
||||
export_function "_BinaryenIf"
|
||||
export_function "_BinaryenLoop"
|
||||
export_function "_BinaryenBreak"
|
||||
export_function "_BinaryenSwitch"
|
||||
export_function "_BinaryenCall"
|
||||
export_function "_BinaryenCallImport"
|
||||
export_function "_BinaryenCallIndirect"
|
||||
export_function "_BinaryenGetLocal"
|
||||
export_function "_BinaryenSetLocal"
|
||||
export_function "_BinaryenTeeLocal"
|
||||
export_function "_BinaryenGetGlobal"
|
||||
export_function "_BinaryenSetGlobal"
|
||||
export_function "_BinaryenLoad"
|
||||
export_function "_BinaryenStore"
|
||||
export_function "_BinaryenConst"
|
||||
export_function "_BinaryenUnary"
|
||||
export_function "_BinaryenBinary"
|
||||
export_function "_BinaryenSelect"
|
||||
export_function "_BinaryenDrop"
|
||||
export_function "_BinaryenReturn"
|
||||
export_function "_BinaryenHost"
|
||||
export_function "_BinaryenNop"
|
||||
export_function "_BinaryenUnreachable"
|
||||
export_function "_BinaryenAtomicRMW"
|
||||
export_function "_BinaryenAtomicCmpxchg"
|
||||
export_function "_BinaryenAtomicWait"
|
||||
export_function "_BinaryenAtomicWake"
|
||||
export_function "_BinaryenExpressionGetId"
|
||||
export_function "_BinaryenExpressionGetType"
|
||||
export_function "_BinaryenExpressionPrint"
|
||||
export_function "_BinaryenConstGetValueI32"
|
||||
export_function "_BinaryenConstGetValueI64Low"
|
||||
export_function "_BinaryenConstGetValueI64High"
|
||||
export_function "_BinaryenConstGetValueF32"
|
||||
export_function "_BinaryenConstGetValueF64"
|
||||
export_function "_BinaryenAddFunction"
|
||||
export_function "_BinaryenAddGlobal"
|
||||
export_function "_BinaryenAddImport"
|
||||
export_function "_BinaryenRemoveImport"
|
||||
export_function "_BinaryenAddExport"
|
||||
export_function "_BinaryenRemoveExport"
|
||||
export_function "_BinaryenSetFunctionTable"
|
||||
export_function "_BinaryenSetMemory"
|
||||
export_function "_BinaryenSetStart"
|
||||
export_function "_BinaryenModuleParse"
|
||||
export_function "_BinaryenModulePrint"
|
||||
export_function "_BinaryenModulePrintAsmjs"
|
||||
export_function "_BinaryenModuleValidate"
|
||||
export_function "_BinaryenModuleOptimize"
|
||||
export_function "_BinaryenModuleRunPasses"
|
||||
export_function "_BinaryenModuleAutoDrop"
|
||||
export_function "_BinaryenModuleWrite"
|
||||
export_function "_BinaryenModuleRead"
|
||||
export_function "_BinaryenModuleInterpret"
|
||||
export_function "_RelooperCreate"
|
||||
export_function "_RelooperAddBlock"
|
||||
export_function "_RelooperAddBranch"
|
||||
export_function "_RelooperAddBlockWithSwitch"
|
||||
export_function "_RelooperAddBranchForSwitch"
|
||||
export_function "_RelooperRenderAndDispose"
|
||||
export_function "_BinaryenSetAPITracing"
|
||||
|
||||
"$EMSCRIPTEN/em++" \
|
||||
$EMCC_ARGS \
|
||||
src/binaryen-c.cpp \
|
||||
shared.bc \
|
||||
-Isrc/ \
|
||||
-s EXPORTED_FUNCTIONS=[${EXPORTED_FUNCTIONS}] \
|
||||
-o bin/binaryen${OUT_FILE_SUFFIX}.js \
|
||||
--pre-js src/js/binaryen.js-pre.js \
|
||||
--post-js src/js/binaryen.js-post.js
|
@ -1,596 +0,0 @@
|
||||
#!/usr/bin/env python2
|
||||
|
||||
# Copyright 2015 WebAssembly Community Group participants
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from scripts.test.support import run_command, split_wast
|
||||
from scripts.test.shared import (
|
||||
BIN_DIR, EMCC, MOZJS, NATIVECC, NATIVEXX, NODEJS, S2WASM_EXE,
|
||||
WASM_AS, WASM_CTOR_EVAL, WASM_OPT, WASM_SHELL, WASM_MERGE, WASM_SHELL_EXE,
|
||||
WASM_DIS, WASM_REDUCE, binary_format_check, delete_from_orbit, fail, fail_with_error,
|
||||
fail_if_not_identical, fail_if_not_contained, has_vanilla_emcc,
|
||||
has_vanilla_llvm, minify_check, num_failures, options, tests,
|
||||
requested, warnings, has_shell_timeout
|
||||
)
|
||||
|
||||
import scripts.test.asm2wasm as asm2wasm
|
||||
import scripts.test.s2wasm as s2wasm
|
||||
import scripts.test.wasm2asm as wasm2asm
|
||||
|
||||
if options.interpreter:
|
||||
print '[ using wasm interpreter at "%s" ]' % options.interpreter
|
||||
assert os.path.exists(options.interpreter), 'interpreter not found'
|
||||
|
||||
# tests
|
||||
|
||||
def run_help_tests():
|
||||
print '[ checking --help is useful... ]\n'
|
||||
|
||||
not_executable_suffix = ['.txt', '.js', '.ilk', '.pdb', '.dll']
|
||||
executables = sorted(filter(lambda x: not any(x.endswith(s) for s in
|
||||
not_executable_suffix) and os.path.isfile(x),
|
||||
os.listdir(options.binaryen_bin)))
|
||||
for e in executables:
|
||||
print '.. %s --help' % e
|
||||
out, err = subprocess.Popen([os.path.join(options.binaryen_bin, e), '--help'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE).communicate()
|
||||
assert len(out) == 0, 'Expected no stdout, got:\n%s' % out
|
||||
assert e.replace('.exe', '') in err, 'Expected help to contain program name, got:\n%s' % err
|
||||
assert len(err.split('\n')) > 8, 'Expected some help, got:\n%s' % err
|
||||
|
||||
def run_wasm_opt_tests():
|
||||
print '\n[ checking wasm-opt -o notation... ]\n'
|
||||
|
||||
wast = os.path.join(options.binaryen_test, 'hello_world.wast')
|
||||
delete_from_orbit('a.wast')
|
||||
cmd = WASM_OPT + [wast, '-o', 'a.wast', '-S']
|
||||
run_command(cmd)
|
||||
fail_if_not_identical(open('a.wast').read(), open(wast).read())
|
||||
|
||||
print '\n[ checking wasm-opt binary reading/writing... ]\n'
|
||||
|
||||
shutil.copyfile(os.path.join(options.binaryen_test, 'hello_world.wast'), 'a.wast')
|
||||
delete_from_orbit('a.wasm')
|
||||
delete_from_orbit('b.wast')
|
||||
run_command(WASM_OPT + ['a.wast', '-o', 'a.wasm'])
|
||||
assert open('a.wasm', 'rb').read()[0] == '\0', 'we emit binary by default'
|
||||
run_command(WASM_OPT + ['a.wasm', '-o', 'b.wast', '-S'])
|
||||
assert open('b.wast', 'rb').read()[0] != '\0', 'we emit text with -S'
|
||||
|
||||
print '\n[ checking wasm-opt passes... ]\n'
|
||||
|
||||
for t in sorted(os.listdir(os.path.join(options.binaryen_test, 'passes'))):
|
||||
if t.endswith(('.wast', '.wasm')):
|
||||
print '..', t
|
||||
binary = '.wasm' in t
|
||||
passname = os.path.basename(t).replace('.wast', '').replace('.wasm', '')
|
||||
opts = [('--' + p if not p.startswith('O') else '-' + p) for p in passname.split('_')]
|
||||
t = os.path.join(options.binaryen_test, 'passes', t)
|
||||
actual = ''
|
||||
for module, asserts in split_wast(t):
|
||||
assert len(asserts) == 0
|
||||
with open('split.wast', 'w') as o: o.write(module)
|
||||
cmd = WASM_OPT + opts + ['split.wast', '--print']
|
||||
curr = run_command(cmd)
|
||||
actual += curr
|
||||
# also check debug mode output is valid
|
||||
debugged = run_command(cmd + ['--debug'], stderr=subprocess.PIPE)
|
||||
fail_if_not_contained(actual, debugged)
|
||||
# also check pass-debug mode
|
||||
old_pass_debug = os.environ.get('BINARYEN_PASS_DEBUG')
|
||||
try:
|
||||
os.environ['BINARYEN_PASS_DEBUG'] = '1'
|
||||
pass_debug = run_command(cmd)
|
||||
fail_if_not_identical(curr, pass_debug)
|
||||
finally:
|
||||
if old_pass_debug is not None:
|
||||
os.environ['BINARYEN_PASS_DEBUG'] = old_pass_debug
|
||||
else:
|
||||
if 'BINARYEN_PASS_DEBUG' in os.environ:
|
||||
del os.environ['BINARYEN_PASS_DEBUG']
|
||||
|
||||
fail_if_not_identical(actual, open(os.path.join('test', 'passes', passname + ('.bin' if binary else '') + '.txt'), 'rb').read())
|
||||
|
||||
if 'emit-js-wrapper' in t:
|
||||
with open('a.js') as actual:
|
||||
with open(t + '.js') as expected:
|
||||
fail_if_not_identical(actual.read(), expected.read())
|
||||
if 'emit-spec-wrapper' in t:
|
||||
with open('a.wat') as actual:
|
||||
with open(t + '.wat') as expected:
|
||||
fail_if_not_identical(actual.read(), expected.read())
|
||||
|
||||
print '\n[ checking wasm-opt parsing & printing... ]\n'
|
||||
|
||||
for t in sorted(os.listdir(os.path.join(options.binaryen_test, 'print'))):
|
||||
if t.endswith('.wast'):
|
||||
print '..', t
|
||||
wasm = os.path.basename(t).replace('.wast', '')
|
||||
cmd = WASM_OPT + [os.path.join(options.binaryen_test, 'print', t), '--print']
|
||||
print ' ', ' '.join(cmd)
|
||||
actual, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
|
||||
fail_if_not_identical(actual, open(os.path.join(options.binaryen_test, 'print', wasm + '.txt')).read())
|
||||
cmd = WASM_OPT + [os.path.join(options.binaryen_test, 'print', t), '--print-minified']
|
||||
print ' ', ' '.join(cmd)
|
||||
actual, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
|
||||
fail_if_not_identical(actual.strip(), open(os.path.join(options.binaryen_test, 'print', wasm + '.minified.txt')).read().strip())
|
||||
|
||||
print '\n[ checking wasm-opt testcases... ]\n'
|
||||
|
||||
for t in tests:
|
||||
if t.endswith('.wast') and not t.startswith('spec'):
|
||||
print '..', t
|
||||
t = os.path.join(options.binaryen_test, t)
|
||||
f = t + '.from-wast'
|
||||
cmd = WASM_OPT + [t, '--print']
|
||||
actual = run_command(cmd)
|
||||
actual = actual.replace('printing before:\n', '')
|
||||
|
||||
expected = open(f, 'rb').read()
|
||||
|
||||
if actual != expected:
|
||||
fail(actual, expected)
|
||||
|
||||
binary_format_check(t, wasm_as_args=['-g']) # test with debuginfo
|
||||
binary_format_check(t, wasm_as_args=[], binary_suffix='.fromBinary.noDebugInfo') # test without debuginfo
|
||||
|
||||
minify_check(t)
|
||||
|
||||
def run_wasm_dis_tests():
|
||||
print '\n[ checking wasm-dis on provided binaries... ]\n'
|
||||
|
||||
for t in tests:
|
||||
if t.endswith('.wasm') and not t.startswith('spec'):
|
||||
print '..', t
|
||||
t = os.path.join(options.binaryen_test, t)
|
||||
cmd = WASM_DIS + [t]
|
||||
if os.path.isfile(t + '.map'): cmd += ['--source-map', t + '.map']
|
||||
|
||||
actual = run_command(cmd)
|
||||
|
||||
with open(t + '.fromBinary') as f:
|
||||
expected = f.read()
|
||||
if actual != expected:
|
||||
fail(actual, expected)
|
||||
|
||||
def run_wasm_merge_tests():
|
||||
print '\n[ checking wasm-merge... ]\n'
|
||||
|
||||
for t in os.listdir(os.path.join('test', 'merge')):
|
||||
if t.endswith(('.wast', '.wasm')):
|
||||
print '..', t
|
||||
t = os.path.join('test', 'merge', t)
|
||||
u = t + '.toMerge'
|
||||
for finalize in [0, 1]:
|
||||
for opt in [0, 1]:
|
||||
cmd = WASM_MERGE + [t, u, '-o', 'a.wast', '-S', '--verbose']
|
||||
if finalize: cmd += ['--finalize-memory-base=1024', '--finalize-table-base=8']
|
||||
if opt: cmd += ['-O']
|
||||
stdout = run_command(cmd)
|
||||
actual = open('a.wast').read()
|
||||
out = t + '.combined'
|
||||
if finalize: out += '.finalized'
|
||||
if opt: out += '.opt'
|
||||
with open(out) as f:
|
||||
fail_if_not_identical(f.read(), actual)
|
||||
with open(out + '.stdout') as f:
|
||||
fail_if_not_identical(f.read(), stdout)
|
||||
|
||||
def run_ctor_eval_tests():
|
||||
print '\n[ checking wasm-ctor-eval... ]\n'
|
||||
|
||||
for t in os.listdir(os.path.join('test', 'ctor-eval')):
|
||||
if t.endswith(('.wast', '.wasm')):
|
||||
print '..', t
|
||||
t = os.path.join('test', 'ctor-eval', t)
|
||||
ctors = open(t + '.ctors').read().strip()
|
||||
cmd = WASM_CTOR_EVAL + [t, '-o', 'a.wast', '-S', '--ctors', ctors]
|
||||
stdout = run_command(cmd)
|
||||
actual = open('a.wast').read()
|
||||
out = t + '.out'
|
||||
with open(out) as f:
|
||||
fail_if_not_identical(f.read(), actual)
|
||||
|
||||
def run_wasm_reduce_tests():
|
||||
print '\n[ checking wasm-reduce ]\n'
|
||||
|
||||
for t in os.listdir(os.path.join('test', 'reduce')):
|
||||
if t.endswith('.wast'):
|
||||
print '..', t
|
||||
t = os.path.join('test', 'reduce', t)
|
||||
# convert to wasm
|
||||
run_command(WASM_AS + [t, '-o', 'a.wasm'])
|
||||
print run_command(WASM_REDUCE + ['a.wasm', '--command=%s b.wasm --fuzz-exec' % WASM_OPT[0], '-t', 'b.wasm', '-w', 'c.wasm'])
|
||||
expected = t + '.txt'
|
||||
run_command(WASM_DIS + ['c.wasm', '-o', 'a.wast'])
|
||||
with open('a.wast') as seen:
|
||||
with open(expected) as correct:
|
||||
fail_if_not_identical(seen.read(), correct.read())
|
||||
|
||||
def run_spec_tests():
|
||||
print '\n[ checking wasm-shell spec testcases... ]\n'
|
||||
|
||||
if len(requested) == 0:
|
||||
BLACKLIST = ['memory.wast', 'binary.wast'] # FIXME we support old and new memory formats, for now, until 0xc, and so can't pass this old-style test.
|
||||
# FIXME to update the spec to 0xd, we need to implement (register "name") for import.wast
|
||||
spec_tests = [os.path.join('spec', t) for t in sorted(os.listdir(os.path.join(options.binaryen_test, 'spec'))) if t not in BLACKLIST]
|
||||
else:
|
||||
spec_tests = requested[:]
|
||||
|
||||
for t in spec_tests:
|
||||
if t.startswith('spec') and t.endswith('.wast'):
|
||||
print '..', t
|
||||
wast = os.path.join(options.binaryen_test, t)
|
||||
|
||||
# skip checks for some tests
|
||||
if os.path.basename(wast) in ['linking.wast', 'nop.wast', 'stack.wast', 'typecheck.wast', 'unwind.wast']: # FIXME
|
||||
continue
|
||||
|
||||
def run_spec_test(wast):
|
||||
cmd = WASM_SHELL + [wast]
|
||||
# we must skip the stack machine portions of spec tests or apply other extra args
|
||||
extra = {
|
||||
}
|
||||
cmd = cmd + (extra.get(os.path.basename(wast)) or [])
|
||||
return run_command(cmd, stderr=subprocess.PIPE)
|
||||
|
||||
def run_opt_test(wast):
|
||||
# check optimization validation
|
||||
cmd = WASM_OPT + [wast, '-O']
|
||||
run_command(cmd)
|
||||
|
||||
def check_expected(actual, expected):
|
||||
if expected and os.path.exists(expected):
|
||||
expected = open(expected).read()
|
||||
# fix it up, our pretty (i32.const 83) must become compared to a homely 83 : i32
|
||||
def fix(x):
|
||||
x = x.strip()
|
||||
if not x: return x
|
||||
v, t = x.split(' : ')
|
||||
if v.endswith('.'): v = v[:-1] # remove trailing '.'
|
||||
return '(' + t + '.const ' + v + ')'
|
||||
expected = '\n'.join(map(fix, expected.split('\n')))
|
||||
print ' (using expected output)'
|
||||
actual = actual.strip()
|
||||
expected = expected.strip()
|
||||
if actual != expected:
|
||||
fail(actual, expected)
|
||||
|
||||
expected = os.path.join(options.binaryen_test, 'spec', 'expected-output', os.path.basename(wast) + '.log')
|
||||
|
||||
# some spec tests should fail (actual process failure, not just assert_invalid)
|
||||
try:
|
||||
actual = run_spec_test(wast)
|
||||
except Exception, e:
|
||||
if ('wasm-validator error' in str(e) or 'parse exception' in str(e)) and '.fail.' in t:
|
||||
print '<< test failed as expected >>'
|
||||
continue # don't try all the binary format stuff TODO
|
||||
else:
|
||||
fail_with_error(str(e))
|
||||
|
||||
check_expected(actual, expected)
|
||||
|
||||
# skip binary checks for tests that reuse previous modules by name, as that's a wast-only feature
|
||||
if os.path.basename(wast) in ['exports.wast']: # FIXME
|
||||
continue
|
||||
|
||||
# we must ignore some binary format splits
|
||||
splits_to_skip = {
|
||||
'func.wast': [2],
|
||||
'return.wast': [2]
|
||||
}
|
||||
|
||||
# check binary format. here we can verify execution of the final result, no need for an output verification
|
||||
split_num = 0
|
||||
if os.path.basename(wast) not in []: # avoid some tests with things still being sorted out in the spec
|
||||
actual = ''
|
||||
for module, asserts in split_wast(wast):
|
||||
skip = splits_to_skip.get(os.path.basename(wast)) or []
|
||||
if split_num in skip:
|
||||
print ' skipping split module', split_num - 1
|
||||
split_num += 1
|
||||
continue
|
||||
print ' testing split module', split_num
|
||||
split_num += 1
|
||||
with open('split.wast', 'w') as o: o.write(module + '\n' + '\n'.join(asserts))
|
||||
run_spec_test('split.wast') # before binary stuff - just check it's still ok split out
|
||||
run_opt_test('split.wast') # also that our optimizer doesn't break on it
|
||||
result_wast = binary_format_check('split.wast', verify_final_result=False)
|
||||
# add the asserts, and verify that the test still passes
|
||||
open(result_wast, 'a').write('\n' + '\n'.join(asserts))
|
||||
actual += run_spec_test(result_wast)
|
||||
# compare all the outputs to the expected output
|
||||
check_expected(actual, os.path.join(options.binaryen_test, 'spec', 'expected-output', os.path.basename(wast) + '.log'))
|
||||
|
||||
def run_binaryen_js_tests():
|
||||
if not MOZJS and not NODEJS:
|
||||
return
|
||||
|
||||
print '\n[ checking binaryen.js testcases... ]\n'
|
||||
|
||||
for s in sorted(os.listdir(os.path.join(options.binaryen_test, 'binaryen.js'))):
|
||||
if not s.endswith('.js'): continue
|
||||
print s
|
||||
f = open('a.js', 'w')
|
||||
f.write(open(os.path.join(options.binaryen_bin, 'binaryen.js')).read())
|
||||
# node test support
|
||||
f.write('\nif (typeof require === "function") var Binaryen = module.exports;\n')
|
||||
test_path = os.path.join(options.binaryen_test, 'binaryen.js', s)
|
||||
test = open(test_path).read()
|
||||
need_wasm = 'WebAssembly.' in test # some tests use wasm support in the VM
|
||||
if MOZJS:
|
||||
cmd = [MOZJS]
|
||||
elif NODEJS and not need_wasm: # TODO: check if node is new and has wasm support
|
||||
cmd = [NODEJS]
|
||||
else:
|
||||
continue # we can't run it
|
||||
cmd += ['a.js']
|
||||
f.write(test)
|
||||
f.close()
|
||||
out = run_command(cmd, stderr=subprocess.STDOUT)
|
||||
expected = open(os.path.join(options.binaryen_test, 'binaryen.js', s + '.txt')).read()
|
||||
if expected not in out:
|
||||
fail(out, expected)
|
||||
|
||||
def run_validator_tests():
|
||||
print '\n[ running validation tests... ]\n'
|
||||
# Ensure the tests validate by default
|
||||
cmd = WASM_AS + [os.path.join(options.binaryen_test, 'validator', 'invalid_export.wast')]
|
||||
run_command(cmd)
|
||||
cmd = WASM_AS + [os.path.join(options.binaryen_test, 'validator', 'invalid_import.wast')]
|
||||
run_command(cmd)
|
||||
cmd = WASM_AS + ['--validate=web', os.path.join(options.binaryen_test, 'validator', 'invalid_export.wast')]
|
||||
run_command(cmd, expected_status=1)
|
||||
cmd = WASM_AS + ['--validate=web', os.path.join(options.binaryen_test, 'validator', 'invalid_import.wast')]
|
||||
run_command(cmd, expected_status=1)
|
||||
cmd = WASM_AS + ['--validate=none', os.path.join(options.binaryen_test, 'validator', 'invalid_return.wast')]
|
||||
run_command(cmd)
|
||||
|
||||
def run_torture_tests():
|
||||
print '\n[ checking torture testcases... ]\n'
|
||||
|
||||
# torture tests are parallel anyhow, don't create multiple threads in each child
|
||||
old_cores = os.environ.get('BINARYEN_CORES')
|
||||
try:
|
||||
os.environ['BINARYEN_CORES'] = '1'
|
||||
|
||||
unexpected_result_count = 0
|
||||
|
||||
import test.waterfall.src.link_assembly_files as link_assembly_files
|
||||
s2wasm_torture_out = os.path.abspath(os.path.join(options.binaryen_test, 's2wasm-torture-out'))
|
||||
if os.path.isdir(s2wasm_torture_out):
|
||||
shutil.rmtree(s2wasm_torture_out)
|
||||
os.mkdir(s2wasm_torture_out)
|
||||
unexpected_result_count += link_assembly_files.run(
|
||||
linker=os.path.abspath(S2WASM_EXE),
|
||||
files=os.path.abspath(os.path.join(options.binaryen_test, 'torture-s', '*.s')),
|
||||
fails=os.path.abspath(os.path.join(options.binaryen_test, 's2wasm_known_gcc_test_failures.txt')),
|
||||
out=s2wasm_torture_out)
|
||||
assert os.path.isdir(s2wasm_torture_out), 'Expected output directory %s' % s2wasm_torture_out
|
||||
|
||||
import test.waterfall.src.execute_files as execute_files
|
||||
unexpected_result_count += execute_files.run(
|
||||
runner=os.path.abspath(WASM_SHELL_EXE),
|
||||
files=os.path.abspath(os.path.join(s2wasm_torture_out, '*.wast')),
|
||||
fails=os.path.abspath(os.path.join(options.binaryen_test, 's2wasm_known_binaryen_shell_test_failures.txt')),
|
||||
out='',
|
||||
wasmjs='')
|
||||
|
||||
shutil.rmtree(s2wasm_torture_out)
|
||||
if unexpected_result_count:
|
||||
fail('%s failures' % unexpected_result_count, '0 failures')
|
||||
|
||||
finally:
|
||||
if old_cores:
|
||||
os.environ['BINARYEN_CORES'] = old_cores
|
||||
else:
|
||||
del os.environ['BINARYEN_CORES']
|
||||
|
||||
def run_vanilla_tests():
|
||||
print '\n[ checking emcc WASM_BACKEND testcases...]\n'
|
||||
|
||||
try:
|
||||
if has_vanilla_llvm:
|
||||
os.environ['LLVM'] = BIN_DIR # use the vanilla LLVM
|
||||
else:
|
||||
# if we did not set vanilla llvm, then we must set this env var to make emcc use the wasm backend.
|
||||
# (if we are using vanilla llvm, things should just work)
|
||||
print '(not using vanilla llvm, so setting env var to tell emcc to use wasm backend)'
|
||||
os.environ['EMCC_WASM_BACKEND'] = '1'
|
||||
VANILLA_EMCC = os.path.join(options.binaryen_test, 'emscripten', 'emcc')
|
||||
# run emcc to make sure it sets itself up properly, if it was never run before
|
||||
command = [VANILLA_EMCC, '-v']
|
||||
print '____' + ' '.join(command)
|
||||
subprocess.check_call(command)
|
||||
|
||||
for c in sorted(os.listdir(os.path.join(options.binaryen_test, 'wasm_backend'))):
|
||||
if not c.endswith('cpp'): continue
|
||||
print '..', c
|
||||
base = c.replace('.cpp', '').replace('.c', '')
|
||||
expected = open(os.path.join(options.binaryen_test, 'wasm_backend', base + '.txt')).read()
|
||||
for opts in [[], ['-O1'], ['-O2']]:
|
||||
only = [] if opts != ['-O1'] or '_only' not in base else ['-s', 'ONLY_MY_CODE=1'] # only my code is a hack we used early in wasm backend dev, which somehow worked, but only with -O1
|
||||
command = [VANILLA_EMCC, '-o', 'a.wasm.js', os.path.join(options.binaryen_test, 'wasm_backend', c)] + opts + only
|
||||
print '....' + ' '.join(command)
|
||||
if os.path.exists('a.wasm.js'): os.unlink('a.wasm.js')
|
||||
subprocess.check_call(command)
|
||||
if NODEJS:
|
||||
print ' (check in node)'
|
||||
cmd = [NODEJS, 'a.wasm.js']
|
||||
out = run_command(cmd)
|
||||
if out.strip() != expected.strip():
|
||||
fail(out, expected)
|
||||
finally:
|
||||
if has_vanilla_llvm:
|
||||
del os.environ['LLVM']
|
||||
else:
|
||||
del os.environ['EMCC_WASM_BACKEND']
|
||||
|
||||
def run_gcc_torture_tests():
|
||||
print '\n[ checking native gcc testcases...]\n'
|
||||
if not NATIVECC or not NATIVEXX:
|
||||
fail_with_error('Native compiler (e.g. gcc/g++) was not found in PATH!')
|
||||
else:
|
||||
for t in sorted(os.listdir(os.path.join(options.binaryen_test, 'example'))):
|
||||
output_file = os.path.join(options.binaryen_bin, 'example')
|
||||
cmd = ['-I' + os.path.join(options.binaryen_root, 'src'), '-g', '-lasmjs', '-lsupport', '-L' + os.path.join(options.binaryen_bin, '..', 'lib'), '-pthread', '-o', output_file]
|
||||
if t.endswith('.txt'):
|
||||
# check if there is a trace in the file, if so, we should build it
|
||||
out = subprocess.Popen([os.path.join('scripts', 'clean_c_api_trace.py'), os.path.join(options.binaryen_test, 'example', t)], stdout=subprocess.PIPE).communicate()[0]
|
||||
if len(out) == 0:
|
||||
print ' (no trace in ', t, ')'
|
||||
continue
|
||||
print ' (will check trace in ', t, ')'
|
||||
src = 'trace.cpp'
|
||||
with open(src, 'w') as o: o.write(out)
|
||||
expected = os.path.join(options.binaryen_test, 'example', t + '.txt')
|
||||
else:
|
||||
src = os.path.join(options.binaryen_test, 'example', t)
|
||||
expected = os.path.join(options.binaryen_test, 'example', '.'.join(t.split('.')[:-1]) + '.txt')
|
||||
if src.endswith(('.c', '.cpp')):
|
||||
# build the C file separately
|
||||
extra = [NATIVECC, src, '-c', '-o', 'example.o',
|
||||
'-I' + os.path.join(options.binaryen_root, 'src'), '-g', '-L' + os.path.join(options.binaryen_bin, '..', 'lib'), '-pthread']
|
||||
print 'build: ', ' '.join(extra)
|
||||
subprocess.check_call(extra)
|
||||
# Link against the binaryen C library DSO, using an executable-relative rpath
|
||||
cmd = ['example.o', '-lbinaryen'] + cmd + ['-Wl,-rpath=$ORIGIN/../lib']
|
||||
else:
|
||||
continue
|
||||
print ' ', t, src, expected
|
||||
if os.environ.get('COMPILER_FLAGS'):
|
||||
for f in os.environ.get('COMPILER_FLAGS').split(' '):
|
||||
cmd.append(f)
|
||||
cmd = [NATIVEXX, '-std=c++11'] + cmd
|
||||
try:
|
||||
print 'link: ', ' '.join(cmd)
|
||||
subprocess.check_call(cmd)
|
||||
print 'run...', output_file
|
||||
proc = subprocess.Popen([output_file], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
actual, err = proc.communicate()
|
||||
assert proc.returncode == 0, [proc.returncode, actual, err]
|
||||
finally:
|
||||
os.remove(output_file)
|
||||
if sys.platform == 'darwin':
|
||||
# Also removes debug directory produced on Mac OS
|
||||
shutil.rmtree(output_file + '.dSYM')
|
||||
|
||||
expected = open(expected).read()
|
||||
if actual != expected:
|
||||
fail(actual, expected)
|
||||
|
||||
def run_emscripten_tests():
|
||||
print '\n[ checking wasm.js methods... ]\n'
|
||||
|
||||
for method_init in ['interpret-asm2wasm', 'interpret-s-expr', 'asmjs', 'interpret-binary', 'asmjs,interpret-binary', 'interpret-binary,asmjs']:
|
||||
# check success and failure for simple modes, only success for combined/fallback ones
|
||||
for success in [1, 0] if ',' not in method_init else [1]:
|
||||
method = method_init
|
||||
command = [EMCC, '-o', 'a.wasm.js', '-s', 'BINARYEN=1', os.path.join(options.binaryen_test, 'hello_world.c') ]
|
||||
command += ['-s', 'BINARYEN_METHOD="' + method + '"']
|
||||
print method, ' : ', ' '.join(command), ' => ', success
|
||||
subprocess.check_call(command)
|
||||
|
||||
see_polyfill = 'var WasmJS = ' in open('a.wasm.js').read()
|
||||
|
||||
if method and 'interpret' not in method:
|
||||
assert not see_polyfill, 'verify polyfill was not added - we specified a method, and it does not need it'
|
||||
else:
|
||||
assert see_polyfill, 'we need the polyfill'
|
||||
|
||||
def break_cashew():
|
||||
asm = open('a.wasm.asm.js').read()
|
||||
asm = asm.replace('"almost asm"', '"use asm"; var not_in_asm = [].length + (true || { x: 5 }.x);')
|
||||
asm = asm.replace("'almost asm'", '"use asm"; var not_in_asm = [].length + (true || { x: 5 }.x);')
|
||||
with open('a.wasm.asm.js', 'w') as o: o.write(asm)
|
||||
if method.startswith('interpret-asm2wasm'):
|
||||
delete_from_orbit('a.wasm.wast') # we should not need the .wast
|
||||
if not success:
|
||||
break_cashew() # we need cashew
|
||||
elif method.startswith('interpret-s-expr'):
|
||||
delete_from_orbit('a.wasm.asm.js') # we should not need the .asm.js
|
||||
if not success:
|
||||
delete_from_orbit('a.wasm.wast')
|
||||
elif method.startswith('asmjs'):
|
||||
delete_from_orbit('a.wasm.wast') # we should not need the .wast
|
||||
break_cashew() # we don't use cashew, so ok to break it
|
||||
if not success:
|
||||
delete_from_orbit('a.wasm.js')
|
||||
elif method.startswith('interpret-binary'):
|
||||
delete_from_orbit('a.wasm.wast') # we should not need the .wast
|
||||
delete_from_orbit('a.wasm.asm.js') # we should not need the .asm.js
|
||||
if not success:
|
||||
delete_from_orbit('a.wasm.wasm')
|
||||
else:
|
||||
1/0
|
||||
if NODEJS:
|
||||
proc = subprocess.Popen([NODEJS, 'a.wasm.js'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
out, err = proc.communicate()
|
||||
if success:
|
||||
assert proc.returncode == 0, err
|
||||
assert 'hello, world!' in out, out
|
||||
else:
|
||||
assert proc.returncode != 0, err
|
||||
assert 'hello, world!' not in out, out
|
||||
|
||||
# Run all the tests
|
||||
def main():
|
||||
run_help_tests()
|
||||
run_wasm_opt_tests()
|
||||
asm2wasm.test_asm2wasm()
|
||||
asm2wasm.test_asm2wasm_binary()
|
||||
run_wasm_dis_tests()
|
||||
run_wasm_merge_tests()
|
||||
run_ctor_eval_tests()
|
||||
if has_shell_timeout():
|
||||
run_wasm_reduce_tests()
|
||||
|
||||
run_spec_tests()
|
||||
run_binaryen_js_tests()
|
||||
s2wasm.test_s2wasm()
|
||||
s2wasm.test_linker()
|
||||
wasm2asm.test_wasm2asm()
|
||||
run_validator_tests()
|
||||
if options.torture and options.test_waterfall:
|
||||
run_torture_tests()
|
||||
if has_vanilla_emcc and has_vanilla_llvm and 0:
|
||||
run_vanilla_tests()
|
||||
print '\n[ checking example testcases... ]\n'
|
||||
if options.run_gcc_tests:
|
||||
run_gcc_torture_tests()
|
||||
if EMCC:
|
||||
run_emscripten_tests()
|
||||
|
||||
# Check/display the results
|
||||
if num_failures == 0:
|
||||
print '\n[ success! ]'
|
||||
|
||||
if warnings:
|
||||
print '\n' + '\n'.join(warnings)
|
||||
|
||||
if num_failures > 0:
|
||||
print '\n[ ' + str(num_failures) + ' failures! ]'
|
||||
|
||||
sys.exit(num_failures)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
@ -1,240 +0,0 @@
|
||||
binaryen.js API
|
||||
===============
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
binaryen.js is a port of Binaryen to the Web, allowing you to generate WebAssembly using a JavaScript API. To get a feel for the API, see the "hello world" test at `test/binaryen.js/hello-world.js`, and other tests in that directory for more examples.
|
||||
|
||||
The API is documented in the rest of this document.
|
||||
|
||||
Types
|
||||
-----
|
||||
|
||||
* `Binaryen.none`: The none type.
|
||||
* `Binaryen.i32`: The i32 type.
|
||||
* `Binaryen.i64`: The i64 type.
|
||||
* `Binaryen.f32`: The f32 type.
|
||||
* `Binaryen.f64`: The f64 type.
|
||||
|
||||
Modules
|
||||
-------
|
||||
|
||||
* `Binaryen.Module()`: Constructor for a Binaryen WebAssembly module. You need to create one of these first.
|
||||
|
||||
`Module` instances have the following properties.
|
||||
|
||||
Module property operations:
|
||||
|
||||
* `addFunctionType(name, resultType, paramTypes)`: Add a function type to the module, with a specified name, result type, and param types.
|
||||
* `addFunction(name, functionType, varTypes, body)`: Add a function, with a name, a function type, an array of local types, and a body.
|
||||
* `addImport(internalName, externalModuleName, externalBaseName, functionType)`: Add an import, with an internal name (used by other things in the module), an external module name (the module from which we import), an external base name (the name we import from that module), and a function type (for function imports).
|
||||
* `addExport(internalName, externalName)`: Add an export, with an internal name and an external name (the name the outside sees it exported as).
|
||||
* `setFunctionTable(funcs)`: Sets the function table to a array of functions.
|
||||
* `setMemory(initial, maximum, exportName, segments)`: Sets the memory to having an initial size, maximum size, optional export name, and array of data segments.
|
||||
* `setStart(start)`: Sets the start function (called when the module is instantiated) to a specified function.
|
||||
|
||||
Module operations:
|
||||
|
||||
* `emitBinary()`: Returns a binary for the module, which you can then compile and run in the browser.
|
||||
* `emitText()`: Returns a text representation of the module, in s-expression format.
|
||||
* `validate()`: Validates the module, checking it for correctness.
|
||||
* `optimize()`: Runs the standard optimization passes on the module.
|
||||
* `runPasses(passes)`: Runs the specified passes on the module.
|
||||
* `autoDrop()`: Automatically inserts `drop` operations. This lets you not worry about dropping when creating your code.
|
||||
* `interpret()`: Run the module in the Binaryen interpreter (creates the module, and calls the start method). Useful for debugging.
|
||||
* `dispose()`: Cleans up the module. If the Binaryen object can be garbage-collected anyhow, you don't need to do this, but if it stays around - e.g. if you create multiple `Module`s over time - then you should call this once a `Module` is no longer needed. (As binaryen.js uses compiled C++ code, we can't just rely on normal garbage collection to clean things up internally.)
|
||||
|
||||
Type-prefixed expressions:
|
||||
|
||||
* `i32`:
|
||||
* `i32.load(offset, align, ptr)`: Create a 32-bit load, with an offset, alignment, and pointer.
|
||||
* `i32.load8_s(offset, align, ptr)`: Create an 8-bit signed load, with an offset, alignment, and pointer.
|
||||
* `i32.load8_u(offset, align, ptr)`: Create an 8-bit unsigned load, with an offset, alignment, and pointer.
|
||||
* `i32.load16_s(offset, align, ptr)`: Create an 16-bit signed load, with an offset, alignment, and pointer.
|
||||
* `i32.load16_u(offset, align, ptr)`: Create an 16-bit unsigned load, with an offset, alignment, and pointer.
|
||||
* `i32.store(offset, align, ptr, value)`: Create a 32-bit store, with an offset, alignment, pointer, and value.
|
||||
* `i32.store8(offset, align, ptr, value)`: Create an 8-bit store, with an offset, alignment, pointer, and value.
|
||||
* `i32.store16(offset, align, ptr, value)`: Create a 16-bit store, with an offset, alignment, pointer, and value.
|
||||
* `i32.const(value)`: Create an `i32` constant of a specified value.
|
||||
* `i32.clz(value)`: Create a count-leading-zeros of a specified value.
|
||||
* `i32.ctz(value)`: Create a count-trailing-zeros of a specified value.
|
||||
* `i32.popcnt(value)`: Create a population-count (number of bits set) of a specified value.
|
||||
* `i32.eqz(value)`: Create an equal-zero of a specified value.
|
||||
* `i32.trunc_s.f32(value)`: Create a signed truncate of an `f32` to an `i32`.
|
||||
* `i32.trunc_s.f64(value)`: Create a signed truncate of an `f64` to an `i32`.
|
||||
* `i32.trunc_u.f32(value)`: Create an unsigned truncate of an `f32` to an `i32`.
|
||||
* `i32.trunc_u.f64(value)`: Create an unsigned truncate of an `f64` to an `i32`.
|
||||
* `i32.reinterpret(value)`: Create a reinterpret of an `f32` to an `i32`.
|
||||
* `i32.wrap(value)`: Create a wrap of an `i64` to an `i32`.
|
||||
* `i32.add(left, right)`: Create an add of two `i32`s.
|
||||
* `i32.sub(left, right)`: Create a subtract of two `i32`s.
|
||||
* `i32.mul(left, right)`: Create a multiply of two `i32`s.
|
||||
* `i32.div_s(left, right)`: Create a signed divide of two `i32`s.
|
||||
* `i32.div_u(left, right)`: Create an unsigned divide of two `i32`s.
|
||||
* `i32.rem_s(left, right)`: Create a signed remainder of two `i32`s.
|
||||
* `i32.rem_u(left, right)`: Create an unsigned remainder of two `i32`s.
|
||||
* `i32.and(left, right)`: Create an and of two `i32`s.
|
||||
* `i32.or(left, right)`: Create an or of two `i32`s.
|
||||
* `i32.xor(left, right)`: Create a xor of two `i32`s.
|
||||
* `i32.shl(left, right)`: Create a shift left on two `i32`s.
|
||||
* `i32.shr_u(left, right)`: Create an unsigned (logical) shift right on two `i32`s.
|
||||
* `i32.shr_s(left, right)`: Create a signed (arithmetic) shift right on two `i32`s.
|
||||
* `i32.rotl(left, right)`: Create a rotate-left on two `i32`s.
|
||||
* `i32.rotr(left, right)`: Create a rotate-right on two `i32`s.
|
||||
* `i32.eq(left, right)`: Create an equals on two `i32`s.
|
||||
* `i32.ne(left, right)`: Create a not-equals on two `i32`s.
|
||||
* `i32.lt_s(left, right)`: Create a signed less-than on two `i32`s.
|
||||
* `i32.lt_u(left, right)`: Create an unsigned less-than on two `i32`s.
|
||||
* `i32.le_s(left, right)`: Create a signed less-or-equal on two `i32`s.
|
||||
* `i32.le_u(left, right)`: Create an unsigned less-or-equal on two `i32`s.
|
||||
* `i32.gt_s(left, right)`: Create a signed greater-than on two `i32`s.
|
||||
* `i32.gt_u(left, right)`: Create an unsigned greater-than on two `i32`s.
|
||||
* `i32.ge_s(left, right)`: Create a signed greater-or-equal on two `i32`s.
|
||||
* `i32.ge_u(left, right)`: Create an unsigned greater-or-equal on two `i32`s.
|
||||
* `i64`:
|
||||
* `i64.load(offset, align, ptr)`: Create a 32-bit load, with an offset, alignment, and pointer.
|
||||
* `i64.load8_s(offset, align, ptr)`: Create an 8-bit signed load, with an offset, alignment, and pointer.
|
||||
* `i64.load8_u(offset, align, ptr)`: Create an 8-bit unsigned load, with an offset, alignment, and pointer.
|
||||
* `i64.load16_s(offset, align, ptr)`: Create an 16-bit signed load, with an offset, alignment, and pointer.
|
||||
* `i64.load16_u(offset, align, ptr)`: Create an 16-bit unsigned load, with an offset, alignment, and pointer.
|
||||
* `i64.load32_s(offset, align, ptr)`: Create a 32-bit signed load, with an offset, alignment, and pointer.
|
||||
* `i64.load32_u(offset, align, ptr)`: Create a 32-bit unsigned load, with an offset, alignment, and pointer.
|
||||
* `i64.store(offset, align, ptr, value)`: Create a 32-bit store, with an offset, alignment, pointer, and value.
|
||||
* `i64.store8(offset, align, ptr, value)`: Create an 8-bit store, with an offset, alignment, pointer, and value.
|
||||
* `i64.store16(offset, align, ptr, value)`: Create a 16-bit store, with an offset, alignment, pointer, and value.
|
||||
* `i64.store32(offset, align, ptr, value)`: Create a 32-bit store, with an offset, alignment, pointer, and value.
|
||||
* `i64.const(low, high)`: Create an `i64` constant of a specified value, provided as low and high 32 bits.
|
||||
* `i64.clz(value)`: Create a count-leading-zeros of a specified value.
|
||||
* `i64.ctz(value)`: Create a count-trailing-zeros of a specified value.
|
||||
* `i64.popcnt(value)`: Create a population-count (number of bits set) of a specified value.
|
||||
* `i64.eqz(value)`: Create an equal-zero of a specified value.
|
||||
* `i64.trunc_s.f32(value)`: Create a signed truncate of an `f32` to an `i64`.
|
||||
* `i64.trunc_s.f64(value)`: Create a signed truncate of an `f64` to an `i64`.
|
||||
* `i64.trunc_u.f32(value)`: Create an unsigned truncate of an `f32` to an `i64`.
|
||||
* `i64.trunc_u.f64(value)`: Create an unsigned truncate of an `f64` to an `i64`.
|
||||
* `i64.reinterpret(value)`: Create a reinterpret of an `f64` to an `i64`.
|
||||
* `i64.extend_s(value)`: Create a signed extend of an `i32` to an `i64`.
|
||||
* `i64.extend_u(value)`: Create an unsigned extend of an `i32` to an `i64`.
|
||||
* `i64.add(left, right)`: Create an add of two `i64`s.
|
||||
* `i64.sub(left, right)`: Create a subtract of two `i64`s.
|
||||
* `i64.mul(left, right)`: Create a multiply of two `i64`s.
|
||||
* `i64.div_s(left, right)`: Create a signed divide of two `i64`s.
|
||||
* `i64.div_u(left, right)`: Create an unsigned divide of two `i64`s.
|
||||
* `i64.rem_s(left, right)`: Create a signed remainder of two `i64`s.
|
||||
* `i64.rem_u(left, right)`: Create an unsigned remainder of two `i64`s.
|
||||
* `i64.and(left, right)`: Create an and of two `i64`s.
|
||||
* `i64.or(left, right)`: Create an or of two `i64`s.
|
||||
* `i64.xor(left, right)`: Create a xor of two `i64`s.
|
||||
* `i64.shl(left, right)`: Create a shift left on two `i64`s.
|
||||
* `i64.shr_u(left, right)`: Create an unsigned (logical) shift right on two `i64`s.
|
||||
* `i64.shr_s(left, right)`: Create a signed (arithmetic) shift right on two `i64`s.
|
||||
* `i64.rotl(left, right)`: Create a rotate-left on two `i64`s.
|
||||
* `i64.rotr(left, right)`: Create a rotate-right on two `i64`s.
|
||||
* `i64.eq(left, right)`: Create an equals on two `i64`s.
|
||||
* `i64.ne(left, right)`: Create a not-equals on two `i64`s.
|
||||
* `i64.lt_s(left, right)`: Create a signed less-than on two `i64`s.
|
||||
* `i64.lt_u(left, right)`: Create an unsigned less-than on two `i64`s.
|
||||
* `i64.le_s(left, right)`: Create a signed less-or-equal on two `i64`s.
|
||||
* `i64.le_u(left, right)`: Create an unsigned less-or-equal on two `i64`s.
|
||||
* `i64.gt_s(left, right)`: Create a signed greater-than on two `i64`s.
|
||||
* `i64.gt_u(left, right)`: Create an unsigned greater-than on two `i64`s.
|
||||
* `i64.ge_s(left, right)`: Create a signed greater-or-equal on two `i64`s.
|
||||
* `i64.ge_u(left, right)`: Create an unsigned greater-or-equal on two `i64`s.
|
||||
* `f32`:
|
||||
* `f32.load(offset, align, ptr)`: Create an `f32` load, with an offset, alignment, and pointer.
|
||||
* `f32.store(offset, align, ptr, value)`: Create an `f32` store, with an offset, alignment, pointer, and value.
|
||||
* `f32.const(value)`: Create an `f32` constant of a specified value.
|
||||
* `f32.const_bits(value)`: Create an `f32` constant of a specified value, reinterpreting the bits (this is useful for creating weird NaNs).
|
||||
* `f32.neg(value)`: Create a negation of an `f32`.
|
||||
* `f32.abs(value)`: Create a absolute value of an `f32`.
|
||||
* `f32.ceil(value)`: Create a ceil of an `f32`.
|
||||
* `f32.floor(value)`: Create a floor of an `f32`.
|
||||
* `f32.trunc(value)`: Create a truncate of an `f32`.
|
||||
* `f32.nearest(value)`: Create a nearest-value of an `f32`.
|
||||
* `f32.sqrt(value)`: Create a square-root of an `f32`.
|
||||
* `f32.reinterpret(value)`: Create a reinterpret of an `i32` to an `f32`.
|
||||
* `f32.convert_s.i32(value)`: Create a signed conversion of an `i32` to an `f32`.
|
||||
* `f32.convert_s.i64(value)`: Create a signed conversion of an `i64` to an `f32`.
|
||||
* `f32.convert_u.i32(value)`: Create an unsigned conversion of an `i32` to an `f32`.
|
||||
* `f32.convert_u.i64(value)`: Create an unsigned conversion of an `i64` to an `f32`.
|
||||
* `f32.demote(value)`: Create a demotion of an `f64` to an `f32`.
|
||||
* `f32.add(left, right)`: Create an add of two `f32`s.
|
||||
* `f32.sub(left, right)`: Create a subtract of two `f32`s.
|
||||
* `f32.mul(left, right)`: Create a multiply of two `f32`s.
|
||||
* `f32.div(left, right)`: Create a divide of two `f32`s.
|
||||
* `f32.copysign(left, right)`: Create a copysign (take magnitude of left, sign of right) of two `f32`s.
|
||||
* `f32.min(left, right)`: Create a minimum on two `f32`s.
|
||||
* `f32.max(left, right)`: Create a maximum on two `f32`s.
|
||||
* `f32.eq(left, right)`: Create an equals on two `f32`s.
|
||||
* `f32.ne(left, right)`: Create a not-equals on two `f32`s.
|
||||
* `f32.lt(left, right)`: Create a less-than on two `f32`s.
|
||||
* `f32.le(left, right)`: Create a less-or-equals on two `f32`s.
|
||||
* `f32.gt(left, right)`: Create a greater-than on two `f32`s.
|
||||
* `f32.ge(left, right)`: Create a greater-or-equals on two `f32`s.
|
||||
* `f64`:
|
||||
* `f64.load(offset, align, ptr)`: Create an `f64` load, with an offset, alignment, and pointer.
|
||||
* `f64.store(offset, align, ptr, value)`: Create an `f64` store, with an offset, alignment, pointer, and value.
|
||||
* `f64.const(value)`: Create an `f64` constant of a specified value.
|
||||
* `f64.const_bits(low, high)`: Create an `f64` constant of a specified value, reinterpreting the low and high 32 bits (this is useful for creating weird NaNs).
|
||||
* `f64.neg(value)`: Create a negation of an `f64`.
|
||||
* `f64.abs(value)`: Create a absolute value of an `f64`.
|
||||
* `f64.ceil(value)`: Create a ceil of an `f64`.
|
||||
* `f64.floor(value)`: Create a floor of an `f64`.
|
||||
* `f64.trunc(value)`: Create a truncate of an `f64`.
|
||||
* `f64.nearest(value)`: Create a nearest-value of an `f64`.
|
||||
* `f64.sqrt(value)`: Create a square-root of an `f64`.
|
||||
* `f64.reinterpret(value)`: Create a reinterpret of an `i32` to an `f64`.
|
||||
* `f64.convert_s.i32(value)`: Create a signed conversion of an `i32` to an `f64`.
|
||||
* `f64.convert_s.i64(value)`: Create a signed conversion of an `i64` to an `f64`.
|
||||
* `f64.convert_u.i32(value)`: Create an unsigned conversion of an `i32` to an `f64`.
|
||||
* `f64.convert_u.i64(value)`: Create an unsigned conversion of an `i64` to an `f64`.
|
||||
* `f64.promote(value)`: Create a promotion of an `f32` to an `f64`.
|
||||
* `f64.add(left, right)`: Create an add of two `f64`s.
|
||||
* `f64.sub(left, right)`: Create a subtract of two `f64`s.
|
||||
* `f64.mul(left, right)`: Create a multiply of two `f64`s.
|
||||
* `f64.div(left, right)`: Create a divide of two `f64`s.
|
||||
* `f64.copysign(left, right)`: Create a copysign (take magnitude of left, sign of right) of two `f64`s.
|
||||
* `f64.min(left, right)`: Create a minimum on two `f64`s.
|
||||
* `f64.max(left, right)`: Create a maximum on two `f64`s.
|
||||
* `f64.eq(left, right)`: Create an equals on two `f64`s.
|
||||
* `f64.ne(left, right)`: Create a not-equals on two `f64`s.
|
||||
* `f64.lt(left, right)`: Create a less-than on two `f64`s.
|
||||
* `f64.le(left, right)`: Create a less-or-equals on two `f64`s.
|
||||
* `f64.gt(left, right)`: Create a greater-than on two `f64`s.
|
||||
* `f64.ge(left, right)`: Create a greater-or-equals on two `f64`s.
|
||||
|
||||
Unprefixed expressions:
|
||||
|
||||
* `block(label, children)`: Create a block (a list of instructions), with an optional label, and list of children.
|
||||
* `if(condition, ifTrue, ifFalse`: Create an if or if-else, with a condition, code to execute if true, and optional code to execute if false.
|
||||
* `loop(label, body)`: Create a loop, with an optional label, and body.
|
||||
* `break(label, condition, value)`: Create a break, to a label, and with an optional condition, and optional value.
|
||||
* `switch(labels, defaultLabel, condition, value)`: Create a switch (aka br_table), with a list of labels, a default label, a condition, and an optional value.
|
||||
* `call(name, operands, type)`: Create a call, to a function name, with operands, and having a specific return type (note that we must specify the return type here as we may not have created the function being called yet, and we may want to optimize this function before we do so, so the API requires that each function be independent of the others, which means that we can't depend on the definition of another function).
|
||||
* `callImport(name, operands, type)`: Similar to `call`, but calls an imported function.
|
||||
* `callIndirect(target, operands, type)`: Similar to `call`, but calls indirectly, i.e., via a function pointer, so an expression replaces the name as the called value.
|
||||
* `getLocal(index, type)`: Create a get_local, for the local at the specified index, and having a specific type (the type is required for the same reasons as in `call`).
|
||||
* `setLocal(index, value)`: Create a set_local, for the local at the specified index, and setting the specified value.
|
||||
* `teeLocal(index, value)`: Create a tee_local, for the local at the specified index, and setting the specified value.
|
||||
* `select(condition, ifTrue, ifFalse)`: Create a select operation, executing the condition, ifTrue, and ifFalse, and returning one of them based on the condition.
|
||||
* `drop(value)`: Create a drop of a value.
|
||||
* `return(value)`: Create a return with an optional value.
|
||||
* `nop()`: Create a nop (no-operation).
|
||||
* `unreachable()`: Create an unreachable (trap).
|
||||
|
||||
(now done with `Module`s, returning to the `Binaryen` object)
|
||||
|
||||
* `Binaryen.readBinary(data)`: Reads a binary wasm module and returns a Binaryen `Module` object created from it.
|
||||
* `Binaryen.emitText(expression)`: Returns a text representation of an individual expression, in s-expression format. Because Binaryen expression do not depend on their function or module, you can do this at any time.
|
||||
* `setAPITracing(on)`: Sets whether API tracing is on. When on, this emits C API commands for everything you do. This can be very useful for filing bug reports.
|
||||
* `Binaryen.Relooper()`: Constructor for a Binaryen Relooper instance. This lets you provide an arbitrary CFG, and the Relooper will structure it for WebAssembly.
|
||||
|
||||
Relooper instances have the following methods:
|
||||
|
||||
* `addBlock(code)`: Adds a new block to the CFG, containing the provided code (expression) as its body.
|
||||
* `addBranch(from, to, condition, code)`: Adds a branch from a block to another block, with a condition (or nothing, if this is the default branch to take from the origin - each block must have one such branch), and optional code to execute on the branch (useful for phis).
|
||||
* `addBlockWithSwitch(code, condition)`: Adds a new block, which ends with a switch/br_table, with provided code and condition (that determines where we go in the switch).
|
||||
* `addBranchForSwitch(from, to, indexes, code)`: Adds a branch from a block ending in a switch, to another block, using an array of indexes that determine where to go, and optional code to execute on the branch.
|
||||
* `renderAndDispose(entry, labelHelper, module)`: Renders and cleans up the Relooper instance. Call this after you have created all the blocks and branches, giving it the entry block (where control flow begins), a label helper variable (an index of a local we can use, necessary for irreducible control flow), and the module. This returns an expression - normal WebAssembly code - that you can use normally anywhere.
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 27 KiB |
@ -1,17 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
# Copyright 2015 WebAssembly Community Group participants
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Empty __init__.py file: Python treats the directory as containing a package.
|
@ -1,23 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
'''
|
||||
Cleans up output from the C api, makes a runnable C file
|
||||
'''
|
||||
|
||||
import sys
|
||||
|
||||
trace = open(sys.argv[1]).read()
|
||||
|
||||
start = trace.find('// beginning a Binaryen API trace')
|
||||
if start >= 0:
|
||||
trace = trace[start:]
|
||||
|
||||
while 1:
|
||||
start = trace.find('\n(')
|
||||
if start < 0:
|
||||
break
|
||||
end = trace.find('\n)', start + 1)
|
||||
assert end > 0
|
||||
trace = trace[:start] + trace[end + 2:]
|
||||
|
||||
print trace
|
@ -1,147 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
# Copyright 2016 WebAssembly Community Group participants
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''
|
||||
This fuzzes passes, by starting with a working program, then running
|
||||
random passes on the wast, and seeing if they break something
|
||||
|
||||
Usage: Provide a base filename for a runnable program, e.g. a.out.js.
|
||||
Then we will modify a.out.wast. Note that the program must
|
||||
be built to run using that wast (BINARYEN_METHOD=interpret-s-expr)
|
||||
|
||||
Other parameters after the first are used when calling the program.
|
||||
'''
|
||||
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
PASSES = [
|
||||
"duplicate-function-elimination",
|
||||
"dce",
|
||||
"remove-unused-brs",
|
||||
"remove-unused-names",
|
||||
"optimize-instructions",
|
||||
"precompute",
|
||||
"simplify-locals",
|
||||
"vacuum",
|
||||
"coalesce-locals",
|
||||
"reorder-locals",
|
||||
"merge-blocks",
|
||||
"remove-unused-functions",
|
||||
]
|
||||
|
||||
# main
|
||||
|
||||
base = sys.argv[1]
|
||||
wast = base[:-3] + '.wast'
|
||||
print '>>> base program:', base, ', wast:', wast
|
||||
|
||||
args = sys.argv[2:]
|
||||
|
||||
|
||||
def run():
|
||||
if os.path.exists(wast):
|
||||
print '>>> running using a wast of size', os.stat(wast).st_size
|
||||
cmd = ['mozjs', base] + args
|
||||
try:
|
||||
return subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
||||
except Exception, e:
|
||||
print ">>> !!! ", e, " !!!"
|
||||
|
||||
|
||||
original_wast = None
|
||||
|
||||
try:
|
||||
# get normal output
|
||||
|
||||
normal = run()
|
||||
print '>>> normal output:\n', normal
|
||||
assert normal, 'must be output'
|
||||
|
||||
# ensure we actually use the wast
|
||||
|
||||
original_wast = wast + '.original.wast'
|
||||
shutil.move(wast, original_wast)
|
||||
assert run() != normal, 'running without the wast must fail'
|
||||
|
||||
# ensure a bad pass makes it fail
|
||||
|
||||
def apply_passes(passes):
|
||||
wasm_opt = os.path.join('bin', 'wasm-opt')
|
||||
subprocess.check_call([wasm_opt, original_wast] + passes + ['-o', wast])
|
||||
|
||||
apply_passes(['--remove-imports'])
|
||||
assert run() != normal, 'running after a breaking pass must fail'
|
||||
|
||||
# loop, looking for failures
|
||||
|
||||
def simplify(passes):
|
||||
# passes is known to fail, try to simplify down by removing
|
||||
more = True
|
||||
while more:
|
||||
more = False
|
||||
print '>>> trying to reduce:', ' '.join(passes),
|
||||
print ' [' + str(len(passes)) + ']'
|
||||
for i in range(len(passes)):
|
||||
smaller = passes[:i] + passes[i + 1:]
|
||||
print '>>>>>> try to reduce to:', ' '.join(smaller),
|
||||
print ' [' + str(len(smaller)) + ']'
|
||||
try:
|
||||
apply_passes(smaller)
|
||||
assert run() == normal
|
||||
except Exception:
|
||||
# this failed too, so it's a good reduction
|
||||
passes = smaller
|
||||
print '>>> reduction successful'
|
||||
more = True
|
||||
break
|
||||
print '>>> reduced to:', ' '.join(passes)
|
||||
|
||||
tested = set()
|
||||
|
||||
def pick_passes():
|
||||
ret = []
|
||||
while 1:
|
||||
str_ret = str(ret)
|
||||
if random.random() < 0.1 and str_ret not in tested:
|
||||
tested.add(str_ret)
|
||||
return ret
|
||||
ret.append('--' + random.choice(PASSES))
|
||||
|
||||
counter = 0
|
||||
|
||||
while 1:
|
||||
passes = pick_passes()
|
||||
print '>>> [' + str(counter) + '] testing:', ' '.join(passes)
|
||||
counter += 1
|
||||
try:
|
||||
apply_passes(passes)
|
||||
except Exception, e:
|
||||
print e
|
||||
simplify(passes)
|
||||
break
|
||||
seen = run()
|
||||
if seen != normal:
|
||||
print '>>> bad output:\n', seen
|
||||
simplify(passes)
|
||||
break
|
||||
|
||||
finally:
|
||||
if original_wast:
|
||||
shutil.move(original_wast, wast)
|
@ -1,139 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
# Copyright 2016 WebAssembly Community Group participants
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''
|
||||
This fuzzes passes, by starting with a wast, then running
|
||||
random passes on the wast, and seeing if they break optimization
|
||||
or validation
|
||||
|
||||
Usage: Provide the filename of the wast.
|
||||
'''
|
||||
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
PASSES = [
|
||||
"duplicate-function-elimination",
|
||||
"dce",
|
||||
"remove-unused-brs",
|
||||
"remove-unused-names",
|
||||
"optimize-instructions",
|
||||
"precompute",
|
||||
"simplify-locals",
|
||||
"vacuum",
|
||||
"coalesce-locals",
|
||||
"reorder-locals",
|
||||
"merge-blocks",
|
||||
"remove-unused-functions",
|
||||
]
|
||||
|
||||
# main
|
||||
|
||||
wast = sys.argv[1]
|
||||
print '>>> wast:', wast
|
||||
|
||||
args = sys.argv[2:]
|
||||
|
||||
|
||||
def run():
|
||||
try:
|
||||
cmd = ['bin/wasm-opt', wast]
|
||||
print 'run', cmd
|
||||
subprocess.check_call(cmd, stderr=open('/dev/null'))
|
||||
except Exception, e:
|
||||
return ">>> !!! ", e, " !!!"
|
||||
return 'ok'
|
||||
|
||||
|
||||
original_wast = None
|
||||
|
||||
try:
|
||||
# get normal output
|
||||
|
||||
normal = run()
|
||||
print '>>> normal output:\n', normal
|
||||
assert normal, 'must be output'
|
||||
|
||||
# ensure we actually use the wast
|
||||
|
||||
original_wast = wast + '.original.wast'
|
||||
shutil.move(wast, original_wast)
|
||||
|
||||
def apply_passes(passes):
|
||||
wasm_opt = os.path.join('bin', 'wasm-opt')
|
||||
subprocess.check_call([wasm_opt, original_wast] + passes + ['-o', wast],
|
||||
stderr=open('/dev/null'))
|
||||
|
||||
# loop, looking for failures
|
||||
|
||||
def simplify(passes):
|
||||
# passes is known to fail, try to simplify down by removing
|
||||
more = True
|
||||
while more:
|
||||
more = False
|
||||
print '>>> trying to reduce:', ' '.join(passes),
|
||||
print ' [' + str(len(passes)) + ']'
|
||||
for i in range(len(passes)):
|
||||
smaller = passes[:i] + passes[i + 1:]
|
||||
print '>>>>>> try to reduce to:', ' '.join(smaller),
|
||||
print ' [' + str(len(smaller)) + ']'
|
||||
try:
|
||||
apply_passes(smaller)
|
||||
assert run() == normal
|
||||
except Exception:
|
||||
# this failed too, so it's a good reduction
|
||||
passes = smaller
|
||||
print '>>> reduction successful'
|
||||
more = True
|
||||
break
|
||||
print '>>> reduced to:', ' '.join(passes)
|
||||
|
||||
tested = set()
|
||||
|
||||
def pick_passes():
|
||||
# return '--waka'.split(' ')
|
||||
ret = []
|
||||
while 1:
|
||||
str_ret = str(ret)
|
||||
if random.random() < 0.5 and str_ret not in tested:
|
||||
tested.add(str_ret)
|
||||
return ret
|
||||
ret.append('--' + random.choice(PASSES))
|
||||
|
||||
counter = 0
|
||||
|
||||
while 1:
|
||||
passes = pick_passes()
|
||||
print '>>> [' + str(counter) + '] testing:', ' '.join(passes)
|
||||
counter += 1
|
||||
try:
|
||||
apply_passes(passes)
|
||||
except Exception, e:
|
||||
print e
|
||||
simplify(passes)
|
||||
break
|
||||
seen = run()
|
||||
if seen != normal:
|
||||
print '>>> bad output:\n', seen
|
||||
simplify(passes)
|
||||
break
|
||||
|
||||
finally:
|
||||
if original_wast:
|
||||
shutil.move(original_wast, wast)
|
@ -1,328 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
# Copyright 2016 WebAssembly Community Group participants
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''
|
||||
This fuzzes the relooper using the C API.
|
||||
'''
|
||||
|
||||
import difflib
|
||||
import os
|
||||
import random
|
||||
import subprocess
|
||||
|
||||
if os.environ.get('LD_LIBRARY_PATH'):
|
||||
os.environ['LD_LIBRARY_PATH'] += os.pathsep + 'lib'
|
||||
else:
|
||||
os.environ['LD_LIBRARY_PATH'] = 'lib'
|
||||
|
||||
counter = 0
|
||||
|
||||
while True:
|
||||
# Random decisions
|
||||
num = random.randint(2, 250)
|
||||
density = random.random() * random.random()
|
||||
max_decision = num * 20
|
||||
decisions = [random.randint(1, max_decision) for x in range(num * 3)]
|
||||
branches = [0] * num
|
||||
defaults = [0] * num
|
||||
for i in range(num):
|
||||
b = set([])
|
||||
bs = random.randint(1, max(1,
|
||||
round(density * random.random() * (num - 1))))
|
||||
for j in range(bs):
|
||||
b.add(random.randint(1, num - 1))
|
||||
b = list(b)
|
||||
defaults[i] = random.choice(b)
|
||||
b.remove(defaults[i])
|
||||
branches[i] = b
|
||||
optimize = random.random() < 0.5
|
||||
print counter, ':', num, density, optimize
|
||||
counter += 1
|
||||
|
||||
for temp in ['fuzz.wasm', 'fuzz.wast', 'fast.txt', 'fuzz.slow.js',
|
||||
'fuzz.c']:
|
||||
try:
|
||||
os.unlink(temp)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
# parts
|
||||
entry = '''
|
||||
var label = 0;
|
||||
var state;
|
||||
var decisions = %s;
|
||||
var index = 0;
|
||||
function check() {
|
||||
if (index == decisions.length) throw 'HALT';
|
||||
console.log('(i32.const ' + (-decisions[index]) + ')');
|
||||
return decisions[index++];
|
||||
}
|
||||
''' % str(decisions)
|
||||
|
||||
slow = entry + '\n'
|
||||
slow += 'label = 0;\n'
|
||||
|
||||
slow += '''
|
||||
while(1) switch(label) {
|
||||
'''
|
||||
|
||||
fast = '''
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "binaryen-c.h"
|
||||
|
||||
// globals: address 4 is index
|
||||
// decisions are at address 8+
|
||||
|
||||
int main() {
|
||||
BinaryenModuleRef module = BinaryenModuleCreate();
|
||||
|
||||
// check()
|
||||
|
||||
// if the end, halt
|
||||
BinaryenExpressionRef halter = BinaryenIf(module,
|
||||
BinaryenBinary(module,
|
||||
BinaryenEqInt32(),
|
||||
BinaryenLoad(module, 4, 0, 0, 0, BinaryenInt32(),
|
||||
BinaryenConst(module, BinaryenLiteralInt32(4))),
|
||||
BinaryenConst(module, BinaryenLiteralInt32(4 * %d)) // jumps of 4 bytes
|
||||
),
|
||||
BinaryenUnreachable(module),
|
||||
NULL
|
||||
);
|
||||
// increment index
|
||||
BinaryenExpressionRef incer = BinaryenStore(module,
|
||||
4, 0, 0,
|
||||
BinaryenConst(module, BinaryenLiteralInt32(4)),
|
||||
BinaryenBinary(module,
|
||||
BinaryenAddInt32(),
|
||||
BinaryenLoad(module, 4, 0, 0, 0, BinaryenInt32(),
|
||||
BinaryenConst(module, BinaryenLiteralInt32(4))),
|
||||
BinaryenConst(module, BinaryenLiteralInt32(4))
|
||||
),
|
||||
BinaryenInt32()
|
||||
);
|
||||
|
||||
// optionally, print the return value
|
||||
BinaryenExpressionRef args[] = {
|
||||
BinaryenBinary(module,
|
||||
BinaryenSubInt32(),
|
||||
BinaryenConst(module, BinaryenLiteralInt32(0)),
|
||||
BinaryenLoad(module,
|
||||
4, 0, 4, 0, BinaryenInt32(),
|
||||
BinaryenLoad(module, 4, 0, 0, 0, BinaryenInt32(),
|
||||
BinaryenConst(module, BinaryenLiteralInt32(4)))
|
||||
)
|
||||
)
|
||||
};
|
||||
BinaryenExpressionRef debugger;
|
||||
if (1) debugger = BinaryenCallImport(module, "print", args, 1,
|
||||
BinaryenNone());
|
||||
else debugger = BinaryenNop(module);
|
||||
|
||||
// return the decision. need to subtract 4 that we just added,
|
||||
// and add 8 since that's where we start, so overall offset 4
|
||||
BinaryenExpressionRef returner = BinaryenLoad(module,
|
||||
4, 0, 4, 0, BinaryenInt32(),
|
||||
BinaryenLoad(module, 4, 0, 0, 0, BinaryenInt32(),
|
||||
BinaryenConst(module, BinaryenLiteralInt32(4)))
|
||||
);
|
||||
BinaryenExpressionRef checkBodyList[] = { halter, incer, debugger,
|
||||
returner };
|
||||
BinaryenExpressionRef checkBody = BinaryenBlock(module,
|
||||
NULL, checkBodyList, sizeof(checkBodyList) / sizeof(BinaryenExpressionRef)
|
||||
);
|
||||
BinaryenFunctionTypeRef i = BinaryenAddFunctionType(module, "i",
|
||||
BinaryenInt32(),
|
||||
NULL, 0);
|
||||
BinaryenAddFunction(module, "check", i, NULL, 0, checkBody);
|
||||
|
||||
// contents of main() begin here
|
||||
|
||||
RelooperRef relooper = RelooperCreate();
|
||||
|
||||
''' % len(decisions)
|
||||
|
||||
for i in range(0, num):
|
||||
slow += ' case %d: console.log("(i32.const %d)"); state = check(); \n' % (
|
||||
i, i)
|
||||
b = branches[i]
|
||||
for j in range(len(b)):
|
||||
slow += ' if (state %% %d == %d) { label = %d; break }\n' % (
|
||||
len(b) + 1, j, b[j]) # TODO: split range 1-n into these options
|
||||
slow += ' label = %d; break\n' % defaults[i]
|
||||
|
||||
use_switch = [random.random() < 0.5 for i in range(num)]
|
||||
|
||||
for i in range(num):
|
||||
fast += '''
|
||||
RelooperBlockRef b%d;
|
||||
{
|
||||
BinaryenExpressionRef args[] = {
|
||||
BinaryenConst(module, BinaryenLiteralInt32(%d))
|
||||
};
|
||||
BinaryenExpressionRef list[] = {
|
||||
BinaryenCallImport(module, "print", args, 1, BinaryenNone()),
|
||||
BinaryenSetLocal(module, 0, BinaryenCall(module, "check", NULL, 0,
|
||||
BinaryenInt32()))
|
||||
};
|
||||
''' % (i, i)
|
||||
if use_switch[i]:
|
||||
fast += '''
|
||||
b%d = RelooperAddBlockWithSwitch(relooper,
|
||||
BinaryenBlock(module, NULL, list, 2),
|
||||
BinaryenBinary(module,
|
||||
BinaryenRemUInt32(),
|
||||
BinaryenGetLocal(module, 0, BinaryenInt32()),
|
||||
BinaryenConst(module, BinaryenLiteralInt32(%d))
|
||||
)
|
||||
);
|
||||
''' % (i, len(branches[i]) + 1)
|
||||
else: # non-switch
|
||||
fast += '''
|
||||
b%d = RelooperAddBlock(relooper, BinaryenBlock(module, NULL, list, 2));
|
||||
''' % i
|
||||
fast += '''
|
||||
}
|
||||
'''
|
||||
|
||||
for i in range(num):
|
||||
b = branches[i]
|
||||
for j in range(len(b)):
|
||||
if use_switch[i]:
|
||||
total = len(b) + 1
|
||||
values = ','.join([str(x) for x in range(random.randint(len(b) + 1,
|
||||
max_decision + 2)) if x % total == j])
|
||||
fast += '''
|
||||
{
|
||||
BinaryenIndex values[] = { %s };
|
||||
RelooperAddBranchForSwitch(b%d, b%d, values,
|
||||
sizeof(values) / sizeof(BinaryenIndex), NULL);
|
||||
}
|
||||
''' % (values, i, b[j])
|
||||
else: # non-switch
|
||||
fast += '''
|
||||
RelooperAddBranch(b%d, b%d, BinaryenBinary(module,
|
||||
BinaryenEqInt32(),
|
||||
BinaryenBinary(module,
|
||||
BinaryenRemUInt32(),
|
||||
BinaryenGetLocal(module, 0, BinaryenInt32()),
|
||||
BinaryenConst(module, BinaryenLiteralInt32(%d))
|
||||
),
|
||||
BinaryenConst(module, BinaryenLiteralInt32(%d))
|
||||
), NULL);
|
||||
''' % (i, b[j], len(b) + 1, j)
|
||||
# default branch
|
||||
if use_switch[i]:
|
||||
fast += '''
|
||||
RelooperAddBranchForSwitch(b%d, b%d, NULL, 0, NULL);
|
||||
''' % (i, defaults[i])
|
||||
else:
|
||||
fast += '''
|
||||
RelooperAddBranch(b%d, b%d, NULL, NULL);
|
||||
''' % (i, defaults[i])
|
||||
|
||||
fast += '''
|
||||
BinaryenExpressionRef body = RelooperRenderAndDispose(relooper, b0, 1,
|
||||
module);
|
||||
|
||||
int decisions[] = { %s };
|
||||
int numDecisions = sizeof(decisions)/sizeof(int);
|
||||
|
||||
// write out all the decisions, then the body of the function
|
||||
BinaryenExpressionRef full[numDecisions + 1];
|
||||
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < numDecisions; i++) {
|
||||
full[i] = BinaryenStore(module,
|
||||
4, 0, 0,
|
||||
BinaryenConst(module, BinaryenLiteralInt32(8 + 4 * i)),
|
||||
BinaryenConst(module, BinaryenLiteralInt32(decisions[i])),
|
||||
BinaryenInt32()
|
||||
);
|
||||
}
|
||||
}
|
||||
full[numDecisions] = body;
|
||||
BinaryenExpressionRef all = BinaryenBlock(module, NULL, full,
|
||||
numDecisions + 1);
|
||||
|
||||
BinaryenFunctionTypeRef v = BinaryenAddFunctionType(module, "v",
|
||||
BinaryenNone(),
|
||||
NULL, 0);
|
||||
// locals: state, free-for-label
|
||||
BinaryenType localTypes[] = { BinaryenInt32(), BinaryenInt32() };
|
||||
BinaryenFunctionRef theMain = BinaryenAddFunction(module, "main", v,
|
||||
localTypes, 2, all);
|
||||
BinaryenSetStart(module, theMain);
|
||||
|
||||
// import
|
||||
|
||||
BinaryenType iparams[] = { BinaryenInt32() };
|
||||
BinaryenFunctionTypeRef vi = BinaryenAddFunctionType(module, "vi",
|
||||
BinaryenNone(),
|
||||
iparams, 1);
|
||||
BinaryenAddImport(module, "print", "spectest", "print", vi);
|
||||
|
||||
// memory
|
||||
BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, 0);
|
||||
|
||||
// optionally, optimize
|
||||
if (%d) BinaryenModuleOptimize(module);
|
||||
|
||||
assert(BinaryenModuleValidate(module));
|
||||
|
||||
// write it out
|
||||
|
||||
BinaryenModulePrint(module);
|
||||
|
||||
BinaryenModuleDispose(module);
|
||||
|
||||
return 0;
|
||||
}
|
||||
''' % (', '.join(map(str, decisions)), optimize)
|
||||
|
||||
slow += '}'
|
||||
|
||||
open('fuzz.slow.js', 'w').write(slow)
|
||||
open('fuzz.c', 'w').write(fast)
|
||||
|
||||
print '.'
|
||||
cmd = [os.environ.get('CC') or 'gcc', 'fuzz.c', '-Isrc',
|
||||
'-lbinaryen', '-lasmjs',
|
||||
'-lsupport', '-Llib/.', '-pthread', '-o', 'fuzz']
|
||||
subprocess.check_call(cmd)
|
||||
print '^'
|
||||
subprocess.check_call(['./fuzz'], stdout=open('fuzz.wast', 'w'))
|
||||
print '*'
|
||||
fast_out = subprocess.Popen(['bin/wasm-shell', 'fuzz.wast'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE).communicate()[0]
|
||||
print '-'
|
||||
slow_out = subprocess.Popen(['nodejs', 'fuzz.slow.js'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE).communicate()[0]
|
||||
print '_'
|
||||
|
||||
if slow_out != fast_out:
|
||||
print ''.join([a.rstrip() + '\n' for a in difflib.unified_diff(
|
||||
slow_out.split('\n'),
|
||||
fast_out.split('\n'),
|
||||
fromfile='slow',
|
||||
tofile='fast')])
|
||||
assert False
|
@ -1,16 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import os
|
||||
|
||||
root = os.path.dirname(os.path.dirname(__file__))
|
||||
|
||||
infile = os.path.join(root, 'src', 'passes', 'OptimizeInstructions.wast')
|
||||
outfile = os.path.join(root, 'src', 'passes',
|
||||
'OptimizeInstructions.wast.processed')
|
||||
|
||||
out = open(outfile, 'w')
|
||||
|
||||
for line in open(infile):
|
||||
out.write('"' + line.strip().replace('"', '\\"') + '\\n"\n')
|
||||
|
||||
out.close()
|
@ -1,52 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
# Copyright 2016 WebAssembly Community Group participants
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''
|
||||
A bunch of hackish fixups for testing of SpiderMonkey support. We should
|
||||
get rid of these ASAP.
|
||||
|
||||
This is meant to be run using BINARYEN_SCRIPTS in emcc, and not standalone.
|
||||
'''
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import emscripten
|
||||
|
||||
binaryen_root = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
js_target = sys.argv[1]
|
||||
wast_target = sys.argv[2]
|
||||
|
||||
wasm_target = wast_target[:-5] + '.wasm'
|
||||
|
||||
# convert to binary using spidermonkey
|
||||
'''
|
||||
using something like
|
||||
mozjs -e 'os.file.writeTypedArrayToFile("moz.wasm",
|
||||
new Uint8Array(wasmTextToBinary(os.file.readFile("a.out.wast"))))'
|
||||
investigate with
|
||||
>>> map(chr, map(ord, open('moz.wasm').read()))
|
||||
or
|
||||
python -c "print str(map(chr,map(ord,
|
||||
open('a.out.wasm').read()))).replace(',', '\n')"
|
||||
'''
|
||||
subprocess.check_call(
|
||||
emscripten.shared.SPIDERMONKEY_ENGINE +
|
||||
['-e', 'os.file.writeTypedArrayToFile("' + wasm_target +
|
||||
'", new Uint8Array(wasmTextToBinary(os.file.readFile("' +
|
||||
wast_target + '"))))'])
|
@ -1,53 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
# Copyright 2016 WebAssembly Community Group participants
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import urllib2
|
||||
|
||||
|
||||
STORAGE_BASE = 'https://storage.googleapis.com/wasm-llvm/builds/git/'
|
||||
|
||||
|
||||
def download_revision(force_latest):
|
||||
name = 'latest' if force_latest else 'lkgr'
|
||||
downloaded = urllib2.urlopen(STORAGE_BASE + name).read().strip()
|
||||
# TODO: for now try opening as JSON, if that doesn't work then the content is
|
||||
# just a hash. The waterfall is in the process of migrating to JSON.
|
||||
info = None
|
||||
try:
|
||||
info = json.loads(downloaded)
|
||||
except ValueError:
|
||||
pass
|
||||
return info['build'] if type(info) == dict else downloaded
|
||||
|
||||
|
||||
def download_tar(tar_pattern, directory, revision):
|
||||
tar_path = os.path.join(directory, tar_pattern)
|
||||
revision_tar_path = tar_path % revision
|
||||
if os.path.isfile(revision_tar_path):
|
||||
print 'Already have `%s`' % revision_tar_path
|
||||
else:
|
||||
print 'Downloading `%s`' % revision_tar_path
|
||||
with open(revision_tar_path, 'w+') as f:
|
||||
f.write(urllib2.urlopen(STORAGE_BASE + tar_pattern % revision).read())
|
||||
# Remove any previous tarfiles.
|
||||
for older_tar in glob.glob(tar_path % '*'):
|
||||
if older_tar != revision_tar_path:
|
||||
print 'Removing older tar file `%s`' % older_tar
|
||||
os.remove(older_tar)
|
||||
return revision_tar_path
|
@ -1,13 +0,0 @@
|
||||
|
||||
'''
|
||||
Removes local names. When you don't care about local names but do want
|
||||
to diff for structural changes, this can help.
|
||||
'''
|
||||
|
||||
import sys
|
||||
|
||||
for line in open(sys.argv[1]).readlines():
|
||||
if '(tee_local ' in line or '(set_local ' in line or '(get_local ' in line:
|
||||
print line[:line.find('$')]
|
||||
else:
|
||||
print line,
|
@ -1,17 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
# Copyright 2015 WebAssembly Community Group participants
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Empty __init__.py file: Python treats the directory as containing a package.
|
@ -1,162 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright 2017 WebAssembly Community Group participants
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from support import run_command
|
||||
from shared import (
|
||||
ASM2WASM, WASM_OPT, binary_format_check, delete_from_orbit,
|
||||
fail, fail_with_error, fail_if_not_identical, options, tests
|
||||
)
|
||||
|
||||
|
||||
def test_asm2wasm():
|
||||
print '[ checking asm2wasm testcases... ]\n'
|
||||
|
||||
for asm in tests:
|
||||
if not asm.endswith('.asm.js'):
|
||||
continue
|
||||
for precise in [0, 1, 2]:
|
||||
for opts in [1, 0]:
|
||||
cmd = ASM2WASM + [os.path.join(options.binaryen_test, asm)]
|
||||
if 'threads' in asm:
|
||||
cmd += ['--enable-threads']
|
||||
wasm = asm.replace('.asm.js', '.fromasm')
|
||||
if not precise:
|
||||
cmd += ['--trap-mode=allow', '--ignore-implicit-traps']
|
||||
wasm += '.imprecise'
|
||||
elif precise == 2:
|
||||
cmd += ['--trap-mode=clamp']
|
||||
wasm += '.clamp'
|
||||
if not opts:
|
||||
wasm += '.no-opts'
|
||||
if precise:
|
||||
cmd += ['-O0'] # test that -O0 does nothing
|
||||
else:
|
||||
cmd += ['-O']
|
||||
if 'debugInfo' in asm:
|
||||
cmd += ['-g']
|
||||
if 'noffi' in asm:
|
||||
cmd += ['--no-legalize-javascript-ffi']
|
||||
if precise and opts:
|
||||
# test mem init importing
|
||||
open('a.mem', 'wb').write(asm)
|
||||
cmd += ['--mem-init=a.mem']
|
||||
if asm[0] == 'e':
|
||||
cmd += ['--mem-base=1024']
|
||||
if 'i64' in asm or 'wasm-only' in asm or 'noffi' in asm:
|
||||
cmd += ['--wasm-only']
|
||||
wasm = os.path.join(options.binaryen_test, wasm)
|
||||
print '..', asm, wasm
|
||||
|
||||
def do_asm2wasm_test():
|
||||
actual = run_command(cmd)
|
||||
|
||||
# verify output
|
||||
if not os.path.exists(wasm):
|
||||
fail_with_error('output .wast file %s does not exist' % wasm)
|
||||
expected = open(wasm, 'rb').read()
|
||||
if actual != expected:
|
||||
fail(actual, expected)
|
||||
|
||||
binary_format_check(wasm, verify_final_result=False)
|
||||
|
||||
# test both normally and with pass debug (so each inter-pass state
|
||||
# is validated)
|
||||
old_pass_debug = os.environ.get('BINARYEN_PASS_DEBUG')
|
||||
try:
|
||||
os.environ['BINARYEN_PASS_DEBUG'] = '1'
|
||||
print "With BINARYEN_PASS_DEBUG=1:"
|
||||
do_asm2wasm_test()
|
||||
del os.environ['BINARYEN_PASS_DEBUG']
|
||||
print "With BINARYEN_PASS_DEBUG disabled:"
|
||||
do_asm2wasm_test()
|
||||
finally:
|
||||
if old_pass_debug is not None:
|
||||
os.environ['BINARYEN_PASS_DEBUG'] = old_pass_debug
|
||||
else:
|
||||
if 'BINARYEN_PASS_DEBUG' in os.environ:
|
||||
del os.environ['BINARYEN_PASS_DEBUG']
|
||||
|
||||
# verify in wasm
|
||||
if options.interpreter:
|
||||
# remove imports, spec interpreter doesn't know what to do with them
|
||||
subprocess.check_call(WASM_OPT + ['--remove-imports', wasm],
|
||||
stdout=open('ztemp.wast', 'w'),
|
||||
stderr=subprocess.PIPE)
|
||||
proc = subprocess.Popen([options.interpreter, 'ztemp.wast'],
|
||||
stderr=subprocess.PIPE)
|
||||
out, err = proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
try: # to parse the error
|
||||
reported = err.split(':')[1]
|
||||
start, end = reported.split('-')
|
||||
start_line, start_col = map(int, start.split('.'))
|
||||
lines = open('ztemp.wast').read().split('\n')
|
||||
print
|
||||
print '=' * 80
|
||||
print lines[start_line - 1]
|
||||
print (' ' * (start_col - 1)) + '^'
|
||||
print (' ' * (start_col - 2)) + '/_\\'
|
||||
print '=' * 80
|
||||
print err
|
||||
except Exception:
|
||||
# failed to pretty-print
|
||||
fail_with_error('wasm interpreter error: ' + err)
|
||||
fail_with_error('wasm interpreter error')
|
||||
|
||||
# verify debug info
|
||||
if 'debugInfo' in asm:
|
||||
jsmap = 'a.wasm.map'
|
||||
cmd += ['--source-map', jsmap,
|
||||
'--source-map-url', 'http://example.org/' + jsmap,
|
||||
'-o', 'a.wasm']
|
||||
run_command(cmd)
|
||||
if not os.path.isfile(jsmap):
|
||||
fail_with_error('Debug info map not created: %s' % jsmap)
|
||||
with open(wasm + '.map', 'rb') as expected:
|
||||
with open(jsmap, 'rb') as actual:
|
||||
fail_if_not_identical(actual.read(), expected.read())
|
||||
with open('a.wasm', 'rb') as binary:
|
||||
url_section_name = bytearray([16]) + bytearray('sourceMappingURL')
|
||||
url = 'http://example.org/' + jsmap
|
||||
assert len(url) < 256, 'name too long'
|
||||
url_section_contents = bytearray([len(url)]) + bytearray(url)
|
||||
print url_section_name
|
||||
binary_contents = bytearray(binary.read())
|
||||
if url_section_name not in binary_contents:
|
||||
fail_with_error('source map url section not found in binary')
|
||||
url_section_index = binary_contents.index(url_section_name)
|
||||
if url_section_contents not in binary_contents[url_section_index:]:
|
||||
fail_with_error('source map url not found in url section')
|
||||
|
||||
|
||||
def test_asm2wasm_binary():
|
||||
print '\n[ checking asm2wasm binary reading/writing... ]\n'
|
||||
|
||||
asmjs = os.path.join(options.binaryen_test, 'hello_world.asm.js')
|
||||
delete_from_orbit('a.wasm')
|
||||
delete_from_orbit('b.wast')
|
||||
run_command(ASM2WASM + [asmjs, '-o', 'a.wasm'])
|
||||
assert open('a.wasm', 'rb').read()[0] == '\0', 'we emit binary by default'
|
||||
run_command(ASM2WASM + [asmjs, '-o', 'b.wast', '-S'])
|
||||
assert open('b.wast', 'rb').read()[0] != '\0', 'we emit text with -S'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_asm2wasm()
|
||||
test_asm2wasm_binary()
|
@ -1,135 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright 2016 WebAssembly Community Group participants
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
from support import run_command
|
||||
from shared import (
|
||||
fail, fail_with_error, fail_if_not_contained,
|
||||
options, S2WASM, WASM_SHELL
|
||||
)
|
||||
|
||||
|
||||
def test_s2wasm():
|
||||
print '\n[ checking .s testcases... ]\n'
|
||||
|
||||
cmd = S2WASM + [
|
||||
os.path.join(options.binaryen_test, 'dot_s', 'basics.s'),
|
||||
'--import-memory']
|
||||
output = run_command(cmd)
|
||||
fail_if_not_contained(
|
||||
output, '(import "env" "memory" (memory $0 1))')
|
||||
|
||||
extension_arg_map = {
|
||||
'.wast': [],
|
||||
'.clamp.wast': ['--trap-mode=clamp'],
|
||||
'.js.wast': ['--trap-mode=js'],
|
||||
}
|
||||
for dot_s_dir in ['dot_s', 'llvm_autogenerated']:
|
||||
dot_s_path = os.path.join(options.binaryen_test, dot_s_dir)
|
||||
for s in sorted(os.listdir(dot_s_path)):
|
||||
if not s.endswith('.s'):
|
||||
continue
|
||||
print '..', s
|
||||
for ext, ext_args in extension_arg_map.iteritems():
|
||||
wasm = s.replace('.s', ext)
|
||||
expected_file = os.path.join(options.binaryen_test, dot_s_dir, wasm)
|
||||
expected_exists = os.path.exists(expected_file)
|
||||
if ext != '.wast' and not expected_exists:
|
||||
continue
|
||||
|
||||
full = os.path.join(options.binaryen_test, dot_s_dir, s)
|
||||
stack_alloc = (['--allocate-stack=1024']
|
||||
if dot_s_dir == 'llvm_autogenerated'
|
||||
else [])
|
||||
cmd = S2WASM + [full, '--emscripten-glue'] + stack_alloc + ext_args
|
||||
if s.startswith('start_'):
|
||||
cmd.append('--start')
|
||||
actual = run_command(cmd)
|
||||
|
||||
# verify output
|
||||
if not expected_exists:
|
||||
print actual
|
||||
fail_with_error('output ' + expected_file + ' does not exist')
|
||||
expected = open(expected_file, 'rb').read()
|
||||
if actual != expected:
|
||||
fail(actual, expected)
|
||||
|
||||
# verify with options
|
||||
cmd = S2WASM + [full, '--global-base=1024'] + stack_alloc
|
||||
run_command(cmd)
|
||||
|
||||
# run wasm-shell on the .wast to verify that it parses
|
||||
cmd = WASM_SHELL + [expected_file]
|
||||
run_command(cmd)
|
||||
|
||||
|
||||
def test_linker():
|
||||
print '\n[ running linker tests... ]\n'
|
||||
# The {main,foo,bar,baz}.s files were created by running clang over the
|
||||
# respective c files. The foobar.bar archive was created by running:
|
||||
# llvm-ar -format=gnu rc foobar.a quux.s foo.s bar.s baz.s
|
||||
cmd = S2WASM + [
|
||||
os.path.join(options.binaryen_test, 'linker', 'main.s'), '-l',
|
||||
os.path.join(options.binaryen_test, 'linker', 'archive', 'foobar.a')]
|
||||
output = run_command(cmd)
|
||||
# foo should come from main.s and return 42
|
||||
fail_if_not_contained(output, '(func $foo')
|
||||
fail_if_not_contained(output, '(i32.const 42)')
|
||||
# bar should be linked in from bar.s
|
||||
fail_if_not_contained(output, '(func $bar')
|
||||
# quux should be linked in from bar.s even though it comes before bar.s in
|
||||
# the archive
|
||||
fail_if_not_contained(output, '(func $quux')
|
||||
# baz should not be linked in at all
|
||||
if 'baz' in output:
|
||||
fail_with_error('output should not contain "baz": ' + output)
|
||||
|
||||
# Test an archive using a string table
|
||||
cmd = S2WASM + [
|
||||
os.path.join(options.binaryen_test, 'linker', 'main.s'), '-l',
|
||||
os.path.join(options.binaryen_test, 'linker', 'archive', 'barlong.a')]
|
||||
output = run_command(cmd)
|
||||
# bar should be linked from the archive
|
||||
fail_if_not_contained(output, '(func $bar')
|
||||
|
||||
# Test exporting memory growth function and emscripten runtime functions
|
||||
cmd = S2WASM + [
|
||||
os.path.join(options.binaryen_test, 'linker', 'main.s'),
|
||||
'--emscripten-glue', '--allow-memory-growth']
|
||||
output = run_command(cmd)
|
||||
expected_funcs = [
|
||||
('__growWasmMemory', '(param $newSize i32)'),
|
||||
('stackSave', '(result i32)'),
|
||||
('stackAlloc', '(param $0 i32) (result i32)'),
|
||||
('stackRestore', '(param $0 i32)'),
|
||||
]
|
||||
for name, extra in expected_funcs:
|
||||
space = ' ' if extra else ''
|
||||
fail_if_not_contained(output, '(export "{0}" (func ${0}))'.format(name))
|
||||
for line in output.split('\n'):
|
||||
if '(func ${0}'.format(name + space) in line:
|
||||
# we found the relevant line for the function definition. remove
|
||||
# a (; X ;) comment with its index
|
||||
start = line.find('(; ')
|
||||
if start >= 0:
|
||||
end = line.find(' ;)')
|
||||
line = line[:start] + line[end + 4:]
|
||||
fail_if_not_contained(line, '(func ${0}'.format(name + space + extra))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_s2wasm()
|
||||
test_linker()
|
@ -1,426 +0,0 @@
|
||||
import argparse
|
||||
import difflib
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib2
|
||||
|
||||
|
||||
usage_str = ("usage: 'python check.py [options]'\n\n"
|
||||
"Runs the Binaryen test suite.")
|
||||
parser = argparse.ArgumentParser(description=usage_str)
|
||||
parser.add_argument(
|
||||
'--torture', dest='torture', action='store_true', default=True,
|
||||
help='Chooses whether to run the torture testcases. Default: true.')
|
||||
parser.add_argument(
|
||||
'--no-torture', dest='torture', action='store_false',
|
||||
help='Disables running the torture testcases.')
|
||||
parser.add_argument(
|
||||
'--only-prepare', dest='only_prepare', action='store_true', default=False,
|
||||
help='If enabled, only fetches the waterfall build. Default: false.')
|
||||
parser.add_argument(
|
||||
# Backwards compatibility
|
||||
'--only_prepare', dest='only_prepare', action='store_true', default=False,
|
||||
help='If enabled, only fetches the waterfall build. Default: false.')
|
||||
parser.add_argument(
|
||||
'--test-waterfall', dest='test_waterfall', action='store_true',
|
||||
default=False,
|
||||
help=('If enabled, fetches and tests the LLVM waterfall builds.'
|
||||
' Default: false.'))
|
||||
parser.add_argument(
|
||||
'--no-test-waterfall', dest='test_waterfall', action='store_false',
|
||||
help='Disables downloading and testing of the LLVM waterfall builds.')
|
||||
parser.add_argument(
|
||||
'--abort-on-first-failure', dest='abort_on_first_failure',
|
||||
action='store_true', default=True,
|
||||
help=('Specifies whether to halt test suite execution on first test error.'
|
||||
' Default: true.'))
|
||||
parser.add_argument(
|
||||
'--no-abort-on-first-failure', dest='abort_on_first_failure',
|
||||
action='store_false',
|
||||
help=('If set, the whole test suite will run to completion independent of'
|
||||
' earlier errors.'))
|
||||
parser.add_argument(
|
||||
'--run-gcc-tests', dest='run_gcc_tests', action='store_true', default=True,
|
||||
help=('Chooses whether to run the tests that require building with native'
|
||||
' GCC. Default: true.'))
|
||||
parser.add_argument(
|
||||
'--no-run-gcc-tests', dest='run_gcc_tests', action='store_false',
|
||||
help='If set, disables the native GCC tests.')
|
||||
|
||||
parser.add_argument(
|
||||
'--interpreter', dest='interpreter', default='',
|
||||
help='Specifies the wasm interpreter executable to run tests on.')
|
||||
parser.add_argument(
|
||||
'--binaryen-bin', dest='binaryen_bin', default='',
|
||||
help=('Specifies a path to where the built Binaryen executables reside at.'
|
||||
' Default: bin/ of current directory (i.e. assume an in-tree build).'
|
||||
' If not specified, the environment variable BINARYEN_ROOT= can also'
|
||||
' be used to adjust this.'))
|
||||
parser.add_argument(
|
||||
'--binaryen-root', dest='binaryen_root', default='',
|
||||
help=('Specifies a path to the root of the Binaryen repository tree.'
|
||||
' Default: the directory where this file check.py resides.'))
|
||||
parser.add_argument(
|
||||
'--valgrind', dest='valgrind', default='',
|
||||
help=('Specifies a path to Valgrind tool, which will be used to validate'
|
||||
' execution if specified. (Pass --valgrind=valgrind to search in'
|
||||
' PATH)'))
|
||||
parser.add_argument(
|
||||
'--valgrind-full-leak-check', dest='valgrind_full_leak_check',
|
||||
action='store_true', default=False,
|
||||
help=('If specified, all unfreed (but still referenced) pointers at the'
|
||||
' end of execution are considered memory leaks. Default: disabled.'))
|
||||
|
||||
parser.add_argument(
|
||||
'positional_args', metavar='tests', nargs=argparse.REMAINDER,
|
||||
help='Names specific tests to run.')
|
||||
options = parser.parse_args()
|
||||
requested = options.positional_args
|
||||
|
||||
num_failures = 0
|
||||
warnings = []
|
||||
|
||||
|
||||
def warn(text):
|
||||
global warnings
|
||||
warnings.append(text)
|
||||
print 'warning:', text
|
||||
|
||||
|
||||
# setup
|
||||
|
||||
# Locate Binaryen build artifacts directory (bin/ by default)
|
||||
if not options.binaryen_bin:
|
||||
if os.environ.get('BINARYEN_ROOT'):
|
||||
if os.path.isdir(os.path.join(os.environ.get('BINARYEN_ROOT'), 'bin')):
|
||||
options.binaryen_bin = os.path.join(
|
||||
os.environ.get('BINARYEN_ROOT'), 'bin')
|
||||
else:
|
||||
options.binaryen_bin = os.environ.get('BINARYEN_ROOT')
|
||||
else:
|
||||
options.binaryen_bin = 'bin'
|
||||
|
||||
options.binaryen_bin = os.path.normpath(options.binaryen_bin)
|
||||
|
||||
wasm_dis_filenames = ['wasm-dis', 'wasm-dis.exe']
|
||||
if not any(os.path.isfile(os.path.join(options.binaryen_bin, f))
|
||||
for f in wasm_dis_filenames):
|
||||
warn('Binaryen not found (or has not been successfully built to bin/ ?')
|
||||
|
||||
# Locate Binaryen source directory if not specified.
|
||||
if not options.binaryen_root:
|
||||
path_parts = os.path.abspath(__file__).split(os.path.sep)
|
||||
options.binaryen_root = os.path.sep.join(path_parts[:-3])
|
||||
|
||||
options.binaryen_test = os.path.join(options.binaryen_root, 'test')
|
||||
|
||||
|
||||
# Finds the given executable 'program' in PATH.
|
||||
# Operates like the Unix tool 'which'.
|
||||
def which(program):
|
||||
def is_exe(fpath):
|
||||
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
|
||||
fpath, fname = os.path.split(program)
|
||||
if fpath:
|
||||
if is_exe(program):
|
||||
return program
|
||||
else:
|
||||
for path in os.environ["PATH"].split(os.pathsep):
|
||||
path = path.strip('"')
|
||||
exe_file = os.path.join(path, program)
|
||||
if is_exe(exe_file):
|
||||
return exe_file
|
||||
if '.' not in fname:
|
||||
if is_exe(exe_file + '.exe'):
|
||||
return exe_file + '.exe'
|
||||
if is_exe(exe_file + '.cmd'):
|
||||
return exe_file + '.cmd'
|
||||
if is_exe(exe_file + '.bat'):
|
||||
return exe_file + '.bat'
|
||||
|
||||
|
||||
WATERFALL_BUILD_DIR = os.path.join(options.binaryen_test, 'wasm-install')
|
||||
BIN_DIR = os.path.abspath(os.path.join(
|
||||
WATERFALL_BUILD_DIR, 'wasm-install', 'bin'))
|
||||
|
||||
NATIVECC = (os.environ.get('CC') or which('mingw32-gcc') or
|
||||
which('gcc') or which('clang'))
|
||||
NATIVEXX = (os.environ.get('CXX') or which('mingw32-g++') or
|
||||
which('g++') or which('clang++'))
|
||||
NODEJS = which('nodejs') or which('node')
|
||||
MOZJS = which('mozjs')
|
||||
EMCC = which('emcc')
|
||||
|
||||
BINARYEN_INSTALL_DIR = os.path.dirname(options.binaryen_bin)
|
||||
WASM_OPT = [os.path.join(options.binaryen_bin, 'wasm-opt')]
|
||||
WASM_AS = [os.path.join(options.binaryen_bin, 'wasm-as')]
|
||||
WASM_DIS = [os.path.join(options.binaryen_bin, 'wasm-dis')]
|
||||
ASM2WASM = [os.path.join(options.binaryen_bin, 'asm2wasm')]
|
||||
WASM2ASM = [os.path.join(options.binaryen_bin, 'wasm2asm')]
|
||||
WASM_CTOR_EVAL = [os.path.join(options.binaryen_bin, 'wasm-ctor-eval')]
|
||||
WASM_SHELL = [os.path.join(options.binaryen_bin, 'wasm-shell')]
|
||||
WASM_MERGE = [os.path.join(options.binaryen_bin, 'wasm-merge')]
|
||||
S2WASM = [os.path.join(options.binaryen_bin, 's2wasm')]
|
||||
WASM_REDUCE = [os.path.join(options.binaryen_bin, 'wasm-reduce')]
|
||||
|
||||
S2WASM_EXE = S2WASM[0]
|
||||
WASM_SHELL_EXE = WASM_SHELL[0]
|
||||
|
||||
|
||||
def wrap_with_valgrind(cmd):
|
||||
# Exit code 97 is arbitrary, used to easily detect when an error occurs that
|
||||
# is detected by Valgrind.
|
||||
valgrind = [options.valgrind, '--quiet', '--error-exitcode=97']
|
||||
if options.valgrind_full_leak_check:
|
||||
valgrind += ['--leak-check=full', '--show-leak-kinds=all']
|
||||
return valgrind + cmd
|
||||
|
||||
|
||||
if options.valgrind:
|
||||
WASM_OPT = wrap_with_valgrind(WASM_OPT)
|
||||
WASM_AS = wrap_with_valgrind(WASM_AS)
|
||||
WASM_DIS = wrap_with_valgrind(WASM_DIS)
|
||||
ASM2WASM = wrap_with_valgrind(ASM2WASM)
|
||||
WASM_SHELL = wrap_with_valgrind(WASM_SHELL)
|
||||
S2WASM = wrap_with_valgrind(S2WASM)
|
||||
|
||||
os.environ['BINARYEN'] = os.getcwd()
|
||||
|
||||
|
||||
def get_platform():
|
||||
return {'linux2': 'linux',
|
||||
'darwin': 'mac',
|
||||
'win32': 'windows',
|
||||
'cygwin': 'windows'}[sys.platform]
|
||||
|
||||
|
||||
def has_shell_timeout():
|
||||
return get_platform() != 'windows' and os.system('timeout 1s pwd') == 0
|
||||
|
||||
|
||||
def fetch_waterfall():
|
||||
rev = open(os.path.join(options.binaryen_test, 'revision')).read().strip()
|
||||
buildername = get_platform()
|
||||
local_rev_path = os.path.join(WATERFALL_BUILD_DIR, 'local-revision')
|
||||
if os.path.exists(local_rev_path):
|
||||
with open(local_rev_path) as f:
|
||||
local_rev = f.read().strip()
|
||||
if local_rev == rev:
|
||||
return
|
||||
# fetch it
|
||||
basename = 'wasm-binaries-' + rev + '.tbz2'
|
||||
url = '/'.join(['https://storage.googleapis.com/wasm-llvm/builds',
|
||||
buildername, rev, basename])
|
||||
print '(downloading waterfall %s: %s)' % (rev, url)
|
||||
downloaded = urllib2.urlopen(url).read().strip()
|
||||
fullname = os.path.join(options.binaryen_test, basename)
|
||||
open(fullname, 'wb').write(downloaded)
|
||||
print '(unpacking)'
|
||||
if os.path.exists(WATERFALL_BUILD_DIR):
|
||||
shutil.rmtree(WATERFALL_BUILD_DIR)
|
||||
os.mkdir(WATERFALL_BUILD_DIR)
|
||||
subprocess.check_call(['tar', '-xf', os.path.abspath(fullname)],
|
||||
cwd=WATERFALL_BUILD_DIR)
|
||||
print '(noting local revision)'
|
||||
with open(local_rev_path, 'w') as o:
|
||||
o.write(rev + '\n')
|
||||
|
||||
|
||||
has_vanilla_llvm = False
|
||||
|
||||
|
||||
def setup_waterfall():
|
||||
# if we can use the waterfall llvm, do so
|
||||
global has_vanilla_llvm
|
||||
CLANG = os.path.join(BIN_DIR, 'clang')
|
||||
print 'trying waterfall clang at', CLANG
|
||||
try:
|
||||
subprocess.check_call([CLANG, '-v'])
|
||||
has_vanilla_llvm = True
|
||||
print '...success'
|
||||
except (OSError, subprocess.CalledProcessError) as e:
|
||||
warn('could not run vanilla LLVM from waterfall: ' + str(e) +
|
||||
', looked for clang at ' + CLANG)
|
||||
|
||||
|
||||
if options.test_waterfall:
|
||||
fetch_waterfall()
|
||||
setup_waterfall()
|
||||
|
||||
if options.only_prepare:
|
||||
print 'waterfall is fetched and setup, exiting since --only-prepare'
|
||||
sys.exit(0)
|
||||
|
||||
# external tools
|
||||
|
||||
try:
|
||||
if NODEJS is not None:
|
||||
subprocess.check_call(
|
||||
[NODEJS, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except (OSError, subprocess.CalledProcessError):
|
||||
NODEJS = None
|
||||
if NODEJS is None:
|
||||
warn('no node found (did not check proper js form)')
|
||||
|
||||
try:
|
||||
if MOZJS is not None:
|
||||
subprocess.check_call(
|
||||
[MOZJS, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except (OSError, subprocess.CalledProcessError):
|
||||
MOZJS = None
|
||||
if MOZJS is None:
|
||||
warn('no mozjs found (did not check native wasm support nor asm.js'
|
||||
' validation)')
|
||||
|
||||
try:
|
||||
if EMCC is not None:
|
||||
subprocess.check_call(
|
||||
[EMCC, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except (OSError, subprocess.CalledProcessError):
|
||||
EMCC = None
|
||||
if EMCC is None:
|
||||
warn('no emcc found (did not check non-vanilla emscripten/binaryen'
|
||||
' integration)')
|
||||
|
||||
has_vanilla_emcc = False
|
||||
try:
|
||||
subprocess.check_call(
|
||||
[os.path.join(options.binaryen_test, 'emscripten', 'emcc'), '--version'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
has_vanilla_emcc = True
|
||||
except (OSError, subprocess.CalledProcessError):
|
||||
pass
|
||||
|
||||
|
||||
# utilities
|
||||
|
||||
# removes a file if it exists, using any and all ways of doing so
|
||||
def delete_from_orbit(filename):
|
||||
try:
|
||||
os.unlink(filename)
|
||||
except OSError:
|
||||
pass
|
||||
if not os.path.exists(filename):
|
||||
return
|
||||
try:
|
||||
shutil.rmtree(filename, ignore_errors=True)
|
||||
except OSError:
|
||||
pass
|
||||
if not os.path.exists(filename):
|
||||
return
|
||||
try:
|
||||
import stat
|
||||
os.chmod(filename, os.stat(filename).st_mode | stat.S_IWRITE)
|
||||
|
||||
def remove_readonly_and_try_again(func, path, exc_info):
|
||||
if not (os.stat(path).st_mode & stat.S_IWRITE):
|
||||
os.chmod(path, os.stat(path).st_mode | stat.S_IWRITE)
|
||||
func(path)
|
||||
else:
|
||||
raise
|
||||
shutil.rmtree(filename, onerror=remove_readonly_and_try_again)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def fail_with_error(msg):
|
||||
global num_failures
|
||||
try:
|
||||
num_failures += 1
|
||||
raise Exception(msg)
|
||||
except Exception, e:
|
||||
print >> sys.stderr, str(e)
|
||||
if options.abort_on_first_failure:
|
||||
raise
|
||||
|
||||
|
||||
def fail(actual, expected):
|
||||
diff_lines = difflib.unified_diff(
|
||||
expected.split('\n'), actual.split('\n'),
|
||||
fromfile='expected', tofile='actual')
|
||||
diff_str = ''.join([a.rstrip() + '\n' for a in diff_lines])[:]
|
||||
fail_with_error("incorrect output, diff:\n\n%s" % diff_str)
|
||||
|
||||
|
||||
def fail_if_not_identical(actual, expected):
|
||||
if expected != actual:
|
||||
fail(actual, expected)
|
||||
|
||||
|
||||
def fail_if_not_contained(actual, expected):
|
||||
if expected not in actual:
|
||||
fail(actual, expected)
|
||||
|
||||
|
||||
if len(requested) == 0:
|
||||
tests = sorted(os.listdir(os.path.join(options.binaryen_test)))
|
||||
else:
|
||||
tests = requested[:]
|
||||
|
||||
if not options.interpreter:
|
||||
warn('no interpreter provided (did not test spec interpreter validation)')
|
||||
|
||||
if not has_vanilla_emcc:
|
||||
warn('no functional emcc submodule found')
|
||||
|
||||
|
||||
# check utilities
|
||||
|
||||
def binary_format_check(wast, verify_final_result=True, wasm_as_args=['-g'],
|
||||
binary_suffix='.fromBinary'):
|
||||
# checks we can convert the wast to binary and back
|
||||
|
||||
print ' (binary format check)'
|
||||
cmd = WASM_AS + [wast, '-o', 'a.wasm'] + wasm_as_args
|
||||
print ' ', ' '.join(cmd)
|
||||
if os.path.exists('a.wasm'):
|
||||
os.unlink('a.wasm')
|
||||
subprocess.check_call(cmd, stdout=subprocess.PIPE)
|
||||
assert os.path.exists('a.wasm')
|
||||
|
||||
cmd = WASM_DIS + ['a.wasm', '-o', 'ab.wast']
|
||||
print ' ', ' '.join(cmd)
|
||||
if os.path.exists('ab.wast'):
|
||||
os.unlink('ab.wast')
|
||||
subprocess.check_call(cmd, stdout=subprocess.PIPE)
|
||||
assert os.path.exists('ab.wast')
|
||||
|
||||
# make sure it is a valid wast
|
||||
cmd = WASM_OPT + ['ab.wast']
|
||||
print ' ', ' '.join(cmd)
|
||||
subprocess.check_call(cmd, stdout=subprocess.PIPE)
|
||||
|
||||
if verify_final_result:
|
||||
expected = open(wast + binary_suffix).read()
|
||||
actual = open('ab.wast').read()
|
||||
if actual != expected:
|
||||
fail(actual, expected)
|
||||
|
||||
return 'ab.wast'
|
||||
|
||||
|
||||
def minify_check(wast, verify_final_result=True):
|
||||
# checks we can parse minified output
|
||||
|
||||
print ' (minify check)'
|
||||
cmd = WASM_OPT + [wast, '--print-minified']
|
||||
print ' ', ' '.join(cmd)
|
||||
subprocess.check_call(
|
||||
WASM_OPT + [wast, '--print-minified'],
|
||||
stdout=open('a.wast', 'w'), stderr=subprocess.PIPE)
|
||||
assert os.path.exists('a.wast')
|
||||
subprocess.check_call(
|
||||
WASM_OPT + ['a.wast', '--print-minified'],
|
||||
stdout=open('b.wast', 'w'), stderr=subprocess.PIPE)
|
||||
assert os.path.exists('b.wast')
|
||||
if verify_final_result:
|
||||
expected = open('a.wast').read()
|
||||
actual = open('b.wast').read()
|
||||
if actual != expected:
|
||||
fail(actual, expected)
|
||||
if os.path.exists('a.wast'):
|
||||
os.unlink('a.wast')
|
||||
if os.path.exists('b.wast'):
|
||||
os.unlink('b.wast')
|
@ -1,167 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
|
||||
# Copyright 2016 WebAssembly Community Group participants
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import filecmp
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
def _open_archive(tarfile, tmp_dir):
|
||||
with tempfile.TemporaryFile(mode='w+') as f:
|
||||
try:
|
||||
subprocess.check_call(['tar', '-xvf', tarfile], cwd=tmp_dir, stdout=f)
|
||||
except Exception:
|
||||
f.seek(0)
|
||||
sys.stderr.write(f.read())
|
||||
raise
|
||||
return os.listdir(tmp_dir)
|
||||
|
||||
|
||||
def _files_same(dir1, dir2, basenames):
|
||||
diff = filecmp.cmpfiles(dir1, dir2, basenames)
|
||||
return 0 == len(diff[1] + diff[2])
|
||||
|
||||
|
||||
def _dirs_same(dir1, dir2, basenames):
|
||||
for d in basenames:
|
||||
left = os.path.join(dir1, d)
|
||||
right = os.path.join(dir2, d)
|
||||
if not (os.path.isdir(left) and os.path.isdir(right)):
|
||||
return False
|
||||
diff = filecmp.dircmp(right, right)
|
||||
if 0 != len(diff.left_only + diff.right_only + diff.diff_files +
|
||||
diff.common_funny + diff.funny_files):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _move_files(dirfrom, dirto, basenames):
|
||||
for f in basenames:
|
||||
from_file = os.path.join(dirfrom, f)
|
||||
to_file = os.path.join(dirto, f)
|
||||
if os.path.isfile(to_file):
|
||||
os.path.remove(to_file)
|
||||
shutil.move(from_file, to_file)
|
||||
|
||||
|
||||
def _move_dirs(dirfrom, dirto, basenames):
|
||||
for d in basenames:
|
||||
from_dir = os.path.join(dirfrom, d)
|
||||
to_dir = os.path.join(dirto, d)
|
||||
if os.path.isdir(to_dir):
|
||||
shutil.rmtree(to_dir)
|
||||
shutil.move(from_dir, to_dir)
|
||||
|
||||
|
||||
def untar(tarfile, outdir):
|
||||
"""Returns True if untar content differs from pre-existing outdir content."""
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
try:
|
||||
untared = _open_archive(tarfile, tmpdir)
|
||||
files = [f for f in untared if os.path.isfile(os.path.join(tmpdir, f))]
|
||||
dirs = [d for d in untared if os.path.isdir(os.path.join(tmpdir, d))]
|
||||
assert len(files) + len(dirs) == len(untared), 'Only files and directories'
|
||||
if _files_same(tmpdir, outdir, files) and _dirs_same(tmpdir, outdir, dirs):
|
||||
# Nothing new or different in the tarfile.
|
||||
return False
|
||||
# Some or all of the files / directories are new.
|
||||
_move_files(tmpdir, outdir, files)
|
||||
_move_dirs(tmpdir, outdir, dirs)
|
||||
return True
|
||||
finally:
|
||||
if os.path.isdir(tmpdir):
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
|
||||
def split_wast(wast):
|
||||
# .wast files can contain multiple modules, and assertions for each one.
|
||||
# this splits out a wast into [(module, assertions), ..]
|
||||
# we ignore module invalidity tests here.
|
||||
wast = open(wast).read()
|
||||
|
||||
# if it's a binary, leave it as is
|
||||
if wast[0] == '\0':
|
||||
return [[wast, '']]
|
||||
|
||||
ret = []
|
||||
|
||||
def to_end(j):
|
||||
depth = 1
|
||||
while depth > 0 and j < len(wast):
|
||||
if wast[j] == '"':
|
||||
while 1:
|
||||
j = wast.find('"', j + 1)
|
||||
if wast[j - 1] == '\\':
|
||||
continue
|
||||
break
|
||||
assert j > 0
|
||||
elif wast[j] == '(':
|
||||
depth += 1
|
||||
elif wast[j] == ')':
|
||||
depth -= 1
|
||||
elif wast[j] == ';' and wast[j + 1] == ';':
|
||||
j = wast.find('\n', j)
|
||||
j += 1
|
||||
return j
|
||||
|
||||
i = 0
|
||||
while i >= 0:
|
||||
start = wast.find('(', i)
|
||||
if start >= 0 and wast[start + 1] == ';':
|
||||
# block comment
|
||||
i = wast.find(';)', start + 2)
|
||||
assert i > 0, wast[start:]
|
||||
i += 2
|
||||
continue
|
||||
skip = wast.find(';', i)
|
||||
if skip >= 0 and skip < start and skip + 1 < len(wast):
|
||||
if wast[skip + 1] == ';':
|
||||
i = wast.find('\n', i) + 1
|
||||
continue
|
||||
if start < 0:
|
||||
break
|
||||
i = to_end(start + 1)
|
||||
chunk = wast[start:i]
|
||||
if chunk.startswith('(module'):
|
||||
ret += [(chunk, [])]
|
||||
elif chunk.startswith('(assert_invalid'):
|
||||
continue
|
||||
elif chunk.startswith(('(assert', '(invoke')):
|
||||
ret[-1][1].append(chunk)
|
||||
return ret
|
||||
|
||||
|
||||
def run_command(cmd, expected_status=0, stderr=None,
|
||||
expected_err=None, err_contains=False):
|
||||
if expected_err is not None:
|
||||
assert stderr == subprocess.PIPE or stderr is None,\
|
||||
"Can't redirect stderr if using expected_err"
|
||||
stderr = subprocess.PIPE
|
||||
print 'executing: ', ' '.join(cmd)
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=stderr)
|
||||
out, err = proc.communicate()
|
||||
code = proc.returncode
|
||||
if code != expected_status:
|
||||
raise Exception(('run_command failed (%s)' % code, out + str(err or '')))
|
||||
err_correct = expected_err is None or \
|
||||
(expected_err in err if err_contains else expected_err == err)
|
||||
if not err_correct:
|
||||
raise Exception(('run_command unexpected stderr',
|
||||
"expected '%s', actual '%s'" % (expected_err, err)))
|
||||
return out
|
@ -1,92 +0,0 @@
|
||||
#!/usr/bin/env python2
|
||||
|
||||
import os
|
||||
|
||||
from support import run_command
|
||||
from shared import (WASM2ASM, MOZJS, NODEJS, fail_if_not_identical, tests)
|
||||
|
||||
|
||||
# tests with i64s, invokes, etc.
|
||||
spec_tests = [os.path.join('spec', t)
|
||||
for t in sorted(os.listdir(os.path.join('test', 'spec')))
|
||||
if '.fail' not in t]
|
||||
extra_tests = [os.path.join('wasm2asm', t) for t in
|
||||
sorted(os.listdir(os.path.join('test', 'wasm2asm')))]
|
||||
assert_tests = ['wasm2asm.wast.asserts']
|
||||
|
||||
|
||||
def test_wasm2asm_output():
|
||||
for wasm in tests + spec_tests + extra_tests:
|
||||
if not wasm.endswith('.wast'):
|
||||
continue
|
||||
|
||||
asm = os.path.basename(wasm).replace('.wast', '.2asm.js')
|
||||
expected_file = os.path.join('test', asm)
|
||||
|
||||
if not os.path.exists(expected_file):
|
||||
continue
|
||||
|
||||
print '..', wasm
|
||||
|
||||
cmd = WASM2ASM + [os.path.join('test', wasm)]
|
||||
out = run_command(cmd)
|
||||
expected = open(expected_file).read()
|
||||
fail_if_not_identical(out, expected)
|
||||
|
||||
if not NODEJS and not MOZJS:
|
||||
print 'No JS interpreters. Skipping spec tests.'
|
||||
continue
|
||||
|
||||
open('a.2asm.js', 'w').write(out)
|
||||
|
||||
cmd += ['--allow-asserts']
|
||||
out = run_command(cmd)
|
||||
|
||||
open('a.2asm.asserts.js', 'w').write(out)
|
||||
|
||||
# verify asm.js is valid js
|
||||
if NODEJS:
|
||||
out = run_command([NODEJS, 'a.2asm.js'])
|
||||
fail_if_not_identical(out, '')
|
||||
out = run_command([NODEJS, 'a.2asm.asserts.js'], expected_err='')
|
||||
fail_if_not_identical(out, '')
|
||||
|
||||
if MOZJS:
|
||||
# verify asm.js validates
|
||||
# check only subset of err because mozjs emits timing info
|
||||
out = run_command([MOZJS, '-w', 'a.2asm.js'],
|
||||
expected_err='Successfully compiled asm.js code',
|
||||
err_contains=True)
|
||||
fail_if_not_identical(out, '')
|
||||
out = run_command([MOZJS, 'a.2asm.asserts.js'], expected_err='')
|
||||
fail_if_not_identical(out, '')
|
||||
|
||||
|
||||
def test_asserts_output():
|
||||
for wasm in assert_tests:
|
||||
print '..', wasm
|
||||
|
||||
asserts = os.path.basename(wasm).replace('.wast.asserts', '.asserts.js')
|
||||
traps = os.path.basename(wasm).replace('.wast.asserts', '.traps.js')
|
||||
asserts_expected_file = os.path.join('test', asserts)
|
||||
traps_expected_file = os.path.join('test', traps)
|
||||
|
||||
cmd = WASM2ASM + [os.path.join('test', wasm), '--allow-asserts']
|
||||
out = run_command(cmd)
|
||||
expected = open(asserts_expected_file).read()
|
||||
fail_if_not_identical(out, expected)
|
||||
|
||||
cmd += ['--pedantic']
|
||||
out = run_command(cmd)
|
||||
expected = open(traps_expected_file).read()
|
||||
fail_if_not_identical(out, expected)
|
||||
|
||||
|
||||
def test_wasm2asm():
|
||||
print '\n[ checking wasm2asm testcases... ]\n'
|
||||
test_wasm2asm_output()
|
||||
test_asserts_output()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_wasm2asm()
|
@ -1,4 +0,0 @@
|
||||
[pep8]
|
||||
ignore = E111,E114
|
||||
[flake8]
|
||||
ignore = E111,E114
|
File diff suppressed because it is too large
Load Diff
@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_asm_v_wasm_h
|
||||
#define wasm_asm_v_wasm_h
|
||||
|
||||
#include "mixed_arena.h"
|
||||
#include "emscripten-optimizer/optimizer.h"
|
||||
#include "wasm.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
WasmType asmToWasmType(AsmType asmType);
|
||||
|
||||
AsmType wasmToAsmType(WasmType type);
|
||||
|
||||
char getSig(WasmType type);
|
||||
|
||||
std::string getSig(const FunctionType *type);
|
||||
|
||||
std::string getSig(Function *func);
|
||||
|
||||
template<typename T,
|
||||
typename std::enable_if<std::is_base_of<Expression, T>::value>::type* = nullptr>
|
||||
std::string getSig(T *call) {
|
||||
std::string ret;
|
||||
ret += getSig(call->type);
|
||||
for (auto operand : call->operands) {
|
||||
ret += getSig(operand->type);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename ListType>
|
||||
std::string getSig(WasmType result, const ListType& operands) {
|
||||
std::string ret;
|
||||
ret += getSig(result);
|
||||
for (auto operand : operands) {
|
||||
ret += getSig(operand->type);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename ListType>
|
||||
std::string getSigFromStructs(WasmType result, const ListType& operands) {
|
||||
std::string ret;
|
||||
ret += getSig(result);
|
||||
for (auto operand : operands) {
|
||||
ret += getSig(operand.type);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
WasmType sigToWasmType(char sig);
|
||||
|
||||
FunctionType* sigToFunctionType(std::string sig);
|
||||
|
||||
FunctionType* ensureFunctionType(std::string sig, Module* wasm);
|
||||
|
||||
// converts an f32 to an f64 if necessary
|
||||
Expression* ensureDouble(Expression* expr, MixedArena& allocator);
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_asm_v_wasm_h
|
@ -1,5 +0,0 @@
|
||||
SET(asmjs_SOURCES
|
||||
asm_v_wasm.cpp
|
||||
shared-constants.cpp
|
||||
)
|
||||
ADD_LIBRARY(asmjs STATIC ${asmjs_SOURCES})
|
@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "asm_v_wasm.h"
|
||||
#include "wasm.h"
|
||||
|
||||
|
||||
namespace wasm {
|
||||
|
||||
WasmType asmToWasmType(AsmType asmType) {
|
||||
switch (asmType) {
|
||||
case ASM_INT: return WasmType::i32;
|
||||
case ASM_DOUBLE: return WasmType::f64;
|
||||
case ASM_FLOAT: return WasmType::f32;
|
||||
case ASM_INT64: return WasmType::i64;
|
||||
case ASM_NONE: return WasmType::none;
|
||||
default: {}
|
||||
}
|
||||
abort();
|
||||
}
|
||||
|
||||
AsmType wasmToAsmType(WasmType type) {
|
||||
switch (type) {
|
||||
case WasmType::i32: return ASM_INT;
|
||||
case WasmType::f32: return ASM_FLOAT;
|
||||
case WasmType::f64: return ASM_DOUBLE;
|
||||
case WasmType::i64: return ASM_INT64;
|
||||
case WasmType::none: return ASM_NONE;
|
||||
default: {}
|
||||
}
|
||||
abort();
|
||||
}
|
||||
|
||||
char getSig(WasmType type) {
|
||||
switch (type) {
|
||||
case i32: return 'i';
|
||||
case i64: return 'j';
|
||||
case f32: return 'f';
|
||||
case f64: return 'd';
|
||||
case none: return 'v';
|
||||
default: abort();
|
||||
}
|
||||
}
|
||||
|
||||
std::string getSig(const FunctionType *type) {
|
||||
std::string ret;
|
||||
ret += getSig(type->result);
|
||||
for (auto param : type->params) {
|
||||
ret += getSig(param);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string getSig(Function *func) {
|
||||
std::string ret;
|
||||
ret += getSig(func->result);
|
||||
for (auto type : func->params) {
|
||||
ret += getSig(type);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
WasmType sigToWasmType(char sig) {
|
||||
switch (sig) {
|
||||
case 'i': return i32;
|
||||
case 'j': return i64;
|
||||
case 'f': return f32;
|
||||
case 'd': return f64;
|
||||
case 'v': return none;
|
||||
default: abort();
|
||||
}
|
||||
}
|
||||
|
||||
FunctionType* sigToFunctionType(std::string sig) {
|
||||
auto ret = new FunctionType;
|
||||
ret->result = sigToWasmType(sig[0]);
|
||||
for (size_t i = 1; i < sig.size(); i++) {
|
||||
ret->params.push_back(sigToWasmType(sig[i]));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
FunctionType* ensureFunctionType(std::string sig, Module* wasm) {
|
||||
cashew::IString name(("FUNCSIG$" + sig).c_str(), false);
|
||||
if (wasm->getFunctionTypeOrNull(name)) {
|
||||
return wasm->getFunctionType(name);
|
||||
}
|
||||
// add new type
|
||||
auto type = new FunctionType;
|
||||
type->name = name;
|
||||
type->result = sigToWasmType(sig[0]);
|
||||
for (size_t i = 1; i < sig.size(); i++) {
|
||||
type->params.push_back(sigToWasmType(sig[i]));
|
||||
}
|
||||
wasm->addFunctionType(type);
|
||||
return type;
|
||||
}
|
||||
|
||||
Expression* ensureDouble(Expression* expr, MixedArena& allocator) {
|
||||
if (expr->type == f32) {
|
||||
auto conv = allocator.alloc<Unary>();
|
||||
conv->op = PromoteFloat32;
|
||||
conv->value = expr;
|
||||
conv->type = WasmType::f64;
|
||||
return conv;
|
||||
}
|
||||
assert(expr->type == f64);
|
||||
return expr;
|
||||
}
|
||||
|
||||
} // namespace wasm
|
@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "asmjs/shared-constants.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
cashew::IString GLOBAL("global"),
|
||||
NAN_("NaN"),
|
||||
INFINITY_("Infinity"),
|
||||
NAN__("nan"),
|
||||
INFINITY__("infinity"),
|
||||
TOPMOST("topmost"),
|
||||
INT8ARRAY("Int8Array"),
|
||||
INT16ARRAY("Int16Array"),
|
||||
INT32ARRAY("Int32Array"),
|
||||
UINT8ARRAY("Uint8Array"),
|
||||
UINT16ARRAY("Uint16Array"),
|
||||
UINT32ARRAY("Uint32Array"),
|
||||
FLOAT32ARRAY("Float32Array"),
|
||||
FLOAT64ARRAY("Float64Array"),
|
||||
ARRAY_BUFFER("ArrayBuffer"),
|
||||
ASM_MODULE("asmModule"),
|
||||
IMPOSSIBLE_CONTINUE("impossible-continue"),
|
||||
MATH("Math"),
|
||||
IMUL("imul"),
|
||||
CLZ32("clz32"),
|
||||
FROUND("fround"),
|
||||
ASM2WASM("asm2wasm"),
|
||||
F64_REM("f64-rem"),
|
||||
F64_TO_INT("f64-to-int"),
|
||||
F64_TO_UINT("f64-to-uint"),
|
||||
F64_TO_INT64("f64-to-int64"),
|
||||
F64_TO_UINT64("f64-to-uint64"),
|
||||
F32_TO_INT("f32-to-int"),
|
||||
F32_TO_UINT("f32-to-uint"),
|
||||
F32_TO_INT64("f32-to-int64"),
|
||||
F32_TO_UINT64("f32-to-uint64"),
|
||||
I32S_DIV("i32s-div"),
|
||||
I32U_DIV("i32u-div"),
|
||||
I32S_REM("i32s-rem"),
|
||||
I32U_REM("i32u-rem"),
|
||||
GLOBAL_MATH("global.Math"),
|
||||
ABS("abs"),
|
||||
FLOOR("floor"),
|
||||
CEIL("ceil"),
|
||||
SQRT("sqrt"),
|
||||
POW("pow"),
|
||||
I32_TEMP("asm2wasm_i32_temp"),
|
||||
DEBUGGER("debugger"),
|
||||
USE_ASM("use asm"),
|
||||
BUFFER("buffer"),
|
||||
ENV("env"),
|
||||
INSTRUMENT("instrument"),
|
||||
MATH_IMUL("Math_imul"),
|
||||
MATH_ABS("Math_abs"),
|
||||
MATH_CEIL("Math_ceil"),
|
||||
MATH_CLZ32("Math_clz32"),
|
||||
MATH_FLOOR("Math_floor"),
|
||||
MATH_TRUNC("Math_trunc"),
|
||||
MATH_NEAREST("Math_NEAREST"),
|
||||
MATH_SQRT("Math_sqrt"),
|
||||
MATH_MIN("Math_min"),
|
||||
MATH_MAX("Math_max"),
|
||||
WASM_CTZ32("__wasm_ctz_i32"),
|
||||
WASM_CTZ64("__wasm_ctz_i64"),
|
||||
WASM_CLZ32("__wasm_clz_i32"),
|
||||
WASM_CLZ64("__wasm_clz_i64"),
|
||||
WASM_POPCNT32("__wasm_popcnt_i32"),
|
||||
WASM_POPCNT64("__wasm_popcnt_i64"),
|
||||
WASM_ROTL32("__wasm_rotl_i32"),
|
||||
WASM_ROTL64("__wasm_rotl_i64"),
|
||||
WASM_ROTR32("__wasm_rotr_i32"),
|
||||
WASM_ROTR64("__wasm_rotr_i64");
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_asmjs_shared_constants_h
|
||||
#define wasm_asmjs_shared_constants_h
|
||||
|
||||
#include "emscripten-optimizer/istring.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
extern cashew::IString GLOBAL,
|
||||
NAN_,
|
||||
INFINITY_,
|
||||
NAN__,
|
||||
INFINITY__,
|
||||
TOPMOST,
|
||||
INT8ARRAY,
|
||||
INT16ARRAY,
|
||||
INT32ARRAY,
|
||||
UINT8ARRAY,
|
||||
UINT16ARRAY,
|
||||
UINT32ARRAY,
|
||||
FLOAT32ARRAY,
|
||||
FLOAT64ARRAY,
|
||||
ARRAY_BUFFER,
|
||||
ASM_MODULE,
|
||||
IMPOSSIBLE_CONTINUE,
|
||||
MATH,
|
||||
IMUL,
|
||||
CLZ32,
|
||||
FROUND,
|
||||
ASM2WASM,
|
||||
F64_REM,
|
||||
F64_TO_INT,
|
||||
F64_TO_UINT,
|
||||
F64_TO_INT64,
|
||||
F64_TO_UINT64,
|
||||
F32_TO_INT,
|
||||
F32_TO_UINT,
|
||||
F32_TO_INT64,
|
||||
F32_TO_UINT64,
|
||||
I32S_DIV,
|
||||
I32U_DIV,
|
||||
I32S_REM,
|
||||
I32U_REM,
|
||||
GLOBAL_MATH,
|
||||
ABS,
|
||||
FLOOR,
|
||||
CEIL,
|
||||
SQRT,
|
||||
POW,
|
||||
I32_TEMP,
|
||||
DEBUGGER,
|
||||
USE_ASM,
|
||||
BUFFER,
|
||||
ENV,
|
||||
INSTRUMENT,
|
||||
MATH_IMUL,
|
||||
MATH_ABS,
|
||||
MATH_CEIL,
|
||||
MATH_CLZ32,
|
||||
MATH_FLOOR,
|
||||
MATH_TRUNC,
|
||||
MATH_NEAREST,
|
||||
MATH_SQRT,
|
||||
MATH_MIN,
|
||||
MATH_MAX,
|
||||
WASM_CTZ32,
|
||||
WASM_CTZ64,
|
||||
WASM_CLZ32,
|
||||
WASM_CLZ64,
|
||||
WASM_POPCNT32,
|
||||
WASM_POPCNT64,
|
||||
WASM_ROTL32,
|
||||
WASM_ROTL64,
|
||||
WASM_ROTR32,
|
||||
WASM_ROTR64;
|
||||
}
|
||||
|
||||
#endif // wasm_asmjs_shared_constants_h
|
File diff suppressed because it is too large
Load Diff
@ -1,538 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//================
|
||||
// Binaryen C API
|
||||
//
|
||||
// The first part of the API lets you create modules and their parts.
|
||||
//
|
||||
// The second part of the API lets you perform operations on modules.
|
||||
//
|
||||
// The third part of the API lets you provide a general control-flow
|
||||
// graph (CFG) as input.
|
||||
//
|
||||
// The final part of the API contains miscellaneous utilities like
|
||||
// debugging/tracing for the API itself.
|
||||
//
|
||||
// ---------------
|
||||
//
|
||||
// Thread safety: You can create Expressions in parallel, as they do not
|
||||
// refer to global state. BinaryenAddFunction and
|
||||
// BinaryenAddFunctionType are also thread-safe, which means
|
||||
// that you can create functions and their contents in multiple
|
||||
// threads. This is important since functions are where the
|
||||
// majority of the work is done.
|
||||
// Other methods - creating imports, exports, etc. - are
|
||||
// not currently thread-safe (as there is typically no need
|
||||
// to parallelize them).
|
||||
//
|
||||
//================
|
||||
|
||||
#ifndef wasm_binaryen_c_h
|
||||
#define wasm_binaryen_c_h
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//
|
||||
// ========== Module Creation ==========
|
||||
//
|
||||
|
||||
// BinaryenIndex
|
||||
//
|
||||
// Used for internal indexes and list sizes.
|
||||
|
||||
typedef uint32_t BinaryenIndex;
|
||||
|
||||
// Core types (call to get the value of each; you can cache them, they
|
||||
// never change)
|
||||
|
||||
typedef uint32_t BinaryenType;
|
||||
|
||||
BinaryenType BinaryenNone(void);
|
||||
BinaryenType BinaryenInt32(void);
|
||||
BinaryenType BinaryenInt64(void);
|
||||
BinaryenType BinaryenFloat32(void);
|
||||
BinaryenType BinaryenFloat64(void);
|
||||
|
||||
// Not a real type. Used as the last parameter to BinaryenBlock to let
|
||||
// the API figure out the type instead of providing one.
|
||||
BinaryenType BinaryenUndefined(void);
|
||||
|
||||
// Expression ids (call to get the value of each; you can cache them)
|
||||
|
||||
typedef uint32_t BinaryenExpressionId;
|
||||
|
||||
BinaryenExpressionId BinaryenInvalidId(void);
|
||||
BinaryenExpressionId BinaryenBlockId(void);
|
||||
BinaryenExpressionId BinaryenIfId(void);
|
||||
BinaryenExpressionId BinaryenLoopId(void);
|
||||
BinaryenExpressionId BinaryenBreakId(void);
|
||||
BinaryenExpressionId BinaryenSwitchId(void);
|
||||
BinaryenExpressionId BinaryenCallId(void);
|
||||
BinaryenExpressionId BinaryenCallImportId(void);
|
||||
BinaryenExpressionId BinaryenCallIndirectId(void);
|
||||
BinaryenExpressionId BinaryenGetLocalId(void);
|
||||
BinaryenExpressionId BinaryenSetLocalId(void);
|
||||
BinaryenExpressionId BinaryenGetGlobalId(void);
|
||||
BinaryenExpressionId BinaryenSetGlobalId(void);
|
||||
BinaryenExpressionId BinaryenLoadId(void);
|
||||
BinaryenExpressionId BinaryenStoreId(void);
|
||||
BinaryenExpressionId BinaryenConstId(void);
|
||||
BinaryenExpressionId BinaryenUnaryId(void);
|
||||
BinaryenExpressionId BinaryenBinaryId(void);
|
||||
BinaryenExpressionId BinaryenSelectId(void);
|
||||
BinaryenExpressionId BinaryenDropId(void);
|
||||
BinaryenExpressionId BinaryenReturnId(void);
|
||||
BinaryenExpressionId BinaryenHostId(void);
|
||||
BinaryenExpressionId BinaryenNopId(void);
|
||||
BinaryenExpressionId BinaryenUnreachableId(void);
|
||||
BinaryenExpressionId BinaryenAtomicCmpxchgId(void);
|
||||
BinaryenExpressionId BinaryenAtomicRMWId(void);
|
||||
BinaryenExpressionId BinaryenAtomicWaitId(void);
|
||||
BinaryenExpressionId BinaryenAtomicWakeId(void);
|
||||
|
||||
// Modules
|
||||
//
|
||||
// Modules contain lists of functions, imports, exports, function types. The
|
||||
// Add* methods create them on a module. The module owns them and will free their
|
||||
// memory when the module is disposed of.
|
||||
//
|
||||
// Expressions are also allocated inside modules, and freed with the module. They
|
||||
// are not created by Add* methods, since they are not added directly on the
|
||||
// module, instead, they are arguments to other expressions (and then they are
|
||||
// the children of that AST node), or to a function (and then they are the body
|
||||
// of that function).
|
||||
//
|
||||
// A module can also contain a function table for indirect calls, a memory,
|
||||
// and a start method.
|
||||
|
||||
typedef void* BinaryenModuleRef;
|
||||
|
||||
BinaryenModuleRef BinaryenModuleCreate(void);
|
||||
void BinaryenModuleDispose(BinaryenModuleRef module);
|
||||
|
||||
// Function types
|
||||
|
||||
typedef void* BinaryenFunctionTypeRef;
|
||||
|
||||
// Add a new function type. This is thread-safe.
|
||||
// Note: name can be NULL, in which case we auto-generate a name
|
||||
BinaryenFunctionTypeRef BinaryenAddFunctionType(BinaryenModuleRef module, const char* name, BinaryenType result, BinaryenType* paramTypes, BinaryenIndex numParams);
|
||||
|
||||
// Literals. These are passed by value.
|
||||
|
||||
struct BinaryenLiteral {
|
||||
int32_t type;
|
||||
union {
|
||||
int32_t i32;
|
||||
int64_t i64;
|
||||
float f32;
|
||||
double f64;
|
||||
};
|
||||
};
|
||||
|
||||
struct BinaryenLiteral BinaryenLiteralInt32(int32_t x);
|
||||
struct BinaryenLiteral BinaryenLiteralInt64(int64_t x);
|
||||
struct BinaryenLiteral BinaryenLiteralFloat32(float x);
|
||||
struct BinaryenLiteral BinaryenLiteralFloat64(double x);
|
||||
struct BinaryenLiteral BinaryenLiteralFloat32Bits(int32_t x);
|
||||
struct BinaryenLiteral BinaryenLiteralFloat64Bits(int64_t x);
|
||||
|
||||
// Expressions
|
||||
//
|
||||
// Some expressions have a BinaryenOp, which is the more
|
||||
// specific operation/opcode.
|
||||
//
|
||||
// Some expressions have optional parameters, like Return may not
|
||||
// return a value. You can supply a NULL pointer in those cases.
|
||||
//
|
||||
// For more information, see wasm.h
|
||||
|
||||
typedef int32_t BinaryenOp;
|
||||
|
||||
BinaryenOp BinaryenClzInt32(void);
|
||||
BinaryenOp BinaryenCtzInt32(void);
|
||||
BinaryenOp BinaryenPopcntInt32(void);
|
||||
BinaryenOp BinaryenNegFloat32(void);
|
||||
BinaryenOp BinaryenAbsFloat32(void);
|
||||
BinaryenOp BinaryenCeilFloat32(void);
|
||||
BinaryenOp BinaryenFloorFloat32(void);
|
||||
BinaryenOp BinaryenTruncFloat32(void);
|
||||
BinaryenOp BinaryenNearestFloat32(void);
|
||||
BinaryenOp BinaryenSqrtFloat32(void);
|
||||
BinaryenOp BinaryenEqZInt32(void);
|
||||
BinaryenOp BinaryenClzInt64(void);
|
||||
BinaryenOp BinaryenCtzInt64(void);
|
||||
BinaryenOp BinaryenPopcntInt64(void);
|
||||
BinaryenOp BinaryenNegFloat64(void);
|
||||
BinaryenOp BinaryenAbsFloat64(void);
|
||||
BinaryenOp BinaryenCeilFloat64(void);
|
||||
BinaryenOp BinaryenFloorFloat64(void);
|
||||
BinaryenOp BinaryenTruncFloat64(void);
|
||||
BinaryenOp BinaryenNearestFloat64(void);
|
||||
BinaryenOp BinaryenSqrtFloat64(void);
|
||||
BinaryenOp BinaryenEqZInt64(void);
|
||||
BinaryenOp BinaryenExtendSInt32(void);
|
||||
BinaryenOp BinaryenExtendUInt32(void);
|
||||
BinaryenOp BinaryenWrapInt64(void);
|
||||
BinaryenOp BinaryenTruncSFloat32ToInt32(void);
|
||||
BinaryenOp BinaryenTruncSFloat32ToInt64(void);
|
||||
BinaryenOp BinaryenTruncUFloat32ToInt32(void);
|
||||
BinaryenOp BinaryenTruncUFloat32ToInt64(void);
|
||||
BinaryenOp BinaryenTruncSFloat64ToInt32(void);
|
||||
BinaryenOp BinaryenTruncSFloat64ToInt64(void);
|
||||
BinaryenOp BinaryenTruncUFloat64ToInt32(void);
|
||||
BinaryenOp BinaryenTruncUFloat64ToInt64(void);
|
||||
BinaryenOp BinaryenReinterpretFloat32(void);
|
||||
BinaryenOp BinaryenReinterpretFloat64(void);
|
||||
BinaryenOp BinaryenConvertSInt32ToFloat32(void);
|
||||
BinaryenOp BinaryenConvertSInt32ToFloat64(void);
|
||||
BinaryenOp BinaryenConvertUInt32ToFloat32(void);
|
||||
BinaryenOp BinaryenConvertUInt32ToFloat64(void);
|
||||
BinaryenOp BinaryenConvertSInt64ToFloat32(void);
|
||||
BinaryenOp BinaryenConvertSInt64ToFloat64(void);
|
||||
BinaryenOp BinaryenConvertUInt64ToFloat32(void);
|
||||
BinaryenOp BinaryenConvertUInt64ToFloat64(void);
|
||||
BinaryenOp BinaryenPromoteFloat32(void);
|
||||
BinaryenOp BinaryenDemoteFloat64(void);
|
||||
BinaryenOp BinaryenReinterpretInt32(void);
|
||||
BinaryenOp BinaryenReinterpretInt64(void);
|
||||
BinaryenOp BinaryenAddInt32(void);
|
||||
BinaryenOp BinaryenSubInt32(void);
|
||||
BinaryenOp BinaryenMulInt32(void);
|
||||
BinaryenOp BinaryenDivSInt32(void);
|
||||
BinaryenOp BinaryenDivUInt32(void);
|
||||
BinaryenOp BinaryenRemSInt32(void);
|
||||
BinaryenOp BinaryenRemUInt32(void);
|
||||
BinaryenOp BinaryenAndInt32(void);
|
||||
BinaryenOp BinaryenOrInt32(void);
|
||||
BinaryenOp BinaryenXorInt32(void);
|
||||
BinaryenOp BinaryenShlInt32(void);
|
||||
BinaryenOp BinaryenShrUInt32(void);
|
||||
BinaryenOp BinaryenShrSInt32(void);
|
||||
BinaryenOp BinaryenRotLInt32(void);
|
||||
BinaryenOp BinaryenRotRInt32(void);
|
||||
BinaryenOp BinaryenEqInt32(void);
|
||||
BinaryenOp BinaryenNeInt32(void);
|
||||
BinaryenOp BinaryenLtSInt32(void);
|
||||
BinaryenOp BinaryenLtUInt32(void);
|
||||
BinaryenOp BinaryenLeSInt32(void);
|
||||
BinaryenOp BinaryenLeUInt32(void);
|
||||
BinaryenOp BinaryenGtSInt32(void);
|
||||
BinaryenOp BinaryenGtUInt32(void);
|
||||
BinaryenOp BinaryenGeSInt32(void);
|
||||
BinaryenOp BinaryenGeUInt32(void);
|
||||
BinaryenOp BinaryenAddInt64(void);
|
||||
BinaryenOp BinaryenSubInt64(void);
|
||||
BinaryenOp BinaryenMulInt64(void);
|
||||
BinaryenOp BinaryenDivSInt64(void);
|
||||
BinaryenOp BinaryenDivUInt64(void);
|
||||
BinaryenOp BinaryenRemSInt64(void);
|
||||
BinaryenOp BinaryenRemUInt64(void);
|
||||
BinaryenOp BinaryenAndInt64(void);
|
||||
BinaryenOp BinaryenOrInt64(void);
|
||||
BinaryenOp BinaryenXorInt64(void);
|
||||
BinaryenOp BinaryenShlInt64(void);
|
||||
BinaryenOp BinaryenShrUInt64(void);
|
||||
BinaryenOp BinaryenShrSInt64(void);
|
||||
BinaryenOp BinaryenRotLInt64(void);
|
||||
BinaryenOp BinaryenRotRInt64(void);
|
||||
BinaryenOp BinaryenEqInt64(void);
|
||||
BinaryenOp BinaryenNeInt64(void);
|
||||
BinaryenOp BinaryenLtSInt64(void);
|
||||
BinaryenOp BinaryenLtUInt64(void);
|
||||
BinaryenOp BinaryenLeSInt64(void);
|
||||
BinaryenOp BinaryenLeUInt64(void);
|
||||
BinaryenOp BinaryenGtSInt64(void);
|
||||
BinaryenOp BinaryenGtUInt64(void);
|
||||
BinaryenOp BinaryenGeSInt64(void);
|
||||
BinaryenOp BinaryenGeUInt64(void);
|
||||
BinaryenOp BinaryenAddFloat32(void);
|
||||
BinaryenOp BinaryenSubFloat32(void);
|
||||
BinaryenOp BinaryenMulFloat32(void);
|
||||
BinaryenOp BinaryenDivFloat32(void);
|
||||
BinaryenOp BinaryenCopySignFloat32(void);
|
||||
BinaryenOp BinaryenMinFloat32(void);
|
||||
BinaryenOp BinaryenMaxFloat32(void);
|
||||
BinaryenOp BinaryenEqFloat32(void);
|
||||
BinaryenOp BinaryenNeFloat32(void);
|
||||
BinaryenOp BinaryenLtFloat32(void);
|
||||
BinaryenOp BinaryenLeFloat32(void);
|
||||
BinaryenOp BinaryenGtFloat32(void);
|
||||
BinaryenOp BinaryenGeFloat32(void);
|
||||
BinaryenOp BinaryenAddFloat64(void);
|
||||
BinaryenOp BinaryenSubFloat64(void);
|
||||
BinaryenOp BinaryenMulFloat64(void);
|
||||
BinaryenOp BinaryenDivFloat64(void);
|
||||
BinaryenOp BinaryenCopySignFloat64(void);
|
||||
BinaryenOp BinaryenMinFloat64(void);
|
||||
BinaryenOp BinaryenMaxFloat64(void);
|
||||
BinaryenOp BinaryenEqFloat64(void);
|
||||
BinaryenOp BinaryenNeFloat64(void);
|
||||
BinaryenOp BinaryenLtFloat64(void);
|
||||
BinaryenOp BinaryenLeFloat64(void);
|
||||
BinaryenOp BinaryenGtFloat64(void);
|
||||
BinaryenOp BinaryenGeFloat64(void);
|
||||
BinaryenOp BinaryenPageSize(void);
|
||||
BinaryenOp BinaryenCurrentMemory(void);
|
||||
BinaryenOp BinaryenGrowMemory(void);
|
||||
BinaryenOp BinaryenHasFeature(void);
|
||||
BinaryenOp BinaryenAtomicRMWAdd(void);
|
||||
BinaryenOp BinaryenAtomicRMWSub(void);
|
||||
BinaryenOp BinaryenAtomicRMWAnd(void);
|
||||
BinaryenOp BinaryenAtomicRMWOr(void);
|
||||
BinaryenOp BinaryenAtomicRMWXor(void);
|
||||
BinaryenOp BinaryenAtomicRMWXchg(void);
|
||||
|
||||
typedef void* BinaryenExpressionRef;
|
||||
|
||||
// Block: name can be NULL. Specifying BinaryenUndefined() as the 'type'
|
||||
// parameter indicates that the block's type shall be figured out
|
||||
// automatically instead of explicitly providing it. This conforms
|
||||
// to the behavior before the 'type' parameter has been introduced.
|
||||
BinaryenExpressionRef BinaryenBlock(BinaryenModuleRef module, const char* name, BinaryenExpressionRef* children, BinaryenIndex numChildren, BinaryenType type);
|
||||
// If: ifFalse can be NULL
|
||||
BinaryenExpressionRef BinaryenIf(BinaryenModuleRef module, BinaryenExpressionRef condition, BinaryenExpressionRef ifTrue, BinaryenExpressionRef ifFalse);
|
||||
BinaryenExpressionRef BinaryenLoop(BinaryenModuleRef module, const char* in, BinaryenExpressionRef body);
|
||||
// Break: value and condition can be NULL
|
||||
BinaryenExpressionRef BinaryenBreak(BinaryenModuleRef module, const char* name, BinaryenExpressionRef condition, BinaryenExpressionRef value);
|
||||
// Switch: value can be NULL
|
||||
BinaryenExpressionRef BinaryenSwitch(BinaryenModuleRef module, const char **names, BinaryenIndex numNames, const char* defaultName, BinaryenExpressionRef condition, BinaryenExpressionRef value);
|
||||
// Call, CallImport: Note the 'returnType' parameter. You must declare the
|
||||
// type returned by the function being called, as that
|
||||
// function might not have been created yet, so we don't
|
||||
// know what it is.
|
||||
// Also note that WebAssembly does not differentiate
|
||||
// between Call and CallImport, but Binaryen does, so you
|
||||
// must use CallImport if calling an import, and vice versa.
|
||||
BinaryenExpressionRef BinaryenCall(BinaryenModuleRef module, const char *target, BinaryenExpressionRef* operands, BinaryenIndex numOperands, BinaryenType returnType);
|
||||
BinaryenExpressionRef BinaryenCallImport(BinaryenModuleRef module, const char *target, BinaryenExpressionRef* operands, BinaryenIndex numOperands, BinaryenType returnType);
|
||||
BinaryenExpressionRef BinaryenCallIndirect(BinaryenModuleRef module, BinaryenExpressionRef target, BinaryenExpressionRef* operands, BinaryenIndex numOperands, const char* type);
|
||||
// GetLocal: Note the 'type' parameter. It might seem redundant, since the
|
||||
// local at that index must have a type. However, this API lets you
|
||||
// build code "top-down": create a node, then its parents, and so
|
||||
// on, and finally create the function at the end. (Note that in fact
|
||||
// you do not mention a function when creating ExpressionRefs, only
|
||||
// a module.) And since GetLocal is a leaf node, we need to be told
|
||||
// its type. (Other nodes detect their type either from their
|
||||
// type or their opcode, or failing that, their children. But
|
||||
// GetLocal has no children, it is where a "stream" of type info
|
||||
// begins.)
|
||||
// Note also that the index of a local can refer to a param or
|
||||
// a var, that is, either a parameter to the function or a variable
|
||||
// declared when you call BinaryenAddFunction. See BinaryenAddFunction
|
||||
// for more details.
|
||||
BinaryenExpressionRef BinaryenGetLocal(BinaryenModuleRef module, BinaryenIndex index, BinaryenType type);
|
||||
BinaryenExpressionRef BinaryenSetLocal(BinaryenModuleRef module, BinaryenIndex index, BinaryenExpressionRef value);
|
||||
BinaryenExpressionRef BinaryenTeeLocal(BinaryenModuleRef module, BinaryenIndex index, BinaryenExpressionRef value);
|
||||
BinaryenExpressionRef BinaryenGetGlobal(BinaryenModuleRef module, const char *name, BinaryenType type);
|
||||
BinaryenExpressionRef BinaryenSetGlobal(BinaryenModuleRef module, const char *name, BinaryenExpressionRef value);
|
||||
// Load: align can be 0, in which case it will be the natural alignment (equal to bytes)
|
||||
BinaryenExpressionRef BinaryenLoad(BinaryenModuleRef module, uint32_t bytes, int8_t signed_, uint32_t offset, uint32_t align, BinaryenType type, BinaryenExpressionRef ptr);
|
||||
// Store: align can be 0, in which case it will be the natural alignment (equal to bytes)
|
||||
BinaryenExpressionRef BinaryenStore(BinaryenModuleRef module, uint32_t bytes, uint32_t offset, uint32_t align, BinaryenExpressionRef ptr, BinaryenExpressionRef value, BinaryenType type);
|
||||
BinaryenExpressionRef BinaryenConst(BinaryenModuleRef module, struct BinaryenLiteral value);
|
||||
BinaryenExpressionRef BinaryenUnary(BinaryenModuleRef module, BinaryenOp op, BinaryenExpressionRef value);
|
||||
BinaryenExpressionRef BinaryenBinary(BinaryenModuleRef module, BinaryenOp op, BinaryenExpressionRef left, BinaryenExpressionRef right);
|
||||
BinaryenExpressionRef BinaryenSelect(BinaryenModuleRef module, BinaryenExpressionRef condition, BinaryenExpressionRef ifTrue, BinaryenExpressionRef ifFalse);
|
||||
BinaryenExpressionRef BinaryenDrop(BinaryenModuleRef module, BinaryenExpressionRef value);
|
||||
// Return: value can be NULL
|
||||
BinaryenExpressionRef BinaryenReturn(BinaryenModuleRef module, BinaryenExpressionRef value);
|
||||
// Host: name may be NULL
|
||||
BinaryenExpressionRef BinaryenHost(BinaryenModuleRef module, BinaryenOp op, const char* name, BinaryenExpressionRef* operands, BinaryenIndex numOperands);
|
||||
BinaryenExpressionRef BinaryenNop(BinaryenModuleRef module);
|
||||
BinaryenExpressionRef BinaryenUnreachable(BinaryenModuleRef module);
|
||||
BinaryenExpressionRef BinaryenAtomicRMW(BinaryenModuleRef module, BinaryenOp op, BinaryenIndex bytes, BinaryenIndex offset, BinaryenExpressionRef ptr, BinaryenExpressionRef value, BinaryenType type);
|
||||
BinaryenExpressionRef BinaryenAtomicCmpxchg(BinaryenModuleRef module, BinaryenIndex bytes, BinaryenIndex offset, BinaryenExpressionRef ptr, BinaryenExpressionRef expected, BinaryenExpressionRef replacement, BinaryenType type);
|
||||
BinaryenExpressionRef BinaryenAtomicWait(BinaryenModuleRef module, BinaryenExpressionRef ptr, BinaryenExpressionRef expected, BinaryenExpressionRef timeout, BinaryenType type);
|
||||
BinaryenExpressionRef BinaryenAtomicWake(BinaryenModuleRef module, BinaryenExpressionRef ptr, BinaryenExpressionRef wakeCount);
|
||||
|
||||
// Gets the id (kind) of the specified expression.
|
||||
BinaryenExpressionId BinaryenExpressionGetId(BinaryenExpressionRef expr);
|
||||
// Gets the type of the specified expression.
|
||||
BinaryenType BinaryenExpressionGetType(BinaryenExpressionRef expr);
|
||||
// Print an expression to stdout. Useful for debugging.
|
||||
void BinaryenExpressionPrint(BinaryenExpressionRef expr);
|
||||
// Gets the 32-bit integer value of the specified `Const` expression.
|
||||
int32_t BinaryenConstGetValueI32(BinaryenExpressionRef expr);
|
||||
// Gets the 64-bit integer value of the specified `Const` expression.
|
||||
int64_t BinaryenConstGetValueI64(BinaryenExpressionRef expr);
|
||||
// Gets the low 32-bits of a 64-bit integer value of the specified `Const` expression. Useful where I64 returning exports are illegal, i.e. binaryen.js.
|
||||
int32_t BinaryenConstGetValueI64Low(BinaryenExpressionRef expr);
|
||||
// Gets the high 32-bits of a 64-bit integer value of the specified `Const` expression. Useful where I64 returning exports are illegal, i.e. binaryen.js.
|
||||
int32_t BinaryenConstGetValueI64High(BinaryenExpressionRef expr);
|
||||
// Gets the 32-bit float value of the specified `Const` expression.
|
||||
float BinaryenConstGetValueF32(BinaryenExpressionRef expr);
|
||||
// Gets the 64-bit float value of the specified `Const` expression.
|
||||
double BinaryenConstGetValueF64(BinaryenExpressionRef expr);
|
||||
|
||||
// Functions
|
||||
|
||||
typedef void* BinaryenFunctionRef;
|
||||
|
||||
// Adds a function to the module. This is thread-safe.
|
||||
// @varTypes: the types of variables. In WebAssembly, vars share
|
||||
// an index space with params. In other words, params come from
|
||||
// the function type, and vars are provided in this call, and
|
||||
// together they are all the locals. The order is first params
|
||||
// and then vars, so if you have one param it will be at index
|
||||
// 0 (and written $0), and if you also have 2 vars they will be
|
||||
// at indexes 1 and 2, etc., that is, they share an index space.
|
||||
BinaryenFunctionRef BinaryenAddFunction(BinaryenModuleRef module, const char* name, BinaryenFunctionTypeRef type, BinaryenType* varTypes, BinaryenIndex numVarTypes, BinaryenExpressionRef body);
|
||||
|
||||
// Imports
|
||||
|
||||
typedef void* BinaryenImportRef;
|
||||
|
||||
BinaryenImportRef BinaryenAddImport(BinaryenModuleRef module, const char* internalName, const char* externalModuleName, const char *externalBaseName, BinaryenFunctionTypeRef type);
|
||||
void BinaryenRemoveImport(BinaryenModuleRef module, const char* internalName);
|
||||
|
||||
// Exports
|
||||
|
||||
typedef void* BinaryenExportRef;
|
||||
|
||||
BinaryenExportRef BinaryenAddExport(BinaryenModuleRef module, const char* internalName, const char* externalName);
|
||||
void BinaryenRemoveExport(BinaryenModuleRef module, const char* externalName);
|
||||
|
||||
// Globals
|
||||
|
||||
typedef void* BinaryenGlobalRef;
|
||||
|
||||
BinaryenGlobalRef BinaryenAddGlobal(BinaryenModuleRef module, const char* name, BinaryenType type, int8_t mutable_, BinaryenExpressionRef init);
|
||||
|
||||
// Function table. One per module
|
||||
|
||||
void BinaryenSetFunctionTable(BinaryenModuleRef module, BinaryenFunctionRef* funcs, BinaryenIndex numFuncs);
|
||||
|
||||
// Memory. One per module
|
||||
|
||||
// Each segment has data in segments, a start offset in segmentOffsets, and a size in segmentSizes.
|
||||
// exportName can be NULL
|
||||
void BinaryenSetMemory(BinaryenModuleRef module, BinaryenIndex initial, BinaryenIndex maximum, const char* exportName, const char **segments, BinaryenExpressionRef* segmentOffsets, BinaryenIndex* segmentSizes, BinaryenIndex numSegments);
|
||||
|
||||
// Start function. One per module
|
||||
|
||||
void BinaryenSetStart(BinaryenModuleRef module, BinaryenFunctionRef start);
|
||||
|
||||
//
|
||||
// ========== Module Operations ==========
|
||||
//
|
||||
|
||||
// Parse a module in s-expression text format
|
||||
BinaryenModuleRef BinaryenModuleParse(const char* text);
|
||||
|
||||
// Print a module to stdout in s-expression text format. Useful for debugging.
|
||||
void BinaryenModulePrint(BinaryenModuleRef module);
|
||||
|
||||
// Print a module to stdout in asm.js syntax.
|
||||
void BinaryenModulePrintAsmjs(BinaryenModuleRef module);
|
||||
|
||||
// Validate a module, showing errors on problems.
|
||||
// @return 0 if an error occurred, 1 if validated succesfully
|
||||
int BinaryenModuleValidate(BinaryenModuleRef module);
|
||||
|
||||
// Run the standard optimization passes on the module.
|
||||
void BinaryenModuleOptimize(BinaryenModuleRef module);
|
||||
|
||||
// Runs the specified passes on the module.
|
||||
void BinaryenModuleRunPasses(BinaryenModuleRef module, const char **passes, BinaryenIndex numPasses);
|
||||
|
||||
// Auto-generate drop() operations where needed. This lets you generate code without
|
||||
// worrying about where they are needed. (It is more efficient to do it yourself,
|
||||
// but simpler to use autodrop).
|
||||
void BinaryenModuleAutoDrop(BinaryenModuleRef module);
|
||||
|
||||
// Serialize a module into binary form.
|
||||
// @return how many bytes were written. This will be less than or equal to outputSize
|
||||
size_t BinaryenModuleWrite(BinaryenModuleRef module, char* output, size_t outputSize);
|
||||
|
||||
// Deserialize a module from binary form.
|
||||
BinaryenModuleRef BinaryenModuleRead(char* input, size_t inputSize);
|
||||
|
||||
// Execute a module in the Binaryen interpreter. This will create an instance of
|
||||
// the module, run it in the interpreter - which means running the start method -
|
||||
// and then destroying the instance.
|
||||
void BinaryenModuleInterpret(BinaryenModuleRef module);
|
||||
|
||||
//
|
||||
// ========== CFG / Relooper ==========
|
||||
//
|
||||
// General usage is (1) create a relooper, (2) create blocks, (3) add
|
||||
// branches between them, (4) render the output.
|
||||
//
|
||||
// See Relooper.h for more details
|
||||
|
||||
typedef void* RelooperRef;
|
||||
typedef void* RelooperBlockRef;
|
||||
|
||||
// Create a relooper instance
|
||||
RelooperRef RelooperCreate(void);
|
||||
|
||||
// Create a basic block that ends with nothing, or with some simple branching
|
||||
RelooperBlockRef RelooperAddBlock(RelooperRef relooper, BinaryenExpressionRef code);
|
||||
|
||||
// Create a branch to another basic block
|
||||
// The branch can have code on it, that is executed as the branch happens. this is useful for phis. otherwise, code can be NULL
|
||||
void RelooperAddBranch(RelooperBlockRef from, RelooperBlockRef to, BinaryenExpressionRef condition, BinaryenExpressionRef code);
|
||||
|
||||
// Create a basic block that ends a switch on a condition
|
||||
RelooperBlockRef RelooperAddBlockWithSwitch(RelooperRef relooper, BinaryenExpressionRef code, BinaryenExpressionRef condition);
|
||||
|
||||
// Create a switch-style branch to another basic block. The block's switch table will have these indexes going to that target
|
||||
void RelooperAddBranchForSwitch(RelooperBlockRef from, RelooperBlockRef to, BinaryenIndex* indexes, BinaryenIndex numIndexes, BinaryenExpressionRef code);
|
||||
|
||||
// Generate structed wasm control flow from the CFG of blocks and branches that were created
|
||||
// on this relooper instance. This returns the rendered output, and also disposes of the
|
||||
// relooper and its blocks and branches, as they are no longer needed.
|
||||
// @param labelHelper To render irreducible control flow, we may need a helper variable to
|
||||
// guide us to the right target label. This value should be an index of
|
||||
// an i32 local variable that is free for us to use.
|
||||
BinaryenExpressionRef RelooperRenderAndDispose(RelooperRef relooper, RelooperBlockRef entry, BinaryenIndex labelHelper, BinaryenModuleRef module);
|
||||
|
||||
//
|
||||
// ========= Other APIs =========
|
||||
//
|
||||
|
||||
// Sets whether API tracing is on or off. It is off by default. When on, each call
|
||||
// to an API method will print out C code equivalent to it, which is useful for
|
||||
// auto-generating standalone testcases from projects using the API.
|
||||
// When calling this to turn on tracing, the prelude of the full program is printed,
|
||||
// and when calling it to turn it off, the ending of the program is printed, giving
|
||||
// you the full compilable testcase.
|
||||
// TODO: compile-time option to enable/disable this feature entirely at build time?
|
||||
void BinaryenSetAPITracing(int on);
|
||||
|
||||
//
|
||||
// ========= Utilities =========
|
||||
//
|
||||
|
||||
// Note that this function has been added because there is no better alternative
|
||||
// currently and is scheduled for removal once there is one. It takes the same set
|
||||
// of parameters as BinaryenAddFunctionType but instead of adding a new function
|
||||
// signature, it returns a pointer to the existing signature or NULL if there is no
|
||||
// such signature yet.
|
||||
BinaryenFunctionTypeRef BinaryenGetFunctionTypeBySignature(BinaryenModuleRef module, BinaryenType result, BinaryenType* paramTypes, BinaryenIndex numParams);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // wasm_binaryen_c_h
|
@ -1,4 +0,0 @@
|
||||
SET(cfg_SOURCES
|
||||
Relooper.cpp
|
||||
)
|
||||
ADD_LIBRARY(cfg STATIC ${cfg_SOURCES})
|
File diff suppressed because it is too large
Load Diff
@ -1,361 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
This is an optimized C++ implemention of the Relooper algorithm originally
|
||||
developed as part of Emscripten. This implementation includes optimizations
|
||||
added since the original academic paper [1] was published about it.
|
||||
|
||||
[1] Alon Zakai. 2011. Emscripten: an LLVM-to-JavaScript compiler. In Proceedings of the ACM international conference companion on Object oriented programming systems languages and applications companion (SPLASH '11). ACM, New York, NY, USA, 301-312. DOI=10.1145/2048147.2048224 http://doi.acm.org/10.1145/2048147.2048224
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
#include "wasm.h"
|
||||
#include "wasm-builder.h"
|
||||
|
||||
namespace CFG {
|
||||
|
||||
class RelooperBuilder : public wasm::Builder {
|
||||
wasm::Index labelHelper;
|
||||
|
||||
public:
|
||||
RelooperBuilder(wasm::Module& wasm, wasm::Index labelHelper) : wasm::Builder(wasm), labelHelper(labelHelper) {}
|
||||
|
||||
wasm::GetLocal* makeGetLabel() {
|
||||
return makeGetLocal(labelHelper, wasm::i32);
|
||||
}
|
||||
wasm::SetLocal* makeSetLabel(wasm::Index value) {
|
||||
return makeSetLocal(labelHelper, makeConst(wasm::Literal(int32_t(value))));
|
||||
}
|
||||
wasm::Binary* makeCheckLabel(wasm::Index value) {
|
||||
return makeBinary(wasm::EqInt32, makeGetLabel(), makeConst(wasm::Literal(int32_t(value))));
|
||||
}
|
||||
|
||||
// breaks are on blocks, as they can be specific, we make one wasm block per basic block
|
||||
wasm::Break* makeBlockBreak(int id) {
|
||||
return wasm::Builder::makeBreak(getBlockBreakName(id));
|
||||
}
|
||||
// continues are on shapes, as there is one per loop, and if we have more than one
|
||||
// going there, it is irreducible control flow anyhow
|
||||
wasm::Break* makeShapeContinue(int id) {
|
||||
return wasm::Builder::makeBreak(getShapeContinueName(id));
|
||||
}
|
||||
|
||||
wasm::Name getBlockBreakName(int id) {
|
||||
return wasm::Name(std::string("block$") + std::to_string(id) + "$break");
|
||||
}
|
||||
wasm::Name getShapeContinueName(int id) {
|
||||
return wasm::Name(std::string("shape$") + std::to_string(id) + "$continue");
|
||||
}
|
||||
};
|
||||
|
||||
struct Block;
|
||||
struct Shape;
|
||||
|
||||
// Info about a branching from one block to another
|
||||
struct Branch {
|
||||
enum FlowType {
|
||||
Direct = 0, // We will directly reach the right location through other means, no need for continue or break
|
||||
Break = 1,
|
||||
Continue = 2
|
||||
};
|
||||
Shape *Ancestor; // If not NULL, this shape is the relevant one for purposes of getting to the target block. We break or continue on it
|
||||
Branch::FlowType Type; // If Ancestor is not NULL, this says whether to break or continue
|
||||
|
||||
// A branch either has a condition expression if the block ends in ifs, or if the block ends in a switch, then a list of indexes, which
|
||||
// becomes the indexes in the table of the switch. If not a switch, the condition can be any expression.
|
||||
wasm::Expression* Condition;
|
||||
std::unique_ptr<std::vector<wasm::Index>> SwitchValues; // switches are rare, so have just a pointer here
|
||||
|
||||
wasm::Expression* Code; // If provided, code that is run right before the branch is taken. This is useful for phis
|
||||
|
||||
Branch(wasm::Expression* ConditionInit, wasm::Expression* CodeInit = nullptr);
|
||||
|
||||
Branch(std::vector<wasm::Index>&& ValuesInit, wasm::Expression* CodeInit = nullptr);
|
||||
|
||||
// Emits code for branch
|
||||
wasm::Expression* Render(RelooperBuilder& Builder, Block *Target, bool SetLabel);
|
||||
};
|
||||
|
||||
// like std::set, except that begin() -> end() iterates in the
|
||||
// order that elements were added to the set (not in the order
|
||||
// of operator<(T, T))
|
||||
template<typename T>
|
||||
struct InsertOrderedSet
|
||||
{
|
||||
std::map<T, typename std::list<T>::iterator> Map;
|
||||
std::list<T> List;
|
||||
|
||||
typedef typename std::list<T>::iterator iterator;
|
||||
iterator begin() { return List.begin(); }
|
||||
iterator end() { return List.end(); }
|
||||
|
||||
void erase(const T& val) {
|
||||
auto it = Map.find(val);
|
||||
if (it != Map.end()) {
|
||||
List.erase(it->second);
|
||||
Map.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void erase(iterator position) {
|
||||
Map.erase(*position);
|
||||
List.erase(position);
|
||||
}
|
||||
|
||||
// cheating a bit, not returning the iterator
|
||||
void insert(const T& val) {
|
||||
auto it = Map.find(val);
|
||||
if (it == Map.end()) {
|
||||
List.push_back(val);
|
||||
Map.insert(std::make_pair(val, --List.end()));
|
||||
}
|
||||
}
|
||||
|
||||
size_t size() const { return Map.size(); }
|
||||
bool empty() const { return Map.empty(); }
|
||||
|
||||
void clear() {
|
||||
Map.clear();
|
||||
List.clear();
|
||||
}
|
||||
|
||||
size_t count(const T& val) const { return Map.count(val); }
|
||||
|
||||
InsertOrderedSet() {}
|
||||
InsertOrderedSet(const InsertOrderedSet& other) {
|
||||
*this = other;
|
||||
}
|
||||
InsertOrderedSet& operator=(const InsertOrderedSet& other) {
|
||||
clear();
|
||||
for (auto i : other.List) {
|
||||
insert(i); // inserting manually creates proper iterators
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
// like std::map, except that begin() -> end() iterates in the
|
||||
// order that elements were added to the map (not in the order
|
||||
// of operator<(Key, Key))
|
||||
template<typename Key, typename T>
|
||||
struct InsertOrderedMap
|
||||
{
|
||||
std::map<Key, typename std::list<std::pair<Key,T>>::iterator> Map;
|
||||
std::list<std::pair<Key,T>> List;
|
||||
|
||||
T& operator[](const Key& k) {
|
||||
auto it = Map.find(k);
|
||||
if (it == Map.end()) {
|
||||
List.push_back(std::make_pair(k, T()));
|
||||
auto e = --List.end();
|
||||
Map.insert(std::make_pair(k, e));
|
||||
return e->second;
|
||||
}
|
||||
return it->second->second;
|
||||
}
|
||||
|
||||
typedef typename std::list<std::pair<Key,T>>::iterator iterator;
|
||||
iterator begin() { return List.begin(); }
|
||||
iterator end() { return List.end(); }
|
||||
|
||||
void erase(const Key& k) {
|
||||
auto it = Map.find(k);
|
||||
if (it != Map.end()) {
|
||||
List.erase(it->second);
|
||||
Map.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void erase(iterator position) {
|
||||
erase(position->first);
|
||||
}
|
||||
|
||||
size_t size() const { return Map.size(); }
|
||||
bool empty() const { return Map.empty(); }
|
||||
size_t count(const Key& k) const { return Map.count(k); }
|
||||
|
||||
InsertOrderedMap() {}
|
||||
InsertOrderedMap(InsertOrderedMap& other) {
|
||||
abort(); // TODO, watch out for iterators
|
||||
}
|
||||
InsertOrderedMap& operator=(const InsertOrderedMap& other) {
|
||||
abort(); // TODO, watch out for iterators
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
typedef InsertOrderedSet<Block*> BlockSet;
|
||||
typedef InsertOrderedMap<Block*, Branch*> BlockBranchMap;
|
||||
|
||||
// Represents a basic block of code - some instructions that end with a
|
||||
// control flow modifier (a branch, return or throw).
|
||||
struct Block {
|
||||
// Branches become processed after we finish the shape relevant to them. For example,
|
||||
// when we recreate a loop, branches to the loop start become continues and are now
|
||||
// processed. When we calculate what shape to generate from a set of blocks, we ignore
|
||||
// processed branches.
|
||||
// Blocks own the Branch objects they use, and destroy them when done.
|
||||
BlockBranchMap BranchesOut;
|
||||
BlockSet BranchesIn;
|
||||
BlockBranchMap ProcessedBranchesOut;
|
||||
BlockSet ProcessedBranchesIn;
|
||||
Shape *Parent; // The shape we are directly inside
|
||||
int Id; // A unique identifier, defined when added to relooper
|
||||
wasm::Expression* Code; // The code in this block. This can be arbitrary wasm code, including internal control flow, it should just not branch to the outside
|
||||
wasm::Expression* SwitchCondition; // If nullptr, then this block ends in ifs (or nothing). otherwise, this block ends in a switch, done on this condition
|
||||
bool IsCheckedMultipleEntry; // If true, we are a multiple entry, so reaching us requires setting the label variable
|
||||
|
||||
Block(wasm::Expression* CodeInit, wasm::Expression* SwitchConditionInit = nullptr);
|
||||
~Block();
|
||||
|
||||
// Add a branch: if the condition holds we branch (or if null, we branch if all others failed)
|
||||
// Note that there can be only one branch from A to B (if you need multiple conditions for the branch,
|
||||
// create a more interesting expression in the Condition).
|
||||
void AddBranchTo(Block *Target, wasm::Expression* Condition, wasm::Expression* Code = nullptr);
|
||||
|
||||
// Add a switch branch: if the switch condition is one of these values, we branch (or if the list is empty, we are the default)
|
||||
// Note that there can be only one branch from A to B (if you need multiple values for the branch, that's what the array and default are for).
|
||||
void AddSwitchBranchTo(Block *Target, std::vector<wasm::Index>&& Values, wasm::Expression* Code = nullptr);
|
||||
|
||||
// Emit code for the block, including its contents and branchings out
|
||||
wasm::Expression* Render(RelooperBuilder& Builder, bool InLoop);
|
||||
};
|
||||
|
||||
// Represents a structured control flow shape, one of
|
||||
//
|
||||
// Simple: No control flow at all, just instructions in a single
|
||||
// basic block.
|
||||
//
|
||||
// Multiple: A shape with at least one entry. We may visit one of
|
||||
// the entries, or none, before continuing to the next
|
||||
// shape after this.
|
||||
//
|
||||
// Loop: An infinite loop. We assume the property that a loop
|
||||
// will always visit one of its entries, and so for example
|
||||
// we cannot have a loop containing a multiple and nothing
|
||||
// else (since we might not visit any of the multiple's
|
||||
// blocks). Multiple entries are possible for the block,
|
||||
// however, which is necessary for irreducible control
|
||||
// flow, of course.
|
||||
//
|
||||
|
||||
struct SimpleShape;
|
||||
struct MultipleShape;
|
||||
struct LoopShape;
|
||||
|
||||
struct Shape {
|
||||
int Id; // A unique identifier. Used to identify loops, labels are Lx where x is the Id. Defined when added to relooper
|
||||
Shape *Next; // The shape that will appear in the code right after this one
|
||||
Shape *Natural; // The shape that control flow gets to naturally (if there is Next, then this is Next)
|
||||
|
||||
enum ShapeType {
|
||||
Simple,
|
||||
Multiple,
|
||||
Loop
|
||||
};
|
||||
ShapeType Type;
|
||||
|
||||
Shape(ShapeType TypeInit) : Id(-1), Next(NULL), Type(TypeInit) {}
|
||||
virtual ~Shape() {}
|
||||
|
||||
virtual wasm::Expression* Render(RelooperBuilder& Builder, bool InLoop) = 0;
|
||||
|
||||
static SimpleShape *IsSimple(Shape *It) { return It && It->Type == Simple ? (SimpleShape*)It : NULL; }
|
||||
static MultipleShape *IsMultiple(Shape *It) { return It && It->Type == Multiple ? (MultipleShape*)It : NULL; }
|
||||
static LoopShape *IsLoop(Shape *It) { return It && It->Type == Loop ? (LoopShape*)It : NULL; }
|
||||
};
|
||||
|
||||
struct SimpleShape : public Shape {
|
||||
Block *Inner;
|
||||
|
||||
SimpleShape() : Shape(Simple), Inner(NULL) {}
|
||||
wasm::Expression* Render(RelooperBuilder& Builder, bool InLoop) override;
|
||||
};
|
||||
|
||||
typedef std::map<int, Shape*> IdShapeMap;
|
||||
|
||||
struct MultipleShape : public Shape {
|
||||
IdShapeMap InnerMap; // entry block ID -> shape
|
||||
|
||||
MultipleShape() : Shape(Multiple) {}
|
||||
|
||||
wasm::Expression* Render(RelooperBuilder& Builder, bool InLoop) override;
|
||||
};
|
||||
|
||||
struct LoopShape : public Shape {
|
||||
Shape *Inner;
|
||||
|
||||
BlockSet Entries; // we must visit at least one of these
|
||||
|
||||
LoopShape() : Shape(Loop), Inner(NULL) {}
|
||||
wasm::Expression* Render(RelooperBuilder& Builder, bool InLoop) override;
|
||||
};
|
||||
|
||||
// Implements the relooper algorithm for a function's blocks.
|
||||
//
|
||||
// Usage:
|
||||
// 1. Instantiate this struct.
|
||||
// 2. Call AddBlock with the blocks you have. Each should already
|
||||
// have its branchings in specified (the branchings out will
|
||||
// be calculated by the relooper).
|
||||
// 3. Call Render().
|
||||
//
|
||||
// Implementation details: The Relooper instance has
|
||||
// ownership of the blocks and shapes, and frees them when done.
|
||||
struct Relooper {
|
||||
std::deque<Block*> Blocks;
|
||||
std::deque<Shape*> Shapes;
|
||||
Shape *Root;
|
||||
bool MinSize;
|
||||
int BlockIdCounter;
|
||||
int ShapeIdCounter;
|
||||
|
||||
Relooper();
|
||||
~Relooper();
|
||||
|
||||
void AddBlock(Block *New, int Id=-1);
|
||||
|
||||
// Calculates the shapes
|
||||
void Calculate(Block *Entry);
|
||||
|
||||
// Renders the result.
|
||||
wasm::Expression* Render(RelooperBuilder& Builder);
|
||||
|
||||
// Sets us to try to minimize size
|
||||
void SetMinSize(bool MinSize_) { MinSize = MinSize_; }
|
||||
};
|
||||
|
||||
typedef InsertOrderedMap<Block*, BlockSet> BlockBlockSetMap;
|
||||
|
||||
#ifdef RELOOPER_DEBUG
|
||||
struct Debugging {
|
||||
static void Dump(BlockSet &Blocks, const char *prefix=NULL);
|
||||
static void Dump(Shape *S, const char *prefix=NULL);
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace CFG
|
@ -1,344 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//
|
||||
// Convert the AST to a CFG, while traversing it.
|
||||
//
|
||||
// Note that this is not the same as the relooper CFG. The relooper is
|
||||
// designed for compilation to an AST, this is for processing. There is
|
||||
// no built-in support for transforming this CFG into the AST back
|
||||
// again, it is just metadata on the side for computation purposes.
|
||||
//
|
||||
// Usage: As the traversal proceeds, you can note information and add it to
|
||||
// the current basic block using currBasicBlock, on the contents
|
||||
// property, whose type is user-defined.
|
||||
//
|
||||
|
||||
#ifndef cfg_traversal_h
|
||||
#define cfg_traversal_h
|
||||
|
||||
#include "wasm.h"
|
||||
#include "wasm-traversal.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
template<typename SubType, typename VisitorType, typename Contents>
|
||||
struct CFGWalker : public ControlFlowWalker<SubType, VisitorType> {
|
||||
|
||||
// public interface
|
||||
|
||||
struct BasicBlock {
|
||||
Contents contents; // custom contents
|
||||
std::vector<BasicBlock*> out, in;
|
||||
};
|
||||
|
||||
BasicBlock* entry; // the entry block
|
||||
|
||||
BasicBlock* makeBasicBlock() { // override this with code to create a BasicBlock if necessary
|
||||
return new BasicBlock();
|
||||
}
|
||||
|
||||
// internal details
|
||||
|
||||
std::vector<std::unique_ptr<BasicBlock>> basicBlocks; // all the blocks
|
||||
std::vector<BasicBlock*> loopTops; // blocks that are the tops of loops, i.e., have backedges to them
|
||||
|
||||
// traversal state
|
||||
BasicBlock* currBasicBlock; // the current block in play during traversal. can be nullptr if unreachable,
|
||||
// but note that we don't do a deep unreachability analysis - just enough
|
||||
// to avoid constructing obviously-unreachable blocks (we do a full reachability
|
||||
// analysis on the CFG once it is constructed).
|
||||
std::map<Expression*, std::vector<BasicBlock*>> branches; // a block or loop => its branches
|
||||
std::vector<BasicBlock*> ifStack;
|
||||
std::vector<BasicBlock*> loopStack;
|
||||
|
||||
void startBasicBlock() {
|
||||
currBasicBlock = makeBasicBlock();
|
||||
basicBlocks.push_back(std::unique_ptr<BasicBlock>(currBasicBlock));
|
||||
}
|
||||
|
||||
void startUnreachableBlock() {
|
||||
currBasicBlock = nullptr;
|
||||
}
|
||||
|
||||
static void doStartUnreachableBlock(SubType* self, Expression** currp) {
|
||||
self->startUnreachableBlock();
|
||||
}
|
||||
|
||||
void link(BasicBlock* from, BasicBlock* to) {
|
||||
if (!from || !to) return; // if one of them is not reachable, ignore
|
||||
from->out.push_back(to);
|
||||
to->in.push_back(from);
|
||||
}
|
||||
|
||||
static void doEndBlock(SubType* self, Expression** currp) {
|
||||
auto* curr = (*currp)->cast<Block>();
|
||||
if (!curr->name.is()) return;
|
||||
auto iter = self->branches.find(curr);
|
||||
if (iter == self->branches.end()) return;
|
||||
auto& origins = iter->second;
|
||||
if (origins.size() == 0) return;
|
||||
// we have branches to here, so we need a new block
|
||||
auto* last = self->currBasicBlock;
|
||||
self->startBasicBlock();
|
||||
self->link(last, self->currBasicBlock); // fallthrough
|
||||
// branches to the new one
|
||||
for (auto* origin : origins) {
|
||||
self->link(origin, self->currBasicBlock);
|
||||
}
|
||||
self->branches.erase(curr);
|
||||
}
|
||||
|
||||
static void doStartIfTrue(SubType* self, Expression** currp) {
|
||||
auto* last = self->currBasicBlock;
|
||||
self->startBasicBlock();
|
||||
self->link(last, self->currBasicBlock); // ifTrue
|
||||
self->ifStack.push_back(last); // the block before the ifTrue
|
||||
}
|
||||
|
||||
static void doStartIfFalse(SubType* self, Expression** currp) {
|
||||
self->ifStack.push_back(self->currBasicBlock); // the ifTrue fallthrough
|
||||
self->startBasicBlock();
|
||||
self->link(self->ifStack[self->ifStack.size() - 2], self->currBasicBlock); // before if -> ifFalse
|
||||
}
|
||||
|
||||
static void doEndIf(SubType* self, Expression** currp) {
|
||||
auto* last = self->currBasicBlock;
|
||||
self->startBasicBlock();
|
||||
self->link(last, self->currBasicBlock); // last one is ifFalse's fallthrough if there was one, otherwise it's the ifTrue fallthrough
|
||||
if ((*currp)->cast<If>()->ifFalse) {
|
||||
// we just linked ifFalse, need to link ifTrue to the end
|
||||
self->link(self->ifStack.back(), self->currBasicBlock);
|
||||
self->ifStack.pop_back();
|
||||
} else {
|
||||
// no ifFalse, so add a fallthrough for if the if is not taken
|
||||
self->link(self->ifStack.back(), self->currBasicBlock);
|
||||
}
|
||||
self->ifStack.pop_back();
|
||||
}
|
||||
|
||||
static void doStartLoop(SubType* self, Expression** currp) {
|
||||
auto* last = self->currBasicBlock;
|
||||
self->startBasicBlock();
|
||||
self->loopTops.push_back(self->currBasicBlock); // a loop with no backedges would still be counted here, but oh well
|
||||
self->link(last, self->currBasicBlock);
|
||||
self->loopStack.push_back(self->currBasicBlock);
|
||||
}
|
||||
|
||||
static void doEndLoop(SubType* self, Expression** currp) {
|
||||
auto* last = self->currBasicBlock;
|
||||
self->startBasicBlock();
|
||||
self->link(last, self->currBasicBlock); // fallthrough
|
||||
auto* curr = (*currp)->cast<Loop>();
|
||||
// branches to the top of the loop
|
||||
if (curr->name.is()) {
|
||||
auto* loopStart = self->loopStack.back();
|
||||
auto& origins = self->branches[curr];
|
||||
for (auto* origin : origins) {
|
||||
self->link(origin, loopStart);
|
||||
}
|
||||
self->branches.erase(curr);
|
||||
}
|
||||
self->loopStack.pop_back();
|
||||
}
|
||||
|
||||
static void doEndBreak(SubType* self, Expression** currp) {
|
||||
auto* curr = (*currp)->cast<Break>();
|
||||
self->branches[self->findBreakTarget(curr->name)].push_back(self->currBasicBlock); // branch to the target
|
||||
if (curr->condition) {
|
||||
auto* last = self->currBasicBlock;
|
||||
self->startBasicBlock();
|
||||
self->link(last, self->currBasicBlock); // we might fall through
|
||||
} else {
|
||||
self->startUnreachableBlock();
|
||||
}
|
||||
}
|
||||
|
||||
static void doEndSwitch(SubType* self, Expression** currp) {
|
||||
auto* curr = (*currp)->cast<Switch>();
|
||||
std::set<Name> seen; // we might see the same label more than once; do not spam branches
|
||||
for (Name target : curr->targets) {
|
||||
if (!seen.count(target)) {
|
||||
self->branches[self->findBreakTarget(target)].push_back(self->currBasicBlock); // branch to the target
|
||||
seen.insert(target);
|
||||
}
|
||||
}
|
||||
if (!seen.count(curr->default_)) {
|
||||
self->branches[self->findBreakTarget(curr->default_)].push_back(self->currBasicBlock); // branch to the target
|
||||
}
|
||||
self->startUnreachableBlock();
|
||||
}
|
||||
|
||||
static void scan(SubType* self, Expression** currp) {
|
||||
Expression* curr = *currp;
|
||||
|
||||
switch (curr->_id) {
|
||||
case Expression::Id::BlockId: {
|
||||
self->pushTask(SubType::doEndBlock, currp);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::IfId: {
|
||||
self->pushTask(SubType::doEndIf, currp);
|
||||
auto* ifFalse = curr->cast<If>()->ifFalse;
|
||||
if (ifFalse) {
|
||||
self->pushTask(SubType::scan, &curr->cast<If>()->ifFalse);
|
||||
self->pushTask(SubType::doStartIfFalse, currp);
|
||||
}
|
||||
self->pushTask(SubType::scan, &curr->cast<If>()->ifTrue);
|
||||
self->pushTask(SubType::doStartIfTrue, currp);
|
||||
self->pushTask(SubType::scan, &curr->cast<If>()->condition);
|
||||
return; // don't do anything else
|
||||
}
|
||||
case Expression::Id::LoopId: {
|
||||
self->pushTask(SubType::doEndLoop, currp);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::BreakId: {
|
||||
self->pushTask(SubType::doEndBreak, currp);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::SwitchId: {
|
||||
self->pushTask(SubType::doEndSwitch, currp);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::ReturnId: {
|
||||
self->pushTask(SubType::doStartUnreachableBlock, currp);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::UnreachableId: {
|
||||
self->pushTask(SubType::doStartUnreachableBlock, currp);
|
||||
break;
|
||||
}
|
||||
default: {}
|
||||
}
|
||||
|
||||
ControlFlowWalker<SubType, VisitorType>::scan(self, currp);
|
||||
|
||||
switch (curr->_id) {
|
||||
case Expression::Id::LoopId: {
|
||||
self->pushTask(SubType::doStartLoop, currp);
|
||||
break;
|
||||
}
|
||||
default: {}
|
||||
}
|
||||
}
|
||||
|
||||
void doWalkFunction(Function* func) {
|
||||
basicBlocks.clear();
|
||||
|
||||
startBasicBlock();
|
||||
entry = currBasicBlock;
|
||||
ControlFlowWalker<SubType, VisitorType>::doWalkFunction(func);
|
||||
|
||||
assert(branches.size() == 0);
|
||||
assert(ifStack.size() == 0);
|
||||
assert(loopStack.size() == 0);
|
||||
}
|
||||
|
||||
std::unordered_set<BasicBlock*> findLiveBlocks() {
|
||||
std::unordered_set<BasicBlock*> alive;
|
||||
std::unordered_set<BasicBlock*> queue;
|
||||
queue.insert(entry);
|
||||
while (queue.size() > 0) {
|
||||
auto iter = queue.begin();
|
||||
auto* curr = *iter;
|
||||
queue.erase(iter);
|
||||
alive.insert(curr);
|
||||
for (auto* out : curr->out) {
|
||||
if (!alive.count(out)) queue.insert(out);
|
||||
}
|
||||
}
|
||||
return alive;
|
||||
}
|
||||
|
||||
void unlinkDeadBlocks(std::unordered_set<BasicBlock*> alive) {
|
||||
for (auto& block : basicBlocks) {
|
||||
if (!alive.count(block.get())) {
|
||||
block->in.clear();
|
||||
block->out.clear();
|
||||
continue;
|
||||
}
|
||||
block->in.erase(std::remove_if(block->in.begin(), block->in.end(), [&alive](BasicBlock* other) {
|
||||
return !alive.count(other);
|
||||
}), block->in.end());
|
||||
block->out.erase(std::remove_if(block->out.begin(), block->out.end(), [&alive](BasicBlock* other) {
|
||||
return !alive.count(other);
|
||||
}), block->out.end());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: utility method for optimizing cfg, removing empty blocks depending on their .content
|
||||
|
||||
std::map<BasicBlock*, size_t> debugIds;
|
||||
|
||||
void generateDebugIds() {
|
||||
if (debugIds.size() > 0) return;
|
||||
for (auto& block : basicBlocks) {
|
||||
debugIds[block.get()] = debugIds.size();
|
||||
}
|
||||
}
|
||||
|
||||
void dumpCFG(std::string message) {
|
||||
std::cout << "<==\nCFG [" << message << "]:\n";
|
||||
generateDebugIds();
|
||||
for (auto& block : basicBlocks) {
|
||||
assert(debugIds.count(block.get()) > 0);
|
||||
std::cout << " block " << debugIds[block.get()] << ":\n";
|
||||
block->contents.dump(static_cast<SubType*>(this)->getFunction());
|
||||
for (auto& in : block->in) {
|
||||
assert(debugIds.count(in) > 0);
|
||||
assert(std::find(in->out.begin(), in->out.end(), block.get()) != in->out.end()); // must be a parallel link back
|
||||
}
|
||||
for (auto& out : block->out) {
|
||||
assert(debugIds.count(out) > 0);
|
||||
std::cout << " out: " << debugIds[out] << "\n";
|
||||
assert(std::find(out->in.begin(), out->in.end(), block.get()) != out->in.end()); // must be a parallel link back
|
||||
}
|
||||
checkDuplicates(block->in);
|
||||
checkDuplicates(block->out);
|
||||
}
|
||||
std::cout << "==>\n";
|
||||
}
|
||||
|
||||
private:
|
||||
// links in out and in must be unique
|
||||
void checkDuplicates(std::vector<BasicBlock*>& list) {
|
||||
std::unordered_set<BasicBlock*> seen;
|
||||
for (auto* curr : list) {
|
||||
assert(seen.count(curr) == 0);
|
||||
seen.insert(curr);
|
||||
}
|
||||
}
|
||||
|
||||
void removeLink(std::vector<BasicBlock*>& list, BasicBlock* toRemove) {
|
||||
if (list.size() == 1) {
|
||||
list.clear();
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < list.size(); i++) {
|
||||
if (list[i] == toRemove) {
|
||||
list[i] = list.back();
|
||||
list.pop_back();
|
||||
return;
|
||||
}
|
||||
}
|
||||
WASM_UNREACHABLE();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // cfg_traversal_h
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_compiler_support_h
|
||||
#define wasm_compiler_support_h
|
||||
|
||||
#ifndef __has_feature
|
||||
# define __has_feature(x) 0
|
||||
#endif
|
||||
|
||||
#ifndef __has_builtin
|
||||
# define __has_builtin(x) 0
|
||||
#endif
|
||||
|
||||
// If control flow reaches the point of the WASM_UNREACHABLE(), the program is
|
||||
// undefined.
|
||||
#if __has_builtin(__builtin_unreachable) && defined(NDEBUG)
|
||||
# define WASM_UNREACHABLE() __builtin_unreachable()
|
||||
#elif defined(_MSC_VER)
|
||||
# define WASM_UNREACHABLE() __assume(false)
|
||||
#elif __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
|
||||
# include "sanitizer/common_interface_defs.h"
|
||||
# define WASM_UNREACHABLE() do { __sanitizer_print_stack_trace(); __builtin_trap(); } while (0)
|
||||
#else
|
||||
# include <stdlib.h>
|
||||
# define WASM_UNREACHABLE() abort()
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define WASM_NORETURN __attribute__((noreturn))
|
||||
#elif defined(_MSC_VER)
|
||||
#define WASM_NORETURN __declspec(noreturn)
|
||||
#else
|
||||
#define WASM_NORETURN
|
||||
#endif
|
||||
|
||||
// The code might contain TODOs or stubs that read some values but do nothing
|
||||
// with them. The compiler might fail with [-Werror,-Wunused-variable].
|
||||
// The WASM_UNUSED(varible) is a wrapper that helps to suppress the error.
|
||||
#define WASM_UNUSED(expr) \
|
||||
do { if (sizeof expr) { (void)0; } } while (0)
|
||||
|
||||
#endif // wasm_compiler_support_h
|
@ -1,6 +0,0 @@
|
||||
SET(emscripten-optimizer_SOURCES
|
||||
optimizer-shared.cpp
|
||||
parser.cpp
|
||||
simple_ast.cpp
|
||||
)
|
||||
ADD_LIBRARY(emscripten-optimizer STATIC ${emscripten-optimizer_SOURCES})
|
@ -1,187 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Interned String type, 100% interned on creation. Comparisons are always just a pointer comparison
|
||||
|
||||
#ifndef wasm_istring_h
|
||||
#define wasm_istring_h
|
||||
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <set>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "support/threads.h"
|
||||
#include "support/utilities.h"
|
||||
|
||||
namespace cashew {
|
||||
|
||||
struct IString {
|
||||
const char *str;
|
||||
|
||||
static size_t hash_c(const char *str) { // see http://www.cse.yorku.ca/~oz/hash.html
|
||||
unsigned int hash = 5381;
|
||||
int c;
|
||||
while ((c = *str++)) {
|
||||
hash = ((hash << 5) + hash) ^ c;
|
||||
}
|
||||
return (size_t)hash;
|
||||
}
|
||||
|
||||
class CStringHash : public std::hash<const char *> {
|
||||
public:
|
||||
size_t operator()(const char *str) const {
|
||||
return IString::hash_c(str);
|
||||
}
|
||||
};
|
||||
class CStringEqual : public std::equal_to<const char *> {
|
||||
public:
|
||||
bool operator()(const char *x, const char *y) const {
|
||||
return strcmp(x, y) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
IString() : str(nullptr) {}
|
||||
IString(const char *s, bool reuse=true) { // if reuse=true, then input is assumed to remain alive; not copied
|
||||
assert(s);
|
||||
set(s, reuse);
|
||||
}
|
||||
|
||||
void set(const char *s, bool reuse=true) {
|
||||
typedef std::unordered_set<const char *, CStringHash, CStringEqual> StringSet;
|
||||
|
||||
// if the string isn't already known, we must use a single global
|
||||
// storage location, guarded by a mutex, so each string is allocated
|
||||
// exactly once
|
||||
static std::mutex mutex;
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
// a single global set contains the actual strings, so we allocate each one
|
||||
// exactly once.
|
||||
static StringSet globalStrings;
|
||||
auto globalExisting = globalStrings.find(s);
|
||||
if (globalExisting == globalStrings.end()) {
|
||||
if (!reuse) {
|
||||
static std::vector<std::unique_ptr<std::string>> allocated;
|
||||
allocated.emplace_back(wasm::make_unique<std::string>(s));
|
||||
s = allocated.back()->c_str(); // we'll never modify it, so this is ok
|
||||
}
|
||||
// insert into global set
|
||||
globalStrings.insert(s);
|
||||
} else {
|
||||
s = *globalExisting;
|
||||
}
|
||||
|
||||
str = s;
|
||||
}
|
||||
|
||||
void set(const IString &s) {
|
||||
str = s.str;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
str = nullptr;
|
||||
}
|
||||
|
||||
bool operator==(const IString& other) const {
|
||||
//assert((str == other.str) == !strcmp(str, other.str));
|
||||
return str == other.str; // fast!
|
||||
}
|
||||
bool operator!=(const IString& other) const {
|
||||
//assert((str == other.str) == !strcmp(str, other.str));
|
||||
return str != other.str; // fast!
|
||||
}
|
||||
bool operator<(const IString& other) const {
|
||||
return strcmp(str ? str : "", other.str ? other.str : "") < 0;
|
||||
}
|
||||
|
||||
char operator[](int x) const {
|
||||
return str[x];
|
||||
}
|
||||
|
||||
bool operator!() const { // no string, or empty string
|
||||
return !str || str[0] == 0;
|
||||
}
|
||||
|
||||
const char *c_str() const { return str; }
|
||||
bool equals(const char *other) const { return !strcmp(str, other); }
|
||||
|
||||
bool is() const { return str != nullptr; }
|
||||
bool isNull() const { return str == nullptr; }
|
||||
};
|
||||
|
||||
} // namespace cashew
|
||||
|
||||
// Utilities for creating hashmaps/sets over IStrings
|
||||
|
||||
namespace std {
|
||||
|
||||
template <> struct hash<cashew::IString> : public unary_function<cashew::IString, size_t> {
|
||||
size_t operator()(const cashew::IString& str) const {
|
||||
size_t hash = size_t(str.str);
|
||||
return hash = ((hash << 5) + hash) ^ 5381; /* (hash * 33) ^ c */
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct equal_to<cashew::IString> : public binary_function<cashew::IString, cashew::IString, bool> {
|
||||
bool operator()(const cashew::IString& x, const cashew::IString& y) const {
|
||||
return x == y;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace cashew {
|
||||
|
||||
// IStringSet
|
||||
|
||||
class IStringSet : public std::unordered_set<IString> {
|
||||
std::vector<char> data;
|
||||
public:
|
||||
IStringSet() {}
|
||||
IStringSet(const char *init) { // comma-delimited list
|
||||
int size = strlen(init) + 1;
|
||||
data.resize(size);
|
||||
char *curr = &data[0];
|
||||
strncpy(curr, init, size);
|
||||
while (1) {
|
||||
char *end = strchr(curr, ' ');
|
||||
if (end) *end = 0;
|
||||
insert(curr);
|
||||
if (!end) break;
|
||||
curr = end + 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool has(const IString& str) {
|
||||
return count(str) > 0;
|
||||
}
|
||||
};
|
||||
|
||||
class IOrderedStringSet : public std::set<IString> {
|
||||
public:
|
||||
bool has(const IString& str) {
|
||||
return count(str) > 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cashew
|
||||
|
||||
#endif // wasm_istring_h
|
@ -1,238 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "optimizer.h"
|
||||
#include "support/safe_integer.h"
|
||||
|
||||
using namespace cashew;
|
||||
|
||||
IString ASM_FLOAT_ZERO;
|
||||
|
||||
IString SIMD_INT8X16_CHECK("SIMD_Int8x16_check"),
|
||||
SIMD_INT16X8_CHECK("SIMD_Int16x8_check"),
|
||||
SIMD_INT32X4_CHECK("SIMD_Int32x4_check"),
|
||||
SIMD_FLOAT32X4_CHECK("SIMD_Float32x4_check"),
|
||||
SIMD_FLOAT64X2_CHECK("SIMD_Float64x2_check");
|
||||
|
||||
int parseInt(const char *str) {
|
||||
int ret = *str - '0';
|
||||
while (*(++str)) {
|
||||
ret *= 10;
|
||||
ret += *str - '0';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
HeapInfo parseHeap(const char *name) {
|
||||
HeapInfo ret;
|
||||
if (name[0] != 'H' || name[1] != 'E' || name[2] != 'A' || name[3] != 'P') {
|
||||
ret.valid = false;
|
||||
return ret;
|
||||
}
|
||||
ret.valid = true;
|
||||
ret.unsign = name[4] == 'U';
|
||||
ret.floaty = name[4] == 'F';
|
||||
ret.bits = parseInt(name + (ret.unsign || ret.floaty ? 5 : 4));
|
||||
ret.type = !ret.floaty ? ASM_INT : (ret.bits == 64 ? ASM_DOUBLE : ASM_FLOAT);
|
||||
return ret;
|
||||
}
|
||||
|
||||
AsmType detectType(Ref node, AsmData *asmData, bool inVarDef, IString minifiedFround, bool allowI64) {
|
||||
if (node->isString()) {
|
||||
if (asmData) {
|
||||
AsmType ret = asmData->getType(node->getCString());
|
||||
if (ret != ASM_NONE) return ret;
|
||||
}
|
||||
if (!inVarDef) {
|
||||
if (node == INF || node == NaN) return ASM_DOUBLE;
|
||||
if (node == TEMP_RET0) return ASM_INT;
|
||||
return ASM_NONE;
|
||||
}
|
||||
// We are in a variable definition, where Math_fround(0) optimized into a global constant becomes f0 = Math_fround(0)
|
||||
if (ASM_FLOAT_ZERO.isNull()) ASM_FLOAT_ZERO = node->getIString();
|
||||
else assert(node == ASM_FLOAT_ZERO);
|
||||
return ASM_FLOAT;
|
||||
}
|
||||
if (node->isNumber()) {
|
||||
if (!wasm::isInteger(node->getNumber())) return ASM_DOUBLE;
|
||||
return ASM_INT;
|
||||
}
|
||||
switch (node[0]->getCString()[0]) {
|
||||
case 'u': {
|
||||
if (node[0] == UNARY_PREFIX) {
|
||||
switch (node[1]->getCString()[0]) {
|
||||
case '+': return ASM_DOUBLE;
|
||||
case '-': return detectType(node[2], asmData, inVarDef, minifiedFround, allowI64);
|
||||
case '!': case '~': return ASM_INT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'c': {
|
||||
if (node[0] == CALL) {
|
||||
if (node[1]->isString()) {
|
||||
IString name = node[1]->getIString();
|
||||
if (name == MATH_FROUND || name == minifiedFround) return ASM_FLOAT;
|
||||
else if (allowI64 && (name == INT64 || name == INT64_CONST)) return ASM_INT64;
|
||||
else if (name == SIMD_FLOAT32X4 || name == SIMD_FLOAT32X4_CHECK) return ASM_FLOAT32X4;
|
||||
else if (name == SIMD_FLOAT64X2 || name == SIMD_FLOAT64X2_CHECK) return ASM_FLOAT64X2;
|
||||
else if (name == SIMD_INT8X16 || name == SIMD_INT8X16_CHECK) return ASM_INT8X16;
|
||||
else if (name == SIMD_INT16X8 || name == SIMD_INT16X8_CHECK) return ASM_INT16X8;
|
||||
else if (name == SIMD_INT32X4 || name == SIMD_INT32X4_CHECK) return ASM_INT32X4;
|
||||
}
|
||||
return ASM_NONE;
|
||||
} else if (node[0] == CONDITIONAL) {
|
||||
return detectType(node[2], asmData, inVarDef, minifiedFround, allowI64);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'b': {
|
||||
if (node[0] == BINARY) {
|
||||
switch (node[1]->getCString()[0]) {
|
||||
case '+': case '-':
|
||||
case '*': case '/': case '%': return detectType(node[2], asmData, inVarDef, minifiedFround, allowI64);
|
||||
case '|': case '&': case '^': case '<': case '>': // handles <<, >>, >>=, <=, >=
|
||||
case '=': case '!': { // handles ==, !=
|
||||
return ASM_INT;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
if (node[0] == SEQ) {
|
||||
return detectType(node[2], asmData, inVarDef, minifiedFround, allowI64);
|
||||
} else if (node[0] == SUB) {
|
||||
assert(node[1]->isString());
|
||||
HeapInfo info = parseHeap(node[1][1]->getCString());
|
||||
if (info.valid) return ASM_NONE;
|
||||
return info.floaty ? ASM_DOUBLE : ASM_INT; // XXX ASM_FLOAT?
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
//dump("horrible", node);
|
||||
//assert(0);
|
||||
return ASM_NONE;
|
||||
}
|
||||
|
||||
static void abort_on(Ref node) {
|
||||
node->stringify(std::cerr);
|
||||
std::cerr << '\n';
|
||||
abort();
|
||||
}
|
||||
|
||||
AsmSign detectSign(Ref node, IString minifiedFround) {
|
||||
if (node->isString()) {
|
||||
return ASM_FLEXIBLE;
|
||||
}
|
||||
if (node->isNumber()) {
|
||||
double value = node->getNumber();
|
||||
if (value < 0) return ASM_SIGNED;
|
||||
if (value > uint32_t(-1) || fmod(value, 1) != 0) return ASM_NONSIGNED;
|
||||
if (wasm::isSInteger32(value)) return ASM_FLEXIBLE;
|
||||
return ASM_UNSIGNED;
|
||||
}
|
||||
IString type = node[0]->getIString();
|
||||
if (type == BINARY) {
|
||||
IString op = node[1]->getIString();
|
||||
switch (op.str[0]) {
|
||||
case '>': {
|
||||
if (op == TRSHIFT) return ASM_UNSIGNED;
|
||||
} // fallthrough
|
||||
case '|': case '&': case '^': case '<': case '=': case '!': return ASM_SIGNED;
|
||||
case '+': case '-': return ASM_FLEXIBLE;
|
||||
case '*': case '/': return ASM_NONSIGNED; // without a coercion, these are double
|
||||
default: abort_on(node);
|
||||
}
|
||||
} else if (type == UNARY_PREFIX) {
|
||||
IString op = node[1]->getIString();
|
||||
switch (op.str[0]) {
|
||||
case '-': return ASM_FLEXIBLE;
|
||||
case '+': return ASM_NONSIGNED; // XXX double
|
||||
case '~': return ASM_SIGNED;
|
||||
default: abort_on(node);
|
||||
}
|
||||
} else if (type == CONDITIONAL) {
|
||||
return detectSign(node[2], minifiedFround);
|
||||
} else if (type == CALL) {
|
||||
if (node[1]->isString() && (node[1] == MATH_FROUND || node[1] == minifiedFround)) return ASM_NONSIGNED;
|
||||
} else if (type == SEQ) {
|
||||
return detectSign(node[2], minifiedFround);
|
||||
}
|
||||
abort_on(node);
|
||||
abort(); // avoid warning
|
||||
}
|
||||
|
||||
Ref makeAsmCoercedZero(AsmType type) {
|
||||
switch (type) {
|
||||
case ASM_INT: return ValueBuilder::makeNum(0); break;
|
||||
case ASM_DOUBLE: return ValueBuilder::makeUnary(PLUS, ValueBuilder::makeNum(0)); break;
|
||||
case ASM_FLOAT: {
|
||||
if (!ASM_FLOAT_ZERO.isNull()) {
|
||||
return ValueBuilder::makeName(ASM_FLOAT_ZERO);
|
||||
} else {
|
||||
return ValueBuilder::makeCall(MATH_FROUND, ValueBuilder::makeNum(0));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ASM_FLOAT32X4: {
|
||||
return ValueBuilder::makeCall(SIMD_FLOAT32X4, ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0));
|
||||
break;
|
||||
}
|
||||
case ASM_FLOAT64X2: {
|
||||
return ValueBuilder::makeCall(SIMD_FLOAT64X2, ValueBuilder::makeNum(0), ValueBuilder::makeNum(0));
|
||||
break;
|
||||
}
|
||||
case ASM_INT8X16: {
|
||||
return ValueBuilder::makeCall(SIMD_INT8X16, ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0));
|
||||
break;
|
||||
}
|
||||
case ASM_INT16X8: {
|
||||
return ValueBuilder::makeCall(SIMD_INT16X8, ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0));
|
||||
break;
|
||||
}
|
||||
case ASM_INT32X4: {
|
||||
return ValueBuilder::makeCall(SIMD_INT32X4, ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0), ValueBuilder::makeNum(0));
|
||||
break;
|
||||
}
|
||||
default: assert(0);
|
||||
}
|
||||
abort();
|
||||
}
|
||||
|
||||
Ref makeAsmCoercion(Ref node, AsmType type) {
|
||||
switch (type) {
|
||||
case ASM_INT: return ValueBuilder::makeBinary(node, OR, ValueBuilder::makeNum(0));
|
||||
case ASM_DOUBLE: return ValueBuilder::makeUnary(PLUS, node);
|
||||
case ASM_FLOAT: return ValueBuilder::makeCall(MATH_FROUND, node);
|
||||
case ASM_FLOAT32X4: return ValueBuilder::makeCall(SIMD_FLOAT32X4_CHECK, node);
|
||||
case ASM_FLOAT64X2: return ValueBuilder::makeCall(SIMD_FLOAT64X2_CHECK, node);
|
||||
case ASM_INT8X16: return ValueBuilder::makeCall(SIMD_INT8X16_CHECK, node);
|
||||
case ASM_INT16X8: return ValueBuilder::makeCall(SIMD_INT16X8_CHECK, node);
|
||||
case ASM_INT32X4: return ValueBuilder::makeCall(SIMD_INT32X4_CHECK, node);
|
||||
case ASM_NONE:
|
||||
default: return node; // non-validating code, emit nothing XXX this is dangerous, we should only allow this when we know we are not validating
|
||||
}
|
||||
}
|
||||
|
||||
Ref makeSigning(Ref node, AsmSign sign) {
|
||||
assert(sign == ASM_SIGNED || sign == ASM_UNSIGNED);
|
||||
return ValueBuilder::makeBinary(node, sign == ASM_SIGNED ? OR : TRSHIFT, ValueBuilder::makeNum(0));
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_optimizer_h
|
||||
#define wasm_optimizer_h
|
||||
|
||||
#include "simple_ast.h"
|
||||
|
||||
extern bool preciseF32,
|
||||
receiveJSON,
|
||||
emitJSON,
|
||||
minifyWhitespace,
|
||||
last;
|
||||
|
||||
extern cashew::Ref extraInfo;
|
||||
|
||||
void eliminateDeadFuncs(cashew::Ref ast);
|
||||
void eliminate(cashew::Ref ast, bool memSafe=false);
|
||||
void eliminateMemSafe(cashew::Ref ast);
|
||||
void simplifyExpressions(cashew::Ref ast);
|
||||
void optimizeFrounds(cashew::Ref ast);
|
||||
void simplifyIfs(cashew::Ref ast);
|
||||
void registerize(cashew::Ref ast);
|
||||
void registerizeHarder(cashew::Ref ast);
|
||||
void minifyLocals(cashew::Ref ast);
|
||||
void asmLastOpts(cashew::Ref ast);
|
||||
|
||||
//
|
||||
|
||||
enum AsmType {
|
||||
ASM_INT = 0,
|
||||
ASM_DOUBLE,
|
||||
ASM_FLOAT,
|
||||
ASM_FLOAT32X4,
|
||||
ASM_FLOAT64X2,
|
||||
ASM_INT8X16,
|
||||
ASM_INT16X8,
|
||||
ASM_INT32X4,
|
||||
ASM_INT64, // non-asm.js
|
||||
ASM_NONE // number of types
|
||||
};
|
||||
|
||||
struct AsmData;
|
||||
|
||||
AsmType detectType(cashew::Ref node, AsmData *asmData=nullptr, bool inVarDef=false, cashew::IString minifiedFround=cashew::IString(), bool allowI64=false);
|
||||
|
||||
struct AsmData {
|
||||
struct Local {
|
||||
Local() {}
|
||||
Local(AsmType type, bool param) : type(type), param(param) {}
|
||||
AsmType type;
|
||||
bool param; // false if a var
|
||||
};
|
||||
typedef std::unordered_map<cashew::IString, Local> Locals;
|
||||
|
||||
Locals locals;
|
||||
std::vector<cashew::IString> params; // in order
|
||||
std::vector<cashew::IString> vars; // in order
|
||||
AsmType ret;
|
||||
|
||||
cashew::Ref func;
|
||||
|
||||
AsmType getType(const cashew::IString& name) {
|
||||
auto ret = locals.find(name);
|
||||
if (ret != locals.end()) return ret->second.type;
|
||||
return ASM_NONE;
|
||||
}
|
||||
void setType(const cashew::IString& name, AsmType type) {
|
||||
locals[name].type = type;
|
||||
}
|
||||
|
||||
bool isLocal(const cashew::IString& name) {
|
||||
return locals.count(name) > 0;
|
||||
}
|
||||
bool isParam(const cashew::IString& name) {
|
||||
return isLocal(name) && locals[name].param;
|
||||
}
|
||||
bool isVar(const cashew::IString& name) {
|
||||
return isLocal(name) && !locals[name].param;
|
||||
}
|
||||
|
||||
AsmData() {} // if you want to fill in the data yourself
|
||||
AsmData(cashew::Ref f); // if you want to read data from f, and modify it as you go (parallel to denormalize)
|
||||
|
||||
void denormalize();
|
||||
|
||||
void addParam(cashew::IString name, AsmType type) {
|
||||
locals[name] = Local(type, true);
|
||||
params.push_back(name);
|
||||
}
|
||||
void addVar(cashew::IString name, AsmType type) {
|
||||
locals[name] = Local(type, false);
|
||||
vars.push_back(name);
|
||||
}
|
||||
|
||||
void deleteVar(cashew::IString name) {
|
||||
locals.erase(name);
|
||||
for (size_t i = 0; i < vars.size(); i++) {
|
||||
if (vars[i] == name) {
|
||||
vars.erase(vars.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
extern cashew::IString ASM_FLOAT_ZERO;
|
||||
|
||||
extern cashew::IString SIMD_INT8X16_CHECK,
|
||||
SIMD_INT16X8_CHECK,
|
||||
SIMD_INT32X4_CHECK,
|
||||
SIMD_FLOAT32X4_CHECK,
|
||||
SIMD_FLOAT64X2_CHECK;
|
||||
|
||||
int parseInt(const char *str);
|
||||
|
||||
struct HeapInfo {
|
||||
bool valid, unsign, floaty;
|
||||
int bits;
|
||||
AsmType type;
|
||||
};
|
||||
|
||||
HeapInfo parseHeap(const char *name);
|
||||
|
||||
enum AsmSign {
|
||||
ASM_FLEXIBLE = 0, // small constants can be signed or unsigned, variables are also flexible
|
||||
ASM_SIGNED = 1,
|
||||
ASM_UNSIGNED = 2,
|
||||
ASM_NONSIGNED = 3,
|
||||
};
|
||||
|
||||
extern AsmSign detectSign(cashew::Ref node, cashew::IString minifiedFround);
|
||||
|
||||
cashew::Ref makeAsmCoercedZero(AsmType type);
|
||||
cashew::Ref makeAsmCoercion(cashew::Ref node, AsmType type);
|
||||
|
||||
cashew::Ref makeSigning(cashew::Ref node, AsmSign sign);
|
||||
|
||||
#endif // wasm_optimizer_h
|
@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "parser.h"
|
||||
|
||||
namespace cashew {
|
||||
|
||||
// common strings
|
||||
|
||||
IString TOPLEVEL("toplevel"),
|
||||
DEFUN("defun"),
|
||||
BLOCK("block"),
|
||||
VAR("var"),
|
||||
CONST("const"),
|
||||
CONDITIONAL("conditional"),
|
||||
BINARY("binary"),
|
||||
RETURN("return"),
|
||||
IF("if"),
|
||||
ELSE("else"),
|
||||
WHILE("while"),
|
||||
DO("do"),
|
||||
FOR("for"),
|
||||
SEQ("seq"),
|
||||
SUB("sub"),
|
||||
CALL("call"),
|
||||
LABEL("label"),
|
||||
BREAK("break"),
|
||||
CONTINUE("continue"),
|
||||
SWITCH("switch"),
|
||||
STRING("string"),
|
||||
TRY("try"),
|
||||
INF("inf"),
|
||||
NaN("nan"),
|
||||
TEMP_RET0("tempRet0"),
|
||||
GET_TEMP_RET0("getTempRet0"),
|
||||
LLVM_CTTZ_I32("_llvm_cttz_i32"),
|
||||
UDIVMODDI4("___udivmoddi4"),
|
||||
UNARY_PREFIX("unary-prefix"),
|
||||
UNARY_POSTFIX("unary-postfix"),
|
||||
MATH_FROUND("Math_fround"),
|
||||
MATH_CLZ32("Math_clz32"),
|
||||
INT64("i64"),
|
||||
INT64_CONST("i64_const"),
|
||||
SIMD_FLOAT32X4("SIMD_Float32x4"),
|
||||
SIMD_FLOAT64X2("SIMD_Float64x2"),
|
||||
SIMD_INT8X16("SIMD_Int8x16"),
|
||||
SIMD_INT16X8("SIMD_Int16x8"),
|
||||
SIMD_INT32X4("SIMD_Int32x4"),
|
||||
PLUS("+"),
|
||||
MINUS("-"),
|
||||
OR("|"),
|
||||
AND("&"),
|
||||
XOR("^"),
|
||||
L_NOT("!"),
|
||||
B_NOT("~"),
|
||||
LT("<"),
|
||||
GE(">="),
|
||||
LE("<="),
|
||||
GT(">"),
|
||||
EQ("=="),
|
||||
NE("!="),
|
||||
DIV("/"),
|
||||
MOD("%"),
|
||||
MUL("*"),
|
||||
RSHIFT(">>"),
|
||||
LSHIFT("<<"),
|
||||
TRSHIFT(">>>"),
|
||||
TEMP_DOUBLE_PTR("tempDoublePtr"),
|
||||
HEAP8("HEAP8"),
|
||||
HEAP16("HEAP16"),
|
||||
HEAP32("HEAP32"),
|
||||
HEAPF32("HEAPF32"),
|
||||
HEAPU8("HEAPU8"),
|
||||
HEAPU16("HEAPU16"),
|
||||
HEAPU32("HEAPU32"),
|
||||
HEAPF64("HEAPF64"),
|
||||
F0("f0"),
|
||||
EMPTY(""),
|
||||
FUNCTION("function"),
|
||||
OPEN_PAREN("("),
|
||||
OPEN_BRACE("["),
|
||||
OPEN_CURLY("{"),
|
||||
CLOSE_CURLY("}"),
|
||||
COMMA(","),
|
||||
QUESTION("?"),
|
||||
COLON(":"),
|
||||
CASE("case"),
|
||||
DEFAULT("default"),
|
||||
DOT("dot"),
|
||||
PERIOD("."),
|
||||
NEW("new"),
|
||||
ARRAY("array"),
|
||||
OBJECT("object"),
|
||||
THROW("throw"),
|
||||
SET("=");
|
||||
|
||||
IStringSet keywords("var const function if else do while for break continue return switch case default throw try catch finally true false null new");
|
||||
|
||||
const char *OPERATOR_INITS = "+-*/%<>&^|~=!,?:.",
|
||||
*SEPARATORS = "([;{}";
|
||||
|
||||
int MAX_OPERATOR_SIZE = 3;
|
||||
|
||||
std::vector<OperatorClass> operatorClasses;
|
||||
|
||||
static std::vector<std::unordered_map<IString, int>> precedences; // op, type => prec
|
||||
|
||||
struct Init {
|
||||
Init() {
|
||||
// operators, rtl, type
|
||||
operatorClasses.emplace_back(".", false, OperatorClass::Binary);
|
||||
operatorClasses.emplace_back("! ~ + -", true, OperatorClass::Prefix);
|
||||
operatorClasses.emplace_back("* / %", false, OperatorClass::Binary);
|
||||
operatorClasses.emplace_back("+ -", false, OperatorClass::Binary);
|
||||
operatorClasses.emplace_back("<< >> >>>", false, OperatorClass::Binary);
|
||||
operatorClasses.emplace_back("< <= > >=", false, OperatorClass::Binary);
|
||||
operatorClasses.emplace_back("== !=", false, OperatorClass::Binary);
|
||||
operatorClasses.emplace_back("&", false, OperatorClass::Binary);
|
||||
operatorClasses.emplace_back("^", false, OperatorClass::Binary);
|
||||
operatorClasses.emplace_back("|", false, OperatorClass::Binary);
|
||||
operatorClasses.emplace_back("? :", true, OperatorClass::Tertiary);
|
||||
operatorClasses.emplace_back("=", true, OperatorClass::Binary);
|
||||
operatorClasses.emplace_back(",", true, OperatorClass::Binary);
|
||||
|
||||
precedences.resize(OperatorClass::Tertiary + 1);
|
||||
|
||||
for (size_t prec = 0; prec < operatorClasses.size(); prec++) {
|
||||
for (auto curr : operatorClasses[prec].ops) {
|
||||
precedences[operatorClasses[prec].type][curr] = prec;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Init init;
|
||||
|
||||
int OperatorClass::getPrecedence(Type type, IString op) {
|
||||
return precedences[type][op];
|
||||
}
|
||||
|
||||
bool OperatorClass::getRtl(int prec) {
|
||||
return operatorClasses[prec].rtl;
|
||||
}
|
||||
|
||||
bool isIdentInit(char x) { return (x >= 'a' && x <= 'z') || (x >= 'A' && x <= 'Z') || x == '_' || x == '$'; }
|
||||
bool isIdentPart(char x) { return isIdentInit(x) || (x >= '0' && x <= '9'); }
|
||||
|
||||
} // namespace cashew
|
@ -1,960 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Pure parsing. Calls methods on a Builder (template argument) to actually construct the AST
|
||||
//
|
||||
// XXX All parsing methods assume they take ownership of the input string. This lets them reuse
|
||||
// parts of it. You will segfault if the input string cannot be reused and written to.
|
||||
|
||||
#ifndef wasm_parser_h
|
||||
#define wasm_parser_h
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "istring.h"
|
||||
#include "support/safe_integer.h"
|
||||
|
||||
namespace cashew {
|
||||
|
||||
// common strings
|
||||
|
||||
extern IString TOPLEVEL,
|
||||
DEFUN,
|
||||
BLOCK,
|
||||
VAR,
|
||||
CONST,
|
||||
CONDITIONAL,
|
||||
BINARY,
|
||||
RETURN,
|
||||
IF,
|
||||
ELSE,
|
||||
WHILE,
|
||||
DO,
|
||||
FOR,
|
||||
SEQ,
|
||||
SUB,
|
||||
CALL,
|
||||
LABEL,
|
||||
BREAK,
|
||||
CONTINUE,
|
||||
SWITCH,
|
||||
STRING,
|
||||
TRY,
|
||||
INF,
|
||||
NaN,
|
||||
TEMP_RET0,
|
||||
GET_TEMP_RET0,
|
||||
LLVM_CTTZ_I32,
|
||||
UDIVMODDI4,
|
||||
UNARY_PREFIX,
|
||||
UNARY_POSTFIX,
|
||||
MATH_FROUND,
|
||||
MATH_CLZ32,
|
||||
INT64,
|
||||
INT64_CONST,
|
||||
SIMD_FLOAT32X4,
|
||||
SIMD_FLOAT64X2,
|
||||
SIMD_INT8X16,
|
||||
SIMD_INT16X8,
|
||||
SIMD_INT32X4,
|
||||
PLUS,
|
||||
MINUS,
|
||||
OR,
|
||||
AND,
|
||||
XOR,
|
||||
L_NOT,
|
||||
B_NOT,
|
||||
LT,
|
||||
GE,
|
||||
LE,
|
||||
GT,
|
||||
EQ,
|
||||
NE,
|
||||
DIV,
|
||||
MOD,
|
||||
MUL,
|
||||
RSHIFT,
|
||||
LSHIFT,
|
||||
TRSHIFT,
|
||||
TEMP_DOUBLE_PTR,
|
||||
HEAP8,
|
||||
HEAP16,
|
||||
HEAP32,
|
||||
HEAPF32,
|
||||
HEAPU8,
|
||||
HEAPU16,
|
||||
HEAPU32,
|
||||
HEAPF64,
|
||||
F0,
|
||||
EMPTY,
|
||||
FUNCTION,
|
||||
OPEN_PAREN,
|
||||
OPEN_BRACE,
|
||||
OPEN_CURLY,
|
||||
CLOSE_CURLY,
|
||||
COMMA,
|
||||
QUESTION,
|
||||
COLON,
|
||||
CASE,
|
||||
DEFAULT,
|
||||
DOT,
|
||||
PERIOD,
|
||||
NEW,
|
||||
ARRAY,
|
||||
OBJECT,
|
||||
THROW,
|
||||
SET;
|
||||
|
||||
extern IStringSet keywords;
|
||||
|
||||
extern const char *OPERATOR_INITS, *SEPARATORS;
|
||||
|
||||
extern int MAX_OPERATOR_SIZE, LOWEST_PREC;
|
||||
|
||||
struct OperatorClass {
|
||||
enum Type {
|
||||
Binary = 0,
|
||||
Prefix = 1,
|
||||
Postfix = 2,
|
||||
Tertiary = 3
|
||||
};
|
||||
|
||||
IStringSet ops;
|
||||
bool rtl;
|
||||
Type type;
|
||||
|
||||
OperatorClass(const char* o, bool r, Type t) : ops(o), rtl(r), type(t) {}
|
||||
|
||||
static int getPrecedence(Type type, IString op);
|
||||
static bool getRtl(int prec);
|
||||
};
|
||||
|
||||
extern std::vector<OperatorClass> operatorClasses;
|
||||
|
||||
extern bool isIdentInit(char x);
|
||||
extern bool isIdentPart(char x);
|
||||
|
||||
// parser
|
||||
|
||||
template<class NodeRef, class Builder>
|
||||
class Parser {
|
||||
|
||||
static bool isSpace(char x) { return x == 32 || x == 9 || x == 10 || x == 13; } /* space, tab, linefeed/newline, or return */
|
||||
static void skipSpace(char*& curr) {
|
||||
while (*curr) {
|
||||
if (isSpace(*curr)) {
|
||||
curr++;
|
||||
continue;
|
||||
}
|
||||
if (curr[0] == '/' && curr[1] == '/') {
|
||||
curr += 2;
|
||||
while (*curr && *curr != '\n') curr++;
|
||||
if (*curr) curr++;
|
||||
continue;
|
||||
}
|
||||
if (curr[0] == '/' && curr[1] == '*') {
|
||||
curr += 2;
|
||||
while (*curr && (curr[0] != '*' || curr[1] != '/')) curr++;
|
||||
curr += 2;
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static bool isDigit(char x) { return x >= '0' && x <= '9'; }
|
||||
|
||||
static bool hasChar(const char* list, char x) { while (*list) if (*list++ == x) return true; return false; }
|
||||
|
||||
// An atomic fragment of something. Stops at a natural boundary.
|
||||
enum FragType {
|
||||
KEYWORD = 0,
|
||||
OPERATOR = 1,
|
||||
IDENT = 2,
|
||||
STRING = 3, // without quotes
|
||||
INT = 4,
|
||||
DOUBLE = 5,
|
||||
SEPARATOR = 6
|
||||
};
|
||||
|
||||
struct Frag {
|
||||
#ifndef _MSC_VER // MSVC does not allow unrestricted unions: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2544.pdf
|
||||
union {
|
||||
#endif
|
||||
IString str;
|
||||
double num;
|
||||
#ifndef _MSC_VER
|
||||
};
|
||||
#endif
|
||||
int size;
|
||||
FragType type;
|
||||
|
||||
bool isNumber() const {
|
||||
return type == INT || type == DOUBLE;
|
||||
}
|
||||
|
||||
explicit Frag(char* src) {
|
||||
char *start = src;
|
||||
if (isIdentInit(*src)) {
|
||||
// read an identifier or a keyword
|
||||
src++;
|
||||
while (isIdentPart(*src)) {
|
||||
src++;
|
||||
}
|
||||
if (*src == 0) {
|
||||
str.set(start);
|
||||
} else {
|
||||
char temp = *src;
|
||||
*src = 0;
|
||||
str.set(start, false);
|
||||
*src = temp;
|
||||
}
|
||||
type = keywords.has(str) ? KEYWORD : IDENT;
|
||||
} else if (isDigit(*src) || (src[0] == '.' && isDigit(src[1]))) {
|
||||
if (src[0] == '0' && (src[1] == 'x' || src[1] == 'X')) {
|
||||
// Explicitly parse hex numbers of form "0x...", because strtod
|
||||
// supports hex number strings only in C++11, and Visual Studio 2013 does
|
||||
// not yet support that functionality.
|
||||
src += 2;
|
||||
num = 0;
|
||||
while (1) {
|
||||
if (*src >= '0' && *src <= '9') { num *= 16; num += *src - '0'; }
|
||||
else if (*src >= 'a' && *src <= 'f') { num *= 16; num += *src - 'a' + 10; }
|
||||
else if (*src >= 'A' && *src <= 'F') { num *= 16; num += *src - 'A' + 10; }
|
||||
else break;
|
||||
src++;
|
||||
}
|
||||
} else {
|
||||
num = strtod(start, &src);
|
||||
}
|
||||
// asm.js must have a '.' for double values. however, we also tolerate
|
||||
// uglify's tendency to emit without a '.' (and fix it later with a +).
|
||||
// for valid asm.js input, the '.' should be enough, and for uglify
|
||||
// in the emscripten optimizer pipeline, we use simple_ast where INT/DOUBLE
|
||||
// is quite the same at this point anyhow
|
||||
type = (std::find(start, src, '.') == src &&
|
||||
(wasm::isSInteger32(num) || wasm::isUInteger32(num)))
|
||||
? INT
|
||||
: DOUBLE;
|
||||
assert(src > start);
|
||||
} else if (hasChar(OPERATOR_INITS, *src)) {
|
||||
switch (*src) {
|
||||
case '!': str = src[1] == '=' ? NE : L_NOT; break;
|
||||
case '%': str = MOD; break;
|
||||
case '&': str = AND; break;
|
||||
case '*': str = MUL; break;
|
||||
case '+': str = PLUS; break;
|
||||
case ',': str = COMMA; break;
|
||||
case '-': str = MINUS; break;
|
||||
case '.': str = PERIOD; break;
|
||||
case '/': str = DIV; break;
|
||||
case ':': str = COLON; break;
|
||||
case '<': str = src[1] == '<' ? LSHIFT : (src[1] == '=' ? LE : LT); break;
|
||||
case '=': str = src[1] == '=' ? EQ : SET; break;
|
||||
case '>': str = src[1] == '>' ? (src[2] == '>' ? TRSHIFT : RSHIFT) : (src[1] == '=' ? GE : GT); break;
|
||||
case '?': str = QUESTION; break;
|
||||
case '^': str = XOR; break;
|
||||
case '|': str = OR; break;
|
||||
case '~': str = B_NOT; break;
|
||||
default: abort();
|
||||
}
|
||||
size = strlen(str.str);
|
||||
#ifndef NDEBUG
|
||||
char temp = start[size];
|
||||
start[size] = 0;
|
||||
assert(strcmp(str.str, start) == 0);
|
||||
start[size] = temp;
|
||||
#endif
|
||||
type = OPERATOR;
|
||||
return;
|
||||
} else if (hasChar(SEPARATORS, *src)) {
|
||||
type = SEPARATOR;
|
||||
char temp = src[1];
|
||||
src[1] = 0;
|
||||
str.set(src, false);
|
||||
src[1] = temp;
|
||||
src++;
|
||||
} else if (*src == '"' || *src == '\'') {
|
||||
char *end = strchr(src+1, *src);
|
||||
*end = 0;
|
||||
str.set(src+1);
|
||||
src = end+1;
|
||||
type = STRING;
|
||||
} else {
|
||||
dump("frag parsing", src);
|
||||
abort();
|
||||
}
|
||||
size = src - start;
|
||||
}
|
||||
};
|
||||
|
||||
struct ExpressionElement {
|
||||
bool isNode;
|
||||
#ifndef _MSC_VER // MSVC does not allow unrestricted unions: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2544.pdf
|
||||
union {
|
||||
#endif
|
||||
NodeRef node;
|
||||
IString op;
|
||||
#ifndef _MSC_VER
|
||||
};
|
||||
#endif
|
||||
ExpressionElement(NodeRef n) : isNode(true), node(n) {}
|
||||
ExpressionElement(IString o) : isNode(false), op(o) {}
|
||||
|
||||
NodeRef getNode() {
|
||||
assert(isNode);
|
||||
return node;
|
||||
}
|
||||
IString getOp() {
|
||||
assert(!isNode);
|
||||
return op;
|
||||
}
|
||||
};
|
||||
|
||||
// This is a list of the current stack of node-operator-node-operator-etc.
|
||||
// this works by each parseExpression call appending to the vector; then recursing out, and the toplevel sorts it all
|
||||
typedef std::vector<ExpressionElement> ExpressionParts;
|
||||
std::vector<ExpressionParts> expressionPartsStack;
|
||||
|
||||
// Parses an element in a list of such elements, e.g. list of statements in a block, or list of parameters in a call
|
||||
NodeRef parseElement(char*& src, const char* seps=";") {
|
||||
//dump("parseElement", src);
|
||||
skipSpace(src);
|
||||
Frag frag(src);
|
||||
src += frag.size;
|
||||
switch (frag.type) {
|
||||
case KEYWORD: {
|
||||
return parseAfterKeyword(frag, src, seps);
|
||||
}
|
||||
case IDENT: {
|
||||
return parseAfterIdent(frag, src, seps);
|
||||
}
|
||||
case STRING:
|
||||
case INT:
|
||||
case DOUBLE: {
|
||||
return parseExpression(parseFrag(frag), src, seps);
|
||||
}
|
||||
case SEPARATOR: {
|
||||
if (frag.str == OPEN_PAREN) return parseExpression(parseAfterParen(src), src, seps);
|
||||
if (frag.str == OPEN_BRACE) return parseExpression(parseAfterBrace(src), src, seps);
|
||||
if (frag.str == OPEN_CURLY) return parseExpression(parseAfterCurly(src), src, seps);
|
||||
abort();
|
||||
}
|
||||
case OPERATOR: {
|
||||
return parseExpression(frag.str, src, seps);
|
||||
}
|
||||
default: /* dump("parseElement", src); printf("bad frag type: %d\n", frag.type); */ abort();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NodeRef parseFrag(Frag& frag) {
|
||||
switch (frag.type) {
|
||||
case IDENT: return Builder::makeName(frag.str);
|
||||
case STRING: return Builder::makeString(frag.str);
|
||||
case INT: return Builder::makeInt(uint32_t(frag.num));
|
||||
case DOUBLE: return Builder::makeDouble(frag.num);
|
||||
default: abort();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NodeRef parseAfterKeyword(Frag& frag, char*& src, const char* seps) {
|
||||
skipSpace(src);
|
||||
if (frag.str == FUNCTION) return parseFunction(src, seps);
|
||||
else if (frag.str == VAR) return parseVar(src, seps, false);
|
||||
else if (frag.str == CONST) return parseVar(src, seps, true);
|
||||
else if (frag.str == RETURN) return parseReturn(src, seps);
|
||||
else if (frag.str == IF) return parseIf(src, seps);
|
||||
else if (frag.str == DO) return parseDo(src, seps);
|
||||
else if (frag.str == WHILE) return parseWhile(src, seps);
|
||||
else if (frag.str == BREAK) return parseBreak(src, seps);
|
||||
else if (frag.str == CONTINUE) return parseContinue(src, seps);
|
||||
else if (frag.str == SWITCH) return parseSwitch(src, seps);
|
||||
else if (frag.str == NEW) return parseNew(src, seps);
|
||||
else if (frag.str == FOR) return parseFor(src, seps);
|
||||
dump(frag.str.str, src);
|
||||
abort();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NodeRef parseFunction(char*& src, const char* seps) {
|
||||
Frag name(src);
|
||||
if (name.type == IDENT) {
|
||||
src += name.size;
|
||||
} else {
|
||||
assert(name.type == SEPARATOR && name.str[0] == '(');
|
||||
name.str = IString();
|
||||
}
|
||||
NodeRef ret = Builder::makeFunction(name.str);
|
||||
skipSpace(src);
|
||||
assert(*src == '(');
|
||||
src++;
|
||||
while (1) {
|
||||
skipSpace(src);
|
||||
if (*src == ')') break;
|
||||
Frag arg(src);
|
||||
assert(arg.type == IDENT);
|
||||
src += arg.size;
|
||||
Builder::appendArgumentToFunction(ret, arg.str);
|
||||
skipSpace(src);
|
||||
if (*src == ')') break;
|
||||
if (*src == ',') {
|
||||
src++;
|
||||
continue;
|
||||
}
|
||||
abort();
|
||||
}
|
||||
src++;
|
||||
Builder::setBlockContent(ret, parseBracketedBlock(src));
|
||||
// TODO: parse expression?
|
||||
return ret;
|
||||
}
|
||||
|
||||
NodeRef parseVar(char*& src, const char* seps, bool is_const) {
|
||||
NodeRef ret = Builder::makeVar(is_const);
|
||||
while (1) {
|
||||
skipSpace(src);
|
||||
if (*src == ';') break;
|
||||
Frag name(src);
|
||||
assert(name.type == IDENT);
|
||||
NodeRef value;
|
||||
src += name.size;
|
||||
skipSpace(src);
|
||||
if (*src == '=') {
|
||||
src++;
|
||||
skipSpace(src);
|
||||
value = parseElement(src, ";,");
|
||||
}
|
||||
Builder::appendToVar(ret, name.str, value);
|
||||
skipSpace(src);
|
||||
if (*src == ';') break;
|
||||
if (*src == ',') {
|
||||
src++;
|
||||
continue;
|
||||
}
|
||||
abort();
|
||||
}
|
||||
src++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
NodeRef parseReturn(char*& src, const char* seps) {
|
||||
skipSpace(src);
|
||||
NodeRef value = !hasChar(seps, *src) ? parseElement(src, seps) : nullptr;
|
||||
skipSpace(src);
|
||||
assert(hasChar(seps, *src));
|
||||
if (*src == ';') src++;
|
||||
return Builder::makeReturn(value);
|
||||
}
|
||||
|
||||
NodeRef parseIf(char*& src, const char* seps) {
|
||||
NodeRef condition = parseParenned(src);
|
||||
NodeRef ifTrue = parseMaybeBracketed(src, seps);
|
||||
skipSpace(src);
|
||||
NodeRef ifFalse;
|
||||
if (!hasChar(seps, *src)) {
|
||||
Frag next(src);
|
||||
if (next.type == KEYWORD && next.str == ELSE) {
|
||||
src += next.size;
|
||||
ifFalse = parseMaybeBracketed(src, seps);
|
||||
}
|
||||
}
|
||||
return Builder::makeIf(condition, ifTrue, ifFalse);
|
||||
}
|
||||
|
||||
NodeRef parseDo(char*& src, const char* seps) {
|
||||
NodeRef body = parseMaybeBracketed(src, seps);
|
||||
skipSpace(src);
|
||||
Frag next(src);
|
||||
assert(next.type == KEYWORD && next.str == WHILE);
|
||||
src += next.size;
|
||||
NodeRef condition = parseParenned(src);
|
||||
return Builder::makeDo(body, condition);
|
||||
}
|
||||
|
||||
NodeRef parseWhile(char*& src, const char* seps) {
|
||||
NodeRef condition = parseParenned(src);
|
||||
NodeRef body = parseMaybeBracketed(src, seps);
|
||||
return Builder::makeWhile(condition, body);
|
||||
}
|
||||
|
||||
NodeRef parseFor(char*& src, const char* seps) {
|
||||
skipSpace(src);
|
||||
assert(*src == '(');
|
||||
src++;
|
||||
NodeRef init = parseElement(src, ";");
|
||||
skipSpace(src);
|
||||
assert(*src == ';');
|
||||
src++;
|
||||
NodeRef condition = parseElement(src, ";");
|
||||
skipSpace(src);
|
||||
assert(*src == ';');
|
||||
src++;
|
||||
NodeRef inc = parseElement(src, ")");
|
||||
skipSpace(src);
|
||||
assert(*src == ')');
|
||||
src++;
|
||||
NodeRef body = parseMaybeBracketed(src, seps);
|
||||
return Builder::makeFor(init, condition, inc, body);
|
||||
}
|
||||
|
||||
NodeRef parseBreak(char*& src, const char* seps) {
|
||||
skipSpace(src);
|
||||
Frag next(src);
|
||||
if (next.type == IDENT) src += next.size;
|
||||
return Builder::makeBreak(next.type == IDENT ? next.str : IString());
|
||||
}
|
||||
|
||||
NodeRef parseContinue(char*& src, const char* seps) {
|
||||
skipSpace(src);
|
||||
Frag next(src);
|
||||
if (next.type == IDENT) src += next.size;
|
||||
return Builder::makeContinue(next.type == IDENT ? next.str : IString());
|
||||
}
|
||||
|
||||
NodeRef parseSwitch(char*& src, const char* seps) {
|
||||
NodeRef ret = Builder::makeSwitch(parseParenned(src));
|
||||
skipSpace(src);
|
||||
assert(*src == '{');
|
||||
src++;
|
||||
while (1) {
|
||||
// find all cases and possibly a default
|
||||
skipSpace(src);
|
||||
if (*src == '}') break;
|
||||
Frag next(src);
|
||||
if (next.type == KEYWORD) {
|
||||
if (next.str == CASE) {
|
||||
src += next.size;
|
||||
skipSpace(src);
|
||||
NodeRef arg;
|
||||
Frag value(src);
|
||||
if (value.isNumber()) {
|
||||
arg = parseFrag(value);
|
||||
src += value.size;
|
||||
} else if (value.type == OPERATOR) {
|
||||
// negative number
|
||||
assert(value.str == MINUS);
|
||||
src += value.size;
|
||||
skipSpace(src);
|
||||
Frag value2(src);
|
||||
assert(value2.isNumber());
|
||||
arg = Builder::makePrefix(MINUS, parseFrag(value2));
|
||||
src += value2.size;
|
||||
} else {
|
||||
// identifier and function call
|
||||
assert(value.type == IDENT);
|
||||
src += value.size;
|
||||
skipSpace(src);
|
||||
arg = parseCall(parseFrag(value), src);
|
||||
}
|
||||
Builder::appendCaseToSwitch(ret, arg);
|
||||
skipSpace(src);
|
||||
assert(*src == ':');
|
||||
src++;
|
||||
continue;
|
||||
} else if (next.str == DEFAULT) {
|
||||
src += next.size;
|
||||
Builder::appendDefaultToSwitch(ret);
|
||||
skipSpace(src);
|
||||
assert(*src == ':');
|
||||
src++;
|
||||
continue;
|
||||
}
|
||||
// otherwise, may be some keyword that happens to start a block (e.g. case 1: _return_ 5)
|
||||
}
|
||||
// not case X: or default: or }, so must be some code
|
||||
skipSpace(src);
|
||||
bool explicitBlock = *src == '{';
|
||||
NodeRef subBlock = explicitBlock ? parseBracketedBlock(src) : parseBlock(src, ";}", CASE, DEFAULT);
|
||||
Builder::appendCodeToSwitch(ret, subBlock, explicitBlock);
|
||||
}
|
||||
skipSpace(src);
|
||||
assert(*src == '}');
|
||||
src++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
NodeRef parseNew(char*& src, const char* seps) {
|
||||
return Builder::makeNew(parseElement(src, seps));
|
||||
}
|
||||
|
||||
NodeRef parseAfterIdent(Frag& frag, char*& src, const char* seps) {
|
||||
skipSpace(src);
|
||||
if (*src == '(') return parseExpression(parseCall(parseFrag(frag), src), src, seps);
|
||||
if (*src == '[') return parseExpression(parseIndexing(parseFrag(frag), src), src, seps);
|
||||
if (*src == ':' && expressionPartsStack.back().size() == 0) {
|
||||
src++;
|
||||
skipSpace(src);
|
||||
NodeRef inner;
|
||||
if (*src == '{') { // context lets us know this is not an object, but a block
|
||||
inner = parseBracketedBlock(src);
|
||||
} else {
|
||||
inner = parseElement(src, seps);
|
||||
}
|
||||
return Builder::makeLabel(frag.str, inner);
|
||||
}
|
||||
if (*src == '.') return parseExpression(parseDotting(parseFrag(frag), src), src, seps);
|
||||
return parseExpression(parseFrag(frag), src, seps);
|
||||
}
|
||||
|
||||
NodeRef parseCall(NodeRef target, char*& src) {
|
||||
expressionPartsStack.resize(expressionPartsStack.size()+1);
|
||||
assert(*src == '(');
|
||||
src++;
|
||||
NodeRef ret = Builder::makeCall(target);
|
||||
while (1) {
|
||||
skipSpace(src);
|
||||
if (*src == ')') break;
|
||||
Builder::appendToCall(ret, parseElement(src, ",)"));
|
||||
skipSpace(src);
|
||||
if (*src == ')') break;
|
||||
if (*src == ',') {
|
||||
src++;
|
||||
continue;
|
||||
}
|
||||
abort();
|
||||
}
|
||||
src++;
|
||||
assert(expressionPartsStack.back().size() == 0);
|
||||
expressionPartsStack.pop_back();
|
||||
return ret;
|
||||
}
|
||||
|
||||
NodeRef parseIndexing(NodeRef target, char*& src) {
|
||||
expressionPartsStack.resize(expressionPartsStack.size()+1);
|
||||
assert(*src == '[');
|
||||
src++;
|
||||
NodeRef ret = Builder::makeIndexing(target, parseElement(src, "]"));
|
||||
skipSpace(src);
|
||||
assert(*src == ']');
|
||||
src++;
|
||||
assert(expressionPartsStack.back().size() == 0);
|
||||
expressionPartsStack.pop_back();
|
||||
return ret;
|
||||
}
|
||||
|
||||
NodeRef parseDotting(NodeRef target, char*& src) {
|
||||
assert(*src == '.');
|
||||
src++;
|
||||
Frag key(src);
|
||||
assert(key.type == IDENT);
|
||||
src += key.size;
|
||||
return Builder::makeDot(target, key.str);
|
||||
}
|
||||
|
||||
NodeRef parseAfterParen(char*& src) {
|
||||
expressionPartsStack.resize(expressionPartsStack.size()+1);
|
||||
skipSpace(src);
|
||||
NodeRef ret = parseElement(src, ")");
|
||||
skipSpace(src);
|
||||
assert(*src == ')');
|
||||
src++;
|
||||
assert(expressionPartsStack.back().size() == 0);
|
||||
expressionPartsStack.pop_back();
|
||||
return ret;
|
||||
}
|
||||
|
||||
NodeRef parseAfterBrace(char*& src) {
|
||||
expressionPartsStack.resize(expressionPartsStack.size()+1);
|
||||
NodeRef ret = Builder::makeArray();
|
||||
while (1) {
|
||||
skipSpace(src);
|
||||
assert(*src);
|
||||
if (*src == ']') break;
|
||||
NodeRef element = parseElement(src, ",]");
|
||||
Builder::appendToArray(ret, element);
|
||||
skipSpace(src);
|
||||
if (*src == ']') break;
|
||||
if (*src == ',') {
|
||||
src++;
|
||||
continue;
|
||||
}
|
||||
abort();
|
||||
}
|
||||
src++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
NodeRef parseAfterCurly(char*& src) {
|
||||
expressionPartsStack.resize(expressionPartsStack.size()+1);
|
||||
NodeRef ret = Builder::makeObject();
|
||||
while (1) {
|
||||
skipSpace(src);
|
||||
assert(*src);
|
||||
if (*src == '}') break;
|
||||
Frag key(src);
|
||||
assert(key.type == IDENT || key.type == STRING);
|
||||
src += key.size;
|
||||
skipSpace(src);
|
||||
assert(*src == ':');
|
||||
src++;
|
||||
NodeRef value = parseElement(src, ",}");
|
||||
Builder::appendToObject(ret, key.str, value);
|
||||
skipSpace(src);
|
||||
if (*src == '}') break;
|
||||
if (*src == ',') {
|
||||
src++;
|
||||
continue;
|
||||
}
|
||||
abort();
|
||||
}
|
||||
src++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void dumpParts(ExpressionParts& parts, int i) {
|
||||
printf("expressionparts: %d (at %d)\n", parts.size(), i);
|
||||
printf("| ");
|
||||
for (int i = 0; i < parts.size(); i++) {
|
||||
if (parts[i].isNode) {
|
||||
parts[i].getNode()->stringify(std::cout);
|
||||
printf(" ");
|
||||
} else {
|
||||
printf(" _%s_ ", parts[i].getOp().str);
|
||||
}
|
||||
}
|
||||
printf("|\n");
|
||||
}
|
||||
|
||||
NodeRef makeBinary(NodeRef left, IString op, NodeRef right) {
|
||||
if (op == PERIOD) {
|
||||
return Builder::makeDot(left, right);
|
||||
} else {
|
||||
return Builder::makeBinary(left, op ,right);
|
||||
}
|
||||
}
|
||||
|
||||
NodeRef parseExpression(ExpressionElement initial, char*&src, const char* seps) {
|
||||
//dump("parseExpression", src);
|
||||
ExpressionParts& parts = expressionPartsStack.back();
|
||||
skipSpace(src);
|
||||
if (*src == 0 || hasChar(seps, *src)) {
|
||||
if (parts.size() > 0) {
|
||||
parts.push_back(initial); // cherry on top of the cake
|
||||
}
|
||||
return initial.getNode();
|
||||
}
|
||||
bool top = parts.size() == 0;
|
||||
if (initial.isNode) {
|
||||
Frag next(src);
|
||||
if (next.type == OPERATOR) {
|
||||
parts.push_back(initial);
|
||||
src += next.size;
|
||||
parts.push_back(next.str);
|
||||
} else {
|
||||
if (*src == '(') {
|
||||
initial = parseCall(initial.getNode(), src);
|
||||
} else if (*src == '[') {
|
||||
initial = parseIndexing(initial.getNode(), src);
|
||||
} else {
|
||||
dump("bad parseExpression state", src);
|
||||
abort();
|
||||
}
|
||||
return parseExpression(initial, src, seps);
|
||||
}
|
||||
} else {
|
||||
parts.push_back(initial);
|
||||
}
|
||||
NodeRef last = parseElement(src, seps);
|
||||
if (!top) return last;
|
||||
{
|
||||
ExpressionParts& parts = expressionPartsStack.back(); // |parts| may have been invalidated by that call
|
||||
// we are the toplevel. sort it all out
|
||||
// collapse right to left, highest priority first
|
||||
//dumpParts(parts, 0);
|
||||
for (auto& ops : operatorClasses) {
|
||||
if (ops.rtl) {
|
||||
// right to left
|
||||
for (int i = parts.size()-1; i >= 0; i--) {
|
||||
if (parts[i].isNode) continue;
|
||||
IString op = parts[i].getOp();
|
||||
if (!ops.ops.has(op)) continue;
|
||||
if (ops.type == OperatorClass::Binary && i > 0 && i < (int)parts.size()-1) {
|
||||
parts[i] = makeBinary(parts[i-1].getNode(), op, parts[i+1].getNode());
|
||||
parts.erase(parts.begin() + i + 1);
|
||||
parts.erase(parts.begin() + i - 1);
|
||||
} else if (ops.type == OperatorClass::Prefix && i < (int)parts.size()-1) {
|
||||
if (i > 0 && parts[i-1].isNode) continue; // cannot apply prefix operator if it would join two nodes
|
||||
parts[i] = Builder::makePrefix(op, parts[i+1].getNode());
|
||||
parts.erase(parts.begin() + i + 1);
|
||||
} else if (ops.type == OperatorClass::Tertiary) {
|
||||
// we must be at X ? Y : Z
|
||||
// ^
|
||||
//dumpParts(parts, i);
|
||||
if (op != COLON) continue;
|
||||
assert(i < (int)parts.size()-1 && i >= 3);
|
||||
if (parts[i-2].getOp() != QUESTION) continue; // e.g. x ? y ? 1 : 0 : 2
|
||||
parts[i-3] = Builder::makeConditional(parts[i-3].getNode(), parts[i-1].getNode(), parts[i+1].getNode());
|
||||
parts.erase(parts.begin() + i - 2, parts.begin() + i + 2);
|
||||
i = parts.size(); // basically a reset, due to things like x ? y ? 1 : 0 : 2
|
||||
} // TODO: postfix
|
||||
}
|
||||
} else {
|
||||
// left to right
|
||||
for (int i = 0; i < (int)parts.size(); i++) {
|
||||
if (parts[i].isNode) continue;
|
||||
IString op = parts[i].getOp();
|
||||
if (!ops.ops.has(op)) continue;
|
||||
if (ops.type == OperatorClass::Binary && i > 0 && i < (int)parts.size()-1) {
|
||||
parts[i] = makeBinary(parts[i-1].getNode(), op, parts[i+1].getNode());
|
||||
parts.erase(parts.begin() + i + 1);
|
||||
parts.erase(parts.begin() + i - 1);
|
||||
i--;
|
||||
} else if (ops.type == OperatorClass::Prefix && i < (int)parts.size()-1) {
|
||||
if (i > 0 && parts[i-1].isNode) continue; // cannot apply prefix operator if it would join two nodes
|
||||
parts[i] = Builder::makePrefix(op, parts[i+1].getNode());
|
||||
parts.erase(parts.begin() + i + 1);
|
||||
i = std::max(i-2, 0); // allow a previous prefix operator to cascade
|
||||
} // TODO: tertiary, postfix
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(parts.size() == 1);
|
||||
NodeRef ret = parts[0].getNode();
|
||||
parts.clear();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// Parses a block of code (e.g. a bunch of statements inside {,}, or the top level of o file)
|
||||
NodeRef parseBlock(char*& src, const char* seps=";", IString keywordSep1=IString(), IString keywordSep2=IString()) {
|
||||
NodeRef block = Builder::makeBlock();
|
||||
//dump("parseBlock", src);
|
||||
while (1) {
|
||||
skipSpace(src);
|
||||
if (*src == 0) break;
|
||||
if (*src == ';') {
|
||||
src++; // skip a statement in this block
|
||||
continue;
|
||||
}
|
||||
if (hasChar(seps, *src)) break;
|
||||
if (!!keywordSep1) {
|
||||
Frag next(src);
|
||||
if (next.type == KEYWORD && next.str == keywordSep1) break;
|
||||
}
|
||||
if (!!keywordSep2) {
|
||||
Frag next(src);
|
||||
if (next.type == KEYWORD && next.str == keywordSep2) break;
|
||||
}
|
||||
NodeRef element = parseElementOrStatement(src, seps);
|
||||
Builder::appendToBlock(block, element);
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
NodeRef parseBracketedBlock(char*& src) {
|
||||
skipSpace(src);
|
||||
assert(*src == '{');
|
||||
src++;
|
||||
NodeRef block = parseBlock(src, ";}"); // the two are not symmetrical, ; is just internally separating, } is the final one - parseBlock knows all this
|
||||
assert(*src == '}');
|
||||
src++;
|
||||
return block;
|
||||
}
|
||||
|
||||
NodeRef parseElementOrStatement(char*& src, const char *seps) {
|
||||
skipSpace(src);
|
||||
if (*src == ';') {
|
||||
src++;
|
||||
return Builder::makeBlock(); // we don't need the brackets here, but oh well
|
||||
}
|
||||
if (*src == '{') { // detect a trivial {} in a statement context
|
||||
char *before = src;
|
||||
src++;
|
||||
skipSpace(src);
|
||||
if (*src == '}') {
|
||||
src++;
|
||||
return Builder::makeBlock(); // we don't need the brackets here, but oh well
|
||||
}
|
||||
src = before;
|
||||
}
|
||||
NodeRef ret = parseElement(src, seps);
|
||||
skipSpace(src);
|
||||
if (*src == ';') {
|
||||
ret = Builder::makeStatement(ret);
|
||||
src++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
NodeRef parseMaybeBracketed(char*& src, const char *seps) {
|
||||
skipSpace(src);
|
||||
return *src == '{' ? parseBracketedBlock(src) : parseElementOrStatement(src, seps);
|
||||
}
|
||||
|
||||
NodeRef parseParenned(char*& src) {
|
||||
skipSpace(src);
|
||||
assert(*src == '(');
|
||||
src++;
|
||||
NodeRef ret = parseElement(src, ")");
|
||||
skipSpace(src);
|
||||
assert(*src == ')');
|
||||
src++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Debugging
|
||||
|
||||
char *allSource;
|
||||
int allSize;
|
||||
|
||||
static void dump(const char *where, char* curr) {
|
||||
/*
|
||||
printf("%s:\n=============\n", where);
|
||||
for (int i = 0; i < allSize; i++) printf("%c", allSource[i] ? allSource[i] : '?');
|
||||
printf("\n");
|
||||
for (int i = 0; i < (curr - allSource); i++) printf(" ");
|
||||
printf("^\n=============\n");
|
||||
*/
|
||||
fprintf(stderr, "%s:\n==========\n", where);
|
||||
int newlinesLeft = 2;
|
||||
int charsLeft = 200;
|
||||
while (*curr) {
|
||||
if (*curr == '\n') {
|
||||
newlinesLeft--;
|
||||
if (newlinesLeft == 0) break;
|
||||
}
|
||||
charsLeft--;
|
||||
if (charsLeft == 0) break;
|
||||
fprintf(stderr, "%c", *curr++);
|
||||
}
|
||||
fprintf(stderr, "\n\n");
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
Parser() : allSource(nullptr), allSize(0) {
|
||||
expressionPartsStack.resize(1);
|
||||
}
|
||||
|
||||
// Highest-level parsing, as of a JavaScript script file.
|
||||
NodeRef parseToplevel(char* src) {
|
||||
allSource = src;
|
||||
allSize = strlen(src);
|
||||
NodeRef toplevel = Builder::makeToplevel();
|
||||
Builder::setBlockContent(toplevel, parseBlock(src));
|
||||
return toplevel;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cashew
|
||||
|
||||
#endif // wasm_parser_h
|
@ -1,371 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "simple_ast.h"
|
||||
|
||||
namespace cashew {
|
||||
|
||||
// Ref methods
|
||||
|
||||
Ref& Ref::operator[](unsigned x) {
|
||||
return (*get())[x];
|
||||
}
|
||||
|
||||
Ref& Ref::operator[](IString x) {
|
||||
return (*get())[x];
|
||||
}
|
||||
|
||||
bool Ref::operator==(const char *str) {
|
||||
return get()->isString() && !strcmp(get()->str.str, str);
|
||||
}
|
||||
|
||||
bool Ref::operator!=(const char *str) {
|
||||
return get()->isString() ? !!strcmp(get()->str.str, str) : true;
|
||||
}
|
||||
|
||||
bool Ref::operator==(const IString &str) {
|
||||
return get()->isString() && get()->str == str;
|
||||
}
|
||||
|
||||
bool Ref::operator!=(const IString &str) {
|
||||
return get()->isString() && get()->str != str;
|
||||
}
|
||||
|
||||
bool Ref::operator==(Ref other) {
|
||||
return **this == *other;
|
||||
}
|
||||
|
||||
bool Ref::operator!() {
|
||||
return !get() || get()->isNull();
|
||||
}
|
||||
|
||||
// Arena
|
||||
|
||||
GlobalMixedArena arena;
|
||||
|
||||
// Value
|
||||
|
||||
Value& Value::setAssign(Ref target, Ref value) {
|
||||
asAssign()->target() = target;
|
||||
asAssign()->value() = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Value& Value::setAssignName(IString target, Ref value) {
|
||||
asAssignName()->target() = target;
|
||||
asAssignName()->value() = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Assign* Value::asAssign() {
|
||||
assert(isAssign());
|
||||
return static_cast<Assign*>(this);
|
||||
}
|
||||
|
||||
AssignName* Value::asAssignName() {
|
||||
assert(isAssignName());
|
||||
return static_cast<AssignName*>(this);
|
||||
}
|
||||
|
||||
void Value::stringify(std::ostream &os, bool pretty) {
|
||||
static int indent = 0;
|
||||
#define indentify() { for (int i_ = 0; i_ < indent; i_++) os << " "; }
|
||||
switch (type) {
|
||||
case String: {
|
||||
if (str.str) {
|
||||
os << '"' << str.str << '"';
|
||||
} else {
|
||||
os << "\"(null)\"";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Number: {
|
||||
os << std::setprecision(17) << num; // doubles can have 17 digits of precision
|
||||
break;
|
||||
}
|
||||
case Array: {
|
||||
if (arr->size() == 0) {
|
||||
os << "[]";
|
||||
break;
|
||||
}
|
||||
os << '[';
|
||||
if (pretty) {
|
||||
os << std::endl;
|
||||
indent++;
|
||||
}
|
||||
for (size_t i = 0; i < arr->size(); i++) {
|
||||
if (i > 0) {
|
||||
if (pretty) os << "," << std::endl;
|
||||
else os << ", ";
|
||||
}
|
||||
indentify();
|
||||
(*arr)[i]->stringify(os, pretty);
|
||||
}
|
||||
if (pretty) {
|
||||
os << std::endl;
|
||||
indent--;
|
||||
}
|
||||
indentify();
|
||||
os << ']';
|
||||
break;
|
||||
}
|
||||
case Null: {
|
||||
os << "null";
|
||||
break;
|
||||
}
|
||||
case Bool: {
|
||||
os << (boo ? "true" : "false");
|
||||
break;
|
||||
}
|
||||
case Object: {
|
||||
os << '{';
|
||||
if (pretty) {
|
||||
os << std::endl;
|
||||
indent++;
|
||||
}
|
||||
bool first = true;
|
||||
for (auto i : *obj) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
os << ", ";
|
||||
if (pretty) os << std::endl;
|
||||
}
|
||||
indentify();
|
||||
os << '"' << i.first.c_str() << "\": ";
|
||||
i.second->stringify(os, pretty);
|
||||
}
|
||||
if (pretty) {
|
||||
os << std::endl;
|
||||
indent--;
|
||||
}
|
||||
indentify();
|
||||
os << '}';
|
||||
break;
|
||||
}
|
||||
case Assign_: {
|
||||
os << "[";
|
||||
ref->stringify(os, pretty);
|
||||
os << ", ";
|
||||
asAssign()->value()->stringify(os, pretty);
|
||||
os << "]";
|
||||
break;
|
||||
}
|
||||
case AssignName_: {
|
||||
os << "[\"" << asAssignName()->target().str << "\"";
|
||||
os << ", ";
|
||||
asAssignName()->value()->stringify(os, pretty);
|
||||
os << "]";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dump
|
||||
|
||||
void dump(const char *str, Ref node, bool pretty) {
|
||||
std::cerr << str << ": ";
|
||||
if (!!node) node->stringify(std::cerr, pretty);
|
||||
else std::cerr << "(nullptr)";
|
||||
std::cerr << std::endl;
|
||||
}
|
||||
|
||||
// AST traversals
|
||||
|
||||
// Traversals
|
||||
|
||||
struct TraverseInfo {
|
||||
TraverseInfo() {}
|
||||
TraverseInfo(Ref node, ArrayStorage* arr) : node(node), arr(arr), index(0) {}
|
||||
Ref node;
|
||||
ArrayStorage* arr;
|
||||
int index;
|
||||
};
|
||||
|
||||
template <class T, int init>
|
||||
struct StackedStack { // a stack, on the stack
|
||||
T stackStorage[init];
|
||||
T* storage;
|
||||
int used, available; // used amount, available amount
|
||||
bool alloced;
|
||||
|
||||
StackedStack() : used(0), available(init), alloced(false) {
|
||||
storage = stackStorage;
|
||||
}
|
||||
~StackedStack() {
|
||||
if (alloced) free(storage);
|
||||
}
|
||||
|
||||
int size() { return used; }
|
||||
|
||||
void push_back(const T& t) {
|
||||
assert(used <= available);
|
||||
if (used == available) {
|
||||
available *= 2;
|
||||
if (!alloced) {
|
||||
T* old = storage;
|
||||
storage = (T*)malloc(sizeof(T)*available);
|
||||
memcpy(storage, old, sizeof(T)*used);
|
||||
alloced = true;
|
||||
} else {
|
||||
T *newStorage = (T*)realloc(storage, sizeof(T)*available);
|
||||
assert(newStorage);
|
||||
storage = newStorage;
|
||||
}
|
||||
}
|
||||
assert(used < available);
|
||||
assert(storage);
|
||||
storage[used++] = t;
|
||||
}
|
||||
|
||||
T& back() {
|
||||
assert(used > 0);
|
||||
return storage[used-1];
|
||||
}
|
||||
|
||||
void pop_back() {
|
||||
assert(used > 0);
|
||||
used--;
|
||||
}
|
||||
};
|
||||
|
||||
#define visitable(node) (node->isArray() && node->size() > 0)
|
||||
|
||||
#define TRAV_STACK 40
|
||||
|
||||
// Traverse, calling visit before the children
|
||||
void traversePre(Ref node, std::function<void (Ref)> visit) {
|
||||
if (!visitable(node)) return;
|
||||
visit(node);
|
||||
StackedStack<TraverseInfo, TRAV_STACK> stack;
|
||||
int index = 0;
|
||||
ArrayStorage* arr = &node->getArray();
|
||||
int arrsize = (int)arr->size();
|
||||
Ref* arrdata = &(*arr)[0];
|
||||
stack.push_back(TraverseInfo(node, arr));
|
||||
while (1) {
|
||||
if (index < arrsize) {
|
||||
Ref sub = *(arrdata+index);
|
||||
index++;
|
||||
if (visitable(sub)) {
|
||||
stack.back().index = index;
|
||||
index = 0;
|
||||
visit(sub);
|
||||
arr = &sub->getArray();
|
||||
arrsize = (int)arr->size();
|
||||
arrdata = &(*arr)[0];
|
||||
stack.push_back(TraverseInfo(sub, arr));
|
||||
}
|
||||
} else {
|
||||
stack.pop_back();
|
||||
if (stack.size() == 0) break;
|
||||
TraverseInfo& back = stack.back();
|
||||
index = back.index;
|
||||
arr = back.arr;
|
||||
arrsize = (int)arr->size();
|
||||
arrdata = &(*arr)[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse, calling visitPre before the children and visitPost after
|
||||
void traversePrePost(Ref node, std::function<void (Ref)> visitPre, std::function<void (Ref)> visitPost) {
|
||||
if (!visitable(node)) return;
|
||||
visitPre(node);
|
||||
StackedStack<TraverseInfo, TRAV_STACK> stack;
|
||||
int index = 0;
|
||||
ArrayStorage* arr = &node->getArray();
|
||||
int arrsize = (int)arr->size();
|
||||
Ref* arrdata = &(*arr)[0];
|
||||
stack.push_back(TraverseInfo(node, arr));
|
||||
while (1) {
|
||||
if (index < arrsize) {
|
||||
Ref sub = *(arrdata+index);
|
||||
index++;
|
||||
if (visitable(sub)) {
|
||||
stack.back().index = index;
|
||||
index = 0;
|
||||
visitPre(sub);
|
||||
arr = &sub->getArray();
|
||||
arrsize = (int)arr->size();
|
||||
arrdata = &(*arr)[0];
|
||||
stack.push_back(TraverseInfo(sub, arr));
|
||||
}
|
||||
} else {
|
||||
visitPost(stack.back().node);
|
||||
stack.pop_back();
|
||||
if (stack.size() == 0) break;
|
||||
TraverseInfo& back = stack.back();
|
||||
index = back.index;
|
||||
arr = back.arr;
|
||||
arrsize = (int)arr->size();
|
||||
arrdata = &(*arr)[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse, calling visitPre before the children and visitPost after. If pre returns false, do not traverse children
|
||||
void traversePrePostConditional(Ref node, std::function<bool (Ref)> visitPre, std::function<void (Ref)> visitPost) {
|
||||
if (!visitable(node)) return;
|
||||
if (!visitPre(node)) return;
|
||||
StackedStack<TraverseInfo, TRAV_STACK> stack;
|
||||
int index = 0;
|
||||
ArrayStorage* arr = &node->getArray();
|
||||
int arrsize = (int)arr->size();
|
||||
Ref* arrdata = &(*arr)[0];
|
||||
stack.push_back(TraverseInfo(node, arr));
|
||||
while (1) {
|
||||
if (index < arrsize) {
|
||||
Ref sub = *(arrdata+index);
|
||||
index++;
|
||||
if (visitable(sub)) {
|
||||
if (visitPre(sub)) {
|
||||
stack.back().index = index;
|
||||
index = 0;
|
||||
arr = &sub->getArray();
|
||||
arrsize = (int)arr->size();
|
||||
arrdata = &(*arr)[0];
|
||||
stack.push_back(TraverseInfo(sub, arr));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
visitPost(stack.back().node);
|
||||
stack.pop_back();
|
||||
if (stack.size() == 0) break;
|
||||
TraverseInfo& back = stack.back();
|
||||
index = back.index;
|
||||
arr = back.arr;
|
||||
arrsize = (int)arr->size();
|
||||
arrdata = &(*arr)[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Traverses all the top-level functions in the document
|
||||
void traverseFunctions(Ref ast, std::function<void (Ref)> visit) {
|
||||
if (!ast || ast->size() == 0) return;
|
||||
if (ast[0] == TOPLEVEL) {
|
||||
Ref stats = ast[1];
|
||||
for (size_t i = 0; i < stats->size(); i++) {
|
||||
Ref curr = stats[i];
|
||||
if (curr[0] == DEFUN) visit(curr);
|
||||
}
|
||||
} else if (ast[0] == DEFUN) {
|
||||
visit(ast);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cashew
|
File diff suppressed because it is too large
Load Diff
@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_snprintf_h
|
||||
#define wasm_snprintf_h
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
// Visual Studio does not support C99, so emulate snprintf support for it manually.
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#define snprintf c99_snprintf
|
||||
|
||||
inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap)
|
||||
{
|
||||
int count = -1;
|
||||
|
||||
if (size != 0)
|
||||
count = _vsnprintf_s(str, size, _TRUNCATE, format, ap);
|
||||
if (count == -1)
|
||||
count = _vscprintf(format, ap);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
inline int c99_snprintf(char* str, size_t size, const char* format, ...)
|
||||
{
|
||||
int count;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
count = c99_vsnprintf(str, size, format, ap);
|
||||
va_end(ap);
|
||||
|
||||
return count;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // wasm_snprintf_h
|
@ -1,6 +0,0 @@
|
||||
SET(ir_SOURCES
|
||||
ExpressionAnalyzer.cpp
|
||||
ExpressionManipulator.cpp
|
||||
LocalGraph.cpp
|
||||
)
|
||||
ADD_LIBRARY(ir STATIC ${ir_SOURCES})
|
@ -1,558 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "support/hash.h"
|
||||
#include "ir/utils.h"
|
||||
#include "ir/load-utils.h"
|
||||
|
||||
namespace wasm {
|
||||
// Given a stack of expressions, checks if the topmost is used as a result.
|
||||
// For example, if the parent is a block and the node is before the last position,
|
||||
// it is not used.
|
||||
bool ExpressionAnalyzer::isResultUsed(std::vector<Expression*> stack, Function* func) {
|
||||
for (int i = int(stack.size()) - 2; i >= 0; i--) {
|
||||
auto* curr = stack[i];
|
||||
auto* above = stack[i + 1];
|
||||
// only if and block can drop values (pre-drop expression was added) FIXME
|
||||
if (curr->is<Block>()) {
|
||||
auto* block = curr->cast<Block>();
|
||||
for (size_t j = 0; j < block->list.size() - 1; j++) {
|
||||
if (block->list[j] == above) return false;
|
||||
}
|
||||
assert(block->list.back() == above);
|
||||
// continue down
|
||||
} else if (curr->is<If>()) {
|
||||
auto* iff = curr->cast<If>();
|
||||
if (above == iff->condition) return true;
|
||||
if (!iff->ifFalse) return false;
|
||||
assert(above == iff->ifTrue || above == iff->ifFalse);
|
||||
// continue down
|
||||
} else {
|
||||
if (curr->is<Drop>()) return false;
|
||||
return true; // all other node types use the result
|
||||
}
|
||||
}
|
||||
// The value might be used, so it depends on if the function returns
|
||||
return func->result != none;
|
||||
}
|
||||
|
||||
// Checks if a value is dropped.
|
||||
bool ExpressionAnalyzer::isResultDropped(std::vector<Expression*> stack) {
|
||||
for (int i = int(stack.size()) - 2; i >= 0; i--) {
|
||||
auto* curr = stack[i];
|
||||
auto* above = stack[i + 1];
|
||||
if (curr->is<Block>()) {
|
||||
auto* block = curr->cast<Block>();
|
||||
for (size_t j = 0; j < block->list.size() - 1; j++) {
|
||||
if (block->list[j] == above) return false;
|
||||
}
|
||||
assert(block->list.back() == above);
|
||||
// continue down
|
||||
} else if (curr->is<If>()) {
|
||||
auto* iff = curr->cast<If>();
|
||||
if (above == iff->condition) return false;
|
||||
if (!iff->ifFalse) return false;
|
||||
assert(above == iff->ifTrue || above == iff->ifFalse);
|
||||
// continue down
|
||||
} else {
|
||||
if (curr->is<Drop>()) return true; // dropped
|
||||
return false; // all other node types use the result
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool ExpressionAnalyzer::flexibleEqual(Expression* left, Expression* right, ExprComparer comparer) {
|
||||
std::vector<Name> nameStack;
|
||||
std::map<Name, std::vector<Name>> rightNames; // for each name on the left, the stack of names on the right (a stack, since names are scoped and can nest duplicatively
|
||||
Nop popNameMarker;
|
||||
std::vector<Expression*> leftStack;
|
||||
std::vector<Expression*> rightStack;
|
||||
|
||||
auto noteNames = [&](Name left, Name right) {
|
||||
if (left.is() != right.is()) return false;
|
||||
if (left.is()) {
|
||||
nameStack.push_back(left);
|
||||
rightNames[left].push_back(right);
|
||||
leftStack.push_back(&popNameMarker);
|
||||
rightStack.push_back(&popNameMarker);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
auto checkNames = [&](Name left, Name right) {
|
||||
auto iter = rightNames.find(left);
|
||||
if (iter == rightNames.end()) return left == right; // non-internal name
|
||||
return iter->second.back() == right;
|
||||
};
|
||||
auto popName = [&]() {
|
||||
auto left = nameStack.back();
|
||||
nameStack.pop_back();
|
||||
rightNames[left].pop_back();
|
||||
};
|
||||
|
||||
leftStack.push_back(left);
|
||||
rightStack.push_back(right);
|
||||
|
||||
while (leftStack.size() > 0 && rightStack.size() > 0) {
|
||||
left = leftStack.back();
|
||||
leftStack.pop_back();
|
||||
right = rightStack.back();
|
||||
rightStack.pop_back();
|
||||
if (!left != !right) return false;
|
||||
if (!left) continue;
|
||||
if (left == &popNameMarker) {
|
||||
popName();
|
||||
continue;
|
||||
}
|
||||
if (comparer(left, right)) continue; // comparison hook, before all the rest
|
||||
// continue with normal structural comparison
|
||||
if (left->_id != right->_id) return false;
|
||||
#define PUSH(clazz, what) \
|
||||
leftStack.push_back(left->cast<clazz>()->what); \
|
||||
rightStack.push_back(right->cast<clazz>()->what);
|
||||
#define CHECK(clazz, what) \
|
||||
if (left->cast<clazz>()->what != right->cast<clazz>()->what) return false;
|
||||
switch (left->_id) {
|
||||
case Expression::Id::BlockId: {
|
||||
if (!noteNames(left->cast<Block>()->name, right->cast<Block>()->name)) return false;
|
||||
CHECK(Block, list.size());
|
||||
for (Index i = 0; i < left->cast<Block>()->list.size(); i++) {
|
||||
PUSH(Block, list[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Expression::Id::IfId: {
|
||||
PUSH(If, condition);
|
||||
PUSH(If, ifTrue);
|
||||
PUSH(If, ifFalse);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::LoopId: {
|
||||
if (!noteNames(left->cast<Loop>()->name, right->cast<Loop>()->name)) return false;
|
||||
PUSH(Loop, body);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::BreakId: {
|
||||
if (!checkNames(left->cast<Break>()->name, right->cast<Break>()->name)) return false;
|
||||
PUSH(Break, condition);
|
||||
PUSH(Break, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::SwitchId: {
|
||||
CHECK(Switch, targets.size());
|
||||
for (Index i = 0; i < left->cast<Switch>()->targets.size(); i++) {
|
||||
if (!checkNames(left->cast<Switch>()->targets[i], right->cast<Switch>()->targets[i])) return false;
|
||||
}
|
||||
if (!checkNames(left->cast<Switch>()->default_, right->cast<Switch>()->default_)) return false;
|
||||
PUSH(Switch, condition);
|
||||
PUSH(Switch, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::CallId: {
|
||||
CHECK(Call, target);
|
||||
CHECK(Call, operands.size());
|
||||
for (Index i = 0; i < left->cast<Call>()->operands.size(); i++) {
|
||||
PUSH(Call, operands[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Expression::Id::CallImportId: {
|
||||
CHECK(CallImport, target);
|
||||
CHECK(CallImport, operands.size());
|
||||
for (Index i = 0; i < left->cast<CallImport>()->operands.size(); i++) {
|
||||
PUSH(CallImport, operands[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Expression::Id::CallIndirectId: {
|
||||
PUSH(CallIndirect, target);
|
||||
CHECK(CallIndirect, fullType);
|
||||
CHECK(CallIndirect, operands.size());
|
||||
for (Index i = 0; i < left->cast<CallIndirect>()->operands.size(); i++) {
|
||||
PUSH(CallIndirect, operands[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Expression::Id::GetLocalId: {
|
||||
CHECK(GetLocal, index);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::SetLocalId: {
|
||||
CHECK(SetLocal, index);
|
||||
CHECK(SetLocal, type); // for tee/set
|
||||
PUSH(SetLocal, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::GetGlobalId: {
|
||||
CHECK(GetGlobal, name);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::SetGlobalId: {
|
||||
CHECK(SetGlobal, name);
|
||||
PUSH(SetGlobal, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::LoadId: {
|
||||
CHECK(Load, bytes);
|
||||
if (LoadUtils::isSignRelevant(left->cast<Load>()) &&
|
||||
LoadUtils::isSignRelevant(right->cast<Load>())) {
|
||||
CHECK(Load, signed_);
|
||||
}
|
||||
CHECK(Load, offset);
|
||||
CHECK(Load, align);
|
||||
PUSH(Load, ptr);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::StoreId: {
|
||||
CHECK(Store, bytes);
|
||||
CHECK(Store, offset);
|
||||
CHECK(Store, align);
|
||||
CHECK(Store, valueType);
|
||||
PUSH(Store, ptr);
|
||||
PUSH(Store, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::AtomicCmpxchgId: {
|
||||
CHECK(AtomicCmpxchg, bytes);
|
||||
CHECK(AtomicCmpxchg, offset);
|
||||
PUSH(AtomicCmpxchg, ptr);
|
||||
PUSH(AtomicCmpxchg, expected);
|
||||
PUSH(AtomicCmpxchg, replacement);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::AtomicRMWId: {
|
||||
CHECK(AtomicRMW, op);
|
||||
CHECK(AtomicRMW, bytes);
|
||||
CHECK(AtomicRMW, offset);
|
||||
PUSH(AtomicRMW, ptr);
|
||||
PUSH(AtomicRMW, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::AtomicWaitId: {
|
||||
CHECK(AtomicWait, expectedType);
|
||||
PUSH(AtomicWait, ptr);
|
||||
PUSH(AtomicWait, expected);
|
||||
PUSH(AtomicWait, timeout);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::AtomicWakeId: {
|
||||
PUSH(AtomicWake, ptr);
|
||||
PUSH(AtomicWake, wakeCount);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::ConstId: {
|
||||
if (!left->cast<Const>()->value.bitwiseEqual(right->cast<Const>()->value)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Expression::Id::UnaryId: {
|
||||
CHECK(Unary, op);
|
||||
PUSH(Unary, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::BinaryId: {
|
||||
CHECK(Binary, op);
|
||||
PUSH(Binary, left);
|
||||
PUSH(Binary, right);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::SelectId: {
|
||||
PUSH(Select, ifTrue);
|
||||
PUSH(Select, ifFalse);
|
||||
PUSH(Select, condition);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::DropId: {
|
||||
PUSH(Drop, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::ReturnId: {
|
||||
PUSH(Return, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::HostId: {
|
||||
CHECK(Host, op);
|
||||
CHECK(Host, nameOperand);
|
||||
CHECK(Host, operands.size());
|
||||
for (Index i = 0; i < left->cast<Host>()->operands.size(); i++) {
|
||||
PUSH(Host, operands[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Expression::Id::NopId: {
|
||||
break;
|
||||
}
|
||||
case Expression::Id::UnreachableId: {
|
||||
break;
|
||||
}
|
||||
default: WASM_UNREACHABLE();
|
||||
}
|
||||
#undef CHECK
|
||||
#undef PUSH
|
||||
}
|
||||
if (leftStack.size() > 0 || rightStack.size() > 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// hash an expression, ignoring superficial details like specific internal names
|
||||
uint32_t ExpressionAnalyzer::hash(Expression* curr) {
|
||||
uint32_t digest = 0;
|
||||
|
||||
auto hash = [&digest](uint32_t hash) {
|
||||
digest = rehash(digest, hash);
|
||||
};
|
||||
auto hash64 = [&digest](uint64_t hash) {
|
||||
digest = rehash(rehash(digest, uint32_t(hash >> 32)), uint32_t(hash));
|
||||
};
|
||||
|
||||
std::vector<Name> nameStack;
|
||||
Index internalCounter = 0;
|
||||
std::map<Name, std::vector<Index>> internalNames; // for each internal name, a vector if unique ids
|
||||
Nop popNameMarker;
|
||||
std::vector<Expression*> stack;
|
||||
|
||||
auto noteName = [&](Name curr) {
|
||||
if (curr.is()) {
|
||||
nameStack.push_back(curr);
|
||||
internalNames[curr].push_back(internalCounter++);
|
||||
stack.push_back(&popNameMarker);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
auto hashName = [&](Name curr) {
|
||||
auto iter = internalNames.find(curr);
|
||||
if (iter == internalNames.end()) hash64(uint64_t(curr.str));
|
||||
else hash(iter->second.back());
|
||||
};
|
||||
auto popName = [&]() {
|
||||
auto curr = nameStack.back();
|
||||
nameStack.pop_back();
|
||||
internalNames[curr].pop_back();
|
||||
};
|
||||
|
||||
stack.push_back(curr);
|
||||
|
||||
while (stack.size() > 0) {
|
||||
curr = stack.back();
|
||||
stack.pop_back();
|
||||
if (!curr) continue;
|
||||
if (curr == &popNameMarker) {
|
||||
popName();
|
||||
continue;
|
||||
}
|
||||
hash(curr->_id);
|
||||
// we often don't need to hash the type, as it is tied to other values
|
||||
// we are hashing anyhow, but there are exceptions: for example, a
|
||||
// get_local's type is determined by the function, so if we are
|
||||
// hashing only expression fragments, then two from different
|
||||
// functions may turn out the same even if the type differs. Likewise,
|
||||
// if we hash between modules, then we need to take int account
|
||||
// call_imports type, etc. The simplest thing is just to hash the
|
||||
// type for all of them.
|
||||
hash(curr->type);
|
||||
|
||||
#define PUSH(clazz, what) \
|
||||
stack.push_back(curr->cast<clazz>()->what);
|
||||
#define HASH(clazz, what) \
|
||||
hash(curr->cast<clazz>()->what);
|
||||
#define HASH64(clazz, what) \
|
||||
hash64(curr->cast<clazz>()->what);
|
||||
#define HASH_NAME(clazz, what) \
|
||||
hash64(uint64_t(curr->cast<clazz>()->what.str));
|
||||
#define HASH_PTR(clazz, what) \
|
||||
hash64(uint64_t(curr->cast<clazz>()->what));
|
||||
switch (curr->_id) {
|
||||
case Expression::Id::BlockId: {
|
||||
noteName(curr->cast<Block>()->name);
|
||||
HASH(Block, list.size());
|
||||
for (Index i = 0; i < curr->cast<Block>()->list.size(); i++) {
|
||||
PUSH(Block, list[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Expression::Id::IfId: {
|
||||
PUSH(If, condition);
|
||||
PUSH(If, ifTrue);
|
||||
PUSH(If, ifFalse);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::LoopId: {
|
||||
noteName(curr->cast<Loop>()->name);
|
||||
PUSH(Loop, body);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::BreakId: {
|
||||
hashName(curr->cast<Break>()->name);
|
||||
PUSH(Break, condition);
|
||||
PUSH(Break, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::SwitchId: {
|
||||
HASH(Switch, targets.size());
|
||||
for (Index i = 0; i < curr->cast<Switch>()->targets.size(); i++) {
|
||||
hashName(curr->cast<Switch>()->targets[i]);
|
||||
}
|
||||
hashName(curr->cast<Switch>()->default_);
|
||||
PUSH(Switch, condition);
|
||||
PUSH(Switch, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::CallId: {
|
||||
HASH_NAME(Call, target);
|
||||
HASH(Call, operands.size());
|
||||
for (Index i = 0; i < curr->cast<Call>()->operands.size(); i++) {
|
||||
PUSH(Call, operands[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Expression::Id::CallImportId: {
|
||||
HASH_NAME(CallImport, target);
|
||||
HASH(CallImport, operands.size());
|
||||
for (Index i = 0; i < curr->cast<CallImport>()->operands.size(); i++) {
|
||||
PUSH(CallImport, operands[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Expression::Id::CallIndirectId: {
|
||||
PUSH(CallIndirect, target);
|
||||
HASH_NAME(CallIndirect, fullType);
|
||||
HASH(CallIndirect, operands.size());
|
||||
for (Index i = 0; i < curr->cast<CallIndirect>()->operands.size(); i++) {
|
||||
PUSH(CallIndirect, operands[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Expression::Id::GetLocalId: {
|
||||
HASH(GetLocal, index);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::SetLocalId: {
|
||||
HASH(SetLocal, index);
|
||||
PUSH(SetLocal, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::GetGlobalId: {
|
||||
HASH_NAME(GetGlobal, name);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::SetGlobalId: {
|
||||
HASH_NAME(SetGlobal, name);
|
||||
PUSH(SetGlobal, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::LoadId: {
|
||||
HASH(Load, bytes);
|
||||
if (LoadUtils::isSignRelevant(curr->cast<Load>())) {
|
||||
HASH(Load, signed_);
|
||||
}
|
||||
HASH(Load, offset);
|
||||
HASH(Load, align);
|
||||
PUSH(Load, ptr);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::StoreId: {
|
||||
HASH(Store, bytes);
|
||||
HASH(Store, offset);
|
||||
HASH(Store, align);
|
||||
HASH(Store, valueType);
|
||||
PUSH(Store, ptr);
|
||||
PUSH(Store, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::AtomicCmpxchgId: {
|
||||
HASH(AtomicCmpxchg, bytes);
|
||||
HASH(AtomicCmpxchg, offset);
|
||||
PUSH(AtomicCmpxchg, ptr);
|
||||
PUSH(AtomicCmpxchg, expected);
|
||||
PUSH(AtomicCmpxchg, replacement);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::AtomicRMWId: {
|
||||
HASH(AtomicRMW, op);
|
||||
HASH(AtomicRMW, bytes);
|
||||
HASH(AtomicRMW, offset);
|
||||
PUSH(AtomicRMW, ptr);
|
||||
PUSH(AtomicRMW, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::AtomicWaitId: {
|
||||
HASH(AtomicWait, expectedType);
|
||||
PUSH(AtomicWait, ptr);
|
||||
PUSH(AtomicWait, expected);
|
||||
PUSH(AtomicWait, timeout);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::AtomicWakeId: {
|
||||
PUSH(AtomicWake, ptr);
|
||||
PUSH(AtomicWake, wakeCount);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::ConstId: {
|
||||
HASH(Const, value.type);
|
||||
HASH64(Const, value.getBits());
|
||||
break;
|
||||
}
|
||||
case Expression::Id::UnaryId: {
|
||||
HASH(Unary, op);
|
||||
PUSH(Unary, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::BinaryId: {
|
||||
HASH(Binary, op);
|
||||
PUSH(Binary, left);
|
||||
PUSH(Binary, right);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::SelectId: {
|
||||
PUSH(Select, ifTrue);
|
||||
PUSH(Select, ifFalse);
|
||||
PUSH(Select, condition);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::DropId: {
|
||||
PUSH(Drop, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::ReturnId: {
|
||||
PUSH(Return, value);
|
||||
break;
|
||||
}
|
||||
case Expression::Id::HostId: {
|
||||
HASH(Host, op);
|
||||
HASH_NAME(Host, nameOperand);
|
||||
HASH(Host, operands.size());
|
||||
for (Index i = 0; i < curr->cast<Host>()->operands.size(); i++) {
|
||||
PUSH(Host, operands[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Expression::Id::NopId: {
|
||||
break;
|
||||
}
|
||||
case Expression::Id::UnreachableId: {
|
||||
break;
|
||||
}
|
||||
default: WASM_UNREACHABLE();
|
||||
}
|
||||
#undef HASH
|
||||
#undef PUSH
|
||||
}
|
||||
return digest;
|
||||
}
|
||||
} // namespace wasm
|
@ -1,177 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ir/utils.h"
|
||||
#include "support/hash.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
namespace ExpressionManipulator {
|
||||
|
||||
Expression* flexibleCopy(Expression* original, Module& wasm, CustomCopier custom) {
|
||||
struct Copier : public Visitor<Copier, Expression*> {
|
||||
Module& wasm;
|
||||
CustomCopier custom;
|
||||
|
||||
Builder builder;
|
||||
|
||||
Copier(Module& wasm, CustomCopier custom) : wasm(wasm), custom(custom), builder(wasm) {}
|
||||
|
||||
Expression* copy(Expression* curr) {
|
||||
if (!curr) return nullptr;
|
||||
auto* ret = custom(curr);
|
||||
if (ret) return ret;
|
||||
return Visitor<Copier, Expression*>::visit(curr);
|
||||
}
|
||||
|
||||
Expression* visitBlock(Block *curr) {
|
||||
ExpressionList list(wasm.allocator);
|
||||
for (Index i = 0; i < curr->list.size(); i++) {
|
||||
list.push_back(copy(curr->list[i]));
|
||||
}
|
||||
return builder.makeBlock(curr->name, list, curr->type);
|
||||
}
|
||||
Expression* visitIf(If *curr) {
|
||||
return builder.makeIf(copy(curr->condition), copy(curr->ifTrue), copy(curr->ifFalse), curr->type);
|
||||
}
|
||||
Expression* visitLoop(Loop *curr) {
|
||||
return builder.makeLoop(curr->name, copy(curr->body));
|
||||
}
|
||||
Expression* visitBreak(Break *curr) {
|
||||
return builder.makeBreak(curr->name, copy(curr->value), copy(curr->condition));
|
||||
}
|
||||
Expression* visitSwitch(Switch *curr) {
|
||||
return builder.makeSwitch(curr->targets, curr->default_, copy(curr->condition), copy(curr->value));
|
||||
}
|
||||
Expression* visitCall(Call *curr) {
|
||||
auto* ret = builder.makeCall(curr->target, {}, curr->type);
|
||||
for (Index i = 0; i < curr->operands.size(); i++) {
|
||||
ret->operands.push_back(copy(curr->operands[i]));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
Expression* visitCallImport(CallImport *curr) {
|
||||
auto* ret = builder.makeCallImport(curr->target, {}, curr->type);
|
||||
for (Index i = 0; i < curr->operands.size(); i++) {
|
||||
ret->operands.push_back(copy(curr->operands[i]));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
Expression* visitCallIndirect(CallIndirect *curr) {
|
||||
auto* ret = builder.makeCallIndirect(curr->fullType, copy(curr->target), {}, curr->type);
|
||||
for (Index i = 0; i < curr->operands.size(); i++) {
|
||||
ret->operands.push_back(copy(curr->operands[i]));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
Expression* visitGetLocal(GetLocal *curr) {
|
||||
return builder.makeGetLocal(curr->index, curr->type);
|
||||
}
|
||||
Expression* visitSetLocal(SetLocal *curr) {
|
||||
if (curr->isTee()) {
|
||||
return builder.makeTeeLocal(curr->index, copy(curr->value));
|
||||
} else {
|
||||
return builder.makeSetLocal(curr->index, copy(curr->value));
|
||||
}
|
||||
}
|
||||
Expression* visitGetGlobal(GetGlobal *curr) {
|
||||
return builder.makeGetGlobal(curr->name, curr->type);
|
||||
}
|
||||
Expression* visitSetGlobal(SetGlobal *curr) {
|
||||
return builder.makeSetGlobal(curr->name, copy(curr->value));
|
||||
}
|
||||
Expression* visitLoad(Load *curr) {
|
||||
if (curr->isAtomic) {
|
||||
return builder.makeAtomicLoad(curr->bytes, curr->offset,
|
||||
copy(curr->ptr), curr->type);
|
||||
}
|
||||
return builder.makeLoad(curr->bytes, curr->signed_, curr->offset, curr->align, copy(curr->ptr), curr->type);
|
||||
}
|
||||
Expression* visitStore(Store *curr) {
|
||||
if (curr->isAtomic) {
|
||||
return builder.makeAtomicStore(curr->bytes, curr->offset, copy(curr->ptr), copy(curr->value), curr->valueType);
|
||||
}
|
||||
return builder.makeStore(curr->bytes, curr->offset, curr->align, copy(curr->ptr), copy(curr->value), curr->valueType);
|
||||
}
|
||||
Expression* visitAtomicRMW(AtomicRMW* curr) {
|
||||
return builder.makeAtomicRMW(curr->op, curr->bytes, curr->offset,
|
||||
copy(curr->ptr), copy(curr->value), curr->type);
|
||||
}
|
||||
Expression* visitAtomicCmpxchg(AtomicCmpxchg* curr) {
|
||||
return builder.makeAtomicCmpxchg(curr->bytes, curr->offset,
|
||||
copy(curr->ptr), copy(curr->expected), copy(curr->replacement),
|
||||
curr->type);
|
||||
}
|
||||
Expression* visitAtomicWait(AtomicWait* curr) {
|
||||
return builder.makeAtomicWait(copy(curr->ptr), copy(curr->expected), copy(curr->timeout), curr->expectedType);
|
||||
}
|
||||
Expression* visitAtomicWake(AtomicWake* curr) {
|
||||
return builder.makeAtomicWake(copy(curr->ptr), copy(curr->wakeCount));
|
||||
}
|
||||
Expression* visitConst(Const *curr) {
|
||||
return builder.makeConst(curr->value);
|
||||
}
|
||||
Expression* visitUnary(Unary *curr) {
|
||||
return builder.makeUnary(curr->op, copy(curr->value));
|
||||
}
|
||||
Expression* visitBinary(Binary *curr) {
|
||||
return builder.makeBinary(curr->op, copy(curr->left), copy(curr->right));
|
||||
}
|
||||
Expression* visitSelect(Select *curr) {
|
||||
return builder.makeSelect(copy(curr->condition), copy(curr->ifTrue), copy(curr->ifFalse));
|
||||
}
|
||||
Expression* visitDrop(Drop *curr) {
|
||||
return builder.makeDrop(copy(curr->value));
|
||||
}
|
||||
Expression* visitReturn(Return *curr) {
|
||||
return builder.makeReturn(copy(curr->value));
|
||||
}
|
||||
Expression* visitHost(Host *curr) {
|
||||
assert(curr->operands.size() == 0);
|
||||
return builder.makeHost(curr->op, curr->nameOperand, {});
|
||||
}
|
||||
Expression* visitNop(Nop *curr) {
|
||||
return builder.makeNop();
|
||||
}
|
||||
Expression* visitUnreachable(Unreachable *curr) {
|
||||
return builder.makeUnreachable();
|
||||
}
|
||||
};
|
||||
|
||||
Copier copier(wasm, custom);
|
||||
return copier.copy(original);
|
||||
}
|
||||
|
||||
|
||||
// Splice an item into the middle of a block's list
|
||||
void spliceIntoBlock(Block* block, Index index, Expression* add) {
|
||||
auto& list = block->list;
|
||||
if (index == list.size()) {
|
||||
list.push_back(add); // simple append
|
||||
} else {
|
||||
// we need to make room
|
||||
list.push_back(nullptr);
|
||||
for (Index i = list.size() - 1; i > index; i--) {
|
||||
list[i] = list[i - 1];
|
||||
}
|
||||
list[index] = add;
|
||||
}
|
||||
block->finalize(block->type);
|
||||
}
|
||||
|
||||
} // namespace ExpressionManipulator
|
||||
|
||||
} // namespace wasm
|
@ -1,273 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#include <wasm-builder.h>
|
||||
#include <wasm-printing.h>
|
||||
#include <ir/find_all.h>
|
||||
#include <ir/local-graph.h>
|
||||
|
||||
namespace wasm {
|
||||
|
||||
LocalGraph::LocalGraph(Function* func, Module* module) {
|
||||
walkFunctionInModule(func, module);
|
||||
|
||||
#ifdef LOCAL_GRAPH_DEBUG
|
||||
std::cout << "LocalGraph::dump\n";
|
||||
for (auto& pair : getSetses) {
|
||||
auto* get = pair.first;
|
||||
auto& sets = pair.second;
|
||||
std::cout << "GET\n" << get << " is influenced by\n";
|
||||
for (auto* set : sets) {
|
||||
std::cout << set << '\n';
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void LocalGraph::computeInfluences() {
|
||||
for (auto& pair : locations) {
|
||||
auto* curr = pair.first;
|
||||
if (auto* set = curr->dynCast<SetLocal>()) {
|
||||
FindAll<GetLocal> findAll(set->value);
|
||||
for (auto* get : findAll.list) {
|
||||
getInfluences[get].insert(set);
|
||||
}
|
||||
} else {
|
||||
auto* get = curr->cast<GetLocal>();
|
||||
for (auto* set : getSetses[get]) {
|
||||
setInfluences[set].insert(get);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LocalGraph::doWalkFunction(Function* func) {
|
||||
numLocals = func->getNumLocals();
|
||||
if (numLocals == 0) return; // nothing to do
|
||||
// We begin with each param being assigned from the incoming value, and the zero-init for the locals,
|
||||
// so the initial state is the identity permutation
|
||||
currMapping.resize(numLocals);
|
||||
for (auto& set : currMapping) {
|
||||
set = { nullptr };
|
||||
}
|
||||
PostWalker<LocalGraph>::walk(func->body);
|
||||
}
|
||||
|
||||
// control flow
|
||||
|
||||
void LocalGraph::visitBlock(Block* curr) {
|
||||
if (curr->name.is() && breakMappings.find(curr->name) != breakMappings.end()) {
|
||||
auto& infos = breakMappings[curr->name];
|
||||
infos.emplace_back(std::move(currMapping));
|
||||
currMapping = std::move(merge(infos));
|
||||
breakMappings.erase(curr->name);
|
||||
}
|
||||
}
|
||||
|
||||
void LocalGraph::finishIf() {
|
||||
// that's it for this if, merge
|
||||
std::vector<Mapping> breaks;
|
||||
breaks.emplace_back(std::move(currMapping));
|
||||
breaks.emplace_back(std::move(mappingStack.back()));
|
||||
mappingStack.pop_back();
|
||||
currMapping = std::move(merge(breaks));
|
||||
}
|
||||
|
||||
void LocalGraph::afterIfCondition(LocalGraph* self, Expression** currp) {
|
||||
self->mappingStack.push_back(self->currMapping);
|
||||
}
|
||||
void LocalGraph::afterIfTrue(LocalGraph* self, Expression** currp) {
|
||||
auto* curr = (*currp)->cast<If>();
|
||||
if (curr->ifFalse) {
|
||||
auto afterCondition = std::move(self->mappingStack.back());
|
||||
self->mappingStack.back() = std::move(self->currMapping);
|
||||
self->currMapping = std::move(afterCondition);
|
||||
} else {
|
||||
self->finishIf();
|
||||
}
|
||||
}
|
||||
void LocalGraph::afterIfFalse(LocalGraph* self, Expression** currp) {
|
||||
self->finishIf();
|
||||
}
|
||||
void LocalGraph::beforeLoop(LocalGraph* self, Expression** currp) {
|
||||
// save the state before entering the loop, for calculation later of the merge at the loop top
|
||||
self->mappingStack.push_back(self->currMapping);
|
||||
self->loopGetStack.push_back({});
|
||||
}
|
||||
void LocalGraph::visitLoop(Loop* curr) {
|
||||
if (curr->name.is() && breakMappings.find(curr->name) != breakMappings.end()) {
|
||||
auto& infos = breakMappings[curr->name];
|
||||
infos.emplace_back(std::move(mappingStack.back()));
|
||||
auto before = infos.back();
|
||||
auto& merged = merge(infos);
|
||||
// every local we created a phi for requires us to update get_local operations in
|
||||
// the loop - the branch back has means that gets in the loop have potentially
|
||||
// more sets reaching them.
|
||||
// we can detect this as follows: if a get of oldIndex has the same sets
|
||||
// as the sets at the entrance to the loop, then it is affected by the loop
|
||||
// header sets, and we can add to there sets that looped back
|
||||
auto linkLoopTop = [&](Index i, Sets& getSets) {
|
||||
auto& beforeSets = before[i];
|
||||
if (getSets.size() < beforeSets.size()) {
|
||||
// the get trivially has fewer sets, so it overrode the loop entry sets
|
||||
return;
|
||||
}
|
||||
std::vector<SetLocal*> intersection;
|
||||
std::set_intersection(beforeSets.begin(), beforeSets.end(),
|
||||
getSets.begin(), getSets.end(),
|
||||
std::back_inserter(intersection));
|
||||
if (intersection.size() < beforeSets.size()) {
|
||||
// the get has not the same sets as in the loop entry
|
||||
return;
|
||||
}
|
||||
// the get has the entry sets, so add any new ones
|
||||
for (auto* set : merged[i]) {
|
||||
getSets.insert(set);
|
||||
}
|
||||
};
|
||||
auto& gets = loopGetStack.back();
|
||||
for (auto* get : gets) {
|
||||
linkLoopTop(get->index, getSetses[get]);
|
||||
}
|
||||
// and the same for the loop fallthrough: any local that still has the
|
||||
// entry sets should also have the loop-back sets as well
|
||||
for (Index i = 0; i < numLocals; i++) {
|
||||
linkLoopTop(i, currMapping[i]);
|
||||
}
|
||||
// finally, breaks still in flight must be updated too
|
||||
for (auto& iter : breakMappings) {
|
||||
auto name = iter.first;
|
||||
if (name == curr->name) continue; // skip our own (which is still in use)
|
||||
auto& mappings = iter.second;
|
||||
for (auto& mapping : mappings) {
|
||||
for (Index i = 0; i < numLocals; i++) {
|
||||
linkLoopTop(i, mapping[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// now that we are done with using the mappings, erase our own
|
||||
breakMappings.erase(curr->name);
|
||||
}
|
||||
mappingStack.pop_back();
|
||||
loopGetStack.pop_back();
|
||||
}
|
||||
void LocalGraph::visitBreak(Break* curr) {
|
||||
if (curr->condition) {
|
||||
breakMappings[curr->name].emplace_back(currMapping);
|
||||
} else {
|
||||
breakMappings[curr->name].emplace_back(std::move(currMapping));
|
||||
setUnreachable(currMapping);
|
||||
}
|
||||
}
|
||||
void LocalGraph::visitSwitch(Switch* curr) {
|
||||
std::set<Name> all;
|
||||
for (auto target : curr->targets) {
|
||||
all.insert(target);
|
||||
}
|
||||
all.insert(curr->default_);
|
||||
for (auto target : all) {
|
||||
breakMappings[target].emplace_back(currMapping);
|
||||
}
|
||||
setUnreachable(currMapping);
|
||||
}
|
||||
void LocalGraph::visitReturn(Return *curr) {
|
||||
setUnreachable(currMapping);
|
||||
}
|
||||
void LocalGraph::visitUnreachable(Unreachable *curr) {
|
||||
setUnreachable(currMapping);
|
||||
}
|
||||
|
||||
// local usage
|
||||
|
||||
void LocalGraph::visitGetLocal(GetLocal* curr) {
|
||||
assert(currMapping.size() == numLocals);
|
||||
assert(curr->index < numLocals);
|
||||
for (auto& loopGets : loopGetStack) {
|
||||
loopGets.push_back(curr);
|
||||
}
|
||||
// current sets are our sets
|
||||
getSetses[curr] = currMapping[curr->index];
|
||||
locations[curr] = getCurrentPointer();
|
||||
}
|
||||
void LocalGraph::visitSetLocal(SetLocal* curr) {
|
||||
assert(currMapping.size() == numLocals);
|
||||
assert(curr->index < numLocals);
|
||||
// current sets are just this set
|
||||
currMapping[curr->index] = { curr }; // TODO optimize?
|
||||
locations[curr] = getCurrentPointer();
|
||||
}
|
||||
|
||||
// traversal
|
||||
|
||||
void LocalGraph::scan(LocalGraph* self, Expression** currp) {
|
||||
if (auto* iff = (*currp)->dynCast<If>()) {
|
||||
// if needs special handling
|
||||
if (iff->ifFalse) {
|
||||
self->pushTask(LocalGraph::afterIfFalse, currp);
|
||||
self->pushTask(LocalGraph::scan, &iff->ifFalse);
|
||||
}
|
||||
self->pushTask(LocalGraph::afterIfTrue, currp);
|
||||
self->pushTask(LocalGraph::scan, &iff->ifTrue);
|
||||
self->pushTask(LocalGraph::afterIfCondition, currp);
|
||||
self->pushTask(LocalGraph::scan, &iff->condition);
|
||||
} else {
|
||||
PostWalker<LocalGraph>::scan(self, currp);
|
||||
}
|
||||
|
||||
// loops need pre-order visiting too
|
||||
if ((*currp)->is<Loop>()) {
|
||||
self->pushTask(LocalGraph::beforeLoop, currp);
|
||||
}
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
void LocalGraph::setUnreachable(Mapping& mapping) {
|
||||
mapping.resize(numLocals); // may have been emptied by a move
|
||||
mapping[0].clear();
|
||||
}
|
||||
|
||||
bool LocalGraph::isUnreachable(Mapping& mapping) {
|
||||
// we must have some set for each index, if only the zero init, so empty means we emptied it for unreachable code
|
||||
return mapping[0].empty();
|
||||
}
|
||||
|
||||
// merges a bunch of infos into one.
|
||||
// if we need phis, writes them into the provided vector. the caller should
|
||||
// ensure those are placed in the right location
|
||||
LocalGraph::Mapping& LocalGraph::merge(std::vector<Mapping>& mappings) {
|
||||
assert(mappings.size() > 0);
|
||||
auto& out = mappings[0];
|
||||
if (mappings.size() == 1) {
|
||||
return out;
|
||||
}
|
||||
// merge into the first
|
||||
for (Index j = 1; j < mappings.size(); j++) {
|
||||
auto& other = mappings[j];
|
||||
for (Index i = 0; i < numLocals; i++) {
|
||||
auto& outSets = out[i];
|
||||
for (auto* set : other[i]) {
|
||||
outSets.insert(set);
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace wasm
|
||||
|
@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_bits_h
|
||||
#define wasm_ir_bits_h
|
||||
|
||||
#include "support/bits.h"
|
||||
#include "wasm-builder.h"
|
||||
#include "ir/literal-utils.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
struct Bits {
|
||||
// get a mask to keep only the low # of bits
|
||||
static int32_t lowBitMask(int32_t bits) {
|
||||
uint32_t ret = -1;
|
||||
if (bits >= 32) return ret;
|
||||
return ret >> (32 - bits);
|
||||
}
|
||||
|
||||
// checks if the input is a mask of lower bits, i.e., all 1s up to some high bit, and all zeros
|
||||
// from there. returns the number of masked bits, or 0 if this is not such a mask
|
||||
static uint32_t getMaskedBits(uint32_t mask) {
|
||||
if (mask == uint32_t(-1)) return 32; // all the bits
|
||||
if (mask == 0) return 0; // trivially not a mask
|
||||
// otherwise, see if adding one turns this into a 1-bit thing, 00011111 + 1 => 00100000
|
||||
if (PopCount(mask + 1) != 1) return 0;
|
||||
// this is indeed a mask
|
||||
return 32 - CountLeadingZeroes(mask);
|
||||
}
|
||||
|
||||
// gets the number of effective shifts a shift operation does. In
|
||||
// wasm, only 5 bits matter for 32-bit shifts, and 6 for 64.
|
||||
static Index getEffectiveShifts(Index amount, WasmType type) {
|
||||
if (type == i32) {
|
||||
return amount & 31;
|
||||
} else if (type == i64) {
|
||||
return amount & 63;
|
||||
}
|
||||
WASM_UNREACHABLE();
|
||||
}
|
||||
|
||||
static Index getEffectiveShifts(Expression* expr) {
|
||||
auto* amount = expr->cast<Const>();
|
||||
if (amount->type == i32) {
|
||||
return getEffectiveShifts(amount->value.geti32(), i32);
|
||||
} else if (amount->type == i64) {
|
||||
return getEffectiveShifts(amount->value.geti64(), i64);
|
||||
}
|
||||
WASM_UNREACHABLE();
|
||||
}
|
||||
|
||||
static Expression* makeSignExt(Expression* value, Index bytes, Module& wasm) {
|
||||
if (value->type == i32) {
|
||||
if (bytes == 1 || bytes == 2) {
|
||||
auto shifts = bytes == 1 ? 24 : 16;
|
||||
Builder builder(wasm);
|
||||
return builder.makeBinary(
|
||||
ShrSInt32,
|
||||
builder.makeBinary(
|
||||
ShlInt32,
|
||||
value,
|
||||
LiteralUtils::makeFromInt32(shifts, i32, wasm)
|
||||
),
|
||||
LiteralUtils::makeFromInt32(shifts, i32, wasm)
|
||||
);
|
||||
}
|
||||
assert(bytes == 4);
|
||||
return value; // nothing to do
|
||||
} else {
|
||||
assert(value->type == i64);
|
||||
if (bytes == 1 || bytes == 2 || bytes == 4) {
|
||||
auto shifts = bytes == 1 ? 56 : (bytes == 2 ? 48 : 32);
|
||||
Builder builder(wasm);
|
||||
return builder.makeBinary(
|
||||
ShrSInt64,
|
||||
builder.makeBinary(
|
||||
ShlInt64,
|
||||
value,
|
||||
LiteralUtils::makeFromInt32(shifts, i64, wasm)
|
||||
),
|
||||
LiteralUtils::makeFromInt32(shifts, i64, wasm)
|
||||
);
|
||||
}
|
||||
assert(bytes == 8);
|
||||
return value; // nothing to do
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_bits_h
|
||||
|
@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_block_h
|
||||
#define wasm_ir_block_h
|
||||
|
||||
#include "literal.h"
|
||||
#include "wasm.h"
|
||||
#include "ir/branch-utils.h"
|
||||
#include "ir/effects.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
namespace BlockUtils {
|
||||
// if a block has just one element, it can often be replaced
|
||||
// with that content
|
||||
template<typename T>
|
||||
inline Expression* simplifyToContents(Block* block, T* parent, bool allowTypeChange = false) {
|
||||
auto& list = block->list;
|
||||
if (list.size() == 1 && !BranchUtils::BranchSeeker::hasNamed(list[0], block->name)) {
|
||||
// just one element. try to replace the block
|
||||
auto* singleton = list[0];
|
||||
auto sideEffects = EffectAnalyzer(parent->getPassOptions(), singleton).hasSideEffects();
|
||||
if (!sideEffects && !isConcreteWasmType(singleton->type)) {
|
||||
// no side effects, and singleton is not returning a value, so we can throw away
|
||||
// the block and its contents, basically
|
||||
return Builder(*parent->getModule()).replaceWithIdenticalType(block);
|
||||
} else if (block->type == singleton->type || allowTypeChange) {
|
||||
return singleton;
|
||||
} else {
|
||||
// (side effects +) type change, must be block with declared value but inside is unreachable
|
||||
// (if both concrete, must match, and since no name on block, we can't be
|
||||
// branched to, so if singleton is unreachable, so is the block)
|
||||
assert(isConcreteWasmType(block->type) && singleton->type == unreachable);
|
||||
// we could replace with unreachable, but would need to update all
|
||||
// the parent's types
|
||||
}
|
||||
} else if (list.size() == 0) {
|
||||
ExpressionManipulator::nop(block);
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
// similar, but when we allow the type to change while doing so
|
||||
template<typename T>
|
||||
inline Expression* simplifyToContentsWithPossibleTypeChange(Block* block, T* parent) {
|
||||
return simplifyToContents(block, parent, true);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_block_h
|
||||
|
@ -1,183 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_branch_h
|
||||
#define wasm_ir_branch_h
|
||||
|
||||
#include "wasm.h"
|
||||
#include "wasm-traversal.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
namespace BranchUtils {
|
||||
|
||||
// Some branches are obviously not actually reachable (e.g. (br $out (unreachable)))
|
||||
|
||||
inline bool isBranchReachable(Break* br) {
|
||||
return !(br->value && br->value->type == unreachable) &&
|
||||
!(br->condition && br->condition->type == unreachable);
|
||||
}
|
||||
|
||||
inline bool isBranchReachable(Switch* sw) {
|
||||
return !(sw->value && sw->value->type == unreachable) &&
|
||||
sw->condition->type != unreachable;
|
||||
}
|
||||
|
||||
inline bool isBranchReachable(Expression* expr) {
|
||||
if (auto* br = expr->dynCast<Break>()) {
|
||||
return isBranchReachable(br);
|
||||
} else if (auto* sw = expr->dynCast<Switch>()) {
|
||||
return isBranchReachable(sw);
|
||||
}
|
||||
WASM_UNREACHABLE();
|
||||
}
|
||||
|
||||
// returns the set of targets to which we branch that are
|
||||
// outside of a node
|
||||
inline std::set<Name> getExitingBranches(Expression* ast) {
|
||||
struct Scanner : public PostWalker<Scanner> {
|
||||
std::set<Name> targets;
|
||||
|
||||
void visitBreak(Break* curr) {
|
||||
targets.insert(curr->name);
|
||||
}
|
||||
void visitSwitch(Switch* curr) {
|
||||
for (auto target : targets) {
|
||||
targets.insert(target);
|
||||
}
|
||||
targets.insert(curr->default_);
|
||||
}
|
||||
void visitBlock(Block* curr) {
|
||||
if (curr->name.is()) {
|
||||
targets.erase(curr->name);
|
||||
}
|
||||
}
|
||||
void visitLoop(Loop* curr) {
|
||||
if (curr->name.is()) {
|
||||
targets.erase(curr->name);
|
||||
}
|
||||
}
|
||||
};
|
||||
Scanner scanner;
|
||||
scanner.walk(ast);
|
||||
// anything not erased is a branch out
|
||||
return scanner.targets;
|
||||
}
|
||||
|
||||
// returns the list of all branch targets in a node
|
||||
|
||||
inline std::set<Name> getBranchTargets(Expression* ast) {
|
||||
struct Scanner : public PostWalker<Scanner> {
|
||||
std::set<Name> targets;
|
||||
|
||||
void visitBlock(Block* curr) {
|
||||
if (curr->name.is()) {
|
||||
targets.insert(curr->name);
|
||||
}
|
||||
}
|
||||
void visitLoop(Loop* curr) {
|
||||
if (curr->name.is()) {
|
||||
targets.insert(curr->name);
|
||||
}
|
||||
}
|
||||
};
|
||||
Scanner scanner;
|
||||
scanner.walk(ast);
|
||||
return scanner.targets;
|
||||
}
|
||||
|
||||
// Finds if there are branches targeting a name. Note that since names are
|
||||
// unique in our IR, we just need to look for the name, and do not need
|
||||
// to analyze scoping.
|
||||
// By default we consider all branches, so any place there is a branch that
|
||||
// names the target. You can unset 'named' to only note branches that appear
|
||||
// reachable (i.e., are not obviously unreachable).
|
||||
struct BranchSeeker : public PostWalker<BranchSeeker> {
|
||||
Name target;
|
||||
bool named = true;
|
||||
|
||||
Index found;
|
||||
WasmType valueType;
|
||||
|
||||
BranchSeeker(Name target) : target(target), found(0) {}
|
||||
|
||||
void noteFound(Expression* value) {
|
||||
found++;
|
||||
if (found == 1) valueType = unreachable;
|
||||
if (!value) valueType = none;
|
||||
else if (value->type != unreachable) valueType = value->type;
|
||||
}
|
||||
|
||||
void visitBreak(Break *curr) {
|
||||
if (!named) {
|
||||
// ignore an unreachable break
|
||||
if (curr->condition && curr->condition->type == unreachable) return;
|
||||
if (curr->value && curr->value->type == unreachable) return;
|
||||
}
|
||||
// check the break
|
||||
if (curr->name == target) noteFound(curr->value);
|
||||
}
|
||||
|
||||
void visitSwitch(Switch *curr) {
|
||||
if (!named) {
|
||||
// ignore an unreachable switch
|
||||
if (curr->condition->type == unreachable) return;
|
||||
if (curr->value && curr->value->type == unreachable) return;
|
||||
}
|
||||
// check the switch
|
||||
for (auto name : curr->targets) {
|
||||
if (name == target) noteFound(curr->value);
|
||||
}
|
||||
if (curr->default_ == target) noteFound(curr->value);
|
||||
}
|
||||
|
||||
static bool hasReachable(Expression* tree, Name target) {
|
||||
if (!target.is()) return false;
|
||||
BranchSeeker seeker(target);
|
||||
seeker.named = false;
|
||||
seeker.walk(tree);
|
||||
return seeker.found > 0;
|
||||
}
|
||||
|
||||
static Index countReachable(Expression* tree, Name target) {
|
||||
if (!target.is()) return 0;
|
||||
BranchSeeker seeker(target);
|
||||
seeker.named = false;
|
||||
seeker.walk(tree);
|
||||
return seeker.found;
|
||||
}
|
||||
|
||||
static bool hasNamed(Expression* tree, Name target) {
|
||||
if (!target.is()) return false;
|
||||
BranchSeeker seeker(target);
|
||||
seeker.walk(tree);
|
||||
return seeker.found > 0;
|
||||
}
|
||||
|
||||
static Index countNamed(Expression* tree, Name target) {
|
||||
if (!target.is()) return 0;
|
||||
BranchSeeker seeker(target);
|
||||
seeker.walk(tree);
|
||||
return seeker.found;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace BranchUtils
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_branch_h
|
||||
|
@ -1,255 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_cost_h
|
||||
#define wasm_ir_cost_h
|
||||
|
||||
namespace wasm {
|
||||
|
||||
// Measure the execution cost of an AST. Very handwave-ey
|
||||
|
||||
struct CostAnalyzer : public Visitor<CostAnalyzer, Index> {
|
||||
CostAnalyzer(Expression *ast) {
|
||||
assert(ast);
|
||||
cost = visit(ast);
|
||||
}
|
||||
|
||||
Index cost;
|
||||
|
||||
Index maybeVisit(Expression* curr) {
|
||||
return curr ? visit(curr) : 0;
|
||||
}
|
||||
|
||||
Index visitBlock(Block *curr) {
|
||||
Index ret = 0;
|
||||
for (auto* child : curr->list) ret += visit(child);
|
||||
return ret;
|
||||
}
|
||||
Index visitIf(If *curr) {
|
||||
return 1 + visit(curr->condition) + std::max(visit(curr->ifTrue), maybeVisit(curr->ifFalse));
|
||||
}
|
||||
Index visitLoop(Loop *curr) {
|
||||
return 5 * visit(curr->body);
|
||||
}
|
||||
Index visitBreak(Break *curr) {
|
||||
return 1 + maybeVisit(curr->value) + maybeVisit(curr->condition);
|
||||
}
|
||||
Index visitSwitch(Switch *curr) {
|
||||
return 2 + visit(curr->condition) + maybeVisit(curr->value);
|
||||
}
|
||||
Index visitCall(Call *curr) {
|
||||
Index ret = 4;
|
||||
for (auto* child : curr->operands) ret += visit(child);
|
||||
return ret;
|
||||
}
|
||||
Index visitCallImport(CallImport *curr) {
|
||||
Index ret = 15;
|
||||
for (auto* child : curr->operands) ret += visit(child);
|
||||
return ret;
|
||||
}
|
||||
Index visitCallIndirect(CallIndirect *curr) {
|
||||
Index ret = 6 + visit(curr->target);
|
||||
for (auto* child : curr->operands) ret += visit(child);
|
||||
return ret;
|
||||
}
|
||||
Index visitGetLocal(GetLocal *curr) {
|
||||
return 0;
|
||||
}
|
||||
Index visitSetLocal(SetLocal *curr) {
|
||||
return 1;
|
||||
}
|
||||
Index visitGetGlobal(GetGlobal *curr) {
|
||||
return 1;
|
||||
}
|
||||
Index visitSetGlobal(SetGlobal *curr) {
|
||||
return 2;
|
||||
}
|
||||
Index visitLoad(Load *curr) {
|
||||
return 1 + visit(curr->ptr) + 10 * curr->isAtomic;
|
||||
}
|
||||
Index visitStore(Store *curr) {
|
||||
return 2 + visit(curr->ptr) + visit(curr->value) + 10 * curr->isAtomic;
|
||||
}
|
||||
Index visitAtomicRMW(AtomicRMW *curr) {
|
||||
return 100;
|
||||
}
|
||||
Index visitAtomicCmpxchg(AtomicCmpxchg* curr) {
|
||||
return 100;
|
||||
}
|
||||
Index visitConst(Const *curr) {
|
||||
return 1;
|
||||
}
|
||||
Index visitUnary(Unary *curr) {
|
||||
Index ret = 0;
|
||||
switch (curr->op) {
|
||||
case ClzInt32:
|
||||
case CtzInt32:
|
||||
case PopcntInt32:
|
||||
case NegFloat32:
|
||||
case AbsFloat32:
|
||||
case CeilFloat32:
|
||||
case FloorFloat32:
|
||||
case TruncFloat32:
|
||||
case NearestFloat32:
|
||||
case ClzInt64:
|
||||
case CtzInt64:
|
||||
case PopcntInt64:
|
||||
case NegFloat64:
|
||||
case AbsFloat64:
|
||||
case CeilFloat64:
|
||||
case FloorFloat64:
|
||||
case TruncFloat64:
|
||||
case NearestFloat64:
|
||||
case EqZInt32:
|
||||
case EqZInt64:
|
||||
case ExtendSInt32:
|
||||
case ExtendUInt32:
|
||||
case WrapInt64:
|
||||
case PromoteFloat32:
|
||||
case DemoteFloat64:
|
||||
case TruncSFloat32ToInt32:
|
||||
case TruncUFloat32ToInt32:
|
||||
case TruncSFloat64ToInt32:
|
||||
case TruncUFloat64ToInt32:
|
||||
case ReinterpretFloat32:
|
||||
case TruncSFloat32ToInt64:
|
||||
case TruncUFloat32ToInt64:
|
||||
case TruncSFloat64ToInt64:
|
||||
case TruncUFloat64ToInt64:
|
||||
case ReinterpretFloat64:
|
||||
case ReinterpretInt32:
|
||||
case ConvertSInt32ToFloat32:
|
||||
case ConvertUInt32ToFloat32:
|
||||
case ConvertSInt64ToFloat32:
|
||||
case ConvertUInt64ToFloat32:
|
||||
case ReinterpretInt64:
|
||||
case ConvertSInt32ToFloat64:
|
||||
case ConvertUInt32ToFloat64:
|
||||
case ConvertSInt64ToFloat64:
|
||||
case ConvertUInt64ToFloat64: ret = 1; break;
|
||||
case SqrtFloat32:
|
||||
case SqrtFloat64: ret = 2; break;
|
||||
default: WASM_UNREACHABLE();
|
||||
}
|
||||
return ret + visit(curr->value);
|
||||
}
|
||||
Index visitBinary(Binary *curr) {
|
||||
Index ret = 0;
|
||||
switch (curr->op) {
|
||||
case AddInt32: ret = 1; break;
|
||||
case SubInt32: ret = 1; break;
|
||||
case MulInt32: ret = 2; break;
|
||||
case DivSInt32: ret = 3; break;
|
||||
case DivUInt32: ret = 3; break;
|
||||
case RemSInt32: ret = 3; break;
|
||||
case RemUInt32: ret = 3; break;
|
||||
case AndInt32: ret = 1; break;
|
||||
case OrInt32: ret = 1; break;
|
||||
case XorInt32: ret = 1; break;
|
||||
case ShlInt32: ret = 1; break;
|
||||
case ShrUInt32: ret = 1; break;
|
||||
case ShrSInt32: ret = 1; break;
|
||||
case RotLInt32: ret = 1; break;
|
||||
case RotRInt32: ret = 1; break;
|
||||
case AddInt64: ret = 1; break;
|
||||
case SubInt64: ret = 1; break;
|
||||
case MulInt64: ret = 2; break;
|
||||
case DivSInt64: ret = 3; break;
|
||||
case DivUInt64: ret = 3; break;
|
||||
case RemSInt64: ret = 3; break;
|
||||
case RemUInt64: ret = 3; break;
|
||||
case AndInt64: ret = 1; break;
|
||||
case OrInt64: ret = 1; break;
|
||||
case XorInt64: ret = 1; break;
|
||||
case ShlInt64: ret = 1; break;
|
||||
case ShrUInt64: ret = 1; break;
|
||||
case ShrSInt64: ret = 1; break;
|
||||
case RotLInt64: ret = 1; break;
|
||||
case RotRInt64: ret = 1; break;
|
||||
case AddFloat32: ret = 1; break;
|
||||
case SubFloat32: ret = 1; break;
|
||||
case MulFloat32: ret = 2; break;
|
||||
case DivFloat32: ret = 3; break;
|
||||
case CopySignFloat32: ret = 1; break;
|
||||
case MinFloat32: ret = 1; break;
|
||||
case MaxFloat32: ret = 1; break;
|
||||
case AddFloat64: ret = 1; break;
|
||||
case SubFloat64: ret = 1; break;
|
||||
case MulFloat64: ret = 2; break;
|
||||
case DivFloat64: ret = 3; break;
|
||||
case CopySignFloat64: ret = 1; break;
|
||||
case MinFloat64: ret = 1; break;
|
||||
case MaxFloat64: ret = 1; break;
|
||||
case LtUInt32: ret = 1; break;
|
||||
case LtSInt32: ret = 1; break;
|
||||
case LeUInt32: ret = 1; break;
|
||||
case LeSInt32: ret = 1; break;
|
||||
case GtUInt32: ret = 1; break;
|
||||
case GtSInt32: ret = 1; break;
|
||||
case GeUInt32: ret = 1; break;
|
||||
case GeSInt32: ret = 1; break;
|
||||
case LtUInt64: ret = 1; break;
|
||||
case LtSInt64: ret = 1; break;
|
||||
case LeUInt64: ret = 1; break;
|
||||
case LeSInt64: ret = 1; break;
|
||||
case GtUInt64: ret = 1; break;
|
||||
case GtSInt64: ret = 1; break;
|
||||
case GeUInt64: ret = 1; break;
|
||||
case GeSInt64: ret = 1; break;
|
||||
case LtFloat32: ret = 1; break;
|
||||
case GtFloat32: ret = 1; break;
|
||||
case LeFloat32: ret = 1; break;
|
||||
case GeFloat32: ret = 1; break;
|
||||
case LtFloat64: ret = 1; break;
|
||||
case GtFloat64: ret = 1; break;
|
||||
case LeFloat64: ret = 1; break;
|
||||
case GeFloat64: ret = 1; break;
|
||||
case EqInt32: ret = 1; break;
|
||||
case NeInt32: ret = 1; break;
|
||||
case EqInt64: ret = 1; break;
|
||||
case NeInt64: ret = 1; break;
|
||||
case EqFloat32: ret = 1; break;
|
||||
case NeFloat32: ret = 1; break;
|
||||
case EqFloat64: ret = 1; break;
|
||||
case NeFloat64: ret = 1; break;
|
||||
default: WASM_UNREACHABLE();
|
||||
}
|
||||
return ret + visit(curr->left) + visit(curr->right);
|
||||
}
|
||||
Index visitSelect(Select *curr) {
|
||||
return 2 + visit(curr->condition) + visit(curr->ifTrue) + visit(curr->ifFalse);
|
||||
}
|
||||
Index visitDrop(Drop *curr) {
|
||||
return visit(curr->value);
|
||||
}
|
||||
Index visitReturn(Return *curr) {
|
||||
return maybeVisit(curr->value);
|
||||
}
|
||||
Index visitHost(Host *curr) {
|
||||
return 100;
|
||||
}
|
||||
Index visitNop(Nop *curr) {
|
||||
return 0;
|
||||
}
|
||||
Index visitUnreachable(Unreachable *curr) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_cost_h
|
||||
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_count_h
|
||||
#define wasm_ir_count_h
|
||||
|
||||
namespace wasm {
|
||||
|
||||
struct GetLocalCounter : public PostWalker<GetLocalCounter> {
|
||||
std::vector<Index> num;
|
||||
|
||||
GetLocalCounter() {}
|
||||
GetLocalCounter(Function* func) {
|
||||
analyze(func, func->body);
|
||||
}
|
||||
GetLocalCounter(Function* func, Expression* ast) {
|
||||
analyze(func, ast);
|
||||
}
|
||||
|
||||
void analyze(Function* func) {
|
||||
analyze(func, func->body);
|
||||
}
|
||||
void analyze(Function* func, Expression* ast) {
|
||||
num.resize(func->getNumLocals());
|
||||
std::fill(num.begin(), num.end(), 0);
|
||||
walk(ast);
|
||||
}
|
||||
|
||||
void visitGetLocal(GetLocal *curr) {
|
||||
num[curr->index]++;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_count_h
|
||||
|
@ -1,281 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_effects_h
|
||||
#define wasm_ir_effects_h
|
||||
|
||||
namespace wasm {
|
||||
|
||||
// Look for side effects, including control flow
|
||||
// TODO: optimize
|
||||
|
||||
struct EffectAnalyzer : public PostWalker<EffectAnalyzer> {
|
||||
EffectAnalyzer(PassOptions& passOptions, Expression *ast = nullptr) {
|
||||
ignoreImplicitTraps = passOptions.ignoreImplicitTraps;
|
||||
debugInfo = passOptions.debugInfo;
|
||||
if (ast) analyze(ast);
|
||||
}
|
||||
|
||||
bool ignoreImplicitTraps;
|
||||
bool debugInfo;
|
||||
|
||||
void analyze(Expression *ast) {
|
||||
breakNames.clear();
|
||||
walk(ast);
|
||||
// if we are left with breaks, they are external
|
||||
if (breakNames.size() > 0) branches = true;
|
||||
}
|
||||
|
||||
bool branches = false; // branches out of this expression, returns, infinite loops, etc
|
||||
bool calls = false;
|
||||
std::set<Index> localsRead;
|
||||
std::set<Index> localsWritten;
|
||||
std::set<Name> globalsRead;
|
||||
std::set<Name> globalsWritten;
|
||||
bool readsMemory = false;
|
||||
bool writesMemory = false;
|
||||
bool implicitTrap = false; // a load or div/rem, which may trap. we ignore trap
|
||||
// differences, so it is ok to reorder these, but we can't
|
||||
// remove them, as they count as side effects, and we
|
||||
// can't move them in a way that would cause other noticeable
|
||||
// (global) side effects
|
||||
bool isAtomic = false; // An atomic load/store/RMW/Cmpxchg or an operator that
|
||||
// has a defined ordering wrt atomics (e.g. grow_memory)
|
||||
|
||||
bool accessesLocal() { return localsRead.size() + localsWritten.size() > 0; }
|
||||
bool accessesGlobal() { return globalsRead.size() + globalsWritten.size() > 0; }
|
||||
bool accessesMemory() { return calls || readsMemory || writesMemory; }
|
||||
bool hasGlobalSideEffects() { return calls || globalsWritten.size() > 0 || writesMemory || isAtomic; }
|
||||
bool hasSideEffects() { return hasGlobalSideEffects() || localsWritten.size() > 0 || branches || implicitTrap; }
|
||||
bool hasAnything() { return branches || calls || accessesLocal() || readsMemory || writesMemory || accessesGlobal() || implicitTrap || isAtomic; }
|
||||
|
||||
// check if we break to anything external from ourselves
|
||||
bool hasExternalBreakTargets() { return !breakNames.empty(); }
|
||||
|
||||
// checks if these effects would invalidate another set (e.g., if we write, we invalidate someone that reads, they can't be moved past us)
|
||||
bool invalidates(EffectAnalyzer& other) {
|
||||
if (branches || other.branches
|
||||
|| ((writesMemory || calls) && other.accessesMemory())
|
||||
|| (accessesMemory() && (other.writesMemory || other.calls))) {
|
||||
return true;
|
||||
}
|
||||
// All atomics are sequentially consistent for now, and ordered wrt other
|
||||
// memory references.
|
||||
if ((isAtomic && other.accessesMemory()) ||
|
||||
(other.isAtomic && accessesMemory())) {
|
||||
return true;
|
||||
}
|
||||
for (auto local : localsWritten) {
|
||||
if (other.localsWritten.count(local) || other.localsRead.count(local)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (auto local : localsRead) {
|
||||
if (other.localsWritten.count(local)) return true;
|
||||
}
|
||||
if ((accessesGlobal() && other.calls) || (other.accessesGlobal() && calls)) {
|
||||
return true;
|
||||
}
|
||||
for (auto global : globalsWritten) {
|
||||
if (other.globalsWritten.count(global) || other.globalsRead.count(global)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (auto global : globalsRead) {
|
||||
if (other.globalsWritten.count(global)) return true;
|
||||
}
|
||||
// we are ok to reorder implicit traps, but not conditionalize them
|
||||
if ((implicitTrap && other.branches) || (other.implicitTrap && branches)) {
|
||||
return true;
|
||||
}
|
||||
// we can't reorder an implicit trap in a way that alters global state
|
||||
if ((implicitTrap && other.hasGlobalSideEffects()) || (other.implicitTrap && hasGlobalSideEffects())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void mergeIn(EffectAnalyzer& other) {
|
||||
branches = branches || other.branches;
|
||||
calls = calls || other.calls;
|
||||
readsMemory = readsMemory || other.readsMemory;
|
||||
writesMemory = writesMemory || other.writesMemory;
|
||||
for (auto i : other.localsRead) localsRead.insert(i);
|
||||
for (auto i : other.localsWritten) localsWritten.insert(i);
|
||||
for (auto i : other.globalsRead) globalsRead.insert(i);
|
||||
for (auto i : other.globalsWritten) globalsWritten.insert(i);
|
||||
}
|
||||
|
||||
// the checks above happen after the node's children were processed, in the order of execution
|
||||
// we must also check for control flow that happens before the children, i.e., loops
|
||||
bool checkPre(Expression* curr) {
|
||||
if (curr->is<Loop>()) {
|
||||
branches = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool checkPost(Expression* curr) {
|
||||
visit(curr);
|
||||
if (curr->is<Loop>()) {
|
||||
branches = true;
|
||||
}
|
||||
return hasAnything();
|
||||
}
|
||||
|
||||
std::set<Name> breakNames;
|
||||
|
||||
void visitBreak(Break *curr) {
|
||||
breakNames.insert(curr->name);
|
||||
}
|
||||
void visitSwitch(Switch *curr) {
|
||||
for (auto name : curr->targets) {
|
||||
breakNames.insert(name);
|
||||
}
|
||||
breakNames.insert(curr->default_);
|
||||
}
|
||||
void visitBlock(Block* curr) {
|
||||
if (curr->name.is()) breakNames.erase(curr->name); // these were internal breaks
|
||||
}
|
||||
void visitLoop(Loop* curr) {
|
||||
if (curr->name.is()) breakNames.erase(curr->name); // these were internal breaks
|
||||
// if the loop is unreachable, then there is branching control flow:
|
||||
// (1) if the body is unreachable because of a (return), uncaught (br) etc., then we
|
||||
// already noted branching, so it is ok to mark it again (if we have *caught*
|
||||
// (br)s, then they did not lead to the loop body being unreachable).
|
||||
// (same logic applies to blocks)
|
||||
// (2) if the loop is unreachable because it only has branches up to the loop
|
||||
// top, but no way to get out, then it is an infinite loop, and we consider
|
||||
// that a branching side effect (note how the same logic does not apply to
|
||||
// blocks).
|
||||
if (curr->type == unreachable) {
|
||||
branches = true;
|
||||
}
|
||||
}
|
||||
|
||||
void visitCall(Call *curr) { calls = true; }
|
||||
void visitCallImport(CallImport *curr) {
|
||||
calls = true;
|
||||
if (debugInfo) {
|
||||
// debugInfo call imports must be preserved very strongly, do not
|
||||
// move code around them
|
||||
branches = true; // !
|
||||
}
|
||||
}
|
||||
void visitCallIndirect(CallIndirect *curr) { calls = true; }
|
||||
void visitGetLocal(GetLocal *curr) {
|
||||
localsRead.insert(curr->index);
|
||||
}
|
||||
void visitSetLocal(SetLocal *curr) {
|
||||
localsWritten.insert(curr->index);
|
||||
}
|
||||
void visitGetGlobal(GetGlobal *curr) {
|
||||
globalsRead.insert(curr->name);
|
||||
}
|
||||
void visitSetGlobal(SetGlobal *curr) {
|
||||
globalsWritten.insert(curr->name);
|
||||
}
|
||||
void visitLoad(Load *curr) {
|
||||
readsMemory = true;
|
||||
isAtomic |= curr->isAtomic;
|
||||
if (!ignoreImplicitTraps) implicitTrap = true;
|
||||
}
|
||||
void visitStore(Store *curr) {
|
||||
writesMemory = true;
|
||||
isAtomic |= curr->isAtomic;
|
||||
if (!ignoreImplicitTraps) implicitTrap = true;
|
||||
}
|
||||
void visitAtomicRMW(AtomicRMW* curr) {
|
||||
readsMemory = true;
|
||||
writesMemory = true;
|
||||
isAtomic = true;
|
||||
if (!ignoreImplicitTraps) implicitTrap = true;
|
||||
}
|
||||
void visitAtomicCmpxchg(AtomicCmpxchg* curr) {
|
||||
readsMemory = true;
|
||||
writesMemory = true;
|
||||
isAtomic = true;
|
||||
if (!ignoreImplicitTraps) implicitTrap = true;
|
||||
}
|
||||
void visitAtomicWait(AtomicWait* curr) {
|
||||
readsMemory = true;
|
||||
// AtomicWait doesn't strictly write memory, but it does modify the waiters
|
||||
// list associated with the specified address, which we can think of as a
|
||||
// write.
|
||||
writesMemory = true;
|
||||
isAtomic = true;
|
||||
if (!ignoreImplicitTraps) implicitTrap = true;
|
||||
}
|
||||
void visitAtomicWake(AtomicWake* curr) {
|
||||
// AtomicWake doesn't strictly write memory, but it does modify the waiters
|
||||
// list associated with the specified address, which we can think of as a
|
||||
// write.
|
||||
readsMemory = true;
|
||||
writesMemory = true;
|
||||
isAtomic = true;
|
||||
if (!ignoreImplicitTraps) implicitTrap = true;
|
||||
};
|
||||
void visitUnary(Unary *curr) {
|
||||
if (!ignoreImplicitTraps) {
|
||||
switch (curr->op) {
|
||||
case TruncSFloat32ToInt32:
|
||||
case TruncSFloat32ToInt64:
|
||||
case TruncUFloat32ToInt32:
|
||||
case TruncUFloat32ToInt64:
|
||||
case TruncSFloat64ToInt32:
|
||||
case TruncSFloat64ToInt64:
|
||||
case TruncUFloat64ToInt32:
|
||||
case TruncUFloat64ToInt64: {
|
||||
implicitTrap = true;
|
||||
break;
|
||||
}
|
||||
default: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
void visitBinary(Binary *curr) {
|
||||
if (!ignoreImplicitTraps) {
|
||||
switch (curr->op) {
|
||||
case DivSInt32:
|
||||
case DivUInt32:
|
||||
case RemSInt32:
|
||||
case RemUInt32:
|
||||
case DivSInt64:
|
||||
case DivUInt64:
|
||||
case RemSInt64:
|
||||
case RemUInt64: {
|
||||
implicitTrap = true;
|
||||
break;
|
||||
}
|
||||
default: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
void visitReturn(Return *curr) { branches = true; }
|
||||
void visitHost(Host *curr) {
|
||||
calls = true;
|
||||
// grow_memory modifies the set of valid addresses, and thus can be modeled as modifying memory
|
||||
writesMemory = true;
|
||||
// Atomics are also sequentially consistent with grow_memory.
|
||||
isAtomic = true;
|
||||
}
|
||||
void visitUnreachable(Unreachable *curr) { branches = true; }
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_effects_h
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_find_all_h
|
||||
#define wasm_ir_find_all_h
|
||||
|
||||
#include <wasm-traversal.h>
|
||||
|
||||
namespace wasm {
|
||||
|
||||
// Find all instances of a certain node type
|
||||
|
||||
template<typename T>
|
||||
struct FindAll {
|
||||
std::vector<T*> list;
|
||||
|
||||
FindAll(Expression* ast) {
|
||||
struct Finder : public PostWalker<Finder, UnifiedExpressionVisitor<Finder>> {
|
||||
std::vector<T*>* list;
|
||||
void visitExpression(Expression* curr) {
|
||||
if (curr->is<T>()) {
|
||||
(*list).push_back(curr->cast<T>());
|
||||
}
|
||||
}
|
||||
};
|
||||
Finder finder;
|
||||
finder.list = &list;
|
||||
finder.walk(ast);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_find_all_h
|
||||
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_global_h
|
||||
#define wasm_ir_global_h
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "literal.h"
|
||||
#include "wasm.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
namespace GlobalUtils {
|
||||
// find a global initialized to the value of an import, or null if no such global
|
||||
inline Global* getGlobalInitializedToImport(Module& wasm, Name module, Name base) {
|
||||
// find the import
|
||||
Name imported;
|
||||
for (auto& import : wasm.imports) {
|
||||
if (import->module == module && import->base == base) {
|
||||
imported = import->name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (imported.isNull()) return nullptr;
|
||||
// find a global inited to it
|
||||
for (auto& global : wasm.globals) {
|
||||
if (auto* init = global->init->dynCast<GetGlobal>()) {
|
||||
if (init->name == imported) {
|
||||
return global.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_global_h
|
||||
|
@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _wasm_ir_hashed_h
|
||||
|
||||
#include "support/hash.h"
|
||||
#include "wasm.h"
|
||||
#include "ir/utils.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
// An expression with a cached hash value
|
||||
struct HashedExpression {
|
||||
Expression* expr;
|
||||
size_t hash;
|
||||
|
||||
HashedExpression(Expression* expr) : expr(expr) {
|
||||
if (expr) {
|
||||
hash = ExpressionAnalyzer::hash(expr);
|
||||
}
|
||||
}
|
||||
|
||||
HashedExpression(const HashedExpression& other) : expr(other.expr), hash(other.hash) {}
|
||||
};
|
||||
|
||||
struct ExpressionHasher {
|
||||
size_t operator()(const HashedExpression value) const {
|
||||
return value.hash;
|
||||
}
|
||||
};
|
||||
|
||||
struct ExpressionComparer {
|
||||
bool operator()(const HashedExpression a, const HashedExpression b) const {
|
||||
if (a.hash != b.hash) return false;
|
||||
return ExpressionAnalyzer::equal(a.expr, b.expr);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class HashedExpressionMap : public std::unordered_map<HashedExpression, T, ExpressionHasher, ExpressionComparer> {
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // _wasm_ir_hashed_h
|
||||
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_import_h
|
||||
#define wasm_ir_import_h
|
||||
|
||||
#include "literal.h"
|
||||
#include "wasm.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
namespace ImportUtils {
|
||||
// find an import by the module.base that is being imported.
|
||||
// return the internal name
|
||||
inline Import* getImport(Module& wasm, Name module, Name base) {
|
||||
for (auto& import : wasm.imports) {
|
||||
if (import->module == module && import->base == base) {
|
||||
return import.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_import_h
|
||||
|
@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_label_h
|
||||
#define wasm_ir_label_h
|
||||
|
||||
#include "wasm.h"
|
||||
#include "wasm-traversal.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
namespace LabelUtils {
|
||||
|
||||
// Handles branch/loop labels in a function; makes it easy to add new
|
||||
// ones without duplicates
|
||||
class LabelManager : public PostWalker<LabelManager> {
|
||||
public:
|
||||
LabelManager(Function* func) {
|
||||
walkFunction(func);
|
||||
}
|
||||
|
||||
Name getUnique(std::string prefix) {
|
||||
while (1) {
|
||||
auto curr = Name(prefix + std::to_string(counter++));
|
||||
if (labels.find(curr) == labels.end()) {
|
||||
labels.insert(curr);
|
||||
return curr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visitBlock(Block* curr) {
|
||||
labels.insert(curr->name);
|
||||
}
|
||||
void visitLoop(Loop* curr) {
|
||||
labels.insert(curr->name);
|
||||
}
|
||||
|
||||
private:
|
||||
std::set<Name> labels;
|
||||
size_t counter = 0;
|
||||
};
|
||||
|
||||
} // namespace LabelUtils
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_label_h
|
||||
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_literal_utils_h
|
||||
#define wasm_ir_literal_utils_h
|
||||
|
||||
#include "wasm.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
namespace LiteralUtils {
|
||||
|
||||
inline Literal makeLiteralFromInt32(int32_t x, WasmType type) {
|
||||
switch (type) {
|
||||
case i32: return Literal(int32_t(x)); break;
|
||||
case i64: return Literal(int64_t(x)); break;
|
||||
case f32: return Literal(float(x)); break;
|
||||
case f64: return Literal(double(x)); break;
|
||||
default: WASM_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
inline Literal makeLiteralZero(WasmType type) {
|
||||
return makeLiteralFromInt32(0, type);
|
||||
}
|
||||
|
||||
inline Expression* makeFromInt32(int32_t x, WasmType type, Module& wasm) {
|
||||
auto* ret = wasm.allocator.alloc<Const>();
|
||||
ret->value = makeLiteralFromInt32(x, type);
|
||||
ret->type = type;
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline Expression* makeZero(WasmType type, Module& wasm) {
|
||||
return makeFromInt32(0, type, wasm);
|
||||
}
|
||||
|
||||
} // namespace LiteralUtils
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_literal_utils_h
|
||||
|
@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_load_h
|
||||
#define wasm_ir_load_h
|
||||
|
||||
#include "wasm.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
namespace LoadUtils {
|
||||
|
||||
// checks if the sign of a load matters, which is when an integer
|
||||
// load is of fewer bytes than the size of the type (so we must
|
||||
// fill in bits either signed or unsigned wise)
|
||||
inline bool isSignRelevant(Load* load) {
|
||||
auto type = load->type;
|
||||
if (load->type == unreachable) return false;
|
||||
return !isWasmTypeFloat(type) && load->bytes < getWasmTypeSize(type);
|
||||
}
|
||||
|
||||
// check if a load can be signed (which some opts want to do)
|
||||
inline bool canBeSigned(Load* load) {
|
||||
return !load->isAtomic;
|
||||
}
|
||||
|
||||
} // namespace LoadUtils
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_load_h
|
||||
|
@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_local_graph_h
|
||||
#define wasm_ir_local_graph_h
|
||||
|
||||
namespace wasm {
|
||||
|
||||
//
|
||||
// Finds the connections between get_locals and set_locals, creating
|
||||
// a graph of those ties. This is useful for "ssa-style" optimization,
|
||||
// in which you want to know exactly which sets are relevant for a
|
||||
// a get, so it is as if each get has just one set, logically speaking
|
||||
// (see the SSA pass for actually creating new local indexes based
|
||||
// on this).
|
||||
//
|
||||
// TODO: the algorithm here is pretty simple, but also pretty slow,
|
||||
// we should optimize it. e.g. we rely on set_interaction
|
||||
// here, and worse we only use it to compute the size...
|
||||
struct LocalGraph : public PostWalker<LocalGraph> {
|
||||
// main API
|
||||
|
||||
// the constructor computes getSetses, the sets affecting each get
|
||||
LocalGraph(Function* func, Module* module);
|
||||
|
||||
// the set_locals relevant for an index or a get.
|
||||
typedef std::set<SetLocal*> Sets;
|
||||
|
||||
// externally useful information
|
||||
std::map<GetLocal*, Sets> getSetses; // the sets affecting each get. a nullptr set means the initial
|
||||
// value (0 for a var, the received value for a param)
|
||||
std::map<Expression*, Expression**> locations; // where each get and set is (for easy replacing)
|
||||
|
||||
// optional computation: compute the influence graphs between sets and gets
|
||||
// (useful for algorithms that propagate changes)
|
||||
|
||||
std::unordered_map<GetLocal*, std::unordered_set<SetLocal*>> getInfluences; // for each get, the sets whose values are influenced by that get
|
||||
std::unordered_map<SetLocal*, std::unordered_set<GetLocal*>> setInfluences; // for each set, the gets whose values are influenced by that set
|
||||
|
||||
void computeInfluences();
|
||||
|
||||
private:
|
||||
// we map local index => the set_locals for that index.
|
||||
// a nullptr set means there is a virtual set, from a param
|
||||
// initial value or the zero init initial value.
|
||||
typedef std::vector<Sets> Mapping;
|
||||
|
||||
// internal state
|
||||
Index numLocals;
|
||||
Mapping currMapping;
|
||||
std::vector<Mapping> mappingStack; // used in ifs, loops
|
||||
std::map<Name, std::vector<Mapping>> breakMappings; // break target => infos that reach it
|
||||
std::vector<std::vector<GetLocal*>> loopGetStack; // stack of loops, all the gets in each, so we can update them for back branches
|
||||
|
||||
public:
|
||||
void doWalkFunction(Function* func);
|
||||
|
||||
// control flow
|
||||
|
||||
void visitBlock(Block* curr);
|
||||
|
||||
void finishIf();
|
||||
|
||||
static void afterIfCondition(LocalGraph* self, Expression** currp);
|
||||
static void afterIfTrue(LocalGraph* self, Expression** currp);
|
||||
static void afterIfFalse(LocalGraph* self, Expression** currp);
|
||||
static void beforeLoop(LocalGraph* self, Expression** currp);
|
||||
void visitLoop(Loop* curr);
|
||||
void visitBreak(Break* curr);
|
||||
void visitSwitch(Switch* curr);
|
||||
void visitReturn(Return *curr);
|
||||
void visitUnreachable(Unreachable *curr);
|
||||
|
||||
// local usage
|
||||
|
||||
void visitGetLocal(GetLocal* curr);
|
||||
void visitSetLocal(SetLocal* curr);
|
||||
|
||||
// traversal
|
||||
|
||||
static void scan(LocalGraph* self, Expression** currp);
|
||||
|
||||
// helpers
|
||||
|
||||
void setUnreachable(Mapping& mapping);
|
||||
|
||||
bool isUnreachable(Mapping& mapping);
|
||||
|
||||
// merges a bunch of infos into one.
|
||||
// if we need phis, writes them into the provided vector. the caller should
|
||||
// ensure those are placed in the right location
|
||||
Mapping& merge(std::vector<Mapping>& mappings);
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_local_graph_h
|
||||
|
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_localizer_h
|
||||
#define wasm_ir_localizer_h
|
||||
|
||||
#include <wasm-builder.h>
|
||||
|
||||
namespace wasm {
|
||||
|
||||
// Make an expression available in a local. If already in one, just
|
||||
// use that local, otherwise use a new local
|
||||
|
||||
struct Localizer {
|
||||
Index index;
|
||||
Expression* expr;
|
||||
|
||||
Localizer(Expression* input, Function* func, Module* wasm) {
|
||||
expr = input;
|
||||
if (auto* get = expr->dynCast<GetLocal>()) {
|
||||
index = get->index;
|
||||
} else if (auto* set = expr->dynCast<SetLocal>()) {
|
||||
index = set->index;
|
||||
} else {
|
||||
index = Builder::addVar(func, expr->type);
|
||||
expr = Builder(*wasm).makeTeeLocal(index, expr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_localizer_h
|
||||
|
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_manipulation_h
|
||||
#define wasm_ir_manipulation_h
|
||||
|
||||
#include "wasm.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
namespace ExpressionManipulator {
|
||||
// Re-use a node's memory. This helps avoid allocation when optimizing.
|
||||
template<typename InputType, typename OutputType>
|
||||
inline OutputType* convert(InputType *input) {
|
||||
static_assert(sizeof(OutputType) <= sizeof(InputType),
|
||||
"Can only convert to a smaller size Expression node");
|
||||
input->~InputType(); // arena-allocaed, so no destructor, but avoid UB.
|
||||
OutputType* output = (OutputType*)(input);
|
||||
new (output) OutputType;
|
||||
return output;
|
||||
}
|
||||
|
||||
// Convenience method for nop, which is a common conversion
|
||||
template<typename InputType>
|
||||
inline Nop* nop(InputType* target) {
|
||||
return convert<InputType, Nop>(target);
|
||||
}
|
||||
|
||||
// Convert a node that allocates
|
||||
template<typename InputType, typename OutputType>
|
||||
inline OutputType* convert(InputType *input, MixedArena& allocator) {
|
||||
assert(sizeof(OutputType) <= sizeof(InputType));
|
||||
input->~InputType(); // arena-allocaed, so no destructor, but avoid UB.
|
||||
OutputType* output = (OutputType*)(input);
|
||||
new (output) OutputType(allocator);
|
||||
return output;
|
||||
}
|
||||
|
||||
using CustomCopier = std::function<Expression*(Expression*)>;
|
||||
Expression* flexibleCopy(Expression* original, Module& wasm, CustomCopier custom);
|
||||
|
||||
inline Expression* copy(Expression* original, Module& wasm) {
|
||||
auto copy = [](Expression* curr) {
|
||||
return nullptr;
|
||||
};
|
||||
return flexibleCopy(original, wasm, copy);
|
||||
}
|
||||
|
||||
// Splice an item into the middle of a block's list
|
||||
void spliceIntoBlock(Block* block, Index index, Expression* add);
|
||||
}
|
||||
|
||||
} // wasm
|
||||
|
||||
#endif // wams_ir_manipulation_h
|
||||
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_memory_h
|
||||
#define wasm_ir_memory_h
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "literal.h"
|
||||
#include "wasm.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
namespace MemoryUtils {
|
||||
// flattens memory into a single data segment. returns true if successful
|
||||
inline bool flatten(Memory& memory) {
|
||||
if (memory.segments.size() == 0) return true;
|
||||
std::vector<char> data;
|
||||
for (auto& segment : memory.segments) {
|
||||
auto* offset = segment.offset->dynCast<Const>();
|
||||
if (!offset) return false;
|
||||
}
|
||||
for (auto& segment : memory.segments) {
|
||||
auto* offset = segment.offset->dynCast<Const>();
|
||||
auto start = offset->value.getInteger();
|
||||
auto end = start + segment.data.size();
|
||||
if (end > data.size()) {
|
||||
data.resize(end);
|
||||
}
|
||||
std::copy(segment.data.begin(), segment.data.end(), data.begin() + start);
|
||||
}
|
||||
memory.segments.resize(1);
|
||||
memory.segments[0].offset->cast<Const>()->value = Literal(int32_t(0));
|
||||
memory.segments[0].data.swap(data);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_memory_h
|
||||
|
@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_module_h
|
||||
#define wasm_ir_module_h
|
||||
|
||||
#include "wasm.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
namespace ModuleUtils {
|
||||
|
||||
// Computes the indexes in a wasm binary, i.e., with function imports
|
||||
// and function implementations sharing a single index space, etc.
|
||||
struct BinaryIndexes {
|
||||
std::unordered_map<Name, Index> functionIndexes;
|
||||
std::unordered_map<Name, Index> globalIndexes;
|
||||
|
||||
BinaryIndexes(Module& wasm) {
|
||||
for (Index i = 0; i < wasm.imports.size(); i++) {
|
||||
auto& import = wasm.imports[i];
|
||||
if (import->kind == ExternalKind::Function) {
|
||||
auto index = functionIndexes.size();
|
||||
functionIndexes[import->name] = index;
|
||||
} else if (import->kind == ExternalKind::Global) {
|
||||
auto index = globalIndexes.size();
|
||||
globalIndexes[import->name] = index;
|
||||
}
|
||||
}
|
||||
for (Index i = 0; i < wasm.functions.size(); i++) {
|
||||
auto index = functionIndexes.size();
|
||||
functionIndexes[wasm.functions[i]->name] = index;
|
||||
}
|
||||
for (Index i = 0; i < wasm.globals.size(); i++) {
|
||||
auto index = globalIndexes.size();
|
||||
globalIndexes[wasm.globals[i]->name] = index;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ModuleUtils
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_module_h
|
||||
|
@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_names_h
|
||||
#define wasm_ir_names_h
|
||||
|
||||
#include "wasm.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
namespace Names {
|
||||
|
||||
// Add explicit names for function locals not yet named, and do not
|
||||
// modify existing names
|
||||
inline void ensureNames(Function* func) {
|
||||
std::unordered_set<Name> seen;
|
||||
for (auto& pair : func->localNames) {
|
||||
seen.insert(pair.second);
|
||||
}
|
||||
Index nameIndex = seen.size();
|
||||
for (Index i = 0; i < func->getNumLocals(); i++) {
|
||||
if (!func->hasLocalName(i)) {
|
||||
while (1) {
|
||||
auto name = Name::fromInt(nameIndex++);
|
||||
if (seen.count(name) == 0) {
|
||||
func->localNames[i] = name;
|
||||
func->localIndices[name] = i;
|
||||
seen.insert(name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Names
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_names_h
|
@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_properties_h
|
||||
#define wasm_ir_properties_h
|
||||
|
||||
#include "wasm.h"
|
||||
#include "ir/bits.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
struct Properties {
|
||||
static bool emitsBoolean(Expression* curr) {
|
||||
if (auto* unary = curr->dynCast<Unary>()) {
|
||||
return unary->isRelational();
|
||||
} else if (auto* binary = curr->dynCast<Binary>()) {
|
||||
return binary->isRelational();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isSymmetric(Binary* binary) {
|
||||
switch (binary->op) {
|
||||
case AddInt32:
|
||||
case MulInt32:
|
||||
case AndInt32:
|
||||
case OrInt32:
|
||||
case XorInt32:
|
||||
case EqInt32:
|
||||
case NeInt32:
|
||||
|
||||
case AddInt64:
|
||||
case MulInt64:
|
||||
case AndInt64:
|
||||
case OrInt64:
|
||||
case XorInt64:
|
||||
case EqInt64:
|
||||
case NeInt64: return true;
|
||||
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if an expression is a sign-extend, and if so, returns the value
|
||||
// that is extended, otherwise nullptr
|
||||
static Expression* getSignExtValue(Expression* curr) {
|
||||
if (auto* outer = curr->dynCast<Binary>()) {
|
||||
if (outer->op == ShrSInt32) {
|
||||
if (auto* outerConst = outer->right->dynCast<Const>()) {
|
||||
if (outerConst->value.geti32() != 0) {
|
||||
if (auto* inner = outer->left->dynCast<Binary>()) {
|
||||
if (inner->op == ShlInt32) {
|
||||
if (auto* innerConst = inner->right->dynCast<Const>()) {
|
||||
if (outerConst->value == innerConst->value) {
|
||||
return inner->left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// gets the size of the sign-extended value
|
||||
static Index getSignExtBits(Expression* curr) {
|
||||
return 32 - Bits::getEffectiveShifts(curr->cast<Binary>()->right);
|
||||
}
|
||||
|
||||
// Check if an expression is almost a sign-extend: perhaps the inner shift
|
||||
// is too large. We can split the shifts in that case, which is sometimes
|
||||
// useful (e.g. if we can remove the signext)
|
||||
static Expression* getAlmostSignExt(Expression* curr) {
|
||||
if (auto* outer = curr->dynCast<Binary>()) {
|
||||
if (outer->op == ShrSInt32) {
|
||||
if (auto* outerConst = outer->right->dynCast<Const>()) {
|
||||
if (outerConst->value.geti32() != 0) {
|
||||
if (auto* inner = outer->left->dynCast<Binary>()) {
|
||||
if (inner->op == ShlInt32) {
|
||||
if (auto* innerConst = inner->right->dynCast<Const>()) {
|
||||
if (Bits::getEffectiveShifts(outerConst) <= Bits::getEffectiveShifts(innerConst)) {
|
||||
return inner->left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// gets the size of the almost sign-extended value, as well as the
|
||||
// extra shifts, if any
|
||||
static Index getAlmostSignExtBits(Expression* curr, Index& extraShifts) {
|
||||
extraShifts = Bits::getEffectiveShifts(curr->cast<Binary>()->left->cast<Binary>()->right) -
|
||||
Bits::getEffectiveShifts(curr->cast<Binary>()->right);
|
||||
return getSignExtBits(curr);
|
||||
}
|
||||
|
||||
// Check if an expression is a zero-extend, and if so, returns the value
|
||||
// that is extended, otherwise nullptr
|
||||
static Expression* getZeroExtValue(Expression* curr) {
|
||||
if (auto* binary = curr->dynCast<Binary>()) {
|
||||
if (binary->op == AndInt32) {
|
||||
if (auto* c = binary->right->dynCast<Const>()) {
|
||||
if (Bits::getMaskedBits(c->value.geti32())) {
|
||||
return binary->right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// gets the size of the sign-extended value
|
||||
static Index getZeroExtBits(Expression* curr) {
|
||||
return Bits::getMaskedBits(curr->cast<Binary>()->right->cast<Const>()->value.geti32());
|
||||
}
|
||||
};
|
||||
|
||||
} // wasm
|
||||
|
||||
#endif // wams_ir_properties_h
|
||||
|
@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_trapping_h
|
||||
#define wasm_ir_trapping_h
|
||||
|
||||
#include <exception>
|
||||
|
||||
#include "pass.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
enum class TrapMode {
|
||||
Allow,
|
||||
Clamp,
|
||||
JS
|
||||
};
|
||||
|
||||
inline void addTrapModePass(PassRunner& runner, TrapMode trapMode) {
|
||||
if (trapMode == TrapMode::Clamp) {
|
||||
runner.add("trap-mode-clamp");
|
||||
} else if (trapMode == TrapMode::JS) {
|
||||
runner.add("trap-mode-js");
|
||||
}
|
||||
}
|
||||
|
||||
class TrappingFunctionContainer {
|
||||
public:
|
||||
TrappingFunctionContainer(TrapMode mode, Module &wasm, bool immediate = false)
|
||||
: mode(mode),
|
||||
wasm(wasm),
|
||||
immediate(immediate) { }
|
||||
|
||||
bool hasFunction(Name name) {
|
||||
return functions.find(name) != functions.end();
|
||||
}
|
||||
bool hasImport(Name name) {
|
||||
return imports.find(name) != imports.end();
|
||||
}
|
||||
|
||||
void addFunction(Function* function) {
|
||||
functions[function->name] = function;
|
||||
if (immediate) {
|
||||
wasm.addFunction(function);
|
||||
}
|
||||
}
|
||||
void addImport(Import* import) {
|
||||
imports[import->name] = import;
|
||||
if (immediate) {
|
||||
wasm.addImport(import);
|
||||
}
|
||||
}
|
||||
|
||||
void addToModule() {
|
||||
if (!immediate) {
|
||||
for (auto &pair : functions) {
|
||||
wasm.addFunction(pair.second);
|
||||
}
|
||||
for (auto &pair : imports) {
|
||||
wasm.addImport(pair.second);
|
||||
}
|
||||
}
|
||||
functions.clear();
|
||||
imports.clear();
|
||||
}
|
||||
|
||||
TrapMode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
Module& getModule() {
|
||||
return wasm;
|
||||
}
|
||||
|
||||
std::map<Name, Function*>& getFunctions() {
|
||||
return functions;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<Name, Function*> functions;
|
||||
std::map<Name, Import*> imports;
|
||||
|
||||
TrapMode mode;
|
||||
Module& wasm;
|
||||
bool immediate;
|
||||
};
|
||||
|
||||
Expression* makeTrappingBinary(Binary* curr, TrappingFunctionContainer &trappingFunctions);
|
||||
Expression* makeTrappingUnary(Unary* curr, TrappingFunctionContainer &trappingFunctions);
|
||||
|
||||
inline TrapMode trapModeFromString(std::string const& str) {
|
||||
if (str == "allow") {
|
||||
return TrapMode::Allow;
|
||||
} else if (str == "clamp") {
|
||||
return TrapMode::Clamp;
|
||||
} else if (str == "js") {
|
||||
return TrapMode::JS;
|
||||
} else {
|
||||
throw std::invalid_argument(
|
||||
"Unsupported trap mode \"" + str + "\". "
|
||||
"Valid modes are \"allow\", \"js\", and \"clamp\"");
|
||||
}
|
||||
}
|
||||
|
||||
} // wasm
|
||||
|
||||
#endif // wasm_ir_trapping_h
|
@ -1,286 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_type_updating_h
|
||||
#define wasm_ir_type_updating_h
|
||||
|
||||
#include "wasm-traversal.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
// a class that tracks type dependencies between nodes, letting you
|
||||
// update types efficiently when removing and altering code.
|
||||
// altering code can alter types in the following way:
|
||||
// * removing a break can make a block unreachable, if nothing else
|
||||
// reaches it
|
||||
// * altering the type of a child to unreachable can make the parent
|
||||
// unreachable
|
||||
struct TypeUpdater : public ExpressionStackWalker<TypeUpdater, UnifiedExpressionVisitor<TypeUpdater>> {
|
||||
// Part 1: Scanning
|
||||
|
||||
// track names to their blocks, so that when we remove a break to
|
||||
// a block, we know how to find it if we need to update it
|
||||
struct BlockInfo {
|
||||
Block* block = nullptr;
|
||||
int numBreaks = 0;
|
||||
};
|
||||
std::map<Name, BlockInfo> blockInfos;
|
||||
|
||||
// track the parent of each node, as child type changes may lead to
|
||||
// unreachability
|
||||
std::map<Expression*, Expression*> parents;
|
||||
|
||||
void visitExpression(Expression* curr) {
|
||||
if (expressionStack.size() > 1) {
|
||||
parents[curr] = expressionStack[expressionStack.size() - 2];
|
||||
} else {
|
||||
parents[curr] = nullptr; // this is the top level
|
||||
}
|
||||
// discover block/break relationships
|
||||
if (auto* block = curr->dynCast<Block>()) {
|
||||
if (block->name.is()) {
|
||||
blockInfos[block->name].block = block;
|
||||
}
|
||||
} else if (auto* br = curr->dynCast<Break>()) {
|
||||
// ensure info exists, discoverBreaks can then fill it
|
||||
blockInfos[br->name];
|
||||
} else if (auto* sw = curr->dynCast<Switch>()) {
|
||||
// ensure info exists, discoverBreaks can then fill it
|
||||
for (auto target : sw->targets) {
|
||||
blockInfos[target];
|
||||
}
|
||||
blockInfos[sw->default_];
|
||||
}
|
||||
// add a break to the info, for break and switch
|
||||
discoverBreaks(curr, +1);
|
||||
}
|
||||
|
||||
// Part 2: Updating
|
||||
|
||||
// Node replacements, additions, removals and type changes should be noted. An
|
||||
// exception is nodes you know will never be looked at again.
|
||||
|
||||
// note the replacement of one node with another. this should be called
|
||||
// after performing the replacement.
|
||||
// this does *not* look into the node by default. see noteReplacementWithRecursiveRemoval
|
||||
// (we don't support recursive addition because in practice we do not create
|
||||
// new trees in the passes that use this, they just move around children)
|
||||
void noteReplacement(Expression* from, Expression* to, bool recursivelyRemove=false) {
|
||||
auto parent = parents[from];
|
||||
if (recursivelyRemove) {
|
||||
noteRecursiveRemoval(from);
|
||||
} else {
|
||||
noteRemoval(from);
|
||||
}
|
||||
// if we are replacing with a child, i.e. a node that was already present
|
||||
// in the ast, then we just have a type and parent to update
|
||||
if (parents.find(to) != parents.end()) {
|
||||
parents[to] = parent;
|
||||
if (from->type != to->type) {
|
||||
propagateTypesUp(to);
|
||||
}
|
||||
} else {
|
||||
noteAddition(to, parent, from);
|
||||
}
|
||||
}
|
||||
|
||||
void noteReplacementWithRecursiveRemoval(Expression* from, Expression* to) {
|
||||
noteReplacement(from, to, true);
|
||||
}
|
||||
|
||||
// note the removal of a node
|
||||
void noteRemoval(Expression* curr) {
|
||||
noteRemovalOrAddition(curr, nullptr);
|
||||
parents.erase(curr);
|
||||
}
|
||||
|
||||
// note the removal of a node and all its children
|
||||
void noteRecursiveRemoval(Expression* curr) {
|
||||
struct Recurser : public PostWalker<Recurser, UnifiedExpressionVisitor<Recurser>> {
|
||||
TypeUpdater& parent;
|
||||
|
||||
Recurser(TypeUpdater& parent, Expression* root) : parent(parent) {
|
||||
walk(root);
|
||||
}
|
||||
|
||||
void visitExpression(Expression* curr) {
|
||||
parent.noteRemoval(curr);
|
||||
}
|
||||
};
|
||||
|
||||
Recurser(*this, curr);
|
||||
}
|
||||
|
||||
void noteAddition(Expression* curr, Expression* parent, Expression* previous = nullptr) {
|
||||
assert(parents.find(curr) == parents.end()); // must not already exist
|
||||
noteRemovalOrAddition(curr, parent);
|
||||
// if we didn't replace with the exact same type, propagate types up
|
||||
if (!(previous && previous->type == curr->type)) {
|
||||
propagateTypesUp(curr);
|
||||
}
|
||||
}
|
||||
|
||||
// if parent is nullptr, this is a removal
|
||||
void noteRemovalOrAddition(Expression* curr, Expression* parent) {
|
||||
parents[curr] = parent;
|
||||
discoverBreaks(curr, parent ? +1 : -1);
|
||||
}
|
||||
|
||||
// adds (or removes) breaks depending on break/switch contents
|
||||
void discoverBreaks(Expression* curr, int change) {
|
||||
if (auto* br = curr->dynCast<Break>()) {
|
||||
noteBreakChange(br->name, change, br->value);
|
||||
} else if (auto* sw = curr->dynCast<Switch>()) {
|
||||
applySwitchChanges(sw, change);
|
||||
}
|
||||
}
|
||||
|
||||
void applySwitchChanges(Switch* sw, int change) {
|
||||
std::set<Name> seen;
|
||||
for (auto target : sw->targets) {
|
||||
if (seen.insert(target).second) {
|
||||
noteBreakChange(target, change, sw->value);
|
||||
}
|
||||
}
|
||||
if (seen.insert(sw->default_).second) {
|
||||
noteBreakChange(sw->default_, change, sw->value);
|
||||
}
|
||||
}
|
||||
|
||||
// note the addition of a node
|
||||
void noteBreakChange(Name name, int change, Expression* value) {
|
||||
auto iter = blockInfos.find(name);
|
||||
if (iter == blockInfos.end()) {
|
||||
return; // we can ignore breaks to loops
|
||||
}
|
||||
auto& info = iter->second;
|
||||
info.numBreaks += change;
|
||||
assert(info.numBreaks >= 0);
|
||||
auto* block = info.block;
|
||||
if (block) { // if to a loop, can ignore
|
||||
if (info.numBreaks == 0) {
|
||||
// dropped to 0! the block may now be unreachable. that
|
||||
// requires that it doesn't have a fallthrough
|
||||
makeBlockUnreachableIfNoFallThrough(block);
|
||||
} else if (change == 1 && info.numBreaks == 1) {
|
||||
// bumped to 1! the block may now be reachable
|
||||
if (block->type != unreachable) {
|
||||
return; // was already reachable, had a fallthrough
|
||||
}
|
||||
changeTypeTo(block, value ? value->type : none);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// alters the type of a node to a new type.
|
||||
// this propagates the type change through all the parents.
|
||||
void changeTypeTo(Expression* curr, WasmType newType) {
|
||||
if (curr->type == newType) return; // nothing to do
|
||||
curr->type = newType;
|
||||
propagateTypesUp(curr);
|
||||
}
|
||||
|
||||
// given a node that has a new type, or is a new node, update
|
||||
// all the parents accordingly. the existence of the node and
|
||||
// any changes to it already occurred, this just updates the
|
||||
// parents following that. i.e., nothing is done to the
|
||||
// node we start on, it's done.
|
||||
// the one thing we need to do here is propagate unreachability,
|
||||
// no other change is possible
|
||||
void propagateTypesUp(Expression* curr) {
|
||||
if (curr->type != unreachable) return;
|
||||
while (1) {
|
||||
auto* child = curr;
|
||||
curr = parents[child];
|
||||
if (!curr) return;
|
||||
// get ready to apply unreachability to this node
|
||||
if (curr->type == unreachable) {
|
||||
return; // already unreachable, stop here
|
||||
}
|
||||
// most nodes become unreachable if a child is unreachable,
|
||||
// but exceptions exist
|
||||
if (auto* block = curr->dynCast<Block>()) {
|
||||
// if the block has a fallthrough, it can keep its type
|
||||
if (isConcreteWasmType(block->list.back()->type)) {
|
||||
return; // did not turn
|
||||
}
|
||||
// if the block has breaks, it can keep its type
|
||||
if (!block->name.is() || blockInfos[block->name].numBreaks == 0) {
|
||||
curr->type = unreachable;
|
||||
} else {
|
||||
return; // did not turn
|
||||
}
|
||||
} else if (auto* iff = curr->dynCast<If>()) {
|
||||
// may not be unreachable if just one side is
|
||||
iff->finalize();
|
||||
if (curr->type != unreachable) {
|
||||
return; // did not turn
|
||||
}
|
||||
} else {
|
||||
curr->type = unreachable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// efficiently update the type of a block, given the data we know. this
|
||||
// can remove a concrete type and turn the block unreachable when it is
|
||||
// unreachable, and it does this efficiently, without scanning the full
|
||||
// contents
|
||||
void maybeUpdateTypeToUnreachable(Block* curr) {
|
||||
if (!isConcreteWasmType(curr->type)) {
|
||||
return; // nothing concrete to change to unreachable
|
||||
}
|
||||
if (curr->name.is() && blockInfos[curr->name].numBreaks > 0) {
|
||||
return; // has a break, not unreachable
|
||||
}
|
||||
// look for a fallthrough
|
||||
makeBlockUnreachableIfNoFallThrough(curr);
|
||||
}
|
||||
|
||||
void makeBlockUnreachableIfNoFallThrough(Block* curr) {
|
||||
if (curr->type == unreachable) {
|
||||
return; // no change possible
|
||||
}
|
||||
if (!curr->list.empty() &&
|
||||
isConcreteWasmType(curr->list.back()->type)) {
|
||||
return; // should keep type due to fallthrough, even if has an unreachable child
|
||||
}
|
||||
for (auto* child : curr->list) {
|
||||
if (child->type == unreachable) {
|
||||
// no fallthrough, and an unreachable, => this block is now unreachable
|
||||
changeTypeTo(curr, unreachable);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// efficiently update the type of an if, given the data we know. this
|
||||
// can remove a concrete type and turn the if unreachable when it is
|
||||
// unreachable
|
||||
void maybeUpdateTypeToUnreachable(If* curr) {
|
||||
if (!isConcreteWasmType(curr->type)) {
|
||||
return; // nothing concrete to change to unreachable
|
||||
}
|
||||
curr->finalize();
|
||||
if (curr->type == unreachable) {
|
||||
propagateTypesUp(curr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_type_updating_h
|
@ -1,360 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_ir_utils_h
|
||||
#define wasm_ir_utils_h
|
||||
|
||||
#include "wasm.h"
|
||||
#include "wasm-traversal.h"
|
||||
#include "wasm-builder.h"
|
||||
#include "pass.h"
|
||||
#include "ir/branch-utils.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
// Measure the size of an AST
|
||||
|
||||
struct Measurer : public PostWalker<Measurer, UnifiedExpressionVisitor<Measurer>> {
|
||||
Index size = 0;
|
||||
|
||||
void visitExpression(Expression* curr) {
|
||||
size++;
|
||||
}
|
||||
|
||||
static Index measure(Expression* tree) {
|
||||
Measurer measurer;
|
||||
measurer.walk(tree);
|
||||
return measurer.size;
|
||||
}
|
||||
};
|
||||
|
||||
struct ExpressionAnalyzer {
|
||||
// Given a stack of expressions, checks if the topmost is used as a result.
|
||||
// For example, if the parent is a block and the node is before the last position,
|
||||
// it is not used.
|
||||
static bool isResultUsed(std::vector<Expression*> stack, Function* func);
|
||||
|
||||
// Checks if a value is dropped.
|
||||
static bool isResultDropped(std::vector<Expression*> stack);
|
||||
|
||||
// Checks if a break is a simple - no condition, no value, just a plain branching
|
||||
static bool isSimple(Break* curr) {
|
||||
return !curr->condition && !curr->value;
|
||||
}
|
||||
|
||||
using ExprComparer = std::function<bool(Expression*, Expression*)>;
|
||||
static bool flexibleEqual(Expression* left, Expression* right, ExprComparer comparer);
|
||||
|
||||
static bool equal(Expression* left, Expression* right) {
|
||||
auto comparer = [](Expression* left, Expression* right) {
|
||||
return false;
|
||||
};
|
||||
return flexibleEqual(left, right, comparer);
|
||||
}
|
||||
|
||||
// hash an expression, ignoring superficial details like specific internal names
|
||||
static uint32_t hash(Expression* curr);
|
||||
};
|
||||
|
||||
// Re-Finalizes all node types
|
||||
// This removes "unnecessary' block/if/loop types, i.e., that are added
|
||||
// specifically, as in
|
||||
// (block (result i32) (unreachable))
|
||||
// vs
|
||||
// (block (unreachable))
|
||||
// This converts to the latter form.
|
||||
struct ReFinalize : public WalkerPass<PostWalker<ReFinalize, OverriddenVisitor<ReFinalize>>> {
|
||||
bool isFunctionParallel() override { return true; }
|
||||
|
||||
Pass* create() override { return new ReFinalize; }
|
||||
|
||||
ReFinalize() { name = "refinalize"; }
|
||||
|
||||
// block finalization is O(bad) if we do each block by itself, so do it in bulk,
|
||||
// tracking break value types so we just do a linear pass
|
||||
|
||||
std::map<Name, WasmType> breakValues;
|
||||
|
||||
void visitBlock(Block *curr) {
|
||||
if (curr->list.size() == 0) {
|
||||
curr->type = none;
|
||||
return;
|
||||
}
|
||||
// do this quickly, without any validation
|
||||
auto old = curr->type;
|
||||
// last element determines type
|
||||
curr->type = curr->list.back()->type;
|
||||
// if concrete, it doesn't matter if we have an unreachable child, and we
|
||||
// don't need to look at breaks
|
||||
if (isConcreteWasmType(curr->type)) return;
|
||||
// otherwise, we have no final fallthrough element to determine the type,
|
||||
// could be determined by breaks
|
||||
if (curr->name.is()) {
|
||||
auto iter = breakValues.find(curr->name);
|
||||
if (iter != breakValues.end()) {
|
||||
// there is a break to here
|
||||
auto type = iter->second;
|
||||
if (type == unreachable) {
|
||||
// all we have are breaks with values of type unreachable, and no
|
||||
// concrete fallthrough either. we must have had an existing type, then
|
||||
curr->type = old;
|
||||
assert(isConcreteWasmType(curr->type));
|
||||
} else {
|
||||
curr->type = type;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (curr->type == unreachable) return;
|
||||
// type is none, but we might be unreachable
|
||||
if (curr->type == none) {
|
||||
for (auto* child : curr->list) {
|
||||
if (child->type == unreachable) {
|
||||
curr->type = unreachable;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void visitIf(If *curr) { curr->finalize(); }
|
||||
void visitLoop(Loop *curr) { curr->finalize(); }
|
||||
void visitBreak(Break *curr) {
|
||||
curr->finalize();
|
||||
updateBreakValueType(curr->name, getValueType(curr->value));
|
||||
}
|
||||
void visitSwitch(Switch *curr) {
|
||||
curr->finalize();
|
||||
auto valueType = getValueType(curr->value);
|
||||
for (auto target : curr->targets) {
|
||||
updateBreakValueType(target, valueType);
|
||||
}
|
||||
updateBreakValueType(curr->default_, valueType);
|
||||
}
|
||||
void visitCall(Call *curr) { curr->finalize(); }
|
||||
void visitCallImport(CallImport *curr) { curr->finalize(); }
|
||||
void visitCallIndirect(CallIndirect *curr) { curr->finalize(); }
|
||||
void visitGetLocal(GetLocal *curr) { curr->finalize(); }
|
||||
void visitSetLocal(SetLocal *curr) { curr->finalize(); }
|
||||
void visitGetGlobal(GetGlobal *curr) { curr->finalize(); }
|
||||
void visitSetGlobal(SetGlobal *curr) { curr->finalize(); }
|
||||
void visitLoad(Load *curr) { curr->finalize(); }
|
||||
void visitStore(Store *curr) { curr->finalize(); }
|
||||
void visitAtomicRMW(AtomicRMW *curr) { curr->finalize(); }
|
||||
void visitAtomicCmpxchg(AtomicCmpxchg *curr) { curr->finalize(); }
|
||||
void visitAtomicWait(AtomicWait* curr) { curr->finalize(); }
|
||||
void visitAtomicWake(AtomicWake* curr) { curr->finalize(); }
|
||||
void visitConst(Const *curr) { curr->finalize(); }
|
||||
void visitUnary(Unary *curr) { curr->finalize(); }
|
||||
void visitBinary(Binary *curr) { curr->finalize(); }
|
||||
void visitSelect(Select *curr) { curr->finalize(); }
|
||||
void visitDrop(Drop *curr) { curr->finalize(); }
|
||||
void visitReturn(Return *curr) { curr->finalize(); }
|
||||
void visitHost(Host *curr) { curr->finalize(); }
|
||||
void visitNop(Nop *curr) { curr->finalize(); }
|
||||
void visitUnreachable(Unreachable *curr) { curr->finalize(); }
|
||||
|
||||
void visitFunction(Function* curr) {
|
||||
// we may have changed the body from unreachable to none, which might be bad
|
||||
// if the function has a return value
|
||||
if (curr->result != none && curr->body->type == none) {
|
||||
Builder builder(*getModule());
|
||||
curr->body = builder.blockify(curr->body, builder.makeUnreachable());
|
||||
}
|
||||
}
|
||||
|
||||
void visitFunctionType(FunctionType* curr) { WASM_UNREACHABLE(); }
|
||||
void visitImport(Import* curr) { WASM_UNREACHABLE(); }
|
||||
void visitExport(Export* curr) { WASM_UNREACHABLE(); }
|
||||
void visitGlobal(Global* curr) { WASM_UNREACHABLE(); }
|
||||
void visitTable(Table* curr) { WASM_UNREACHABLE(); }
|
||||
void visitMemory(Memory* curr) { WASM_UNREACHABLE(); }
|
||||
void visitModule(Module* curr) { WASM_UNREACHABLE(); }
|
||||
|
||||
WasmType getValueType(Expression* value) {
|
||||
return value ? value->type : none;
|
||||
}
|
||||
|
||||
void updateBreakValueType(Name name, WasmType type) {
|
||||
if (type != unreachable || breakValues.count(name) == 0) {
|
||||
breakValues[name] = type;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Re-finalize a single node. This is slow, if you want to refinalize
|
||||
// an entire ast, use ReFinalize
|
||||
struct ReFinalizeNode : public OverriddenVisitor<ReFinalizeNode> {
|
||||
void visitBlock(Block *curr) { curr->finalize(); }
|
||||
void visitIf(If *curr) { curr->finalize(); }
|
||||
void visitLoop(Loop *curr) { curr->finalize(); }
|
||||
void visitBreak(Break *curr) { curr->finalize(); }
|
||||
void visitSwitch(Switch *curr) { curr->finalize(); }
|
||||
void visitCall(Call *curr) { curr->finalize(); }
|
||||
void visitCallImport(CallImport *curr) { curr->finalize(); }
|
||||
void visitCallIndirect(CallIndirect *curr) { curr->finalize(); }
|
||||
void visitGetLocal(GetLocal *curr) { curr->finalize(); }
|
||||
void visitSetLocal(SetLocal *curr) { curr->finalize(); }
|
||||
void visitGetGlobal(GetGlobal *curr) { curr->finalize(); }
|
||||
void visitSetGlobal(SetGlobal *curr) { curr->finalize(); }
|
||||
void visitLoad(Load *curr) { curr->finalize(); }
|
||||
void visitStore(Store *curr) { curr->finalize(); }
|
||||
void visitAtomicRMW(AtomicRMW* curr) { curr->finalize(); }
|
||||
void visitAtomicCmpxchg(AtomicCmpxchg* curr) { curr->finalize(); }
|
||||
void visitAtomicWait(AtomicWait* curr) { curr->finalize(); }
|
||||
void visitAtomicWake(AtomicWake* curr) { curr->finalize(); }
|
||||
void visitConst(Const *curr) { curr->finalize(); }
|
||||
void visitUnary(Unary *curr) { curr->finalize(); }
|
||||
void visitBinary(Binary *curr) { curr->finalize(); }
|
||||
void visitSelect(Select *curr) { curr->finalize(); }
|
||||
void visitDrop(Drop *curr) { curr->finalize(); }
|
||||
void visitReturn(Return *curr) { curr->finalize(); }
|
||||
void visitHost(Host *curr) { curr->finalize(); }
|
||||
void visitNop(Nop *curr) { curr->finalize(); }
|
||||
void visitUnreachable(Unreachable *curr) { curr->finalize(); }
|
||||
|
||||
void visitFunctionType(FunctionType* curr) { WASM_UNREACHABLE(); }
|
||||
void visitImport(Import* curr) { WASM_UNREACHABLE(); }
|
||||
void visitExport(Export* curr) { WASM_UNREACHABLE(); }
|
||||
void visitGlobal(Global* curr) { WASM_UNREACHABLE(); }
|
||||
void visitTable(Table* curr) { WASM_UNREACHABLE(); }
|
||||
void visitMemory(Memory* curr) { WASM_UNREACHABLE(); }
|
||||
void visitModule(Module* curr) { WASM_UNREACHABLE(); }
|
||||
|
||||
// given a stack of nested expressions, update them all from child to parent
|
||||
static void updateStack(std::vector<Expression*>& expressionStack) {
|
||||
for (int i = int(expressionStack.size()) - 1; i >= 0; i--) {
|
||||
auto* curr = expressionStack[i];
|
||||
ReFinalizeNode().visit(curr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Adds drop() operations where necessary. This lets you not worry about adding drop when
|
||||
// generating code.
|
||||
// This also refinalizes before and after, as dropping can change types, and depends
|
||||
// on types being cleaned up - no unnecessary block/if/loop types (see refinalize)
|
||||
// TODO: optimize that, interleave them
|
||||
struct AutoDrop : public WalkerPass<ExpressionStackWalker<AutoDrop>> {
|
||||
bool isFunctionParallel() override { return true; }
|
||||
|
||||
Pass* create() override { return new AutoDrop; }
|
||||
|
||||
AutoDrop() { name = "autodrop"; }
|
||||
|
||||
bool maybeDrop(Expression*& child) {
|
||||
bool acted = false;
|
||||
if (isConcreteWasmType(child->type)) {
|
||||
expressionStack.push_back(child);
|
||||
if (!ExpressionAnalyzer::isResultUsed(expressionStack, getFunction()) && !ExpressionAnalyzer::isResultDropped(expressionStack)) {
|
||||
child = Builder(*getModule()).makeDrop(child);
|
||||
acted = true;
|
||||
}
|
||||
expressionStack.pop_back();
|
||||
}
|
||||
return acted;
|
||||
}
|
||||
|
||||
void reFinalize() {
|
||||
ReFinalizeNode::updateStack(expressionStack);
|
||||
}
|
||||
|
||||
void visitBlock(Block* curr) {
|
||||
if (curr->list.size() == 0) return;
|
||||
for (Index i = 0; i < curr->list.size() - 1; i++) {
|
||||
auto* child = curr->list[i];
|
||||
if (isConcreteWasmType(child->type)) {
|
||||
curr->list[i] = Builder(*getModule()).makeDrop(child);
|
||||
}
|
||||
}
|
||||
if (maybeDrop(curr->list.back())) {
|
||||
reFinalize();
|
||||
assert(curr->type == none || curr->type == unreachable);
|
||||
}
|
||||
}
|
||||
|
||||
void visitIf(If* curr) {
|
||||
bool acted = false;
|
||||
if (maybeDrop(curr->ifTrue)) acted = true;
|
||||
if (curr->ifFalse) {
|
||||
if (maybeDrop(curr->ifFalse)) acted = true;
|
||||
}
|
||||
if (acted) {
|
||||
reFinalize();
|
||||
assert(curr->type == none);
|
||||
}
|
||||
}
|
||||
|
||||
void doWalkFunction(Function* curr) {
|
||||
ReFinalize().walkFunctionInModule(curr, getModule());
|
||||
walk(curr->body);
|
||||
if (curr->result == none && isConcreteWasmType(curr->body->type)) {
|
||||
curr->body = Builder(*getModule()).makeDrop(curr->body);
|
||||
}
|
||||
ReFinalize().walkFunctionInModule(curr, getModule());
|
||||
}
|
||||
};
|
||||
|
||||
struct I64Utilities {
|
||||
static Expression* recreateI64(Builder& builder, Expression* low, Expression* high) {
|
||||
return
|
||||
builder.makeBinary(
|
||||
OrInt64,
|
||||
builder.makeUnary(
|
||||
ExtendUInt32,
|
||||
low
|
||||
),
|
||||
builder.makeBinary(
|
||||
ShlInt64,
|
||||
builder.makeUnary(
|
||||
ExtendUInt32,
|
||||
high
|
||||
),
|
||||
builder.makeConst(Literal(int64_t(32)))
|
||||
)
|
||||
)
|
||||
;
|
||||
};
|
||||
|
||||
static Expression* recreateI64(Builder& builder, Index low, Index high) {
|
||||
return recreateI64(builder, builder.makeGetLocal(low, i32), builder.makeGetLocal(high, i32));
|
||||
};
|
||||
|
||||
static Expression* getI64High(Builder& builder, Index index) {
|
||||
return
|
||||
builder.makeUnary(
|
||||
WrapInt64,
|
||||
builder.makeBinary(
|
||||
ShrUInt64,
|
||||
builder.makeGetLocal(index, i64),
|
||||
builder.makeConst(Literal(int64_t(32)))
|
||||
)
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
static Expression* getI64Low(Builder& builder, Index index) {
|
||||
return
|
||||
builder.makeUnary(
|
||||
WrapInt64,
|
||||
builder.makeGetLocal(index, i64)
|
||||
)
|
||||
;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_ir_utils_h
|
File diff suppressed because it is too large
Load Diff
@ -1,2 +0,0 @@
|
||||
(function() {
|
||||
"use strict";
|
@ -1,175 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_literal_h
|
||||
#define wasm_literal_h
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "support/hash.h"
|
||||
#include "support/utilities.h"
|
||||
#include "compiler-support.h"
|
||||
#include "wasm-type.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
class Literal {
|
||||
public:
|
||||
WasmType type;
|
||||
|
||||
private:
|
||||
// store only integers, whose bits are deterministic. floats
|
||||
// can have their signalling bit set, for example.
|
||||
union {
|
||||
int32_t i32;
|
||||
int64_t i64;
|
||||
};
|
||||
|
||||
// The RHS of shl/shru/shrs must be masked by bitwidth.
|
||||
template <typename T>
|
||||
static T shiftMask(T val) {
|
||||
return val & (sizeof(T) * 8 - 1);
|
||||
}
|
||||
|
||||
public:
|
||||
Literal() : type(WasmType::none), i64(0) {}
|
||||
explicit Literal(WasmType type) : type(type), i64(0) {}
|
||||
explicit Literal(int32_t init) : type(WasmType::i32), i32(init) {}
|
||||
explicit Literal(uint32_t init) : type(WasmType::i32), i32(init) {}
|
||||
explicit Literal(int64_t init) : type(WasmType::i64), i64(init) {}
|
||||
explicit Literal(uint64_t init) : type(WasmType::i64), i64(init) {}
|
||||
explicit Literal(float init) : type(WasmType::f32), i32(bit_cast<int32_t>(init)) {}
|
||||
explicit Literal(double init) : type(WasmType::f64), i64(bit_cast<int64_t>(init)) {}
|
||||
|
||||
bool isConcrete() { return type != none; }
|
||||
bool isNull() { return type == none; }
|
||||
|
||||
Literal castToF32();
|
||||
Literal castToF64();
|
||||
Literal castToI32();
|
||||
Literal castToI64();
|
||||
|
||||
int32_t geti32() const { assert(type == WasmType::i32); return i32; }
|
||||
int64_t geti64() const { assert(type == WasmType::i64); return i64; }
|
||||
float getf32() const { assert(type == WasmType::f32); return bit_cast<float>(i32); }
|
||||
double getf64() const { assert(type == WasmType::f64); return bit_cast<double>(i64); }
|
||||
|
||||
int32_t* geti32Ptr() { assert(type == WasmType::i32); return &i32; } // careful!
|
||||
|
||||
int32_t reinterpreti32() const { assert(type == WasmType::f32); return i32; }
|
||||
int64_t reinterpreti64() const { assert(type == WasmType::f64); return i64; }
|
||||
float reinterpretf32() const { assert(type == WasmType::i32); return bit_cast<float>(i32); }
|
||||
double reinterpretf64() const { assert(type == WasmType::i64); return bit_cast<double>(i64); }
|
||||
|
||||
int64_t getInteger() const;
|
||||
double getFloat() const;
|
||||
int64_t getBits() const;
|
||||
bool operator==(const Literal& other) const;
|
||||
bool operator!=(const Literal& other) const;
|
||||
bool bitwiseEqual(const Literal& other) const;
|
||||
|
||||
static uint32_t NaNPayload(float f);
|
||||
static uint64_t NaNPayload(double f);
|
||||
static float setQuietNaN(float f);
|
||||
static double setQuietNaN(double f);
|
||||
|
||||
static void printFloat(std::ostream &o, float f);
|
||||
static void printDouble(std::ostream& o, double d);
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& o, Literal literal);
|
||||
|
||||
Literal countLeadingZeroes() const;
|
||||
Literal countTrailingZeroes() const;
|
||||
Literal popCount() const;
|
||||
|
||||
Literal extendToSI64() const;
|
||||
Literal extendToUI64() const;
|
||||
Literal extendToF64() const;
|
||||
Literal truncateToI32() const;
|
||||
Literal truncateToF32() const;
|
||||
|
||||
Literal convertSToF32() const;
|
||||
Literal convertUToF32() const;
|
||||
Literal convertSToF64() const;
|
||||
Literal convertUToF64() const;
|
||||
|
||||
Literal neg() const;
|
||||
Literal abs() const;
|
||||
Literal ceil() const;
|
||||
Literal floor() const;
|
||||
Literal trunc() const;
|
||||
Literal nearbyint() const;
|
||||
Literal sqrt() const;
|
||||
|
||||
Literal add(const Literal& other) const;
|
||||
Literal sub(const Literal& other) const;
|
||||
Literal mul(const Literal& other) const;
|
||||
Literal div(const Literal& other) const;
|
||||
Literal divS(const Literal& other) const;
|
||||
Literal divU(const Literal& other) const;
|
||||
Literal remS(const Literal& other) const;
|
||||
Literal remU(const Literal& other) const;
|
||||
Literal and_(const Literal& other) const;
|
||||
Literal or_(const Literal& other) const;
|
||||
Literal xor_(const Literal& other) const;
|
||||
Literal shl(const Literal& other) const;
|
||||
Literal shrS(const Literal& other) const;
|
||||
Literal shrU(const Literal& other) const;
|
||||
Literal rotL(const Literal& other) const;
|
||||
Literal rotR(const Literal& other) const;
|
||||
|
||||
Literal eq(const Literal& other) const;
|
||||
Literal ne(const Literal& other) const;
|
||||
Literal ltS(const Literal& other) const;
|
||||
Literal ltU(const Literal& other) const;
|
||||
Literal lt(const Literal& other) const;
|
||||
Literal leS(const Literal& other) const;
|
||||
Literal leU(const Literal& other) const;
|
||||
Literal le(const Literal& other) const;
|
||||
|
||||
Literal gtS(const Literal& other) const;
|
||||
Literal gtU(const Literal& other) const;
|
||||
Literal gt(const Literal& other) const;
|
||||
Literal geS(const Literal& other) const;
|
||||
Literal geU(const Literal& other) const;
|
||||
Literal ge(const Literal& other) const;
|
||||
|
||||
Literal min(const Literal& other) const;
|
||||
Literal max(const Literal& other) const;
|
||||
Literal copysign(const Literal& other) const;
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
namespace std {
|
||||
template<> struct hash<wasm::Literal> {
|
||||
size_t operator()(const wasm::Literal& a) const {
|
||||
return wasm::rehash(
|
||||
uint64_t(hash<size_t>()(size_t(a.type))),
|
||||
uint64_t(hash<int64_t>()(a.getBits()))
|
||||
);
|
||||
}
|
||||
};
|
||||
template<> struct less<wasm::Literal> {
|
||||
bool operator()(const wasm::Literal& a, const wasm::Literal& b) const {
|
||||
if (a.type < b.type) return true;
|
||||
if (a.type > b.type) return false;
|
||||
return a.getBits() < b.getBits();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif // wasm_literal_h
|
@ -1,322 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_mixed_arena_h
|
||||
#define wasm_mixed_arena_h
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
//
|
||||
// Arena allocation for mixed-type data.
|
||||
//
|
||||
// Arena-style bump allocation is important for two reasons: First, so that
|
||||
// allocation is quick, and second, so that allocated items are close together,
|
||||
// which is cache-friendy. Arena allocation is also useful for a minor third
|
||||
// reason which is to make freeing all the items in an arena very quick.
|
||||
//
|
||||
// Each WebAssembly Module has an arena allocator, which should be used
|
||||
// for all of its AST nodes and so forth. When the Module is destroyed, the
|
||||
// entire arena is cleaned up.
|
||||
//
|
||||
// When allocating an object in an arena, the object's proper constructor
|
||||
// is called. Note that destructors are not called, because to make the
|
||||
// arena simple and fast we do not track internal allocations inside it
|
||||
// (and we can also avoid the need for virtual destructors).
|
||||
//
|
||||
// In general, optimization passes avoid allocation as much as possible.
|
||||
// Many passes only remove or modify nodes anyhow, others can often
|
||||
// reuse nodes that are being optimized out. This keeps things
|
||||
// cache-friendly, and also makes the operations trivially thread-safe.
|
||||
// In the rare case that a pass does need to allocate, and it is a
|
||||
// parallel pass (so multiple threads might access the allocator),
|
||||
// the MixedArena instance will notice if it is on a different thread
|
||||
// than that arena's original thread, and will perform the allocation
|
||||
// in a side arena for that other thread. This is done in a transparent
|
||||
// way to the outside; as a result, it is always safe to allocate using
|
||||
// a MixedArena, no matter which thread you are on. Allocations will
|
||||
// of course be fastest on the original thread for the arena.
|
||||
//
|
||||
|
||||
struct MixedArena {
|
||||
// fast bump allocation
|
||||
std::vector<char*> chunks;
|
||||
size_t chunkSize = 32768;
|
||||
size_t index; // in last chunk
|
||||
|
||||
std::thread::id threadId;
|
||||
|
||||
// multithreaded allocation - each arena is valid on a specific thread.
|
||||
// if we are on the wrong thread, we atomically look in the linked
|
||||
// list of next, adding an allocator if necessary
|
||||
std::atomic<MixedArena*> next;
|
||||
|
||||
MixedArena() {
|
||||
threadId = std::this_thread::get_id();
|
||||
next.store(nullptr);
|
||||
}
|
||||
|
||||
void* allocSpace(size_t size) {
|
||||
// the bump allocator data should not be modified by multiple threads at once.
|
||||
auto myId = std::this_thread::get_id();
|
||||
if (myId != threadId) {
|
||||
MixedArena* curr = this;
|
||||
MixedArena* allocated = nullptr;
|
||||
while (myId != curr->threadId) {
|
||||
auto seen = curr->next.load();
|
||||
if (seen) {
|
||||
curr = seen;
|
||||
continue;
|
||||
}
|
||||
// there is a nullptr for next, so we may be able to place a new
|
||||
// allocator for us there. but carefully, as others may do so as
|
||||
// well. we may waste a few allocations here, but it doesn't matter
|
||||
// as this can only happen as the chain is built up, i.e.,
|
||||
// O(# of cores) per allocator, and our allocatrs are long-lived.
|
||||
if (!allocated) {
|
||||
allocated = new MixedArena(); // has our thread id
|
||||
}
|
||||
if (curr->next.compare_exchange_weak(seen, allocated)) {
|
||||
// we replaced it, so we are the next in the chain
|
||||
// we can forget about allocated, it is owned by the chain now
|
||||
allocated = nullptr;
|
||||
break;
|
||||
}
|
||||
// otherwise, the cmpxchg updated seen, and we continue to loop
|
||||
curr = seen;
|
||||
}
|
||||
if (allocated) delete allocated;
|
||||
return curr->allocSpace(size);
|
||||
}
|
||||
size = (size + 7) & (-8); // same alignment as malloc TODO optimize?
|
||||
bool mustAllocate = false;
|
||||
while (chunkSize <= size) {
|
||||
chunkSize *= 2;
|
||||
mustAllocate = true;
|
||||
}
|
||||
if (chunks.size() == 0 || index + size >= chunkSize || mustAllocate) {
|
||||
chunks.push_back(new char[chunkSize]);
|
||||
index = 0;
|
||||
}
|
||||
auto* ret = chunks.back() + index;
|
||||
index += size;
|
||||
return static_cast<void*>(ret);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T* alloc() {
|
||||
auto* ret = static_cast<T*>(allocSpace(sizeof(T)));
|
||||
new (ret) T(*this); // allocated objects receive the allocator, so they can allocate more later if necessary
|
||||
return ret;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
for (char* chunk : chunks) {
|
||||
delete[] chunk;
|
||||
}
|
||||
chunks.clear();
|
||||
}
|
||||
|
||||
~MixedArena() {
|
||||
clear();
|
||||
if (next.load()) delete next.load();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// A vector that allocates in an arena.
|
||||
//
|
||||
// TODO: specialize on the initial size of the array
|
||||
|
||||
template <typename SubType, typename T>
|
||||
class ArenaVectorBase {
|
||||
protected:
|
||||
T* data = nullptr;
|
||||
size_t usedElements = 0,
|
||||
allocatedElements = 0;
|
||||
|
||||
void reallocate(size_t size) {
|
||||
T* old = data;
|
||||
static_cast<SubType*>(this)->allocate(size);
|
||||
for (size_t i = 0; i < usedElements; i++) {
|
||||
data[i] = old[i];
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
struct Iterator;
|
||||
|
||||
T& operator[](size_t index) const {
|
||||
assert(index < usedElements);
|
||||
return data[index];
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return usedElements;
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
void resize(size_t size) {
|
||||
if (size > allocatedElements) {
|
||||
reallocate(size);
|
||||
}
|
||||
// construct new elements
|
||||
for (size_t i = usedElements; i < size; i++) {
|
||||
new (data + i) T();
|
||||
}
|
||||
usedElements = size;
|
||||
}
|
||||
|
||||
T& back() const {
|
||||
assert(usedElements > 0);
|
||||
return data[usedElements - 1];
|
||||
}
|
||||
|
||||
T& pop_back() {
|
||||
assert(usedElements > 0);
|
||||
usedElements--;
|
||||
return data[usedElements];
|
||||
}
|
||||
|
||||
void push_back(T item) {
|
||||
if (usedElements == allocatedElements) {
|
||||
reallocate((allocatedElements + 1) * 2); // TODO: optimize
|
||||
}
|
||||
data[usedElements] = item;
|
||||
usedElements++;
|
||||
}
|
||||
|
||||
void erase(Iterator start_it, Iterator end_it) {
|
||||
assert(start_it.parent == end_it.parent && start_it.parent == this);
|
||||
assert(start_it.index <= end_it.index && end_it.index <= usedElements);
|
||||
size_t size = end_it.index - start_it.index;
|
||||
for (size_t cur = start_it.index; cur + size < usedElements; ++cur) {
|
||||
data[cur] = data[cur + size];
|
||||
}
|
||||
usedElements -= size;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
usedElements = 0;
|
||||
}
|
||||
|
||||
void reserve(size_t size) {
|
||||
if (size > allocatedElements) {
|
||||
reallocate(size);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename ListType>
|
||||
void set(const ListType& list) {
|
||||
size_t size = list.size();
|
||||
if (allocatedElements < size) {
|
||||
static_cast<SubType*>(this)->allocate(size);
|
||||
}
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
data[i] = list[i];
|
||||
}
|
||||
usedElements = size;
|
||||
}
|
||||
|
||||
void operator=(SubType& other) {
|
||||
set(other);
|
||||
}
|
||||
|
||||
void swap(SubType& other) {
|
||||
data = other.data;
|
||||
usedElements = other.usedElements;
|
||||
allocatedElements = other.allocatedElements;
|
||||
|
||||
other.data = nullptr;
|
||||
other.usedElements = other.allocatedElements = 0;
|
||||
}
|
||||
|
||||
// iteration
|
||||
|
||||
struct Iterator {
|
||||
const SubType* parent;
|
||||
size_t index;
|
||||
|
||||
Iterator(const SubType* parent, size_t index) : parent(parent), index(index) {}
|
||||
|
||||
bool operator!=(const Iterator& other) const {
|
||||
return index != other.index || parent != other.parent;
|
||||
}
|
||||
|
||||
void operator++() {
|
||||
index++;
|
||||
}
|
||||
|
||||
Iterator& operator+=(int off) {
|
||||
index += off;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const Iterator operator+(int off) const {
|
||||
return Iterator(*this) += off;
|
||||
}
|
||||
|
||||
T& operator*() {
|
||||
return (*parent)[index];
|
||||
}
|
||||
};
|
||||
|
||||
Iterator begin() const {
|
||||
return Iterator(static_cast<const SubType*>(this), 0);
|
||||
}
|
||||
Iterator end() const {
|
||||
return Iterator(static_cast<const SubType*>(this), usedElements);
|
||||
}
|
||||
|
||||
void allocate(size_t size) {
|
||||
abort(); // must be implemented in children
|
||||
}
|
||||
};
|
||||
|
||||
// A vector that has an allocator for arena allocation
|
||||
//
|
||||
// TODO: consider not saving the allocator, but requiring it be
|
||||
// passed in when needed, would make this (and thus Blocks etc.
|
||||
// smaller)
|
||||
|
||||
template <typename T>
|
||||
class ArenaVector : public ArenaVectorBase<ArenaVector<T>, T> {
|
||||
private:
|
||||
MixedArena& allocator;
|
||||
|
||||
public:
|
||||
ArenaVector(MixedArena& allocator) : allocator(allocator) {}
|
||||
|
||||
ArenaVector(ArenaVector<T>&& other) : allocator(other.allocator) {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
void allocate(size_t size) {
|
||||
this->allocatedElements = size;
|
||||
this->data = static_cast<T*>(allocator.allocSpace(sizeof(T) * this->allocatedElements));
|
||||
}
|
||||
};
|
||||
|
||||
#endif // wasm_mixed_arena_h
|
@ -1,315 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_parsing_h
|
||||
#define wasm_parsing_h
|
||||
|
||||
#include <cmath>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "shared-constants.h"
|
||||
#include "asmjs/shared-constants.h"
|
||||
#include "mixed_arena.h"
|
||||
#include "support/colors.h"
|
||||
#include "support/utilities.h"
|
||||
#include "wasm.h"
|
||||
#include "wasm-printing.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
struct ParseException {
|
||||
std::string text;
|
||||
size_t line, col;
|
||||
|
||||
ParseException() : text("unknown parse error"), line(-1), col(-1) {}
|
||||
ParseException(std::string text) : text(text), line(-1), col(-1) {}
|
||||
ParseException(std::string text, size_t line, size_t col) : text(text), line(line), col(col) {}
|
||||
|
||||
void dump(std::ostream& o) {
|
||||
Colors::magenta(o);
|
||||
o << "[";
|
||||
Colors::red(o);
|
||||
o << "parse exception: ";
|
||||
Colors::green(o);
|
||||
o << text;
|
||||
if (line != size_t(-1)) {
|
||||
Colors::normal(o);
|
||||
o << " (at " << line << ":" << col << ")";
|
||||
}
|
||||
Colors::magenta(o);
|
||||
o << "]";
|
||||
Colors::normal(o);
|
||||
}
|
||||
};
|
||||
|
||||
struct MapParseException {
|
||||
std::string text;
|
||||
|
||||
MapParseException() : text("unknown parse error") {}
|
||||
MapParseException(std::string text) : text(text) {}
|
||||
|
||||
void dump(std::ostream& o) {
|
||||
Colors::magenta(o);
|
||||
o << "[";
|
||||
Colors::red(o);
|
||||
o << "map parse exception: ";
|
||||
Colors::green(o);
|
||||
o << text;
|
||||
Colors::magenta(o);
|
||||
o << "]";
|
||||
Colors::normal(o);
|
||||
}
|
||||
};
|
||||
|
||||
inline Expression* parseConst(cashew::IString s, WasmType type, MixedArena& allocator) {
|
||||
const char *str = s.str;
|
||||
auto ret = allocator.alloc<Const>();
|
||||
ret->type = type;
|
||||
if (isWasmTypeFloat(type)) {
|
||||
if (s == _INFINITY) {
|
||||
switch (type) {
|
||||
case f32: ret->value = Literal(std::numeric_limits<float>::infinity()); break;
|
||||
case f64: ret->value = Literal(std::numeric_limits<double>::infinity()); break;
|
||||
default: return nullptr;
|
||||
}
|
||||
//std::cerr << "make constant " << str << " ==> " << ret->value << '\n';
|
||||
return ret;
|
||||
}
|
||||
if (s == NEG_INFINITY) {
|
||||
switch (type) {
|
||||
case f32: ret->value = Literal(-std::numeric_limits<float>::infinity()); break;
|
||||
case f64: ret->value = Literal(-std::numeric_limits<double>::infinity()); break;
|
||||
default: return nullptr;
|
||||
}
|
||||
//std::cerr << "make constant " << str << " ==> " << ret->value << '\n';
|
||||
return ret;
|
||||
}
|
||||
if (s == _NAN) {
|
||||
switch (type) {
|
||||
case f32: ret->value = Literal(float(std::nan(""))); break;
|
||||
case f64: ret->value = Literal(double(std::nan(""))); break;
|
||||
default: return nullptr;
|
||||
}
|
||||
//std::cerr << "make constant " << str << " ==> " << ret->value << '\n';
|
||||
return ret;
|
||||
}
|
||||
bool negative = str[0] == '-';
|
||||
const char *positive = negative ? str + 1 : str;
|
||||
if (!negative) {
|
||||
if (positive[0] == '+') {
|
||||
positive++;
|
||||
}
|
||||
}
|
||||
if (positive[0] == 'n' && positive[1] == 'a' && positive[2] == 'n') {
|
||||
const char * modifier = positive[3] == ':' ? positive + 4 : nullptr;
|
||||
if (!(modifier ? positive[4] == '0' && positive[5] == 'x' : 1)) {
|
||||
throw ParseException("bad nan input");
|
||||
}
|
||||
switch (type) {
|
||||
case f32: {
|
||||
uint32_t pattern;
|
||||
if (modifier) {
|
||||
std::istringstream istr(modifier);
|
||||
istr >> std::hex >> pattern;
|
||||
pattern |= 0x7f800000U;
|
||||
} else {
|
||||
pattern = 0x7fc00000U;
|
||||
}
|
||||
if (negative) pattern |= 0x80000000U;
|
||||
if (!std::isnan(bit_cast<float>(pattern))) pattern |= 1U;
|
||||
ret->value = Literal(pattern).castToF32();
|
||||
break;
|
||||
}
|
||||
case f64: {
|
||||
uint64_t pattern;
|
||||
if (modifier) {
|
||||
std::istringstream istr(modifier);
|
||||
istr >> std::hex >> pattern;
|
||||
pattern |= 0x7ff0000000000000ULL;
|
||||
} else {
|
||||
pattern = 0x7ff8000000000000UL;
|
||||
}
|
||||
if (negative) pattern |= 0x8000000000000000ULL;
|
||||
if (!std::isnan(bit_cast<double>(pattern))) pattern |= 1ULL;
|
||||
ret->value = Literal(pattern).castToF64();
|
||||
break;
|
||||
}
|
||||
default: return nullptr;
|
||||
}
|
||||
//std::cerr << "make constant " << str << " ==> " << ret->value << '\n';
|
||||
return ret;
|
||||
}
|
||||
if (s == NEG_NAN) {
|
||||
switch (type) {
|
||||
case f32: ret->value = Literal(float(-std::nan(""))); break;
|
||||
case f64: ret->value = Literal(double(-std::nan(""))); break;
|
||||
default: return nullptr;
|
||||
}
|
||||
//std::cerr << "make constant " << str << " ==> " << ret->value << '\n';
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
switch (type) {
|
||||
case i32: {
|
||||
if ((str[0] == '0' && str[1] == 'x') || (str[0] == '-' && str[1] == '0' && str[2] == 'x')) {
|
||||
bool negative = str[0] == '-';
|
||||
if (negative) str++;
|
||||
std::istringstream istr(str);
|
||||
uint32_t temp;
|
||||
istr >> std::hex >> temp;
|
||||
ret->value = Literal(negative ? -temp : temp);
|
||||
} else {
|
||||
std::istringstream istr(str[0] == '-' ? str + 1 : str);
|
||||
uint32_t temp;
|
||||
istr >> temp;
|
||||
ret->value = Literal(str[0] == '-' ? -temp : temp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case i64: {
|
||||
if ((str[0] == '0' && str[1] == 'x') || (str[0] == '-' && str[1] == '0' && str[2] == 'x')) {
|
||||
bool negative = str[0] == '-';
|
||||
if (negative) str++;
|
||||
std::istringstream istr(str);
|
||||
uint64_t temp;
|
||||
istr >> std::hex >> temp;
|
||||
ret->value = Literal(negative ? -temp : temp);
|
||||
} else {
|
||||
std::istringstream istr(str[0] == '-' ? str + 1 : str);
|
||||
uint64_t temp;
|
||||
istr >> temp;
|
||||
ret->value = Literal(str[0] == '-' ? -temp : temp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case f32: {
|
||||
char *end;
|
||||
ret->value = Literal(strtof(str, &end));
|
||||
break;
|
||||
}
|
||||
case f64: {
|
||||
char *end;
|
||||
ret->value = Literal(strtod(str, &end));
|
||||
break;
|
||||
}
|
||||
default: return nullptr;
|
||||
}
|
||||
if (ret->value.type != type) {
|
||||
throw ParseException("parsed type does not match expected type");
|
||||
}
|
||||
//std::cerr << "make constant " << str << " ==> " << ret->value << '\n';
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Helper for parsers that may not have unique label names. This transforms
|
||||
// the names into unique ones, as required by Binaryen IR.
|
||||
struct UniqueNameMapper {
|
||||
std::vector<Name> labelStack;
|
||||
std::map<Name, std::vector<Name>> labelMappings; // name in source => stack of uniquified names
|
||||
std::map<Name, Name> reverseLabelMapping; // uniquified name => name in source
|
||||
|
||||
Index otherIndex = 0;
|
||||
|
||||
Name getPrefixedName(Name prefix) {
|
||||
if (reverseLabelMapping.find(prefix) == reverseLabelMapping.end()) return prefix;
|
||||
// make sure to return a unique name not already on the stack
|
||||
while (1) {
|
||||
Name ret = Name(prefix.str + std::to_string(otherIndex++));
|
||||
if (reverseLabelMapping.find(ret) == reverseLabelMapping.end()) return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// receives a source name. generates a unique name, pushes it, and returns it
|
||||
Name pushLabelName(Name sName) {
|
||||
Name name = getPrefixedName(sName);
|
||||
labelStack.push_back(name);
|
||||
labelMappings[sName].push_back(name);
|
||||
reverseLabelMapping[name] = sName;
|
||||
return name;
|
||||
}
|
||||
|
||||
void popLabelName(Name name) {
|
||||
assert(labelStack.back() == name);
|
||||
labelStack.pop_back();
|
||||
labelMappings[reverseLabelMapping[name]].pop_back();
|
||||
}
|
||||
|
||||
Name sourceToUnique(Name sName) {
|
||||
if (labelMappings.find(sName) == labelMappings.end()) {
|
||||
throw ParseException("bad label in sourceToUnique");
|
||||
}
|
||||
if (labelMappings[sName].empty()) {
|
||||
throw ParseException("use of popped label in sourceToUnique");
|
||||
}
|
||||
return labelMappings[sName].back();
|
||||
}
|
||||
|
||||
Name uniqueToSource(Name name) {
|
||||
if (reverseLabelMapping.find(name) == reverseLabelMapping.end()) {
|
||||
throw ParseException("label mismatch in uniqueToSource");
|
||||
}
|
||||
return reverseLabelMapping[name];
|
||||
}
|
||||
|
||||
void clear() {
|
||||
labelStack.clear();
|
||||
labelMappings.clear();
|
||||
reverseLabelMapping.clear();
|
||||
}
|
||||
|
||||
// Given an expression, ensures all names are unique
|
||||
static void uniquify(Expression* curr) {
|
||||
struct Walker : public ControlFlowWalker<Walker, Visitor<Walker>> {
|
||||
UniqueNameMapper mapper;
|
||||
|
||||
static void doPreVisitControlFlow(Walker* self, Expression** currp) {
|
||||
auto* curr = *currp;
|
||||
if (auto* block = curr->dynCast<Block>()) {
|
||||
if (block->name.is()) block->name = self->mapper.pushLabelName(block->name);
|
||||
} else if (auto* loop = curr->dynCast<Loop>()) {
|
||||
if (loop->name.is()) loop->name = self->mapper.pushLabelName(loop->name);
|
||||
}
|
||||
}
|
||||
static void doPostVisitControlFlow(Walker* self, Expression** currp) {
|
||||
auto* curr = *currp;
|
||||
if (auto* block = curr->dynCast<Block>()) {
|
||||
if (block->name.is()) self->mapper.popLabelName(block->name);
|
||||
} else if (auto* loop = curr->dynCast<Loop>()) {
|
||||
if (loop->name.is()) self->mapper.popLabelName(loop->name);
|
||||
}
|
||||
}
|
||||
|
||||
void visitBreak(Break *curr) {
|
||||
curr->name = mapper.sourceToUnique(curr->name);
|
||||
}
|
||||
void visitSwitch(Switch *curr) {
|
||||
for (auto& target : curr->targets) {
|
||||
target = mapper.sourceToUnique(target);
|
||||
}
|
||||
curr->default_ = mapper.sourceToUnique(curr->default_);
|
||||
}
|
||||
};
|
||||
|
||||
Walker walker;
|
||||
walker.walk(curr);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_parsing_h
|
@ -1,271 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef wasm_pass_h
|
||||
#define wasm_pass_h
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "wasm.h"
|
||||
#include "wasm-traversal.h"
|
||||
#include "mixed_arena.h"
|
||||
#include "support/utilities.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
class Pass;
|
||||
|
||||
//
|
||||
// Global registry of all passes in /passes/
|
||||
//
|
||||
struct PassRegistry {
|
||||
PassRegistry();
|
||||
|
||||
static PassRegistry* get();
|
||||
|
||||
typedef std::function<Pass* ()> Creator;
|
||||
|
||||
void registerPass(const char* name, const char *description, Creator create);
|
||||
Pass* createPass(std::string name);
|
||||
std::vector<std::string> getRegisteredNames();
|
||||
std::string getPassDescription(std::string name);
|
||||
|
||||
private:
|
||||
void registerPasses();
|
||||
|
||||
struct PassInfo {
|
||||
std::string description;
|
||||
Creator create;
|
||||
PassInfo() {}
|
||||
PassInfo(std::string description, Creator create) : description(description), create(create) {}
|
||||
};
|
||||
std::map<std::string, PassInfo> passInfos;
|
||||
};
|
||||
|
||||
struct PassOptions {
|
||||
bool debug = false; // run passes in debug mode, doing extra validation and timing checks
|
||||
bool validateGlobally = false; // when validating validate globally and not just locally
|
||||
int optimizeLevel = 0; // 0, 1, 2 correspond to -O0, -O1, -O2, etc.
|
||||
int shrinkLevel = 0; // 0, 1, 2 correspond to -O0, -Os, -Oz
|
||||
bool ignoreImplicitTraps = false; // optimize assuming things like div by 0, bad load/store, will not trap
|
||||
bool debugInfo = false; // whether to try to preserve debug info through, which are special calls
|
||||
FeatureSet features = Feature::MVP; // Which wasm features to accept, and be allowed to use
|
||||
};
|
||||
|
||||
//
|
||||
// Runs a set of passes, in order
|
||||
//
|
||||
struct PassRunner {
|
||||
Module* wasm;
|
||||
MixedArena* allocator;
|
||||
std::vector<Pass*> passes;
|
||||
PassOptions options;
|
||||
|
||||
PassRunner(Module* wasm) : wasm(wasm), allocator(&wasm->allocator) {}
|
||||
PassRunner(Module* wasm, PassOptions options) : wasm(wasm), allocator(&wasm->allocator), options(options) {}
|
||||
|
||||
// no copying, we control |passes|
|
||||
PassRunner(const PassRunner&) = delete;
|
||||
PassRunner& operator=(const PassRunner&) = delete;
|
||||
|
||||
void setDebug(bool debug_) {
|
||||
options.debug = debug_;
|
||||
options.validateGlobally = debug_; // validate everything by default if debugging
|
||||
}
|
||||
void setValidateGlobally(bool validate) {
|
||||
options.validateGlobally = validate;
|
||||
}
|
||||
void setFeatures(FeatureSet features) {
|
||||
options.features = features;
|
||||
}
|
||||
|
||||
void add(std::string passName) {
|
||||
auto pass = PassRegistry::get()->createPass(passName);
|
||||
if (!pass) Fatal() << "Could not find pass: " << passName << "\n";
|
||||
doAdd(pass);
|
||||
}
|
||||
|
||||
template<class P>
|
||||
void add() {
|
||||
doAdd(new P());
|
||||
}
|
||||
|
||||
template<class P, class Arg>
|
||||
void add(Arg arg){
|
||||
doAdd(new P(arg));
|
||||
}
|
||||
|
||||
// Adds the default set of optimization passes; this is
|
||||
// what -O does.
|
||||
void addDefaultOptimizationPasses();
|
||||
|
||||
// Adds the default optimization passes that work on
|
||||
// individual functions.
|
||||
void addDefaultFunctionOptimizationPasses();
|
||||
|
||||
// Adds the default optimization passes that work on
|
||||
// entire modules as a whole, and make sense to
|
||||
// run before function passes.
|
||||
void addDefaultGlobalOptimizationPrePasses();
|
||||
|
||||
// Adds the default optimization passes that work on
|
||||
// entire modules as a whole, and make sense to
|
||||
// run after function passes.
|
||||
void addDefaultGlobalOptimizationPostPasses();
|
||||
|
||||
// Run the passes on the module
|
||||
void run();
|
||||
|
||||
// Run the passes on a specific function
|
||||
void runFunction(Function* func);
|
||||
|
||||
// Get the last pass that was already executed of a certain type.
|
||||
template<class P>
|
||||
P* getLast();
|
||||
|
||||
~PassRunner();
|
||||
|
||||
// When running a pass runner within another pass runner, this
|
||||
// flag should be set. This influences how pass debugging works,
|
||||
// and may influence other things in the future too.
|
||||
void setIsNested(bool nested) {
|
||||
isNested = nested;
|
||||
}
|
||||
|
||||
// BINARYEN_PASS_DEBUG is a convenient commandline way to log out the toplevel passes, their times,
|
||||
// and validate between each pass.
|
||||
// (we don't recurse pass debug into sub-passes, as it doesn't help anyhow and
|
||||
// also is bad for e.g. printing which is a pass)
|
||||
// this method returns whether we are in passDebug mode, and which value:
|
||||
// 1: run pass by pass, validating in between
|
||||
// 2: also save the last pass, so it breakage happens we can print the last one
|
||||
// 3: also dump out byn-* files for each pass
|
||||
static int getPassDebug();
|
||||
|
||||
protected:
|
||||
bool isNested = false;
|
||||
|
||||
private:
|
||||
void doAdd(Pass* pass);
|
||||
|
||||
void runPassOnFunction(Pass* pass, Function* func);
|
||||
};
|
||||
|
||||
//
|
||||
// Core pass class
|
||||
//
|
||||
class Pass {
|
||||
public:
|
||||
virtual ~Pass() {};
|
||||
|
||||
// Override this to perform preparation work before the pass runs.
|
||||
// This will be called before the pass is run on a module.
|
||||
virtual void prepareToRun(PassRunner* runner, Module* module) {}
|
||||
|
||||
// Implement this with code to run the pass on the whole module
|
||||
virtual void run(PassRunner* runner, Module* module) {
|
||||
WASM_UNREACHABLE();
|
||||
}
|
||||
|
||||
// Implement this with code to run the pass on a single function, for
|
||||
// a function-parallel pass
|
||||
virtual void runFunction(PassRunner* runner, Module* module, Function* function) {
|
||||
WASM_UNREACHABLE();
|
||||
}
|
||||
|
||||
// Function parallelism. By default, passes are not run in parallel, but you
|
||||
// can override this method to say that functions are parallelizable. This
|
||||
// should always be safe *unless* you do something in the pass that makes it
|
||||
// not thread-safe; in other words, the Module and Function objects and
|
||||
// so forth are set up so that Functions can be processed in parallel, so
|
||||
// if you do not ad global state that could be raced on, your pass could be
|
||||
// function-parallel.
|
||||
//
|
||||
// Function-parallel passes create an instance of the Walker class per function.
|
||||
// That means that you can't rely on Walker object properties to persist across
|
||||
// your functions, and you can't expect a new object to be created for each
|
||||
// function either (which could be very inefficient).
|
||||
virtual bool isFunctionParallel() { return false; }
|
||||
|
||||
// This method is used to create instances per function for a function-parallel
|
||||
// pass. You may need to override this if you subclass a Walker, as otherwise
|
||||
// this will create the parent class.
|
||||
virtual Pass* create() { WASM_UNREACHABLE(); }
|
||||
|
||||
std::string name;
|
||||
|
||||
protected:
|
||||
Pass() {}
|
||||
Pass(Pass &) {}
|
||||
Pass &operator=(const Pass&) = delete;
|
||||
};
|
||||
|
||||
//
|
||||
// Core pass class that uses AST walking. This class can be parameterized by
|
||||
// different types of AST walkers.
|
||||
//
|
||||
template <typename WalkerType>
|
||||
class WalkerPass : public Pass, public WalkerType {
|
||||
PassRunner *runner;
|
||||
|
||||
protected:
|
||||
typedef WalkerPass<WalkerType> super;
|
||||
|
||||
public:
|
||||
void run(PassRunner* runner, Module* module) override {
|
||||
setPassRunner(runner);
|
||||
WalkerType::setModule(module);
|
||||
WalkerType::walkModule(module);
|
||||
}
|
||||
|
||||
void runFunction(PassRunner* runner, Module* module, Function* func) override {
|
||||
setPassRunner(runner);
|
||||
WalkerType::setModule(module);
|
||||
WalkerType::walkFunction(func);
|
||||
}
|
||||
|
||||
PassRunner* getPassRunner() {
|
||||
return runner;
|
||||
}
|
||||
|
||||
PassOptions& getPassOptions() {
|
||||
return runner->options;
|
||||
}
|
||||
|
||||
void setPassRunner(PassRunner* runner_) {
|
||||
runner = runner_;
|
||||
}
|
||||
};
|
||||
|
||||
// Standard passes. All passes in /passes/ are runnable from the shell,
|
||||
// but registering them here in addition allows them to communicate
|
||||
// e.g. through PassRunner::getLast
|
||||
|
||||
// Prints out a module
|
||||
class Printer : public Pass {
|
||||
protected:
|
||||
std::ostream& o;
|
||||
|
||||
public:
|
||||
Printer() : o(std::cout) {}
|
||||
Printer(std::ostream* o) : o(*o) {}
|
||||
|
||||
void run(PassRunner* runner, Module* module) override;
|
||||
};
|
||||
|
||||
} // namespace wasm
|
||||
|
||||
#endif // wasm_pass_h
|
@ -1,44 +0,0 @@
|
||||
SET(passes_SOURCES
|
||||
pass.cpp
|
||||
CoalesceLocals.cpp
|
||||
CodePushing.cpp
|
||||
CodeFolding.cpp
|
||||
ConstHoisting.cpp
|
||||
DeadCodeElimination.cpp
|
||||
DuplicateFunctionElimination.cpp
|
||||
ExtractFunction.cpp
|
||||
Flatten.cpp
|
||||
Inlining.cpp
|
||||
LegalizeJSInterface.cpp
|
||||
LocalCSE.cpp
|
||||
LogExecution.cpp
|
||||
I64ToI32Lowering.cpp
|
||||
InstrumentLocals.cpp
|
||||
InstrumentMemory.cpp
|
||||
MemoryPacking.cpp
|
||||
MergeBlocks.cpp
|
||||
Metrics.cpp
|
||||
NameList.cpp
|
||||
OptimizeInstructions.cpp
|
||||
PickLoadSigns.cpp
|
||||
PostEmscripten.cpp
|
||||
Precompute.cpp
|
||||
Print.cpp
|
||||
PrintCallGraph.cpp
|
||||
RelooperJumpThreading.cpp
|
||||
ReReloop.cpp
|
||||
RemoveImports.cpp
|
||||
RemoveMemory.cpp
|
||||
RemoveUnusedBrs.cpp
|
||||
RemoveUnusedNames.cpp
|
||||
RemoveUnusedModuleElements.cpp
|
||||
ReorderLocals.cpp
|
||||
ReorderFunctions.cpp
|
||||
TrapMode.cpp
|
||||
SafeHeap.cpp
|
||||
SimplifyLocals.cpp
|
||||
SSAify.cpp
|
||||
Untee.cpp
|
||||
Vacuum.cpp
|
||||
)
|
||||
ADD_LIBRARY(passes STATIC ${passes_SOURCES})
|
@ -1,828 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
//
|
||||
// Coalesce locals, in order to reduce the total number of locals. This
|
||||
// is similar to register allocation, however, there is never any
|
||||
// spilling, and there isn't a fixed number of locals.
|
||||
//
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "wasm.h"
|
||||
#include "pass.h"
|
||||
#include "ir/utils.h"
|
||||
#include "cfg/cfg-traversal.h"
|
||||
#include "wasm-builder.h"
|
||||
#include "support/learning.h"
|
||||
#include "support/permutations.h"
|
||||
#ifdef CFG_PROFILE
|
||||
#include "support/timing.h"
|
||||
#endif
|
||||
|
||||
namespace wasm {
|
||||
|
||||
// A set of locals. This is optimized for comparisons,
|
||||
// mergings, and iteration on elements, assuming that there
|
||||
// may be a great many potential elements but actual sets
|
||||
// may be fairly small. Specifically, we use a sorted
|
||||
// vector.
|
||||
struct LocalSet : std::vector<Index> {
|
||||
LocalSet() {}
|
||||
|
||||
LocalSet merge(const LocalSet& other) const {
|
||||
LocalSet ret;
|
||||
ret.resize(size() + other.size());
|
||||
Index i = 0, j = 0, t = 0;
|
||||
while (i < size() && j < other.size()) {
|
||||
auto left = (*this)[i];
|
||||
auto right = other[j];
|
||||
if (left < right) {
|
||||
ret[t++] = left;
|
||||
i++;
|
||||
} else if (left > right) {
|
||||
ret[t++] = right;
|
||||
j++;
|
||||
} else {
|
||||
ret[t++] = left;
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
while (i < size()) {
|
||||
ret[t++] = (*this)[i];
|
||||
i++;
|
||||
}
|
||||
while (j < other.size()) {
|
||||
ret[t++] = other[j];
|
||||
j++;
|
||||
}
|
||||
ret.resize(t);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void insert(Index x) {
|
||||
auto it = std::lower_bound(begin(), end(), x);
|
||||
if (it == end()) push_back(x);
|
||||
else if (*it > x) {
|
||||
Index i = it - begin();
|
||||
resize(size() + 1);
|
||||
std::move_backward(begin() + i, begin() + size() - 1, end());
|
||||
(*this)[i] = x;
|
||||
}
|
||||
}
|
||||
|
||||
bool erase(Index x) {
|
||||
auto it = std::lower_bound(begin(), end(), x);
|
||||
if (it != end() && *it == x) {
|
||||
std::move(it + 1, end(), it);
|
||||
resize(size() - 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool has(Index x) {
|
||||
auto it = std::lower_bound(begin(), end(), x);
|
||||
return it != end() && *it == x;
|
||||
}
|
||||
|
||||
void verify() const {
|
||||
for (Index i = 1; i < size(); i++) {
|
||||
assert((*this)[i - 1] < (*this)[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void dump(const char* str = nullptr) const {
|
||||
std::cout << "LocalSet " << (str ? str : "") << ": ";
|
||||
for (auto x : *this) std::cout << x << " ";
|
||||
std::cout << '\n';
|
||||
}
|
||||
};
|
||||
|
||||
// a liveness-relevant action
|
||||
struct Action {
|
||||
enum What {
|
||||
Get, Set
|
||||
};
|
||||
What what;
|
||||
Index index; // the local index read or written
|
||||
Expression** origin; // the origin
|
||||
bool effective; // whether a store is actually effective, i.e., may be read
|
||||
|
||||
Action(What what, Index index, Expression** origin) : what(what), index(index), origin(origin), effective(false) {}
|
||||
|
||||
bool isGet() { return what == Get; }
|
||||
bool isSet() { return what == Set; }
|
||||
};
|
||||
|
||||
// information about liveness in a basic block
|
||||
struct Liveness {
|
||||
LocalSet start, end; // live locals at the start and end
|
||||
std::vector<Action> actions; // actions occurring in this block
|
||||
|
||||
void dump(Function* func) {
|
||||
if (actions.empty()) return;
|
||||
std::cout << " actions:\n";
|
||||
for (auto& action : actions) {
|
||||
std::cout << " " << (action.isGet() ? "get" : "set") << " " << func->getLocalName(action.index) << "\n";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct CoalesceLocals : public WalkerPass<CFGWalker<CoalesceLocals, Visitor<CoalesceLocals>, Liveness>> {
|
||||
bool isFunctionParallel() override { return true; }
|
||||
|
||||
Pass* create() override { return new CoalesceLocals; }
|
||||
|
||||
Index numLocals;
|
||||
|
||||
// cfg traversal work
|
||||
|
||||
static void doVisitGetLocal(CoalesceLocals* self, Expression** currp) {
|
||||
auto* curr = (*currp)->cast<GetLocal>();
|
||||
// if in unreachable code, ignore
|
||||
if (!self->currBasicBlock) {
|
||||
*currp = Builder(*self->getModule()).replaceWithIdenticalType(curr);
|
||||
return;
|
||||
}
|
||||
self->currBasicBlock->contents.actions.emplace_back(Action::Get, curr->index, currp);
|
||||
}
|
||||
|
||||
static void doVisitSetLocal(CoalesceLocals* self, Expression** currp) {
|
||||
auto* curr = (*currp)->cast<SetLocal>();
|
||||
// if in unreachable code, we don't need the tee (but might need the value, if it has side effects)
|
||||
if (!self->currBasicBlock) {
|
||||
if (curr->isTee()) {
|
||||
*currp = curr->value;
|
||||
} else {
|
||||
*currp = Builder(*self->getModule()).makeDrop(curr->value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
self->currBasicBlock->contents.actions.emplace_back(Action::Set, curr->index, currp);
|
||||
// if this is a copy, note it
|
||||
if (auto* get = self->getCopy(curr)) {
|
||||
// add 2 units, so that backedge prioritization can decide ties, but not much more
|
||||
self->addCopy(curr->index, get->index);
|
||||
self->addCopy(curr->index, get->index);
|
||||
}
|
||||
}
|
||||
|
||||
// A simple copy is a set of a get. A more interesting copy
|
||||
// is a set of an if with a value, where one side a get.
|
||||
// That can happen when we create an if value in simplify-locals. TODO: recurse into
|
||||
// nested ifs, and block return values? Those cases are trickier, need to
|
||||
// count to see if worth it.
|
||||
// TODO: an if can have two copies
|
||||
GetLocal* getCopy(SetLocal* set) {
|
||||
if (auto* get = set->value->dynCast<GetLocal>()) return get;
|
||||
if (auto* iff = set->value->dynCast<If>()) {
|
||||
if (auto* get = iff->ifTrue->dynCast<GetLocal>()) return get;
|
||||
if (iff->ifFalse) {
|
||||
if (auto* get = iff->ifFalse->dynCast<GetLocal>()) return get;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// main entry point
|
||||
|
||||
void doWalkFunction(Function* func);
|
||||
|
||||
void increaseBackEdgePriorities();
|
||||
|
||||
void flowLiveness();
|
||||
|
||||
void calculateInterferences();
|
||||
|
||||
void calculateInterferences(const LocalSet& locals);
|
||||
|
||||
// merge starts of a list of blocks, adding new interferences as necessary. return
|
||||
// whether anything changed vs an old state (which indicates further processing is necessary).
|
||||
bool mergeStartsAndCheckChange(std::vector<BasicBlock*>& blocks, LocalSet& old, LocalSet& ret);
|
||||
|
||||
void scanLivenessThroughActions(std::vector<Action>& actions, LocalSet& live);
|
||||
|
||||
void pickIndicesFromOrder(std::vector<Index>& order, std::vector<Index>& indices);
|
||||
void pickIndicesFromOrder(std::vector<Index>& order, std::vector<Index>& indices, Index& removedCopies);
|
||||
|
||||
virtual void pickIndices(std::vector<Index>& indices); // returns a vector of oldIndex => newIndex
|
||||
|
||||
void applyIndices(std::vector<Index>& indices, Expression* root);
|
||||
|
||||
// interference state
|
||||
|
||||
std::vector<bool> interferences; // canonicalized - accesses should check (low, high)
|
||||
std::unordered_set<BasicBlock*> liveBlocks;
|
||||
|
||||
void interfere(Index i, Index j) {
|
||||
if (i == j) return;
|
||||
interferences[std::min(i, j) * numLocals + std::max(i, j)] = 1;
|
||||
}
|
||||
|
||||
void interfereLowHigh(Index low, Index high) { // optimized version where you know that low < high
|
||||
assert(low < high);
|
||||
interferences[low * numLocals + high] = 1;
|
||||
}
|
||||
|
||||
bool interferes(Index i, Index j) {
|
||||
return interferences[std::min(i, j) * numLocals + std::max(i, j)];
|
||||
}
|
||||
|
||||
// copying state
|
||||
|
||||
std::vector<uint8_t> copies; // canonicalized - accesses should check (low, high) TODO: use a map for high N, as this tends to be sparse? or don't look at copies at all for big N?
|
||||
std::vector<Index> totalCopies; // total # of copies for each local, with all others
|
||||
|
||||
void addCopy(Index i, Index j) {
|
||||
auto k = std::min(i, j) * numLocals + std::max(i, j);
|
||||
copies[k] = std::min(copies[k], uint8_t(254)) + 1;
|
||||
totalCopies[i]++;
|
||||
totalCopies[j]++;
|
||||
}
|
||||
|
||||
uint8_t getCopies(Index i, Index j) {
|
||||
return copies[std::min(i, j) * numLocals + std::max(i, j)];
|
||||
}
|
||||
};
|
||||
|
||||
void CoalesceLocals::doWalkFunction(Function* func) {
|
||||
numLocals = func->getNumLocals();
|
||||
copies.resize(numLocals * numLocals);
|
||||
std::fill(copies.begin(), copies.end(), 0);
|
||||
totalCopies.resize(numLocals);
|
||||
std::fill(totalCopies.begin(), totalCopies.end(), 0);
|
||||
// collect initial liveness info
|
||||
super::doWalkFunction(func);
|
||||
// ignore links to dead blocks, so they don't confuse us and we can see their stores are all ineffective
|
||||
liveBlocks = findLiveBlocks();
|
||||
unlinkDeadBlocks(liveBlocks);
|
||||
// increase the cost of costly backedges
|
||||
increaseBackEdgePriorities();
|
||||
#ifdef CFG_DEBUG
|
||||
dumpCFG("the cfg");
|
||||
#endif
|
||||
// flow liveness across blocks
|
||||
#ifdef CFG_PROFILE
|
||||
static Timer timer("flow");
|
||||
timer.start();
|
||||
#endif
|
||||
flowLiveness();
|
||||
#ifdef CFG_PROFILE
|
||||
timer.stop();
|
||||
timer.dump();
|
||||
#endif
|
||||
// use liveness to find interference
|
||||
calculateInterferences();
|
||||
// pick new indices
|
||||
std::vector<Index> indices;
|
||||
pickIndices(indices);
|
||||
// apply indices
|
||||
applyIndices(indices, func->body);
|
||||
}
|
||||
|
||||
// A copy on a backedge can be especially costly, forcing us to branch just to do that copy.
|
||||
// Add weight to such copies, so we prioritize getting rid of them.
|
||||
void CoalesceLocals::increaseBackEdgePriorities() {
|
||||
for (auto* loopTop : loopTops) {
|
||||
// ignore the first edge, it is the initial entry, we just want backedges
|
||||
auto& in = loopTop->in;
|
||||
for (Index i = 1; i < in.size(); i++) {
|
||||
auto* arrivingBlock = in[i];
|
||||
if (arrivingBlock->out.size() > 1) continue; // we just want unconditional branches to the loop top, true phi fragments
|
||||
for (auto& action : arrivingBlock->contents.actions) {
|
||||
if (action.what == Action::Set) {
|
||||
auto* set = (*action.origin)->cast<SetLocal>();
|
||||
if (auto* get = getCopy(set)) {
|
||||
// this is indeed a copy, add to the cost (default cost is 2, so this adds 50%, and can mostly break ties)
|
||||
addCopy(set->index, get->index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CoalesceLocals::flowLiveness() {
|
||||
interferences.resize(numLocals * numLocals);
|
||||
std::fill(interferences.begin(), interferences.end(), 0);
|
||||
// keep working while stuff is flowing
|
||||
std::unordered_set<BasicBlock*> queue;
|
||||
for (auto& curr : basicBlocks) {
|
||||
if (liveBlocks.count(curr.get()) == 0) continue; // ignore dead blocks
|
||||
queue.insert(curr.get());
|
||||
// do the first scan through the block, starting with nothing live at the end, and updating the liveness at the start
|
||||
scanLivenessThroughActions(curr->contents.actions, curr->contents.start);
|
||||
}
|
||||
// at every point in time, we assume we already noted interferences between things already known alive at the end, and scanned back through the block using that
|
||||
while (queue.size() > 0) {
|
||||
auto iter = queue.begin();
|
||||
auto* curr = *iter;
|
||||
queue.erase(iter);
|
||||
LocalSet live;
|
||||
if (!mergeStartsAndCheckChange(curr->out, curr->contents.end, live)) continue;
|
||||
#ifdef CFG_DEBUG
|
||||
std::cout << "change noticed at end of " << debugIds[curr] << " from " << curr->contents.end.size() << " to " << live.size() << " (out of " << numLocals << ")\n";
|
||||
#endif
|
||||
assert(curr->contents.end.size() < live.size());
|
||||
curr->contents.end = live;
|
||||
scanLivenessThroughActions(curr->contents.actions, live);
|
||||
// liveness is now calculated at the start. if something
|
||||
// changed, all predecessor blocks need recomputation
|
||||
if (curr->contents.start == live) continue;
|
||||
#ifdef CFG_DEBUG
|
||||
std::cout << "change noticed at start of " << debugIds[curr] << " from " << curr->contents.start.size() << " to " << live.size() << ", more work to do\n";
|
||||
#endif
|
||||
assert(curr->contents.start.size() < live.size());
|
||||
curr->contents.start = live;
|
||||
for (auto* in : curr->in) {
|
||||
queue.insert(in);
|
||||
}
|
||||
}
|
||||
#ifdef CFG_DEBUG
|
||||
std::hash<std::vector<bool>> hasher;
|
||||
std::cout << getFunction()->name << ": interference hash: " << hasher(*(std::vector<bool>*)&interferences) << "\n";
|
||||
for (Index i = 0; i < numLocals; i++) {
|
||||
std::cout << "int for " << getFunction()->getLocalName(i) << " [" << i << "]: ";
|
||||
for (Index j = 0; j < numLocals; j++) {
|
||||
if (interferes(i, j)) std::cout << getFunction()->getLocalName(j) << " ";
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// merge starts of a list of blocks. return
|
||||
// whether anything changed vs an old state (which indicates further processing is necessary).
|
||||
bool CoalesceLocals::mergeStartsAndCheckChange(std::vector<BasicBlock*>& blocks, LocalSet& old, LocalSet& ret) {
|
||||
if (blocks.size() == 0) return false;
|
||||
ret = blocks[0]->contents.start;
|
||||
if (blocks.size() > 1) {
|
||||
// more than one, so we must merge
|
||||
for (Index i = 1; i < blocks.size(); i++) {
|
||||
ret = ret.merge(blocks[i]->contents.start);
|
||||
}
|
||||
}
|
||||
return old != ret;
|
||||
}
|
||||
|
||||
void CoalesceLocals::scanLivenessThroughActions(std::vector<Action>& actions, LocalSet& live) {
|
||||
// move towards the front
|
||||
for (int i = int(actions.size()) - 1; i >= 0; i--) {
|
||||
auto& action = actions[i];
|
||||
if (action.isGet()) {
|
||||
live.insert(action.index);
|
||||
} else {
|
||||
live.erase(action.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CoalesceLocals::calculateInterferences() {
|
||||
for (auto& curr : basicBlocks) {
|
||||
if (liveBlocks.count(curr.get()) == 0) continue; // ignore dead blocks
|
||||
// everything coming in might interfere, as it might come from a different block
|
||||
auto live = curr->contents.end;
|
||||
calculateInterferences(live);
|
||||
// scan through the block itself
|
||||
auto& actions = curr->contents.actions;
|
||||
for (int i = int(actions.size()) - 1; i >= 0; i--) {
|
||||
auto& action = actions[i];
|
||||
auto index = action.index;
|
||||
if (action.isGet()) {
|
||||
// new live local, interferes with all the rest
|
||||
live.insert(index);
|
||||
for (auto i : live) {
|
||||
interfere(i, index);
|
||||
}
|
||||
} else {
|
||||
if (live.erase(index)) {
|
||||
action.effective = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Params have a value on entry, so mark them as live, as variables
|
||||
// live at the entry expect their zero-init value.
|
||||
LocalSet start = entry->contents.start;
|
||||
auto numParams = getFunction()->getNumParams();
|
||||
for (Index i = 0; i < numParams; i++) {
|
||||
start.insert(i);
|
||||
}
|
||||
calculateInterferences(start);
|
||||
}
|
||||
|
||||
void CoalesceLocals::calculateInterferences(const LocalSet& locals) {
|
||||
Index size = locals.size();
|
||||
for (Index i = 0; i < size; i++) {
|
||||
for (Index j = i + 1; j < size; j++) {
|
||||
interfereLowHigh(locals[i], locals[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Indices decision making
|
||||
|
||||
void CoalesceLocals::pickIndicesFromOrder(std::vector<Index>& order, std::vector<Index>& indices) {
|
||||
Index removedCopies;
|
||||
pickIndicesFromOrder(order, indices, removedCopies);
|
||||
}
|
||||
|
||||
void CoalesceLocals::pickIndicesFromOrder(std::vector<Index>& order, std::vector<Index>& indices, Index& removedCopies) {
|
||||
// mostly-simple greedy coloring
|
||||
#if CFG_DEBUG
|
||||
std::cerr << "\npickIndicesFromOrder on " << getFunction()->name << '\n';
|
||||
std::cerr << getFunction()->body << '\n';
|
||||
std::cerr << "order:\n";
|
||||
for (auto i : order) std::cerr << i << ' ';
|
||||
std::cerr << '\n';
|
||||
std::cerr << "interferences:\n";
|
||||
for (Index i = 0; i < numLocals; i++) {
|
||||
for (Index j = 0; j < i + 1; j++) {
|
||||
std::cerr << " ";
|
||||
}
|
||||
for (Index j = i + 1; j < numLocals; j++) {
|
||||
std::cerr << int(interferes(i, j)) << ' ';
|
||||
}
|
||||
std::cerr << " : $" << i << '\n';
|
||||
}
|
||||
std::cerr << "copies:\n";
|
||||
for (Index i = 0; i < numLocals; i++) {
|
||||
for (Index j = 0; j < i + 1; j++) {
|
||||
std::cerr << " ";
|
||||
}
|
||||
for (Index j = i + 1; j < numLocals; j++) {
|
||||
std::cerr << int(getCopies(i, j)) << ' ';
|
||||
}
|
||||
std::cerr << " : $" << i << '\n';
|
||||
}
|
||||
std::cerr << "total copies:\n";
|
||||
for (Index i = 0; i < numLocals; i++) {
|
||||
std::cerr << " $" << i << ": " << totalCopies[i] << '\n';
|
||||
}
|
||||
#endif
|
||||
// TODO: take into account distribution (99-1 is better than 50-50 with two registers, for gzip)
|
||||
std::vector<WasmType> types;
|
||||
std::vector<bool> newInterferences; // new index * numLocals => list of all interferences of locals merged to it
|
||||
std::vector<uint8_t> newCopies; // new index * numLocals => list of all copies of locals merged to it
|
||||
indices.resize(numLocals);
|
||||
types.resize(numLocals);
|
||||
newInterferences.resize(numLocals * numLocals);
|
||||
std::fill(newInterferences.begin(), newInterferences.end(), 0);
|
||||
auto numParams = getFunction()->getNumParams();
|
||||
newCopies.resize(numParams * numLocals); // start with enough room for the params
|
||||
std::fill(newCopies.begin(), newCopies.end(), 0);
|
||||
Index nextFree = 0;
|
||||
removedCopies = 0;
|
||||
// we can't reorder parameters, they are fixed in order, and cannot coalesce
|
||||
Index i = 0;
|
||||
for (; i < numParams; i++) {
|
||||
assert(order[i] == i); // order must leave the params in place
|
||||
indices[i] = i;
|
||||
types[i] = getFunction()->getLocalType(i);
|
||||
for (Index j = numParams; j < numLocals; j++) {
|
||||
newInterferences[numLocals * i + j] = interferes(i, j);
|
||||
newCopies[numLocals * i + j] = getCopies(i, j);
|
||||
}
|
||||
nextFree++;
|
||||
}
|
||||
for (; i < numLocals; i++) {
|
||||
Index actual = order[i];
|
||||
Index found = -1;
|
||||
uint8_t foundCopies = -1;
|
||||
for (Index j = 0; j < nextFree; j++) {
|
||||
if (!newInterferences[j * numLocals + actual] && getFunction()->getLocalType(actual) == types[j]) {
|
||||
// this does not interfere, so it might be what we want. but pick the one eliminating the most copies
|
||||
// (we could stop looking forward when there are no more items that have copies anyhow, but it doesn't seem to help)
|
||||
auto currCopies = newCopies[j * numLocals + actual];
|
||||
if (found == Index(-1) || currCopies > foundCopies) {
|
||||
indices[actual] = found = j;
|
||||
foundCopies = currCopies;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found == Index(-1)) {
|
||||
indices[actual] = found = nextFree;
|
||||
types[found] = getFunction()->getLocalType(actual);
|
||||
nextFree++;
|
||||
removedCopies += getCopies(found, actual);
|
||||
newCopies.resize(nextFree * numLocals);
|
||||
} else {
|
||||
removedCopies += foundCopies;
|
||||
}
|
||||
#if CFG_DEBUG
|
||||
std::cerr << "set local $" << actual << " to $" << found << '\n';
|
||||
#endif
|
||||
// merge new interferences and copies for the new index
|
||||
for (Index k = i + 1; k < numLocals; k++) {
|
||||
auto j = order[k]; // go in the order, we only need to update for those we will see later
|
||||
newInterferences[found * numLocals + j] = newInterferences[found * numLocals + j] | interferes(actual, j);
|
||||
newCopies[found * numLocals + j] += getCopies(actual, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// given a baseline order, adjust it based on an important order of priorities (higher values
|
||||
// are higher priority). The priorities take precedence, unless they are equal and then
|
||||
// the original order should be kept.
|
||||
std::vector<Index> adjustOrderByPriorities(std::vector<Index>& baseline, std::vector<Index>& priorities) {
|
||||
std::vector<Index> ret = baseline;
|
||||
std::vector<Index> reversed = makeReversed(baseline);
|
||||
std::sort(ret.begin(), ret.end(), [&priorities, &reversed](Index x, Index y) {
|
||||
return priorities[x] > priorities[y] || (priorities[x] == priorities[y] && reversed[x] < reversed[y]);
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
void CoalesceLocals::pickIndices(std::vector<Index>& indices) {
|
||||
if (numLocals == 0) return;
|
||||
if (numLocals == 1) {
|
||||
indices.push_back(0);
|
||||
return;
|
||||
}
|
||||
if (getFunction()->getNumVars() <= 1) {
|
||||
// nothing to think about here, since we can't reorder params
|
||||
indices = makeIdentity(numLocals);
|
||||
return;
|
||||
}
|
||||
// take into account total copies. but we must keep params in place, so give them max priority
|
||||
auto adjustedTotalCopies = totalCopies;
|
||||
auto numParams = getFunction()->getNumParams();
|
||||
for (Index i = 0; i < numParams; i++) {
|
||||
adjustedTotalCopies[i] = std::numeric_limits<Index>::max();
|
||||
}
|
||||
// first try the natural order. this is less arbitrary than it seems, as the program
|
||||
// may have a natural order of locals inherent in it.
|
||||
auto order = makeIdentity(numLocals);
|
||||
order = adjustOrderByPriorities(order, adjustedTotalCopies);
|
||||
Index removedCopies;
|
||||
pickIndicesFromOrder(order, indices, removedCopies);
|
||||
auto maxIndex = *std::max_element(indices.begin(), indices.end());
|
||||
// next try the reverse order. this both gives us another chance at something good,
|
||||
// and also the very naturalness of the simple order may be quite suboptimal
|
||||
setIdentity(order);
|
||||
for (Index i = numParams; i < numLocals; i++) {
|
||||
order[i] = numParams + numLocals - 1 - i;
|
||||
}
|
||||
order = adjustOrderByPriorities(order, adjustedTotalCopies);
|
||||
std::vector<Index> reverseIndices;
|
||||
Index reverseRemovedCopies;
|
||||
pickIndicesFromOrder(order, reverseIndices, reverseRemovedCopies);
|
||||
auto reverseMaxIndex = *std::max_element(reverseIndices.begin(), reverseIndices.end());
|
||||
// prefer to remove copies foremost, as it matters more for code size (minus gzip), and
|
||||
// improves throughput.
|
||||
if (reverseRemovedCopies > removedCopies || (reverseRemovedCopies == removedCopies && reverseMaxIndex < maxIndex)) {
|
||||
indices.swap(reverseIndices);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a copy from a set of an if, where one if arm is a get of the same set
|
||||
static void removeIfCopy(Expression** origin, SetLocal* set, If* iff, Expression*& copy, Expression*& other, Module* module) {
|
||||
// replace the origin with the if, and sink the set into the other non-copying arm
|
||||
bool tee = set->isTee();
|
||||
*origin = iff;
|
||||
set->value = other;
|
||||
set->finalize();
|
||||
other = set;
|
||||
// if this is not a tee, then we can get rid of the copy in that arm
|
||||
if (!tee) {
|
||||
// we don't need the copy at all
|
||||
copy = nullptr;
|
||||
if (!iff->ifTrue) {
|
||||
Builder(*module).flip(iff);
|
||||
}
|
||||
iff->finalize();
|
||||
}
|
||||
}
|
||||
|
||||
void CoalesceLocals::applyIndices(std::vector<Index>& indices, Expression* root) {
|
||||
assert(indices.size() == numLocals);
|
||||
for (auto& curr : basicBlocks) {
|
||||
auto& actions = curr->contents.actions;
|
||||
for (auto& action : actions) {
|
||||
if (action.isGet()) {
|
||||
auto* get = (*action.origin)->cast<GetLocal>();
|
||||
get->index = indices[get->index];
|
||||
} else {
|
||||
auto* set = (*action.origin)->cast<SetLocal>();
|
||||
set->index = indices[set->index];
|
||||
// in addition, we can optimize out redundant copies and ineffective sets
|
||||
GetLocal* get;
|
||||
if ((get = set->value->dynCast<GetLocal>()) && get->index == set->index) {
|
||||
if (set->isTee()) {
|
||||
*action.origin = get;
|
||||
} else {
|
||||
ExpressionManipulator::nop(set);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// remove ineffective actions
|
||||
if (!action.effective) {
|
||||
*action.origin = set->value; // value may have no side effects, further optimizations can eliminate it
|
||||
if (!set->isTee()) {
|
||||
// we need to drop it
|
||||
Drop* drop = ExpressionManipulator::convert<SetLocal, Drop>(set);
|
||||
drop->value = *action.origin;
|
||||
*action.origin = drop;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (auto* iff = set->value->dynCast<If>()) {
|
||||
if (auto* get = iff->ifTrue->dynCast<GetLocal>()) {
|
||||
if (get->index == set->index) {
|
||||
removeIfCopy(action.origin, set, iff, iff->ifTrue, iff->ifFalse, getModule());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (auto* get = iff->ifFalse->dynCast<GetLocal>()) {
|
||||
if (get->index == set->index) {
|
||||
removeIfCopy(action.origin, set, iff, iff->ifFalse, iff->ifTrue, getModule());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// update type list
|
||||
auto numParams = getFunction()->getNumParams();
|
||||
Index newNumLocals = 0;
|
||||
for (auto index : indices) {
|
||||
newNumLocals = std::max(newNumLocals, index + 1);
|
||||
}
|
||||
auto oldVars = getFunction()->vars;
|
||||
getFunction()->vars.resize(newNumLocals - numParams);
|
||||
for (Index index = numParams; index < numLocals; index++) {
|
||||
Index newIndex = indices[index];
|
||||
if (newIndex >= numParams) {
|
||||
getFunction()->vars[newIndex - numParams] = oldVars[index - numParams];
|
||||
}
|
||||
}
|
||||
// names are gone
|
||||
getFunction()->localNames.clear();
|
||||
getFunction()->localIndices.clear();
|
||||
}
|
||||
|
||||
struct CoalesceLocalsWithLearning : public CoalesceLocals {
|
||||
virtual Pass* create() override { return new CoalesceLocalsWithLearning; }
|
||||
|
||||
virtual void pickIndices(std::vector<Index>& indices) override;
|
||||
};
|
||||
|
||||
void CoalesceLocalsWithLearning::pickIndices(std::vector<Index>& indices) {
|
||||
if (getFunction()->getNumVars() <= 1) {
|
||||
// nothing to think about here
|
||||
CoalesceLocals::pickIndices(indices);
|
||||
return;
|
||||
}
|
||||
|
||||
struct Order : public std::vector<Index> {
|
||||
void setFitness(double f) { fitness = f; }
|
||||
double getFitness() { return fitness; }
|
||||
void dump(std::string text) {
|
||||
std::cout << text + ": ( ";
|
||||
for (Index i = 0; i < size(); i++) std::cout << (*this)[i] << " ";
|
||||
std::cout << ")\n";
|
||||
std::cout << "of quality: " << getFitness() << "\n";
|
||||
}
|
||||
private:
|
||||
double fitness;
|
||||
};
|
||||
|
||||
struct Generator {
|
||||
Generator(CoalesceLocalsWithLearning* parent) : parent(parent), noise(42) {}
|
||||
|
||||
void calculateFitness(Order* order) {
|
||||
// apply the order
|
||||
std::vector<Index> indices; // the phenotype
|
||||
Index removedCopies;
|
||||
parent->pickIndicesFromOrder(*order, indices, removedCopies);
|
||||
auto maxIndex = *std::max_element(indices.begin(), indices.end());
|
||||
assert(maxIndex <= parent->numLocals);
|
||||
// main part of fitness is the number of locals
|
||||
double fitness = parent->numLocals - maxIndex; // higher fitness is better
|
||||
// secondarily, it is nice to not reorder locals unnecessarily
|
||||
double fragment = 1.0 / (2.0 * parent->numLocals);
|
||||
for (Index i = 0; i < parent->numLocals; i++) {
|
||||
if ((*order)[i] == i) fitness += fragment; // boost for each that wasn't moved
|
||||
}
|
||||
fitness = (100 * fitness) + removedCopies; // removing copies is a secondary concern
|
||||
order->setFitness(fitness);
|
||||
}
|
||||
|
||||
Order* makeRandom() {
|
||||
auto* ret = new Order;
|
||||
ret->resize(parent->numLocals);
|
||||
for (Index i = 0; i < parent->numLocals; i++) {
|
||||
(*ret)[i] = i;
|
||||
}
|
||||
if (first) {
|
||||
// as the first guess, use the natural order. this is not arbitrary for two reasons.
|
||||
// first, there may be an inherent order in the input (frequent indices are lower,
|
||||
// etc.). second, by ensuring we start with the natural order, we ensure we are at
|
||||
// least as good as the non-learning variant.
|
||||
// TODO: use ::pickIndices from the parent, so we literally get the simpler approach
|
||||
// as our first option
|
||||
first = false;
|
||||
} else {
|
||||
// leave params alone, shuffle the rest
|
||||
std::shuffle(ret->begin() + parent->getFunction()->getNumParams(), ret->end(), noise);
|
||||
}
|
||||
calculateFitness(ret);
|
||||
#ifdef CFG_LEARN_DEBUG
|
||||
order->dump("new rando");
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
Order* makeMixture(Order* left, Order* right) {
|
||||
// perturb left using right. this is useful since
|
||||
// we don't care about absolute locations, relative ones matter more,
|
||||
// and a true merge of two vectors could obscure that (e.g.
|
||||
// a.......... and ..........a would merge a into the middle, for no
|
||||
// reason), and cause a lot of unnecessary noise
|
||||
Index size = left->size();
|
||||
Order reverseRight; // reverseRight[x] is the index of x in right
|
||||
reverseRight.resize(size);
|
||||
for (Index i = 0; i < size; i++) {
|
||||
reverseRight[(*right)[i]] = i;
|
||||
}
|
||||
auto* ret = new Order;
|
||||
*ret = *left;
|
||||
assert(size >= 1);
|
||||
for (Index i = parent->getFunction()->getNumParams(); i < size - 1; i++) {
|
||||
// if (i, i + 1) is in reverse order in right, flip them
|
||||
if (reverseRight[(*ret)[i]] > reverseRight[(*ret)[i + 1]]) {
|
||||
std::swap((*ret)[i], (*ret)[i + 1]);
|
||||
i++; // if we don't skip, we might end up pushing an element all the way to the end, which is not very perturbation-y
|
||||
}
|
||||
}
|
||||
calculateFitness(ret);
|
||||
#ifdef CFG_LEARN_DEBUG
|
||||
ret->dump("new mixture");
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
CoalesceLocalsWithLearning* parent;
|
||||
std::mt19937 noise;
|
||||
bool first = true;
|
||||
};
|
||||
|
||||
#ifdef CFG_LEARN_DEBUG
|
||||
std::cout << "[learning for " << getFunction()->name << "]\n";
|
||||
#endif
|
||||
auto numVars = this->getFunction()->getNumVars();
|
||||
const int GENERATION_SIZE = std::min(Index(numVars * (numVars - 1)), Index(20));
|
||||
Generator generator(this);
|
||||
GeneticLearner<Order, double, Generator> learner(generator, GENERATION_SIZE);
|
||||
#ifdef CFG_LEARN_DEBUG
|
||||
learner.getBest()->dump("first best");
|
||||
#endif
|
||||
// keep working while we see improvement
|
||||
auto oldBest = learner.getBest()->getFitness();
|
||||
while (1) {
|
||||
learner.runGeneration();
|
||||
auto newBest = learner.getBest()->getFitness();
|
||||
if (newBest == oldBest) break; // unlikely we can improve
|
||||
oldBest = newBest;
|
||||
#ifdef CFG_LEARN_DEBUG
|
||||
learner.getBest()->dump("current best");
|
||||
#endif
|
||||
}
|
||||
#ifdef CFG_LEARN_DEBUG
|
||||
learner.getBest()->dump("the best");
|
||||
#endif
|
||||
this->pickIndicesFromOrder(*learner.getBest(), indices); // TODO: cache indices in Orders, at the cost of more memory?
|
||||
}
|
||||
|
||||
// declare passes
|
||||
|
||||
Pass *createCoalesceLocalsPass() {
|
||||
return new CoalesceLocals();
|
||||
}
|
||||
|
||||
Pass *createCoalesceLocalsWithLearningPass() {
|
||||
return new CoalesceLocalsWithLearning();
|
||||
}
|
||||
|
||||
} // namespace wasm
|
@ -1,616 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//
|
||||
// Folds duplicate code together, saving space.
|
||||
//
|
||||
// We fold tails of code where they merge and moving the code
|
||||
// to the merge point is helpful. There are two cases here: (1) expressions,
|
||||
// in which we merge to right after the expression itself, in these cases:
|
||||
// * blocks, we merge the fallthrough + the breaks
|
||||
// * if-else, we merge the arms
|
||||
// and (2) the function body as a whole, in which we can merge returns or
|
||||
// unreachables, putting the merged code at the end of the function body.
|
||||
//
|
||||
// For example, with an if-else, we might merge this:
|
||||
// (if (condition)
|
||||
// (block
|
||||
// A
|
||||
// C
|
||||
// )
|
||||
// (block
|
||||
// B
|
||||
// C
|
||||
// )
|
||||
// )
|
||||
// to
|
||||
// (if (condition)
|
||||
// (block
|
||||
// A
|
||||
// )
|
||||
// (block
|
||||
// B
|
||||
// )
|
||||
// )
|
||||
// C
|
||||
//
|
||||
// Note that the merged code, C in the example above, can be anything,
|
||||
// including code with control flow. If C is identical in all the locations,
|
||||
// then it must be safe to merge (if it contains a branch to something
|
||||
// higher up, then since our branch target names are unique, it must be
|
||||
// to the same thing, and after merging it can still reach it).
|
||||
//
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#include "wasm.h"
|
||||
#include "pass.h"
|
||||
#include "wasm-builder.h"
|
||||
#include "ir/utils.h"
|
||||
#include "ir/branch-utils.h"
|
||||
#include "ir/effects.h"
|
||||
#include "ir/label-utils.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
static const Index WORTH_ADDING_BLOCK_TO_REMOVE_THIS_MUCH = 3;
|
||||
|
||||
struct ExpressionMarker : public PostWalker<ExpressionMarker, UnifiedExpressionVisitor<ExpressionMarker>> {
|
||||
std::set<Expression*>& marked;
|
||||
|
||||
ExpressionMarker(std::set<Expression*>& marked, Expression* expr) : marked(marked) {
|
||||
walk(expr);
|
||||
}
|
||||
|
||||
void visitExpression(Expression* expr) {
|
||||
marked.insert(expr);
|
||||
}
|
||||
};
|
||||
|
||||
struct CodeFolding : public WalkerPass<ControlFlowWalker<CodeFolding>> {
|
||||
bool isFunctionParallel() override { return true; }
|
||||
|
||||
Pass* create() override { return new CodeFolding; }
|
||||
|
||||
// information about a "tail" - code that reaches a point that we can
|
||||
// merge (e.g., a branch and some code leading up to it)
|
||||
struct Tail {
|
||||
Expression* expr; // nullptr if this is a fallthrough
|
||||
Block* block; // the enclosing block of code we hope to merge at its tail
|
||||
Expression** pointer; // for an expr with no parent block, the location it is at, so we can replace it
|
||||
|
||||
// For a fallthrough
|
||||
Tail(Block* block) : expr(nullptr), block(block), pointer(nullptr) {}
|
||||
// For a break
|
||||
Tail(Expression* expr, Block* block) : expr(expr), block(block), pointer(nullptr) {
|
||||
validate();
|
||||
}
|
||||
Tail(Expression* expr, Expression** pointer) : expr(expr), block(nullptr), pointer(pointer) {}
|
||||
|
||||
bool isFallthrough() const { return expr == nullptr; }
|
||||
|
||||
void validate() const {
|
||||
if (expr && block) {
|
||||
assert(block->list.back() == expr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// state
|
||||
|
||||
bool anotherPass;
|
||||
|
||||
// pass state
|
||||
|
||||
std::map<Name, std::vector<Tail>> breakTails; // break target name => tails that reach it
|
||||
std::vector<Tail> unreachableTails; // tails leading to (unreachable)
|
||||
std::vector<Tail> returnTails; // tails leading to (return)
|
||||
std::set<Name> unoptimizables; // break target names that we can't handle
|
||||
std::set<Expression*> modifieds; // modified code should not be processed again, wait for next pass
|
||||
|
||||
// walking
|
||||
|
||||
void visitBreak(Break* curr) {
|
||||
if (curr->condition || curr->value) {
|
||||
unoptimizables.insert(curr->name);
|
||||
} else {
|
||||
// we can only optimize if we are at the end of the parent block
|
||||
Block* parent = controlFlowStack.back()->dynCast<Block>();
|
||||
if (parent && curr == parent->list.back()) {
|
||||
breakTails[curr->name].push_back(Tail(curr, parent));
|
||||
} else {
|
||||
unoptimizables.insert(curr->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visitSwitch(Switch* curr) {
|
||||
for (auto target : curr->targets) {
|
||||
unoptimizables.insert(target);
|
||||
}
|
||||
unoptimizables.insert(curr->default_);
|
||||
}
|
||||
|
||||
void visitUnreachable(Unreachable* curr) {
|
||||
// we can only optimize if we are at the end of the parent block
|
||||
if (!controlFlowStack.empty()) {
|
||||
Block* parent = controlFlowStack.back()->dynCast<Block>();
|
||||
if (parent && curr == parent->list.back()) {
|
||||
unreachableTails.push_back(Tail(curr, parent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visitReturn(Return* curr) {
|
||||
if (!controlFlowStack.empty()) {
|
||||
// we can easily optimize if we are at the end of the parent block
|
||||
Block* parent = controlFlowStack.back()->dynCast<Block>();
|
||||
if (parent && curr == parent->list.back()) {
|
||||
returnTails.push_back(Tail(curr, parent));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// otherwise, if we have a large value, it might be worth optimizing us as well
|
||||
returnTails.push_back(Tail(curr, getCurrentPointer()));
|
||||
}
|
||||
|
||||
void visitBlock(Block* curr) {
|
||||
if (!curr->name.is()) return;
|
||||
if (unoptimizables.count(curr->name) > 0) return;
|
||||
auto iter = breakTails.find(curr->name);
|
||||
if (iter == breakTails.end()) return;
|
||||
// looks promising
|
||||
auto& tails = iter->second;
|
||||
// see if there is a fallthrough
|
||||
bool hasFallthrough = true;
|
||||
for (auto* child : curr->list) {
|
||||
if (child->type == unreachable) {
|
||||
hasFallthrough = false;
|
||||
}
|
||||
}
|
||||
if (hasFallthrough) {
|
||||
tails.push_back({ Tail(curr) });
|
||||
}
|
||||
optimizeExpressionTails(tails, curr);
|
||||
}
|
||||
|
||||
void visitIf(If* curr) {
|
||||
if (!curr->ifFalse) return;
|
||||
// if both sides are identical, this is easy to fold
|
||||
// (except if the condition is unreachable and we return a value, then we can't just replace
|
||||
// outselves with a drop
|
||||
if (ExpressionAnalyzer::equal(curr->ifTrue, curr->ifFalse)) {
|
||||
Builder builder(*getModule());
|
||||
// remove if (4 bytes), remove one arm, add drop (1), add block (3),
|
||||
// so this must be a net savings
|
||||
markAsModified(curr);
|
||||
auto* ret = builder.makeSequence(
|
||||
builder.makeDrop(curr->condition),
|
||||
curr->ifTrue
|
||||
);
|
||||
// we must ensure we present the same type as the if had
|
||||
ret->finalize(curr->type);
|
||||
replaceCurrent(ret);
|
||||
} else {
|
||||
// if both are blocks, look for a tail we can merge
|
||||
auto* left = curr->ifTrue->dynCast<Block>();
|
||||
auto* right = curr->ifFalse->dynCast<Block>();
|
||||
// we need nameless blocks, as if there is a name, someone might branch
|
||||
// to the end, skipping the code we want to merge
|
||||
if (left && right &&
|
||||
!left->name.is() && !right->name.is()) {
|
||||
std::vector<Tail> tails = { Tail(left), Tail(right) };
|
||||
optimizeExpressionTails(tails, curr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void doWalkFunction(Function* func) {
|
||||
anotherPass = true;
|
||||
while (anotherPass) {
|
||||
anotherPass = false;
|
||||
super::doWalkFunction(func);
|
||||
optimizeTerminatingTails(unreachableTails);
|
||||
// optimize returns at the end, so we can benefit from a fallthrough if there is a value TODO: separate passes for them?
|
||||
optimizeTerminatingTails(returnTails);
|
||||
// TODO add fallthrough for returns
|
||||
// TODO optimize returns not in blocks, a big return value can be worth it
|
||||
// clean up
|
||||
breakTails.clear();
|
||||
unreachableTails.clear();
|
||||
returnTails.clear();
|
||||
unoptimizables.clear();
|
||||
modifieds.clear();
|
||||
// if we did any work, types may need to be propagated
|
||||
if (anotherPass) {
|
||||
ReFinalize().walkFunctionInModule(func, getModule());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// check if we can move a list of items out of another item. we can't do so
|
||||
// if one of the items has a branch to something inside outOf that is not
|
||||
// inside that item
|
||||
bool canMove(const std::vector<Expression*>& items, Expression* outOf) {
|
||||
auto allTargets = BranchUtils::getBranchTargets(outOf);
|
||||
for (auto* item : items) {
|
||||
auto exiting = BranchUtils::getExitingBranches(item);
|
||||
std::vector<Name> intersection;
|
||||
std::set_intersection(allTargets.begin(), allTargets.end(), exiting.begin(), exiting.end(),
|
||||
std::back_inserter(intersection));
|
||||
if (intersection.size() > 0) {
|
||||
// anything exiting that is in all targets is something bad
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// optimize tails that reach the outside of an expression. code that is identical in all
|
||||
// paths leading to the block exit can be merged.
|
||||
template<typename T>
|
||||
void optimizeExpressionTails(std::vector<Tail>& tails, T* curr) {
|
||||
if (tails.size() < 2) return;
|
||||
// see if anything is untoward, and we should not do this
|
||||
for (auto& tail : tails) {
|
||||
if (tail.expr && modifieds.count(tail.expr) > 0) return;
|
||||
if (modifieds.count(tail.block) > 0) return;
|
||||
// if we were not modified, then we should be valid for processing
|
||||
tail.validate();
|
||||
}
|
||||
// we can ignore the final br in a tail
|
||||
auto effectiveSize = [&](const Tail& tail) {
|
||||
auto ret = tail.block->list.size();
|
||||
if (!tail.isFallthrough()) {
|
||||
ret--;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
// the mergeable items do not include the final br in a tail
|
||||
auto getMergeable = [&](const Tail& tail, Index num) {
|
||||
return tail.block->list[effectiveSize(tail) - num - 1];
|
||||
};
|
||||
// we are going to remove duplicate elements and add a block.
|
||||
// so for this to make sense, we need the size of the duplicate
|
||||
// elements to be worth that extra block (although, there is
|
||||
// some chance the block would get merged higher up, see later)
|
||||
std::vector<Expression*> mergeable; // the elements we can merge
|
||||
Index num = 0; // how many elements back from the tail to look at
|
||||
Index saved = 0; // how much we can save
|
||||
while (1) {
|
||||
// check if this num is still relevant
|
||||
bool stop = false;
|
||||
for (auto& tail : tails) {
|
||||
assert(tail.block);
|
||||
if (num >= effectiveSize(tail)) {
|
||||
// one of the lists is too short
|
||||
stop = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stop) break;
|
||||
auto* item = getMergeable(tails[0], num);
|
||||
for (auto& tail : tails) {
|
||||
if (!ExpressionAnalyzer::equal(item, getMergeable(tail, num))) {
|
||||
// one of the lists has a different item
|
||||
stop = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stop) break;
|
||||
// we may have found another one we can merge - can we move it?
|
||||
if (!canMove({ item }, curr)) break;
|
||||
// we found another one we can merge
|
||||
mergeable.push_back(item);
|
||||
num++;
|
||||
saved += Measurer::measure(item);
|
||||
}
|
||||
if (saved == 0) return;
|
||||
// we may be able to save enough.
|
||||
if (saved < WORTH_ADDING_BLOCK_TO_REMOVE_THIS_MUCH) {
|
||||
// it's not obvious we can save enough. see if we get rid
|
||||
// of a block, that would justify this
|
||||
bool willEmptyBlock = false;
|
||||
for (auto& tail : tails) {
|
||||
// it is enough to zero out the block, or leave just one
|
||||
// element, as then the block can be replaced with that
|
||||
if (num >= tail.block->list.size() - 1) {
|
||||
willEmptyBlock = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!willEmptyBlock) {
|
||||
// last chance, if our parent is a block, then it should be
|
||||
// fine to create a new block here, it will be merged up
|
||||
assert(curr == controlFlowStack.back()); // we are an if or a block, at the top
|
||||
if (controlFlowStack.size() <= 1) {
|
||||
return; // no parent at all
|
||||
// TODO: if we are the toplevel in the function, then in the binary format
|
||||
// we might avoid emitting a block, so the same logic applies here?
|
||||
}
|
||||
auto* parent = controlFlowStack[controlFlowStack.size() - 2]->dynCast<Block>();
|
||||
if (!parent) {
|
||||
return; // parent is not a block
|
||||
}
|
||||
bool isChild = false;
|
||||
for (auto* child : parent->list) {
|
||||
if (child == curr) {
|
||||
isChild = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isChild) {
|
||||
return; // not a child, something in between
|
||||
}
|
||||
}
|
||||
}
|
||||
// this is worth doing, do it!
|
||||
for (auto& tail : tails) {
|
||||
// remove the items we are merging / moving
|
||||
// first, mark them as modified, so we don't try to handle them
|
||||
// again in this pass, which might be buggy
|
||||
markAsModified(tail.block);
|
||||
// we must preserve the br if there is one
|
||||
Expression* last = nullptr;
|
||||
if (!tail.isFallthrough()) {
|
||||
last = tail.block->list.back();
|
||||
tail.block->list.pop_back();
|
||||
}
|
||||
for (Index i = 0; i < mergeable.size(); i++) {
|
||||
tail.block->list.pop_back();
|
||||
}
|
||||
if (!tail.isFallthrough()) {
|
||||
tail.block->list.push_back(last);
|
||||
}
|
||||
// the block type may change if we removed final values
|
||||
tail.block->finalize();
|
||||
}
|
||||
// since we managed a merge, then it might open up more opportunities later
|
||||
anotherPass = true;
|
||||
// make a block with curr + the merged code
|
||||
Builder builder(*getModule());
|
||||
auto* block = builder.makeBlock();
|
||||
block->list.push_back(curr);
|
||||
while (!mergeable.empty()) {
|
||||
block->list.push_back(mergeable.back());
|
||||
mergeable.pop_back();
|
||||
}
|
||||
auto oldType = curr->type;
|
||||
// NB: we template-specialize so that this calls the proper finalizer for
|
||||
// the type
|
||||
curr->finalize();
|
||||
// ensure the replacement has the same type, so the outside is not surprised
|
||||
block->finalize(oldType);
|
||||
replaceCurrent(block);
|
||||
}
|
||||
|
||||
// optimize tails that terminate control flow in this function, so we
|
||||
// are (1) merge just a few of them, we don't need all like with the
|
||||
// branches to a block, and (2) we do it on the function body.
|
||||
// num is the depth, i.e., how many tail items we can merge. 0 means
|
||||
// we are just starting; num > 0 means that tails is guaranteed to be
|
||||
// equal in the last num items, so we can merge there, but we look for
|
||||
// deeper merges first.
|
||||
// returns whether we optimized something.
|
||||
bool optimizeTerminatingTails(std::vector<Tail>& tails, Index num = 0) {
|
||||
if (tails.size() < 2) return false;
|
||||
// remove things that are untoward and cannot be optimized
|
||||
tails.erase(std::remove_if(tails.begin(), tails.end(), [&](Tail& tail) {
|
||||
if (tail.expr && modifieds.count(tail.expr) > 0) return true;
|
||||
if (tail.block && modifieds.count(tail.block) > 0) return true;
|
||||
// if we were not modified, then we should be valid for processing
|
||||
tail.validate();
|
||||
return false;
|
||||
}), tails.end());
|
||||
// now let's try to find subsets that are mergeable. we don't look hard
|
||||
// for the most optimal; further passes may find more
|
||||
// effectiveSize: TODO: special-case fallthrough, matters for returns
|
||||
auto effectiveSize = [&](Tail& tail) -> Index {
|
||||
if (tail.block) {
|
||||
return tail.block->list.size();
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
// getItem: returns the relevant item from the tail. this includes the
|
||||
// final item
|
||||
// TODO: special-case fallthrough, matters for returns
|
||||
auto getItem = [&](Tail& tail, Index num) {
|
||||
if (tail.block) {
|
||||
return tail.block->list[effectiveSize(tail) - num - 1];
|
||||
} else {
|
||||
return tail.expr;
|
||||
}
|
||||
};
|
||||
// gets the tail elements of a certain depth
|
||||
auto getTailItems = [&](Index num, std::vector<Tail>& tails) {
|
||||
std::vector<Expression*> items;
|
||||
for (Index i = 0; i < num; i++) {
|
||||
auto item = getItem(tails[0], i);
|
||||
items.push_back(item);
|
||||
}
|
||||
return items;
|
||||
};
|
||||
// estimate if a merging is worth the cost
|
||||
auto worthIt = [&](Index num, std::vector<Tail>& tails) {
|
||||
auto items = getTailItems(num, tails); // the elements we can merge
|
||||
Index saved = 0; // how much we can save
|
||||
for (auto* item : items) {
|
||||
saved += Measurer::measure(item) * (tails.size() - 1);
|
||||
}
|
||||
// compure the cost: in non-fallthroughs, we are replacing the final
|
||||
// element with a br; for a fallthrough, if there is one, we must
|
||||
// add a return element (for the function body, so it doesn't reach us)
|
||||
// TODO: handle fallthroughts for return
|
||||
Index cost = tails.size();
|
||||
// we also need to add two blocks: for us to break to, and to contain
|
||||
// that block and the merged code. very possibly one of the blocks
|
||||
// can be removed, though
|
||||
cost += WORTH_ADDING_BLOCK_TO_REMOVE_THIS_MUCH;
|
||||
// if we cannot merge to the end, then we definitely need 2 blocks,
|
||||
// and a branch
|
||||
if (!canMove(items, getFunction()->body)) { // TODO: efficiency, entire body
|
||||
cost += 1 + WORTH_ADDING_BLOCK_TO_REMOVE_THIS_MUCH;
|
||||
// TODO: to do this, we need to maintain a map of element=>parent,
|
||||
// so that we can insert the new blocks in the right place
|
||||
// for now, just don't do this optimization
|
||||
return false;
|
||||
}
|
||||
// is it worth it?
|
||||
return saved > cost;
|
||||
};
|
||||
// let's see if we can merge deeper than num, to num + 1
|
||||
auto next = tails;
|
||||
// remove tails that are too short, or that we hit an item we can't handle
|
||||
next.erase(std::remove_if(next.begin(), next.end(), [&](Tail& tail) {
|
||||
if (effectiveSize(tail) < num + 1) return true;
|
||||
auto* newItem = getItem(tail, num);
|
||||
// ignore tails that break to outside blocks. we want to move code to
|
||||
// the very outermost position, so such code cannot be moved
|
||||
// TODO: this should not be a problem in *non*-terminating tails,
|
||||
// but double-verify that
|
||||
if (EffectAnalyzer(getPassOptions(), newItem).hasExternalBreakTargets()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}), next.end());
|
||||
// if we have enough to investigate, do so
|
||||
if (next.size() >= 2) {
|
||||
// now we want to find a mergeable item - any item that is equal among a subset
|
||||
std::map<uint32_t, std::vector<Expression*>> hashed; // hash value => expressions with that hash
|
||||
for (auto& tail : next) {
|
||||
auto* item = getItem(tail, num);
|
||||
hashed[ExpressionAnalyzer::hash(item)].push_back(item);
|
||||
}
|
||||
for (auto& iter : hashed) {
|
||||
auto& items = iter.second;
|
||||
if (items.size() == 1) continue;
|
||||
assert(items.size() > 0);
|
||||
// look for an item that has another match.
|
||||
while (items.size() >= 2) {
|
||||
auto first = items[0];
|
||||
std::vector<Expression*> others;
|
||||
items.erase(std::remove_if(items.begin(), items.end(), [&](Expression* item) {
|
||||
if (item == first || // don't bother comparing the first
|
||||
ExpressionAnalyzer::equal(item, first)) {
|
||||
// equal, keep it
|
||||
return false;
|
||||
} else {
|
||||
// unequal, look at it later
|
||||
others.push_back(item);
|
||||
return true;
|
||||
}
|
||||
}), items.end());
|
||||
if (items.size() >= 2) {
|
||||
// possible merge here, investigate it
|
||||
auto* correct = items[0];
|
||||
auto explore = next;
|
||||
explore.erase(std::remove_if(explore.begin(), explore.end(), [&](Tail& tail) {
|
||||
auto* item = getItem(tail, num);
|
||||
return !ExpressionAnalyzer::equal(item, correct);
|
||||
}), explore.end());
|
||||
// try to optimize this deeper tail. if we succeed, then stop here, as the
|
||||
// changes may influence us. we leave further opts to further passes (as this
|
||||
// is rare in practice, it's generally not a perf issue, but TODO optimize)
|
||||
if (optimizeTerminatingTails(explore, num + 1)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
items.swap(others);
|
||||
}
|
||||
}
|
||||
}
|
||||
// we explored deeper (higher num) options, but perhaps there
|
||||
// was nothing there while there is something we can do at this level
|
||||
// but if we are at num == 0, then we found nothing at all
|
||||
if (num == 0) return false;
|
||||
// if not worth it, stop
|
||||
if (!worthIt(num, tails)) return false;
|
||||
// this is worth doing, do it!
|
||||
auto mergeable = getTailItems(num, tails); // the elements we can merge
|
||||
// since we managed a merge, then it might open up more opportunities later
|
||||
anotherPass = true;
|
||||
Builder builder(*getModule());
|
||||
LabelUtils::LabelManager labels(getFunction()); // TODO: don't create one per merge, linear in function size
|
||||
Name innerName = labels.getUnique("folding-inner");
|
||||
for (auto& tail : tails) {
|
||||
// remove the items we are merging / moving, and add a break
|
||||
// also mark as modified, so we don't try to handle them
|
||||
// again in this pass, which might be buggy
|
||||
if (tail.block) {
|
||||
markAsModified(tail.block);
|
||||
for (Index i = 0; i < mergeable.size(); i++) {
|
||||
tail.block->list.pop_back();
|
||||
}
|
||||
tail.block->list.push_back(builder.makeBreak(innerName));
|
||||
tail.block->finalize(tail.block->type);
|
||||
} else {
|
||||
markAsModified(tail.expr);
|
||||
*tail.pointer = builder.makeBreak(innerName);
|
||||
}
|
||||
}
|
||||
// make a block with the old body + the merged code
|
||||
auto* old = getFunction()->body;
|
||||
auto* inner = builder.makeBlock();
|
||||
inner->name = innerName;
|
||||
if (old->type == unreachable) {
|
||||
// the old body is not flowed out of anyhow, so just put it there
|
||||
inner->list.push_back(old);
|
||||
} else {
|
||||
// otherwise, we must not flow out to the merged code
|
||||
if (old->type == none) {
|
||||
inner->list.push_back(old);
|
||||
inner->list.push_back(builder.makeReturn());
|
||||
} else {
|
||||
// looks like we must return this. but if it's a toplevel block
|
||||
// then it might be marked as having a type, but not actually
|
||||
// returning it (we marked it as such for wasm type-checking
|
||||
// rules, and now it won't be toplevel in the function, it can
|
||||
// change)
|
||||
auto* toplevel = old->dynCast<Block>();
|
||||
if (toplevel) toplevel->finalize();
|
||||
if (old->type != unreachable) {
|
||||
inner->list.push_back(builder.makeReturn(old));
|
||||
} else {
|
||||
inner->list.push_back(old);
|
||||
}
|
||||
}
|
||||
}
|
||||
inner->finalize();
|
||||
auto* outer = builder.makeBlock();
|
||||
outer->list.push_back(inner);
|
||||
while (!mergeable.empty()) {
|
||||
outer->list.push_back(mergeable.back());
|
||||
mergeable.pop_back();
|
||||
}
|
||||
// ensure the replacement has the same type, so the outside is not surprised
|
||||
outer->finalize(getFunction()->result);
|
||||
getFunction()->body = outer;
|
||||
return true;
|
||||
}
|
||||
|
||||
void markAsModified(Expression* curr) {
|
||||
ExpressionMarker marker(modifieds, curr);
|
||||
}
|
||||
};
|
||||
|
||||
Pass *createCodeFoldingPass() {
|
||||
return new CodeFolding();
|
||||
}
|
||||
|
||||
} // namespace wasm
|
||||
|
@ -1,265 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//
|
||||
// Pushes code "forward" as much as possible, potentially into
|
||||
// a location behind a condition, where it might not always execute.
|
||||
//
|
||||
|
||||
#include <wasm.h>
|
||||
#include <pass.h>
|
||||
#include <wasm-builder.h>
|
||||
#include <ir/effects.h>
|
||||
|
||||
namespace wasm {
|
||||
|
||||
//
|
||||
// Analyzers some useful local properties: # of sets and gets, and SFA.
|
||||
//
|
||||
// Single First Assignment (SFA) form: the local has a single set_local, is
|
||||
// not a parameter, and has no get_locals before the set_local in postorder.
|
||||
// This is a much weaker property than SSA, obviously, but together with
|
||||
// our implicit dominance properties in the structured AST is quite useful.
|
||||
//
|
||||
struct LocalAnalyzer : public PostWalker<LocalAnalyzer> {
|
||||
std::vector<bool> sfa;
|
||||
std::vector<Index> numSets;
|
||||
std::vector<Index> numGets;
|
||||
|
||||
void analyze(Function* func) {
|
||||
auto num = func->getNumLocals();
|
||||
numSets.resize(num);
|
||||
std::fill(numSets.begin(), numSets.end(), 0);
|
||||
numGets.resize(num);
|
||||
std::fill(numGets.begin(), numGets.end(), 0);
|
||||
sfa.resize(num);
|
||||
std::fill(sfa.begin(), sfa.begin() + func->getNumParams(), false);
|
||||
std::fill(sfa.begin() + func->getNumParams(), sfa.end(), true);
|
||||
walk(func->body);
|
||||
for (Index i = 0; i < num; i++) {
|
||||
if (numSets[i] == 0) sfa[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool isSFA(Index i) {
|
||||
return sfa[i];
|
||||
}
|
||||
|
||||
Index getNumGets(Index i) {
|
||||
return numGets[i];
|
||||
}
|
||||
|
||||
void visitGetLocal(GetLocal *curr) {
|
||||
if (numSets[curr->index] == 0) {
|
||||
sfa[curr->index] = false;
|
||||
}
|
||||
numGets[curr->index]++;
|
||||
}
|
||||
|
||||
void visitSetLocal(SetLocal *curr) {
|
||||
numSets[curr->index]++;
|
||||
if (numSets[curr->index] > 1) {
|
||||
sfa[curr->index] = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Implement core optimization logic in a struct, used and then discarded entirely
|
||||
// for each block
|
||||
class Pusher {
|
||||
ExpressionList& list;
|
||||
LocalAnalyzer& analyzer;
|
||||
std::vector<Index>& numGetsSoFar;
|
||||
PassOptions& passOptions;
|
||||
|
||||
public:
|
||||
Pusher(Block* block, LocalAnalyzer& analyzer, std::vector<Index>& numGetsSoFar, PassOptions& passOptions) : list(block->list), analyzer(analyzer), numGetsSoFar(numGetsSoFar), passOptions(passOptions) {
|
||||
// Find an optimization segment: from the first pushable thing, to the first
|
||||
// point past which we want to push. We then push in that range before
|
||||
// continuing forward.
|
||||
Index relevant = list.size() - 1; // we never need to push past a final element, as
|
||||
// we couldn't be used after it.
|
||||
Index nothing = -1;
|
||||
Index i = 0;
|
||||
Index firstPushable = nothing;
|
||||
while (i < relevant) {
|
||||
if (firstPushable == nothing && isPushable(list[i])) {
|
||||
firstPushable = i;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (firstPushable != nothing && isPushPoint(list[i])) {
|
||||
// optimize this segment, and proceed from where it tells us
|
||||
i = optimizeSegment(firstPushable, i);
|
||||
firstPushable = nothing;
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
SetLocal* isPushable(Expression* curr) {
|
||||
auto* set = curr->dynCast<SetLocal>();
|
||||
if (!set) return nullptr;
|
||||
auto index = set->index;
|
||||
// to be pushable, this must be SFA and the right # of gets,
|
||||
// but also have no side effects, as it may not execute if pushed.
|
||||
if (analyzer.isSFA(index) &&
|
||||
numGetsSoFar[index] == analyzer.getNumGets(index) &&
|
||||
!EffectAnalyzer(passOptions, set->value).hasSideEffects()) {
|
||||
return set;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Push past conditional control flow.
|
||||
// TODO: push into ifs as well
|
||||
bool isPushPoint(Expression* curr) {
|
||||
// look through drops
|
||||
if (auto* drop = curr->dynCast<Drop>()) {
|
||||
curr = drop->value;
|
||||
}
|
||||
if (curr->is<If>()) return true;
|
||||
if (auto* br = curr->dynCast<Break>()) {
|
||||
return !!br->condition;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Index optimizeSegment(Index firstPushable, Index pushPoint) {
|
||||
// The interesting part. Starting at firstPushable, try to push
|
||||
// code past pushPoint. We start at the end since we are pushing
|
||||
// forward, that way we can push later things out of the way
|
||||
// of earlier ones. Once we know all we can push, we push it all
|
||||
// in one pass, keeping the order of the pushables intact.
|
||||
assert(firstPushable != Index(-1) && pushPoint != Index(-1) && firstPushable < pushPoint);
|
||||
EffectAnalyzer cumulativeEffects(passOptions); // everything that matters if you want
|
||||
// to be pushed past the pushPoint
|
||||
cumulativeEffects.analyze(list[pushPoint]);
|
||||
cumulativeEffects.branches = false; // it is ok to ignore the branching here,
|
||||
// that is the crucial point of this opt
|
||||
std::vector<SetLocal*> toPush;
|
||||
Index i = pushPoint - 1;
|
||||
while (1) {
|
||||
auto* pushable = isPushable(list[i]);
|
||||
if (pushable) {
|
||||
auto iter = pushableEffects.find(pushable);
|
||||
if (iter == pushableEffects.end()) {
|
||||
iter = pushableEffects.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(pushable),
|
||||
std::forward_as_tuple(passOptions, pushable)
|
||||
).first;
|
||||
}
|
||||
auto& effects = iter->second;
|
||||
if (cumulativeEffects.invalidates(effects)) {
|
||||
// we can't push this, so further pushables must pass it
|
||||
cumulativeEffects.mergeIn(effects);
|
||||
} else {
|
||||
// we can push this, great!
|
||||
toPush.push_back(pushable);
|
||||
}
|
||||
if (i == firstPushable) {
|
||||
// no point in looking further
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// something that can't be pushed, so it might block further pushing
|
||||
cumulativeEffects.analyze(list[i]);
|
||||
}
|
||||
assert(i > 0);
|
||||
i--;
|
||||
}
|
||||
if (toPush.size() == 0) {
|
||||
// nothing to do, can only continue after the push point
|
||||
return pushPoint + 1;
|
||||
}
|
||||
// we have work to do!
|
||||
Index total = toPush.size();
|
||||
Index last = total - 1;
|
||||
Index skip = 0;
|
||||
for (Index i = firstPushable; i <= pushPoint; i++) {
|
||||
// we see the first elements at the end of toPush
|
||||
if (skip < total && list[i] == toPush[last - skip]) {
|
||||
// this is one of our elements to push, skip it
|
||||
skip++;
|
||||
} else {
|
||||
if (skip) {
|
||||
list[i - skip] = list[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(skip == total);
|
||||
// write out the skipped elements
|
||||
for (Index i = 0; i < total; i++) {
|
||||
list[pushPoint - i] = toPush[i];
|
||||
}
|
||||
// proceed right after the push point, we may push the pushed elements again
|
||||
return pushPoint - total + 1;
|
||||
}
|
||||
|
||||
// Pushables may need to be scanned more than once, so cache their effects.
|
||||
std::unordered_map<SetLocal*, EffectAnalyzer> pushableEffects;
|
||||
};
|
||||
|
||||
struct CodePushing : public WalkerPass<PostWalker<CodePushing>> {
|
||||
bool isFunctionParallel() override { return true; }
|
||||
|
||||
Pass* create() override { return new CodePushing; }
|
||||
|
||||
LocalAnalyzer analyzer;
|
||||
|
||||
// gets seen so far in the main traversal
|
||||
std::vector<Index> numGetsSoFar;
|
||||
|
||||
void doWalkFunction(Function* func) {
|
||||
// pre-scan to find which vars are sfa, and also count their gets&sets
|
||||
analyzer.analyze(func);
|
||||
// prepare to walk
|
||||
numGetsSoFar.resize(func->getNumLocals());
|
||||
std::fill(numGetsSoFar.begin(), numGetsSoFar.end(), 0);
|
||||
// walk and optimize
|
||||
walk(func->body);
|
||||
}
|
||||
|
||||
void visitGetLocal(GetLocal *curr) {
|
||||
numGetsSoFar[curr->index]++;
|
||||
}
|
||||
|
||||
void visitBlock(Block* curr) {
|
||||
// Pushing code only makes sense if we are size 3 or above: we need
|
||||
// one element to push, an element to push it past, and an element to use
|
||||
// what we pushed.
|
||||
if (curr->list.size() < 3) return;
|
||||
// At this point in the postorder traversal we have gone through all our children.
|
||||
// Therefore any variable whose gets seen so far is equal to the total gets must
|
||||
// have no further users after this block. And therefore when we see an SFA
|
||||
// variable defined here, we know it isn't used before it either, and has just this
|
||||
// one assign. So we can push it forward while we don't hit a non-control-flow
|
||||
// ordering invalidation issue, since if this isn't a loop, it's fine (we're not
|
||||
// used outside), and if it is, we hit the assign before any use (as we can't
|
||||
// push it past a use).
|
||||
Pusher pusher(curr, analyzer, numGetsSoFar, getPassOptions());
|
||||
}
|
||||
};
|
||||
|
||||
Pass *createCodePushingPass() {
|
||||
return new CodePushing();
|
||||
}
|
||||
|
||||
} // namespace wasm
|
||||
|
@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//
|
||||
// Hoists repeated constants to a local. A get_local takes 2 bytes
|
||||
// in most cases, and if a const is larger than that, it may be
|
||||
// better to store it to a local, then get it from that local.
|
||||
//
|
||||
// WARNING: this often shrinks code size, but can *increase* gzip
|
||||
// size. apparently having the constants in their proper
|
||||
// places lets them be compressed better, across
|
||||
// functions, etc.
|
||||
//
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <wasm.h>
|
||||
#include <pass.h>
|
||||
#include <wasm-binary.h>
|
||||
#include <wasm-builder.h>
|
||||
|
||||
namespace wasm {
|
||||
|
||||
// with fewer uses than this, it is never beneficial to hoist
|
||||
static const Index MIN_USES = 2;
|
||||
|
||||
struct ConstHoisting : public WalkerPass<PostWalker<ConstHoisting>> {
|
||||
bool isFunctionParallel() override { return true; }
|
||||
|
||||
Pass* create() override { return new ConstHoisting; }
|
||||
|
||||
std::map<Literal, std::vector<Expression**>> uses;
|
||||
|
||||
void visitConst(Const* curr) {
|
||||
uses[curr->value].push_back(getCurrentPointer());
|
||||
}
|
||||
|
||||
void visitFunction(Function* curr) {
|
||||
std::vector<Expression*> prelude;
|
||||
for (auto& pair : uses) {
|
||||
auto value = pair.first;
|
||||
auto& vec = pair.second;
|
||||
auto num = vec.size();
|
||||
if (worthHoisting(value, num)) {
|
||||
prelude.push_back(hoist(vec));
|
||||
}
|
||||
}
|
||||
if (!prelude.empty()) {
|
||||
Builder builder(*getModule());
|
||||
// merge-blocks can optimize this into a single block later in most cases
|
||||
curr->body = builder.makeSequence(
|
||||
builder.makeBlock(prelude),
|
||||
curr->body
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool worthHoisting(Literal value, Index num) {
|
||||
if (num < MIN_USES) return false;
|
||||
// measure the size of the constant
|
||||
Index size;
|
||||
switch (value.type) {
|
||||
case i32: {
|
||||
size = getWrittenSize(S32LEB(value.geti32()));
|
||||
break;
|
||||
}
|
||||
case i64: {
|
||||
size = getWrittenSize(S64LEB(value.geti64()));
|
||||
break;
|
||||
}
|
||||
case f32:
|
||||
case f64: {
|
||||
size = getWasmTypeSize(value.type);
|
||||
break;
|
||||
}
|
||||
default: WASM_UNREACHABLE();
|
||||
}
|
||||
// compute the benefit, of replacing the uses with
|
||||
// one use + a set and then a get for each use
|
||||
// doing the algebra, the criterion here is when
|
||||
// size > 2(1+num)/(num-1)
|
||||
// or
|
||||
// num > (size+2)/(size-2)
|
||||
auto before = num * size;
|
||||
auto after = size + 2 /* set_local */ + (2 /* get_local */ * num);
|
||||
return after < before;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Index getWrittenSize(const T& thing) {
|
||||
BufferWithRandomAccess buffer;
|
||||
buffer << thing;
|
||||
return buffer.size();
|
||||
}
|
||||
|
||||
// replace all the uses with gets, for a local set at the top. returns
|
||||
// the set.
|
||||
Expression* hoist(std::vector<Expression**>& vec) {
|
||||
auto type = (*(vec[0]))->type;
|
||||
Builder builder(*getModule());
|
||||
auto temp = builder.addVar(getFunction(), type);
|
||||
auto* ret = builder.makeSetLocal(
|
||||
temp,
|
||||
*(vec[0])
|
||||
);
|
||||
for (auto item : vec) {
|
||||
*item = builder.makeGetLocal(temp, type);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
Pass *createConstHoistingPass() {
|
||||
return new ConstHoisting();
|
||||
}
|
||||
|
||||
} // namespace wasm
|
@ -1,407 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//
|
||||
// Removes dead, i.e. unreachable, code.
|
||||
//
|
||||
// We keep a record of when control flow is reachable. When it isn't, we
|
||||
// kill (turn into unreachable). We then fold away entire unreachable
|
||||
// expressions.
|
||||
//
|
||||
// When dead code causes an operation to not happen, like a store, a call
|
||||
// or an add, we replace with a block with a list of what does happen.
|
||||
// That isn't necessarily smaller, but blocks are friendlier to other
|
||||
// optimizations: blocks can be merged and eliminated, and they clearly
|
||||
// have no side effects.
|
||||
//
|
||||
|
||||
#include <vector>
|
||||
#include <wasm.h>
|
||||
#include <pass.h>
|
||||
#include <wasm-builder.h>
|
||||
#include <ir/block-utils.h>
|
||||
#include <ir/branch-utils.h>
|
||||
#include <ir/type-updating.h>
|
||||
|
||||
namespace wasm {
|
||||
|
||||
struct DeadCodeElimination : public WalkerPass<PostWalker<DeadCodeElimination>> {
|
||||
bool isFunctionParallel() override { return true; }
|
||||
|
||||
Pass* create() override { return new DeadCodeElimination; }
|
||||
|
||||
// as we remove code, we must keep the types of other nodes valid
|
||||
TypeUpdater typeUpdater;
|
||||
|
||||
Expression* replaceCurrent(Expression* expression) {
|
||||
auto* old = getCurrent();
|
||||
if (old == expression) return expression;
|
||||
super::replaceCurrent(expression);
|
||||
// also update the type updater
|
||||
typeUpdater.noteReplacement(old, expression);
|
||||
return expression;
|
||||
}
|
||||
|
||||
// whether the current code is actually reachable
|
||||
bool reachable;
|
||||
|
||||
void doWalkFunction(Function* func) {
|
||||
reachable = true;
|
||||
typeUpdater.walk(func->body);
|
||||
walk(func->body);
|
||||
}
|
||||
|
||||
std::set<Name> reachableBreaks;
|
||||
|
||||
void addBreak(Name name) {
|
||||
// we normally have already reduced unreachable code into (unreachable)
|
||||
// nodes, so we would not get to this function at all anyhow, the breaking
|
||||
// instruction itself would be removed. However, an exception are things
|
||||
// like (block (result i32) (call $x) (unreachable)) , which has type i32
|
||||
// despite not being exited.
|
||||
// TODO: optimize such cases
|
||||
if (reachable) {
|
||||
reachableBreaks.insert(name);
|
||||
}
|
||||
}
|
||||
|
||||
// if a child exists and is unreachable, we can replace ourselves with it
|
||||
bool isDead(Expression* child) {
|
||||
return child && child->type == unreachable;
|
||||
}
|
||||
|
||||
// a similar check, assumes the child exists
|
||||
bool isUnreachable(Expression* child) {
|
||||
return child->type == unreachable;
|
||||
}
|
||||
|
||||
// things that stop control flow
|
||||
|
||||
void visitBreak(Break* curr) {
|
||||
if (isDead(curr->value)) {
|
||||
// the condition is evaluated last, so if the value was unreachable, the whole thing is
|
||||
replaceCurrent(curr->value);
|
||||
return;
|
||||
}
|
||||
if (isDead(curr->condition)) {
|
||||
if (curr->value) {
|
||||
auto* block = getModule()->allocator.alloc<Block>();
|
||||
block->list.resize(2);
|
||||
block->list[0] = drop(curr->value);
|
||||
block->list[1] = curr->condition;
|
||||
// if we previously returned a value, then this block
|
||||
// must have the same type, so it fits in the ast
|
||||
// properly. it ends in an unreachable
|
||||
// anyhow, so that is ok.
|
||||
block->finalize(curr->type);
|
||||
replaceCurrent(block);
|
||||
} else {
|
||||
replaceCurrent(curr->condition);
|
||||
}
|
||||
return;
|
||||
}
|
||||
addBreak(curr->name);
|
||||
if (!curr->condition) {
|
||||
reachable = false;
|
||||
}
|
||||
}
|
||||
|
||||
void visitSwitch(Switch* curr) {
|
||||
if (isDead(curr->value)) {
|
||||
replaceCurrent(curr->value);
|
||||
return;
|
||||
}
|
||||
if (isUnreachable(curr->condition)) {
|
||||
if (curr->value) {
|
||||
auto* block = getModule()->allocator.alloc<Block>();
|
||||
block->list.resize(2);
|
||||
block->list[0] = drop(curr->value);
|
||||
block->list[1] = curr->condition;
|
||||
block->finalize(curr->type);
|
||||
replaceCurrent(block);
|
||||
} else {
|
||||
replaceCurrent(curr->condition);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (auto target : curr->targets) {
|
||||
addBreak(target);
|
||||
}
|
||||
addBreak(curr->default_);
|
||||
reachable = false;
|
||||
}
|
||||
|
||||
void visitReturn(Return* curr) {
|
||||
if (isDead(curr->value)) {
|
||||
replaceCurrent(curr->value);
|
||||
return;
|
||||
}
|
||||
reachable = false;
|
||||
}
|
||||
|
||||
void visitUnreachable(Unreachable* curr) {
|
||||
reachable = false;
|
||||
}
|
||||
|
||||
void visitBlock(Block* curr) {
|
||||
auto& list = curr->list;
|
||||
// if we are currently unreachable (before we take into account
|
||||
// breaks to the block) then a child may be unreachable, and we
|
||||
// can shorten
|
||||
if (!reachable && list.size() > 1) {
|
||||
// to do here: nothing to remove after it)
|
||||
for (Index i = 0; i < list.size() - 1; i++) {
|
||||
if (list[i]->type == unreachable) {
|
||||
list.resize(i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (curr->name.is()) {
|
||||
reachable = reachable || reachableBreaks.count(curr->name);
|
||||
reachableBreaks.erase(curr->name);
|
||||
}
|
||||
if (list.size() == 1 && isUnreachable(list[0])) {
|
||||
replaceCurrent(BlockUtils::simplifyToContentsWithPossibleTypeChange(curr, this));
|
||||
} else {
|
||||
// the block may have had a type, but can now be unreachable, which allows more reduction outside
|
||||
typeUpdater.maybeUpdateTypeToUnreachable(curr);
|
||||
}
|
||||
}
|
||||
|
||||
void visitLoop(Loop* curr) {
|
||||
if (curr->name.is()) {
|
||||
reachableBreaks.erase(curr->name);
|
||||
}
|
||||
if (isUnreachable(curr->body) && !BranchUtils::BranchSeeker::hasNamed(curr->body, curr->name)) {
|
||||
replaceCurrent(curr->body);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ifs need special handling
|
||||
|
||||
std::vector<bool> ifStack; // stack of reachable state, for forking and joining
|
||||
|
||||
static void doAfterIfCondition(DeadCodeElimination* self, Expression** currp) {
|
||||
self->ifStack.push_back(self->reachable);
|
||||
}
|
||||
|
||||
static void doAfterIfElseTrue(DeadCodeElimination* self, Expression** currp) {
|
||||
assert((*currp)->cast<If>()->ifFalse);
|
||||
bool reachableBefore = self->ifStack.back();
|
||||
self->ifStack.pop_back();
|
||||
self->ifStack.push_back(self->reachable);
|
||||
self->reachable = reachableBefore;
|
||||
}
|
||||
|
||||
void visitIf(If* curr) {
|
||||
// the ifStack has the branch that joins us, either from before if just an if, or the ifTrue if an if-else
|
||||
reachable = reachable || ifStack.back();
|
||||
ifStack.pop_back();
|
||||
if (isUnreachable(curr->condition)) {
|
||||
replaceCurrent(curr->condition);
|
||||
}
|
||||
// the if may have had a type, but can now be unreachable, which allows more reduction outside
|
||||
typeUpdater.maybeUpdateTypeToUnreachable(curr);
|
||||
}
|
||||
|
||||
static void scan(DeadCodeElimination* self, Expression** currp) {
|
||||
auto* curr = *currp;
|
||||
if (!self->reachable) {
|
||||
// convert to an unreachable safely
|
||||
#define DELEGATE(CLASS_TO_VISIT) { \
|
||||
auto* parent = self->typeUpdater.parents[curr]; \
|
||||
self->typeUpdater.noteRecursiveRemoval(curr); \
|
||||
ExpressionManipulator::convert<CLASS_TO_VISIT, Unreachable>(static_cast<CLASS_TO_VISIT*>(curr)); \
|
||||
self->typeUpdater.noteAddition(curr, parent); \
|
||||
break; \
|
||||
}
|
||||
switch (curr->_id) {
|
||||
case Expression::Id::BlockId: DELEGATE(Block);
|
||||
case Expression::Id::IfId: DELEGATE(If);
|
||||
case Expression::Id::LoopId: DELEGATE(Loop);
|
||||
case Expression::Id::BreakId: DELEGATE(Break);
|
||||
case Expression::Id::SwitchId: DELEGATE(Switch);
|
||||
case Expression::Id::CallId: DELEGATE(Call);
|
||||
case Expression::Id::CallImportId: DELEGATE(CallImport);
|
||||
case Expression::Id::CallIndirectId: DELEGATE(CallIndirect);
|
||||
case Expression::Id::GetLocalId: DELEGATE(GetLocal);
|
||||
case Expression::Id::SetLocalId: DELEGATE(SetLocal);
|
||||
case Expression::Id::GetGlobalId: DELEGATE(GetGlobal);
|
||||
case Expression::Id::SetGlobalId: DELEGATE(SetGlobal);
|
||||
case Expression::Id::LoadId: DELEGATE(Load);
|
||||
case Expression::Id::StoreId: DELEGATE(Store);
|
||||
case Expression::Id::ConstId: DELEGATE(Const);
|
||||
case Expression::Id::UnaryId: DELEGATE(Unary);
|
||||
case Expression::Id::BinaryId: DELEGATE(Binary);
|
||||
case Expression::Id::SelectId: DELEGATE(Select);
|
||||
case Expression::Id::DropId: DELEGATE(Drop);
|
||||
case Expression::Id::ReturnId: DELEGATE(Return);
|
||||
case Expression::Id::HostId: DELEGATE(Host);
|
||||
case Expression::Id::NopId: DELEGATE(Nop);
|
||||
case Expression::Id::UnreachableId: break;
|
||||
case Expression::Id::AtomicCmpxchgId: DELEGATE(AtomicCmpxchg);
|
||||
case Expression::Id::AtomicRMWId: DELEGATE(AtomicRMW);
|
||||
case Expression::Id::AtomicWaitId: DELEGATE(AtomicWait);
|
||||
case Expression::Id::AtomicWakeId: DELEGATE(AtomicWake);
|
||||
case Expression::Id::InvalidId:
|
||||
default: WASM_UNREACHABLE();
|
||||
}
|
||||
#undef DELEGATE
|
||||
return;
|
||||
}
|
||||
if (curr->is<If>()) {
|
||||
self->pushTask(DeadCodeElimination::doVisitIf, currp);
|
||||
if (curr->cast<If>()->ifFalse) {
|
||||
self->pushTask(DeadCodeElimination::scan, &curr->cast<If>()->ifFalse);
|
||||
self->pushTask(DeadCodeElimination::doAfterIfElseTrue, currp);
|
||||
}
|
||||
self->pushTask(DeadCodeElimination::scan, &curr->cast<If>()->ifTrue);
|
||||
self->pushTask(DeadCodeElimination::doAfterIfCondition, currp);
|
||||
self->pushTask(DeadCodeElimination::scan, &curr->cast<If>()->condition);
|
||||
} else {
|
||||
super::scan(self, currp);
|
||||
}
|
||||
}
|
||||
|
||||
// other things
|
||||
|
||||
// we don't need to drop unreachable nodes
|
||||
Expression* drop(Expression* toDrop) {
|
||||
if (toDrop->type == unreachable) return toDrop;
|
||||
return Builder(*getModule()).makeDrop(toDrop);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Expression* handleCall(T* curr) {
|
||||
for (Index i = 0; i < curr->operands.size(); i++) {
|
||||
if (isUnreachable(curr->operands[i])) {
|
||||
if (i > 0) {
|
||||
auto* block = getModule()->allocator.alloc<Block>();
|
||||
Index newSize = i + 1;
|
||||
block->list.resize(newSize);
|
||||
Index j = 0;
|
||||
for (; j < newSize; j++) {
|
||||
block->list[j] = drop(curr->operands[j]);
|
||||
}
|
||||
block->finalize(curr->type);
|
||||
return replaceCurrent(block);
|
||||
} else {
|
||||
return replaceCurrent(curr->operands[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return curr;
|
||||
}
|
||||
|
||||
void visitCall(Call* curr) {
|
||||
handleCall(curr);
|
||||
}
|
||||
|
||||
void visitCallImport(CallImport* curr) {
|
||||
handleCall(curr);
|
||||
}
|
||||
|
||||
void visitCallIndirect(CallIndirect* curr) {
|
||||
if (handleCall(curr) != curr) return;
|
||||
if (isUnreachable(curr->target)) {
|
||||
auto* block = getModule()->allocator.alloc<Block>();
|
||||
for (auto* operand : curr->operands) {
|
||||
block->list.push_back(drop(operand));
|
||||
}
|
||||
block->list.push_back(curr->target);
|
||||
block->finalize(curr->type);
|
||||
replaceCurrent(block);
|
||||
}
|
||||
}
|
||||
|
||||
// Append the reachable operands of the current node to a block, and replace
|
||||
// it with the block
|
||||
void blockifyReachableOperands(std::vector<Expression*>&& list, WasmType type) {
|
||||
for (size_t i = 0; i < list.size(); ++i) {
|
||||
auto* elem = list[i];
|
||||
if (isUnreachable(elem)) {
|
||||
auto* replacement = elem;
|
||||
if (i > 0) {
|
||||
auto* block = getModule()->allocator.alloc<Block>();
|
||||
for (size_t j = 0; j < i; ++j) {
|
||||
block->list.push_back(drop(list[j]));
|
||||
}
|
||||
block->list.push_back(list[i]);
|
||||
block->finalize(type);
|
||||
replacement = block;
|
||||
}
|
||||
replaceCurrent(replacement);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visitSetLocal(SetLocal* curr) {
|
||||
blockifyReachableOperands({ curr->value }, curr->type);
|
||||
}
|
||||
|
||||
void visitSetGlobal(SetGlobal* curr) {
|
||||
blockifyReachableOperands({ curr->value }, curr->type);
|
||||
}
|
||||
|
||||
void visitLoad(Load* curr) {
|
||||
blockifyReachableOperands({ curr->ptr }, curr->type);
|
||||
}
|
||||
|
||||
void visitStore(Store* curr) {
|
||||
blockifyReachableOperands({ curr->ptr, curr->value }, curr->type);
|
||||
}
|
||||
|
||||
void visitAtomicRMW(AtomicRMW* curr) {
|
||||
blockifyReachableOperands({ curr->ptr, curr->value }, curr->type);
|
||||
}
|
||||
|
||||
void visitAtomicCmpxchg(AtomicCmpxchg* curr) {
|
||||
blockifyReachableOperands({ curr->ptr, curr->expected, curr->replacement }, curr->type);
|
||||
}
|
||||
|
||||
void visitUnary(Unary* curr) {
|
||||
blockifyReachableOperands({ curr->value }, curr->type);
|
||||
}
|
||||
|
||||
void visitBinary(Binary* curr) {
|
||||
blockifyReachableOperands({ curr->left, curr->right }, curr->type);
|
||||
}
|
||||
|
||||
void visitSelect(Select* curr) {
|
||||
blockifyReachableOperands({ curr->ifTrue, curr->ifFalse, curr->condition }, curr->type);
|
||||
}
|
||||
|
||||
void visitDrop(Drop* curr) {
|
||||
blockifyReachableOperands({ curr->value }, curr->type);
|
||||
}
|
||||
|
||||
void visitHost(Host* curr) {
|
||||
handleCall(curr);
|
||||
}
|
||||
|
||||
void visitFunction(Function* curr) {
|
||||
assert(reachableBreaks.size() == 0);
|
||||
}
|
||||
};
|
||||
|
||||
Pass *createDeadCodeEliminationPass() {
|
||||
return new DeadCodeElimination();
|
||||
}
|
||||
|
||||
} // namespace wasm
|
@ -1,186 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//
|
||||
// Removes duplicate functions. That can happen due to C++ templates,
|
||||
// and also due to types being different at the source level, but
|
||||
// identical when finally lowered into concrete wasm code.
|
||||
//
|
||||
|
||||
#include "wasm.h"
|
||||
#include "pass.h"
|
||||
#include "ir/utils.h"
|
||||
#include "support/hash.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
struct FunctionHasher : public WalkerPass<PostWalker<FunctionHasher>> {
|
||||
bool isFunctionParallel() override { return true; }
|
||||
|
||||
FunctionHasher(std::map<Function*, uint32_t>* output) : output(output) {}
|
||||
|
||||
FunctionHasher* create() override {
|
||||
return new FunctionHasher(output);
|
||||
}
|
||||
|
||||
void doWalkFunction(Function* func) {
|
||||
assert(digest == 0);
|
||||
hash(func->getNumParams());
|
||||
for (auto type : func->params) hash(type);
|
||||
hash(func->getNumVars());
|
||||
for (auto type : func->vars) hash(type);
|
||||
hash(func->result);
|
||||
hash64(func->type.is() ? uint64_t(func->type.str) : uint64_t(0));
|
||||
hash(ExpressionAnalyzer::hash(func->body));
|
||||
output->at(func) = digest;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<Function*, uint32_t>* output;
|
||||
uint32_t digest = 0;
|
||||
|
||||
void hash(uint32_t hash) {
|
||||
digest = rehash(digest, hash);
|
||||
}
|
||||
void hash64(uint64_t hash) {
|
||||
digest = rehash(rehash(digest, uint32_t(hash >> 32)), uint32_t(hash));
|
||||
};
|
||||
};
|
||||
|
||||
struct FunctionReplacer : public WalkerPass<PostWalker<FunctionReplacer>> {
|
||||
bool isFunctionParallel() override { return true; }
|
||||
|
||||
FunctionReplacer(std::map<Name, Name>* replacements) : replacements(replacements) {}
|
||||
|
||||
FunctionReplacer* create() override {
|
||||
return new FunctionReplacer(replacements);
|
||||
}
|
||||
|
||||
void visitCall(Call* curr) {
|
||||
auto iter = replacements->find(curr->target);
|
||||
if (iter != replacements->end()) {
|
||||
curr->target = iter->second;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<Name, Name>* replacements;
|
||||
};
|
||||
|
||||
struct DuplicateFunctionElimination : public Pass {
|
||||
void run(PassRunner* runner, Module* module) override {
|
||||
while (1) {
|
||||
// Hash all the functions
|
||||
hashes.clear();
|
||||
for (auto& func : module->functions) {
|
||||
hashes[func.get()] = 0; // ensure an entry for each function - we must not modify the map shape in parallel, just the values
|
||||
}
|
||||
PassRunner hasherRunner(module);
|
||||
hasherRunner.setIsNested(true);
|
||||
hasherRunner.add<FunctionHasher>(&hashes);
|
||||
hasherRunner.run();
|
||||
// Find hash-equal groups
|
||||
std::map<uint32_t, std::vector<Function*>> hashGroups;
|
||||
for (auto& func : module->functions) {
|
||||
hashGroups[hashes[func.get()]].push_back(func.get());
|
||||
}
|
||||
// Find actually equal functions and prepare to replace them
|
||||
std::map<Name, Name> replacements;
|
||||
std::set<Name> duplicates;
|
||||
for (auto& pair : hashGroups) {
|
||||
auto& group = pair.second;
|
||||
if (group.size() == 1) continue;
|
||||
// pick a base for each group, and try to replace everyone else to it. TODO: multiple bases per hash group, for collisions
|
||||
#if 0
|
||||
// for comparison purposes, pick in a deterministic way based on the names
|
||||
Function* base = nullptr;
|
||||
for (auto* func : group) {
|
||||
if (!base || strcmp(func->name.str, base->name.str) < 0) {
|
||||
base = func;
|
||||
}
|
||||
}
|
||||
#else
|
||||
Function* base = group[0];
|
||||
#endif
|
||||
for (auto* func : group) {
|
||||
if (func != base && equal(func, base)) {
|
||||
replacements[func->name] = base->name;
|
||||
duplicates.insert(func->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
// perform replacements
|
||||
if (replacements.size() > 0) {
|
||||
// remove the duplicates
|
||||
auto& v = module->functions;
|
||||
v.erase(std::remove_if(v.begin(), v.end(), [&](const std::unique_ptr<Function>& curr) {
|
||||
return duplicates.count(curr->name) > 0;
|
||||
}), v.end());
|
||||
module->updateMaps();
|
||||
// replace direct calls
|
||||
PassRunner replacerRunner(module);
|
||||
replacerRunner.setIsNested(true);
|
||||
replacerRunner.add<FunctionReplacer>(&replacements);
|
||||
replacerRunner.run();
|
||||
// replace in table
|
||||
for (auto& segment : module->table.segments) {
|
||||
for (auto& name : segment.data) {
|
||||
auto iter = replacements.find(name);
|
||||
if (iter != replacements.end()) {
|
||||
name = iter->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
// replace in start
|
||||
if (module->start.is()) {
|
||||
auto iter = replacements.find(module->start);
|
||||
if (iter != replacements.end()) {
|
||||
module->start = iter->second;
|
||||
}
|
||||
}
|
||||
// replace in exports
|
||||
for (auto& exp : module->exports) {
|
||||
auto iter = replacements.find(exp->value);
|
||||
if (iter != replacements.end()) {
|
||||
exp->value = iter->second;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<Function*, uint32_t> hashes;
|
||||
|
||||
bool equal(Function* left, Function* right) {
|
||||
if (left->getNumParams() != right->getNumParams()) return false;
|
||||
if (left->getNumVars() != right->getNumVars()) return false;
|
||||
for (Index i = 0; i < left->getNumLocals(); i++) {
|
||||
if (left->getLocalType(i) != right->getLocalType(i)) return false;
|
||||
}
|
||||
if (left->result != right->result) return false;
|
||||
if (left->type != right->type) return false;
|
||||
return ExpressionAnalyzer::equal(left->body, right->body);
|
||||
}
|
||||
};
|
||||
|
||||
Pass *createDuplicateFunctionEliminationPass() {
|
||||
return new DuplicateFunctionElimination();
|
||||
}
|
||||
|
||||
} // namespace wasm
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Removes code from all functions but one, leaving a valid module
|
||||
// with (mostly) just the code you want to debug (function-parallel,
|
||||
// non-lto) passes on.
|
||||
|
||||
#include "wasm.h"
|
||||
#include "pass.h"
|
||||
|
||||
namespace wasm {
|
||||
|
||||
|
||||
struct ExtractFunction : public Pass {
|
||||
void run(PassRunner* runner, Module* module) override {
|
||||
auto* leave = getenv("BYN_LEAVE");
|
||||
if (!leave) {
|
||||
std::cerr << "usage: set BYN_LEAVE in the env\n";
|
||||
abort();
|
||||
}
|
||||
Name LEAVE(leave);
|
||||
std::cerr << "keeping " << LEAVE << "\n";
|
||||
for (auto& func : module->functions) {
|
||||
if (func->name != LEAVE) {
|
||||
// wipe out the body
|
||||
func->body = module->allocator.alloc<Unreachable>();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// declare pass
|
||||
|
||||
Pass *createExtractFunctionPass() {
|
||||
return new ExtractFunction();
|
||||
}
|
||||
|
||||
} // namespace wasm
|
||||
|
@ -1,351 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 WebAssembly Community Group participants
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//
|
||||
// Flattens code, removing nesting.e.g. an if return value would be
|
||||
// converted to a local
|
||||
//
|
||||
// (i32.add
|
||||
// (if (..condition..)
|
||||
// (..if true..)
|
||||
// (..if false..)
|
||||
// )
|
||||
// (i32.const 1)
|
||||
// )
|
||||
// =>
|
||||
// (if (..condition..)
|
||||
// (set_local $temp
|
||||
// (..if true..)
|
||||
// )
|
||||
// (set_local $temp
|
||||
// (..if false..)
|
||||
// )
|
||||
// )
|
||||
// (i32.add
|
||||
// (get_local $temp)
|
||||
// (i32.const 1)
|
||||
// )
|
||||
//
|
||||
// Formally, this pass flattens in the precise sense of
|
||||
// making the AST have these properties:
|
||||
//
|
||||
// 1. The operands of an instruction must be a get_local or a const.
|
||||
// anything else is written to a local earlier.
|
||||
// 2. Disallow block, loop, and if return values, i.e., do not use
|
||||
// control flow to pass around values.
|
||||
// 3. Disallow tee_local, setting a local is always done in a set_local
|
||||
// on a non-nested-expression location.
|
||||
//
|
||||
|
||||
#include <wasm.h>
|
||||
#include <pass.h>
|
||||
#include <wasm-builder.h>
|
||||
#include <ir/utils.h>
|
||||
#include <ir/effects.h>
|
||||
|
||||
namespace wasm {
|
||||
|
||||
// We use the following algorithm: we maintain a list of "preludes", code
|
||||
// that runs right before an expression. When we visit an expression we
|
||||
// must handle it and its preludes. If the expression has side effects,
|
||||
// we reduce it to a get_local and add a prelude for that. We then handle
|
||||
// the preludes, by moving them to the parent or handling them directly.
|
||||
// we can move them to the parent if the parent is not a control flow
|
||||
// structure. Otherwise, if the parent is a control flow structure, it
|
||||
// will incorporate the preludes of its children accordingly.
|
||||
// As a result, when we reach a node, we know its children have no
|
||||
// side effects (they have been moved to a prelude), or we are a
|
||||
// control flow structure (which allows children with side effects,
|
||||
// e.g. a return as a block element).
|
||||
// Once exception is that we allow an (unreachable) node, which is used
|
||||
// when we move something unreachable to another place, and need a
|
||||
// placeholder. We will never reach that (unreachable) anyhow
|
||||
struct Flatten : public WalkerPass<ExpressionStackWalker<Flatten, UnifiedExpressionVisitor<Flatten>>> {
|
||||
bool isFunctionParallel() override { return true; }
|
||||
|
||||
Pass* create() override { return new Flatten; }
|
||||
|
||||
// For each expression, a bunch of expressions that should execute right before it
|
||||
std::unordered_map<Expression*, std::vector<Expression*>> preludes;
|
||||
|
||||
// Break values are sent through a temp local
|
||||
std::unordered_map<Name, Index> breakTemps;
|
||||
|
||||
void visitExpression(Expression* curr) {
|
||||
std::vector<Expression*> ourPreludes;
|
||||
Builder builder(*getModule());
|
||||
|
||||
if (isControlFlowStructure(curr)) {
|
||||
// handle control flow explicitly. our children do not have control flow,
|
||||
// but they do have preludes which we need to set up in the right place
|
||||
assert(preludes.find(curr) == preludes.end()); // no one should have given us preludes, they are on the children
|
||||
if (auto* block = curr->dynCast<Block>()) {
|
||||
// make a new list, where each item's preludes are added before it
|
||||
ExpressionList newList(getModule()->allocator);
|
||||
for (auto* item : block->list) {
|
||||
auto iter = preludes.find(item);
|
||||
if (iter != preludes.end()) {
|
||||
auto& itemPreludes = iter->second;
|
||||
for (auto* prelude : itemPreludes) {
|
||||
newList.push_back(prelude);
|
||||
}
|
||||
itemPreludes.clear();
|
||||
}
|
||||
newList.push_back(item);
|
||||
}
|
||||
block->list.swap(newList);
|
||||
// remove a block return value
|
||||
auto type = block->type;
|
||||
if (isConcreteWasmType(type)) {
|
||||
// if there is a temp index for breaking to the block, use that
|
||||
Index temp;
|
||||
auto iter = breakTemps.find(block->name);
|
||||
if (iter != breakTemps.end()) {
|
||||
temp = iter->second;
|
||||
} else {
|
||||
temp = builder.addVar(getFunction(), type);
|
||||
}
|
||||
auto*& last = block->list.back();
|
||||
if (isConcreteWasmType(last->type)) {
|
||||
last = builder.makeSetLocal(temp, last);
|
||||
}
|
||||
block->finalize(none);
|
||||
// and we leave just a get of the value
|
||||
auto* rep = builder.makeGetLocal(temp, type);
|
||||
replaceCurrent(rep);
|
||||
// the whole block is now a prelude
|
||||
ourPreludes.push_back(block);
|
||||
}
|
||||
// the block now has no return value, and may have become unreachable
|
||||
block->finalize(none);
|
||||
} else if (auto* iff = curr->dynCast<If>()) {
|
||||
// condition preludes go before the entire if
|
||||
auto* rep = getPreludesWithExpression(iff->condition, iff);
|
||||
// arm preludes go in the arms. we must also remove an if value
|
||||
auto* originalIfTrue = iff->ifTrue;
|
||||
auto* originalIfFalse = iff->ifFalse;
|
||||
auto type = iff->type;
|
||||
Expression* prelude = nullptr;
|
||||
if (isConcreteWasmType(type)) {
|
||||
Index temp = builder.addVar(getFunction(), type);
|
||||
if (isConcreteWasmType(iff->ifTrue->type)) {
|
||||
iff->ifTrue = builder.makeSetLocal(temp, iff->ifTrue);
|
||||
}
|
||||
if (iff->ifFalse && isConcreteWasmType(iff->ifFalse->type)) {
|
||||
iff->ifFalse = builder.makeSetLocal(temp, iff->ifFalse);
|
||||
}
|
||||
// the whole if (+any preludes from the condition) is now a prelude
|
||||
prelude = rep;
|
||||
// and we leave just a get of the value
|
||||
rep = builder.makeGetLocal(temp, type);
|
||||
}
|
||||
iff->ifTrue = getPreludesWithExpression(originalIfTrue, iff->ifTrue);
|
||||
if (iff->ifFalse) iff->ifFalse = getPreludesWithExpression(originalIfFalse, iff->ifFalse);
|
||||
iff->finalize();
|
||||
if (prelude) {
|
||||
ReFinalizeNode().visit(prelude);
|
||||
ourPreludes.push_back(prelude);
|
||||
}
|
||||
replaceCurrent(rep);
|
||||
} else if (auto* loop = curr->dynCast<Loop>()) {
|
||||
// remove a loop value
|
||||
Expression* rep = loop;
|
||||
auto* originalBody = loop->body;
|
||||
auto type = loop->type;
|
||||
if (isConcreteWasmType(type)) {
|
||||
Index temp = builder.addVar(getFunction(), type);
|
||||
loop->body = builder.makeSetLocal(temp, loop->body);
|
||||
// and we leave just a get of the value
|
||||
rep = builder.makeGetLocal(temp, type);
|
||||
// the whole if is now a prelude
|
||||
ourPreludes.push_back(loop);
|
||||
loop->type = none;
|
||||
}
|
||||
loop->body = getPreludesWithExpression(originalBody, loop->body);
|
||||
loop->finalize();
|
||||
replaceCurrent(rep);
|
||||
} else {
|
||||
WASM_UNREACHABLE();
|
||||
}
|
||||
} else {
|
||||
// for anything else, there may be existing preludes
|
||||
auto iter = preludes.find(curr);
|
||||
if (iter != preludes.end()) {
|
||||
ourPreludes.swap(iter->second);
|
||||
}
|
||||
// special handling
|
||||
if (auto* set = curr->dynCast<SetLocal>()) {
|
||||
if (set->isTee()) {
|
||||
// we disallow tee_local
|
||||
if (set->value->type == unreachable) {
|
||||
replaceCurrent(set->value); // trivial, no set happens
|
||||
} else {
|
||||
// use a set in a prelude + a get
|
||||
set->setTee(false);
|
||||
ourPreludes.push_back(set);
|
||||
replaceCurrent(builder.makeGetLocal(set->index, set->value->type));
|
||||
}
|
||||
}
|
||||
} else if (auto* br = curr->dynCast<Break>()) {
|
||||
if (br->value) {
|
||||
auto type = br->value->type;
|
||||
if (isConcreteWasmType(type)) {
|
||||
// we are sending a value. use a local instead
|
||||
Index temp = getTempForBreakTarget(br->name, type);
|
||||
ourPreludes.push_back(builder.makeSetLocal(temp, br->value));
|
||||
if (br->condition) {
|
||||
// the value must also flow out
|
||||
ourPreludes.push_back(br);
|
||||
if (isConcreteWasmType(br->type)) {
|
||||
replaceCurrent(builder.makeGetLocal(temp, type));
|
||||
} else {
|
||||
assert(br->type == unreachable);
|
||||
replaceCurrent(builder.makeUnreachable());
|
||||
}
|
||||
}
|
||||
br->value = nullptr;
|
||||
br->finalize();
|
||||
} else {
|
||||
assert(type == unreachable);
|
||||
// we don't need the br at all
|
||||
replaceCurrent(br->value);
|
||||
}
|
||||
}
|
||||
} else if (auto* sw = curr->dynCast<Switch>()) {
|
||||
if (sw->value) {
|
||||
auto type = sw->value->type;
|
||||
if (isConcreteWasmType(type)) {
|
||||
// we are sending a value. use a local instead
|
||||
Index temp = builder.addVar(getFunction(), type);
|
||||
ourPreludes.push_back(builder.makeSetLocal(temp, sw->value));
|
||||
// we don't know which break target will be hit - assign to them all
|
||||
std::set<Name> names;
|
||||
for (auto target : sw->targets) {
|
||||
names.insert(target);
|
||||
}
|
||||
names.insert(sw->default_);
|
||||
for (auto name : names) {
|
||||
ourPreludes.push_back(builder.makeSetLocal(
|
||||
getTempForBreakTarget(name, type),
|
||||
builder.makeGetLocal(temp, type)
|
||||
));
|
||||
}
|
||||
sw->value = nullptr;
|
||||
sw->finalize();
|
||||
} else {
|
||||
assert(type == unreachable);
|
||||
// we don't need the br at all
|
||||
replaceCurrent(sw->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// continue for general handling of everything, control flow or otherwise
|
||||
curr = getCurrent(); // we may have replaced it
|
||||
// we have changed children
|
||||
ReFinalizeNode().visit(curr);
|
||||
// handle side effects and control flow, things we need to be
|
||||
// in the prelude. note that we must handle anything here, not just
|
||||
// side effects, as a sibling after us may have side effect for us,
|
||||
// and thus we need to move in anticipation of that (e.g., we are
|
||||
// a get, and a later sibling is a tee - if just the tee moves,
|
||||
// that is bade) TODO optimize
|
||||
if (isControlFlowStructure(curr) || EffectAnalyzer(getPassOptions(), curr).hasAnything()) {
|
||||
// we need to move the side effects to the prelude
|
||||
if (curr->type == unreachable) {
|
||||
ourPreludes.push_back(curr);
|
||||
replaceCurrent(builder.makeUnreachable());
|
||||
} else if (curr->type == none) {
|
||||
if (!curr->is<Nop>()) {
|
||||
ourPreludes.push_back(curr);
|
||||
replaceCurrent(builder.makeNop());
|
||||
}
|
||||
} else {
|
||||
// use a local
|
||||
auto type = curr->type;
|
||||
Index temp = builder.addVar(getFunction(), type);
|
||||
ourPreludes.push_back(builder.makeSetLocal(temp, curr));
|
||||
replaceCurrent(builder.makeGetLocal(temp, type));
|
||||
}
|
||||
}
|
||||
// next, finish up: migrate our preludes if we can
|
||||
if (!ourPreludes.empty()) {
|
||||
auto* parent = getParent();
|
||||
if (parent && !isControlFlowStructure(parent)) {
|
||||
auto& parentPreludes = preludes[parent];
|
||||
for (auto* prelude : ourPreludes) {
|
||||
parentPreludes.push_back(prelude);
|
||||
}
|
||||
} else {
|
||||
// keep our preludes, parent will handle them
|
||||
preludes[getCurrent()].swap(ourPreludes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visitFunction(Function* curr) {
|
||||
auto* originalBody = curr->body;
|
||||
// if the body is a block with a result, turn that into a return
|
||||
if (isConcreteWasmType(curr->body->type)) {
|
||||
curr->body = Builder(*getModule()).makeReturn(curr->body);
|
||||
}
|
||||
// the body may have preludes
|
||||
curr->body = getPreludesWithExpression(originalBody, curr->body);
|
||||
}
|
||||
|
||||
private:
|
||||
bool isControlFlowStructure(Expression* curr) {
|
||||
return curr->is<Block>() || curr->is<If>() || curr->is<Loop>();
|
||||
}
|
||||
|
||||
// gets an expression, either by itself, or in a block with its
|
||||
// preludes (which we use up) before it
|
||||
Expression* getPreludesWithExpression(Expression* curr) {
|
||||
return getPreludesWithExpression(curr, curr);
|
||||
}
|
||||
|
||||
// gets an expression, either by itself, or in a block with some
|
||||
// preludes (which we use up) for another expression before it
|
||||
Expression* getPreludesWithExpression(Expression* preluder, Expression* after) {
|
||||
auto iter = preludes.find(preluder);
|
||||
if (iter == preludes.end()) return after;
|
||||
// we have preludes
|
||||
auto& thePreludes = iter->second;
|
||||
auto* ret = Builder(*getModule()).makeBlock(thePreludes);
|
||||
thePreludes.clear();
|
||||
ret->list.push_back(after);
|
||||
ret->finalize();
|
||||
return ret;
|
||||
}
|
||||
|
||||
// get the temp local to be used for breaks to that target. allocates
|
||||
// one if there isn't one yet
|
||||
Index getTempForBreakTarget(Name name, WasmType type) {
|
||||
auto iter = breakTemps.find(name);
|
||||
if (iter != breakTemps.end()) {
|
||||
return iter->second;
|
||||
} else {
|
||||
return breakTemps[name] = Builder(*getModule()).addVar(getFunction(), type);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Pass *createFlattenPass() {
|
||||
return new Flatten();
|
||||
}
|
||||
|
||||
} // namespace wasm
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user