1
1
mirror of https://github.com/tweag/asterius.git synced 2024-09-21 05:48:04 +03:00

Fix binaryen marshalling for BinaryenSetFunctionTable (+2 squashed commit)

Squashed commit:

[6d4708d] no message

[565c21b] no message
This commit is contained in:
Shao Cheng 2018-09-02 08:37:44 +08:00
parent 9da177941c
commit 90fb707f1e
176 changed files with 20372 additions and 2689 deletions

View File

@ -1,4 +1,4 @@
FROM ubuntu:bionic
FROM debian:unstable-slim
COPY asterius /root/asterius
COPY binaryen /root/binaryen
@ -18,16 +18,16 @@ RUN \
curl \
gnupg && \
curl https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - && \
echo "deb https://deb.nodesource.com/node_10.x bionic main" >> /etc/apt/sources.list && \
echo "deb https://deb.nodesource.com/node_10.x sid main" >> /etc/apt/sources.list && \
apt update && \
apt dist-upgrade -y && \
apt install -y \
autoconf \
automake \
cmake \
g++ \
libffi-dev \
libgmp-dev \
libncurses5-dev \
libncurses-dev \
libnuma-dev \
make \
nodejs \

View File

@ -12,7 +12,7 @@ build_script:
- mklink /D %LOCALAPPDATA%\\Programs\\stack\\x86_64-windows\\msys2-20161025 C:\\msys64
- copy %LOCALAPPDATA%\\Programs\\stack\\x86_64-windows\\ghc-8.7.*.installed %LOCALAPPDATA%\\Programs\\stack\\x86_64-windows\\msys2-20161025.installed
- stack --no-terminal exec pacman -- -S autoconf coreutils make mingw-w64-x86_64-cmake mingw-w64-x86_64-gcc --needed --noconfirm --noprogressbar
- stack --no-terminal build --haddock --test --no-run-tests
- stack --no-terminal build --test --no-run-tests
- stack --no-terminal exec ahc-boot
- stack --no-terminal exec ahc-boot -- --rts-only
- stack --no-terminal test asterius:fib --test-arguments="--debug"

View File

@ -415,14 +415,10 @@ marshalGlobal pool m k Global {..} = do
kp <- marshalSBS pool k
c_BinaryenAddGlobal m kp (marshalValueType valueType) (marshalBool mutable) i
marshalFunctionTable ::
Pool
-> BinaryenModuleRef
-> M.Map SBS.ShortByteString BinaryenFunctionRef
-> FunctionTable
-> IO ()
marshalFunctionTable pool m fps FunctionTable {..} = do
(fnp, fnl) <- marshalV pool $ map (fps !) functionNames
marshalFunctionTable :: Pool -> BinaryenModuleRef -> FunctionTable -> IO ()
marshalFunctionTable pool m FunctionTable {..} = do
func_name_ptrs <- for functionNames $ marshalSBS pool
(fnp, fnl) <- marshalV pool func_name_ptrs
c_BinaryenSetFunctionTable m fnp fnl
marshalMemory :: Pool -> BinaryenModuleRef -> Memory -> IO ()
@ -478,7 +474,7 @@ marshalModule pool Module {..} = do
forM_ globalExports $ marshalGlobalExport pool m
for_ (M.toList globalMap) $ uncurry (marshalGlobal pool m)
case functionTable of
Just ft -> marshalFunctionTable pool m fps ft
Just ft -> marshalFunctionTable pool m ft
_ -> pure ()
case memory of
Just mem -> marshalMemory pool m mem

View File

@ -0,0 +1,17 @@
This document describes changes between tagged Binaryen versions.
To browse or download snapshots of old tagged versions, visit
https://github.com/WebAssembly/binaryen/releases.
Not all changes are documented here. In particular, new features, user-oriented
fixes, options, command-line parameters, usage changes, deprecations,
significant internal modifications and optimizations etc. generally deserve a
mention. To examine the full set of changes between versions, visit the link to
full changeset diff at the end of each section.
Current Trunk
-------------
### BREAKING CHANGES
- `BinaryenSetFunctionTable` in the C API no longer accepts an array of functions, instead it accepts an array of function names, `const char** funcNames`. Previously, you could not include imported functions because they are of type `BinaryenImportRef` instead of `BinaryenFunctionRef`. [#1650](https://github.com/WebAssembly/binaryen/pull/1650)

View File

@ -170,10 +170,10 @@ INSTALL(FILES src/binaryen-c.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
# if binaryen.js and wasm.js were built (using "./build-js.sh", currently
# optional), install them
IF(EXISTS "${CMAKE_SOURCE_DIR}/bin/binaryen.js")
IF(EXISTS "${PROJECT_SOURCE_DIR}/bin/binaryen.js")
INSTALL(FILES bin/binaryen.js DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME})
ENDIF()
IF(EXISTS "${CMAKE_SOURCE_DIR}/bin/wasm.js")
IF(EXISTS "${PROJECT_SOURCE_DIR}/bin/wasm.js")
INSTALL(FILES bin/wasm.js DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME})
ENDIF()
@ -227,15 +227,15 @@ 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
SET(wasm2js_SOURCES
src/tools/wasm2js.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})
ADD_EXECUTABLE(wasm2js
${wasm2js_SOURCES})
TARGET_LINK_LIBRARIES(wasm2js passes wasm asmjs emscripten-optimizer ir cfg support)
SET_PROPERTY(TARGET wasm2js PROPERTY CXX_STANDARD 11)
SET_PROPERTY(TARGET wasm2js PROPERTY CXX_STANDARD_REQUIRED ON)
INSTALL(TARGETS wasm2js DESTINATION ${CMAKE_INSTALL_BINDIR})
SET(wasm-emscripten-finalize_SOURCES
src/tools/wasm-emscripten-finalize.cpp

View File

@ -12,7 +12,7 @@ Compilers built using Binaryen include
* [`asm2wasm`](https://github.com/WebAssembly/binaryen/blob/master/src/asm2wasm.h) which compiles asm.js to WebAssembly
* [`AssemblyScript`](https://github.com/AssemblyScript/assemblyscript) which compiles TypeScript to Binaryen IR
* [`wasm2asm`](https://github.com/WebAssembly/binaryen/blob/master/src/wasm2asm.h) which compiles WebAssembly to asm.js
* [`wasm2js`](https://github.com/WebAssembly/binaryen/blob/master/src/wasm2js.h) which compiles WebAssembly to JS
* [`Asterius`](https://github.com/tweag/asterius) which compiles Haskell to WebAssembly
Binaryen also provides a set of **toolchain utilities** that can
@ -64,7 +64,7 @@ This repository contains code that builds the following tools in `bin/`:
* **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).
* **wasm2js**: A WebAssembly-to-JS compiler (still experimental).
* **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-emscripten-finalize**: Takes a wasm binary produced by llvm+lld and performs emscripten-specific passes over it.
@ -212,11 +212,11 @@ This is separate from that. `asm2wasm` focuses on compiling asm.js to WebAssembl
* 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`.
Experimentation with this is happening, in `wasm2js`.
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.
We just have to do more work on `wasm2js` and see how efficient we can make it.
* Can `asm2wasm` compile any asm.js code?

View File

@ -22,10 +22,10 @@ import sys
from scripts.test.support import run_command, split_wast, node_test_glue, node_has_webassembly
from scripts.test.shared import (
ASM2WASM, MOZJS, NODEJS, WASM_OPT, WASM_AS, WASM_DIS,
WASM_CTOR_EVAL, WASM_MERGE, WASM_REDUCE, WASM2ASM, WASM_METADCE,
WASM_CTOR_EVAL, WASM_MERGE, WASM_REDUCE, WASM2JS, WASM_METADCE,
WASM_EMSCRIPTEN_FINALIZE, BINARYEN_INSTALL_DIR, BINARYEN_JS,
files_with_pattern, has_shell_timeout, options)
from scripts.test.wasm2asm import tests, spec_tests, extra_wasm2asm_tests, assert_tests, wasm2asm_dir
from scripts.test.wasm2js import tests, spec_tests, extra_wasm2js_tests, assert_tests, wasm2js_dir, wasm2js_blacklist
def update_asm_js_tests():
@ -225,7 +225,7 @@ def update_example_tests():
print os.getcwd()
subprocess.check_call(extra)
# Link against the binaryen C library DSO, using rpath
cmd = ['example.o', '-L' + libdir, '-lbinaryen', '-Wl,-rpath=' + os.path.abspath(libdir)] + cmd
cmd = ['example.o', '-L' + libdir, '-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(' '):
@ -339,25 +339,28 @@ def update_ctor_eval_tests():
o.write(actual)
def update_wasm2asm_tests():
print '\n[ checking wasm2asm ]\n'
for wasm in tests + spec_tests + extra_wasm2asm_tests:
def update_wasm2js_tests():
print '\n[ checking wasm2js ]\n'
for wasm in tests + spec_tests + extra_wasm2js_tests:
if not wasm.endswith('.wast'):
continue
asm = os.path.basename(wasm).replace('.wast', '.2asm.js')
expected_file = os.path.join(wasm2asm_dir, asm)
if os.path.basename(wasm) in wasm2js_blacklist:
continue
# we run wasm2asm on tests and spec tests only if the output
asm = os.path.basename(wasm).replace('.wast', '.2asm.js')
expected_file = os.path.join(wasm2js_dir, asm)
# we run wasm2js on tests and spec tests only if the output
# exists - only some work so far. the tests in extra are in
# the test/wasm2asm dir and so are specific to wasm2asm, and
# the test/wasm2js dir and so are specific to wasm2js, and
# we run all of those.
if wasm not in extra_wasm2asm_tests and not os.path.exists(expected_file):
if wasm not in extra_wasm2js_tests and not os.path.exists(expected_file):
continue
print '..', wasm
cmd = WASM2ASM + [os.path.join('test', wasm)]
cmd = WASM2JS + [os.path.join('test', wasm)]
out = run_command(cmd)
with open(expected_file, 'w') as o:
o.write(out)
@ -370,7 +373,7 @@ def update_wasm2asm_tests():
asserts_expected_file = os.path.join('test', asserts)
traps_expected_file = os.path.join('test', traps)
cmd = WASM2ASM + [os.path.join(wasm2asm_dir, wasm), '--allow-asserts']
cmd = WASM2JS + [os.path.join(wasm2js_dir, wasm), '--allow-asserts']
out = run_command(cmd)
with open(asserts_expected_file, 'w') as o:
o.write(out)
@ -423,7 +426,7 @@ def main():
update_wasm_merge_tests()
update_binaryen_js_tests()
update_ctor_eval_tests()
update_wasm2asm_tests()
update_wasm2js_tests()
update_metadce_tests()
update_reduce_tests()

File diff suppressed because one or more lines are too long

View File

@ -93,6 +93,7 @@ echo "building shared bitcode"
$BINARYEN_SRC/passes/CodeFolding.cpp \
$BINARYEN_SRC/passes/CodePushing.cpp \
$BINARYEN_SRC/passes/ConstHoisting.cpp \
$BINARYEN_SRC/passes/DataFlowOpts.cpp \
$BINARYEN_SRC/passes/DeadCodeElimination.cpp \
$BINARYEN_SRC/passes/DuplicateFunctionElimination.cpp \
$BINARYEN_SRC/passes/ExtractFunction.cpp \
@ -129,6 +130,7 @@ echo "building shared bitcode"
$BINARYEN_SRC/passes/ReReloop.cpp \
$BINARYEN_SRC/passes/SafeHeap.cpp \
$BINARYEN_SRC/passes/SimplifyLocals.cpp \
$BINARYEN_SRC/passes/Souperify.cpp \
$BINARYEN_SRC/passes/SpillPointers.cpp \
$BINARYEN_SRC/passes/SSAify.cpp \
$BINARYEN_SRC/passes/StackIR.cpp \

View File

@ -31,7 +31,7 @@ from scripts.test.shared import (
import scripts.test.asm2wasm as asm2wasm
import scripts.test.lld as lld
import scripts.test.wasm2asm as wasm2asm
import scripts.test.wasm2js as wasm2js
if options.interpreter:
print '[ using wasm interpreter at "%s" ]' % options.interpreter
@ -239,6 +239,21 @@ def run_crash_tests():
run_command(cmd, expected_err='parse exception:', err_contains=True, expected_status=1)
def run_dylink_tests():
print "\n[ we emit dylink sections properly... ]\n"
for t in os.listdir(options.binaryen_test):
if t.startswith('dylib') and t.endswith('.wasm'):
print '..', t
t = os.path.join(options.binaryen_test, t)
cmd = WASM_OPT + [t, '-o', 'a.wasm']
run_command(cmd)
with open('a.wasm') as output:
index = output.read().find('dylink')
print ' ', index
assert index == 11, 'dylink section must be first, right after the magic number etc.'
def run_ctor_eval_tests():
print '\n[ checking wasm-ctor-eval... ]\n'
@ -547,7 +562,7 @@ def run_gcc_tests():
print 'build: ', ' '.join(extra)
subprocess.check_call(extra)
# Link against the binaryen C library DSO, using an executable-relative rpath
cmd = ['example.o', '-L' + os.path.join(options.binaryen_bin, '..', 'lib'), '-lbinaryen'] + cmd + ['-Wl,-rpath=$ORIGIN/../lib']
cmd = ['example.o', '-L' + os.path.join(options.binaryen_bin, '..', 'lib'), '-lbinaryen'] + cmd + ['-Wl,-rpath,$ORIGIN/../lib']
else:
continue
print ' ', t, src, expected
@ -640,6 +655,7 @@ def main():
run_wasm_dis_tests()
run_wasm_merge_tests()
run_crash_tests()
run_dylink_tests()
run_ctor_eval_tests()
run_wasm_metadce_tests()
if has_shell_timeout():
@ -648,7 +664,7 @@ def main():
run_spec_tests()
run_binaryen_js_tests()
lld.test_wasm_emscripten_finalize()
wasm2asm.test_wasm2asm()
wasm2js.test_wasm2js()
run_validator_tests()
if has_vanilla_emcc and has_vanilla_llvm and 0:
run_vanilla_tests()

View File

@ -0,0 +1,32 @@
// originally lifted from https://nodejs.org/api/esm.html
import path from 'path';
import process from 'process';
import Module from 'module';
const builtins = Module.builtinModules;
const baseURL = new URL('file://');
baseURL.pathname = `${process.cwd()}/`;
export function resolve(specifier, parentModuleURL = baseURL, defaultResolve) {
if (builtins.includes(specifier)) {
return {
url: specifier,
format: 'builtin'
};
}
// Resolve the 'spectest' module to our special module which has some builtins
if (specifier == 'spectest') {
const resolved = new URL('./scripts/test/spectest.js', parentModuleURL);
return {
url: resolved.href,
format: 'esm'
};
}
const resolved = new URL(specifier, parentModuleURL);
return {
url: resolved.href,
format: 'esm'
};
}

View File

@ -166,7 +166,7 @@ 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')]
WASM2JS = [os.path.join(options.binaryen_bin, 'wasm2js')]
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')]

View File

@ -0,0 +1,3 @@
export function print(...args) {
console.log(...args);
}

View File

@ -147,7 +147,7 @@ def split_wast(wastFile):
def run_command(cmd, expected_status=0, stderr=None,
expected_err=None, err_contains=False):
expected_err=None, err_contains=False, err_ignore=None):
if expected_err is not None:
assert stderr == subprocess.PIPE or stderr is None,\
"Can't redirect stderr if using expected_err"
@ -158,11 +158,13 @@ def run_command(cmd, expected_status=0, stderr=None,
code = proc.returncode
if expected_status is not None and 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)))
if expected_err is not None:
if err_ignore is not None:
err = "\n".join([line for line in err.split('\n') if err_ignore not in line])
err_correct = 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

View File

@ -18,7 +18,7 @@ import os
from support import run_command
from shared import (
WASM2ASM, MOZJS, NODEJS, fail_if_not_identical, options, tests,
WASM2JS, MOZJS, NODEJS, fail_if_not_identical, options, tests,
fail_if_not_identical_to_file
)
@ -27,26 +27,31 @@ spec_dir = os.path.join(options.binaryen_test, 'spec')
spec_tests = [os.path.join(spec_dir, t)
for t in sorted(os.listdir(spec_dir))
if '.fail' not in t]
wasm2asm_dir = os.path.join(options.binaryen_test, 'wasm2asm')
extra_wasm2asm_tests = [os.path.join(wasm2asm_dir, t) for t in
sorted(os.listdir(wasm2asm_dir))]
assert_tests = ['wasm2asm.wast.asserts']
wasm2js_dir = os.path.join(options.binaryen_test, 'wasm2js')
extra_wasm2js_tests = [os.path.join(wasm2js_dir, t) for t in
sorted(os.listdir(wasm2js_dir))]
assert_tests = ['wasm2js.wast.asserts']
# These tests exercise functionality not supported by wasm2js
wasm2js_blacklist = ['empty_imported_table.wast']
def test_wasm2asm_output():
for wasm in tests + spec_tests + extra_wasm2asm_tests:
def test_wasm2js_output():
for wasm in tests + spec_tests + extra_wasm2js_tests:
if not wasm.endswith('.wast'):
continue
basename = os.path.basename(wasm)
if basename in wasm2js_blacklist:
continue
asm = os.path.basename(wasm).replace('.wast', '.2asm.js')
expected_file = os.path.join(wasm2asm_dir, asm)
asm = basename.replace('.wast', '.2asm.js')
expected_file = os.path.join(wasm2js_dir, asm)
if not os.path.exists(expected_file):
continue
print '..', wasm
cmd = WASM2ASM + [os.path.join(options.binaryen_test, wasm)]
cmd = WASM2JS + [os.path.join(options.binaryen_test, wasm)]
out = run_command(cmd)
fail_if_not_identical_to_file(out, expected_file)
@ -54,32 +59,27 @@ def test_wasm2asm_output():
print 'No JS interpreters. Skipping spec tests.'
continue
open('a.2asm.js', 'w').write(out)
open('a.2asm.mjs', 'w').write(out)
cmd += ['--allow-asserts']
out = run_command(cmd)
open('a.2asm.asserts.js', 'w').write(out)
open('a.2asm.asserts.mjs', 'w').write(out)
# verify asm.js is valid js
# verify asm.js is valid js, note that we're using --experimental-modules
# to enable ESM syntax and we're also passing a custom loader to handle the
# `spectest` module in our tests.
if NODEJS:
out = run_command([NODEJS, 'a.2asm.js'])
node = [NODEJS, '--experimental-modules', '--loader', './scripts/test/node-esm-loader.mjs']
cmd = node[:]
cmd.append('a.2asm.mjs')
out = run_command(cmd)
fail_if_not_identical(out, '')
out = run_command([NODEJS, 'a.2asm.asserts.js'], expected_err='')
cmd = node[:]
cmd.append('a.2asm.asserts.mjs')
out = run_command(cmd, expected_err='', err_ignore='The ESM module loader is experimental')
fail_if_not_identical(out, '')
if MOZJS:
# verify asm.js validates, if this is asm.js code (we emit
# almost-asm instead when we need to)
if 'use asm' in open('a.2asm.js').read():
# 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:
@ -90,8 +90,8 @@ def test_asserts_output():
asserts_expected_file = os.path.join(options.binaryen_test, asserts)
traps_expected_file = os.path.join(options.binaryen_test, traps)
wasm = os.path.join(wasm2asm_dir, wasm)
cmd = WASM2ASM + [wasm, '--allow-asserts']
wasm = os.path.join(wasm2js_dir, wasm)
cmd = WASM2JS + [wasm, '--allow-asserts']
out = run_command(cmd)
fail_if_not_identical_to_file(out, asserts_expected_file)
@ -100,11 +100,11 @@ def test_asserts_output():
fail_if_not_identical_to_file(out, traps_expected_file)
def test_wasm2asm():
print '\n[ checking wasm2asm testcases... ]\n'
test_wasm2asm_output()
def test_wasm2js():
print '\n[ checking wasm2js testcases... ]\n'
test_wasm2js_output()
test_asserts_output()
if __name__ == "__main__":
test_wasm2asm()
test_wasm2js()

View File

@ -29,7 +29,7 @@
#include "wasm-printing.h"
#include "wasm-s-parser.h"
#include "wasm-validator.h"
#include "wasm2asm.h"
#include "wasm2js.h"
#include "cfg/Relooper.h"
#include "ir/utils.h"
#include "shell-interface.h"
@ -77,7 +77,7 @@ static PassOptions globalPassOptions = PassOptions::getWithDefaultOptimizationOp
static int tracing = 0;
void traceNameOrNULL(const char *name) {
void traceNameOrNULL(const char* name) {
if (name) std::cout << "\"" << name << "\"";
else std::cout << "NULL";
}
@ -459,7 +459,7 @@ BinaryenExpressionRef BinaryenBreak(BinaryenModuleRef module, const char* name,
return static_cast<Expression*>(ret);
}
BinaryenExpressionRef BinaryenSwitch(BinaryenModuleRef module, const char **names, BinaryenIndex numNames, const char* defaultName, BinaryenExpressionRef condition, BinaryenExpressionRef value) {
BinaryenExpressionRef BinaryenSwitch(BinaryenModuleRef module, const char** names, BinaryenIndex numNames, const char* defaultName, BinaryenExpressionRef condition, BinaryenExpressionRef value) {
auto* ret = ((Module*)module)->allocator.alloc<Switch>();
if (tracing) {
@ -485,7 +485,7 @@ BinaryenExpressionRef BinaryenSwitch(BinaryenModuleRef module, const char **name
ret->finalize();
return static_cast<Expression*>(ret);
}
BinaryenExpressionRef BinaryenCall(BinaryenModuleRef module, const char *target, BinaryenExpressionRef* operands, BinaryenIndex numOperands, BinaryenType returnType) {
BinaryenExpressionRef BinaryenCall(BinaryenModuleRef module, const char* target, BinaryenExpressionRef* operands, BinaryenIndex numOperands, BinaryenType returnType) {
auto* ret = ((Module*)module)->allocator.alloc<Call>();
if (tracing) {
@ -510,7 +510,7 @@ BinaryenExpressionRef BinaryenCall(BinaryenModuleRef module, const char *target,
ret->finalize();
return static_cast<Expression*>(ret);
}
BinaryenExpressionRef BinaryenCallImport(BinaryenModuleRef module, const char *target, BinaryenExpressionRef* operands, BinaryenIndex numOperands, BinaryenType returnType) {
BinaryenExpressionRef BinaryenCallImport(BinaryenModuleRef module, const char* target, BinaryenExpressionRef* operands, BinaryenIndex numOperands, BinaryenType returnType) {
auto* ret = ((Module*)module)->allocator.alloc<CallImport>();
if (tracing) {
@ -603,7 +603,7 @@ BinaryenExpressionRef BinaryenTeeLocal(BinaryenModuleRef module, BinaryenIndex i
ret->finalize();
return static_cast<Expression*>(ret);
}
BinaryenExpressionRef BinaryenGetGlobal(BinaryenModuleRef module, const char *name, BinaryenType type) {
BinaryenExpressionRef BinaryenGetGlobal(BinaryenModuleRef module, const char* name, BinaryenType type) {
auto* ret = ((Module*)module)->allocator.alloc<GetGlobal>();
if (tracing) {
@ -616,7 +616,7 @@ BinaryenExpressionRef BinaryenGetGlobal(BinaryenModuleRef module, const char *na
ret->finalize();
return static_cast<Expression*>(ret);
}
BinaryenExpressionRef BinaryenSetGlobal(BinaryenModuleRef module, const char *name, BinaryenExpressionRef value) {
BinaryenExpressionRef BinaryenSetGlobal(BinaryenModuleRef module, const char* name, BinaryenExpressionRef value) {
auto* ret = ((Module*)module)->allocator.alloc<SetGlobal>();
if (tracing) {
@ -1704,10 +1704,10 @@ BinaryenGlobalRef BinaryenAddGlobal(BinaryenModuleRef module, const char* name,
// Imports
WASM_DEPRECATED BinaryenImportRef BinaryenAddImport(BinaryenModuleRef module, const char* internalName, const char* externalModuleName, const char *externalBaseName, BinaryenFunctionTypeRef type) {
WASM_DEPRECATED BinaryenImportRef BinaryenAddImport(BinaryenModuleRef module, const char* internalName, const char* externalModuleName, const char* externalBaseName, BinaryenFunctionTypeRef type) {
return BinaryenAddFunctionImport(module, internalName, externalModuleName, externalBaseName, type);
}
BinaryenImportRef BinaryenAddFunctionImport(BinaryenModuleRef module, const char* internalName, const char* externalModuleName, const char *externalBaseName, BinaryenFunctionTypeRef functionType) {
BinaryenImportRef BinaryenAddFunctionImport(BinaryenModuleRef module, const char* internalName, const char* externalModuleName, const char* externalBaseName, BinaryenFunctionTypeRef functionType) {
auto* ret = new Import();
auto* wasm = (Module*)module;
@ -1882,33 +1882,32 @@ void BinaryenRemoveExport(BinaryenModuleRef module, const char* externalName) {
// Function table. One per module
void BinaryenSetFunctionTable(BinaryenModuleRef module, BinaryenFunctionRef* funcs, BinaryenIndex numFuncs) {
void BinaryenSetFunctionTable(BinaryenModuleRef module, const char** funcNames, BinaryenIndex numFuncNames) {
if (tracing) {
std::cout << " {\n";
std::cout << " BinaryenFunctionRef funcs[] = { ";
for (BinaryenIndex i = 0; i < numFuncs; i++) {
std::cout << " const char* funcNames[] = { ";
for (BinaryenIndex i = 0; i < numFuncNames; i++) {
if (i > 0) std::cout << ", ";
std::cout << "functions[" << functions[funcs[i]] << "]";
std::cout << "\"" << funcNames[i] << "\"";
}
if (numFuncs == 0) std::cout << "0"; // ensure the array is not empty, otherwise a compiler error on VS
std::cout << " };\n";
std::cout << " BinaryenSetFunctionTable(the_module, funcs, " << numFuncs << ");\n";
std::cout << " BinaryenSetFunctionTable(the_module, funcNames, " << numFuncNames << ");\n";
std::cout << " }\n";
}
auto* wasm = (Module*)module;
wasm->table.exists = true;
Table::Segment segment(wasm->allocator.alloc<Const>()->set(Literal(int32_t(0))));
for (BinaryenIndex i = 0; i < numFuncs; i++) {
segment.data.push_back(((Function*)funcs[i])->name);
for (BinaryenIndex i = 0; i < numFuncNames; i++) {
segment.data.push_back(funcNames[i]);
}
wasm->table.segments.push_back(segment);
wasm->table.initial = wasm->table.max = numFuncs;
wasm->table.initial = wasm->table.max = numFuncNames;
}
// Memory. One per module
void BinaryenSetMemory(BinaryenModuleRef module, BinaryenIndex initial, BinaryenIndex maximum, const char* exportName, const char **segments, BinaryenExpressionRef* segmentOffsets, BinaryenIndex* segmentSizes, BinaryenIndex numSegments) {
void BinaryenSetMemory(BinaryenModuleRef module, BinaryenIndex initial, BinaryenIndex maximum, const char* exportName, const char** segments, BinaryenExpressionRef* segmentOffsets, BinaryenIndex* segmentSizes, BinaryenIndex numSegments) {
if (tracing) {
std::cout << " {\n";
for (BinaryenIndex i = 0; i < numSegments; i++) {
@ -2008,9 +2007,9 @@ void BinaryenModulePrintAsmjs(BinaryenModuleRef module) {
}
Module* wasm = (Module*)module;
Wasm2AsmBuilder::Flags builderFlags;
Wasm2AsmBuilder wasm2asm(builderFlags);
Ref asmjs = wasm2asm.processWasm(wasm);
Wasm2JSBuilder::Flags builderFlags;
Wasm2JSBuilder wasm2js(builderFlags);
Ref asmjs = wasm2js.processWasm(wasm);
JSPrinter jser(true, true, asmjs);
jser.printAst();
@ -2088,7 +2087,7 @@ void BinaryenSetDebugInfo(int on) {
globalPassOptions.debugInfo = on != 0;
}
void BinaryenModuleRunPasses(BinaryenModuleRef module, const char **passes, BinaryenIndex numPasses) {
void BinaryenModuleRunPasses(BinaryenModuleRef module, const char** passes, BinaryenIndex numPasses) {
if (tracing) {
std::cout << " {\n";
std::cout << " const char* passes[] = { ";
@ -2347,7 +2346,7 @@ void BinaryenFunctionOptimize(BinaryenFunctionRef func, BinaryenModuleRef module
passRunner.addDefaultOptimizationPasses();
passRunner.runOnFunction((Function*)func);
}
void BinaryenFunctionRunPasses(BinaryenFunctionRef func, BinaryenModuleRef module, const char **passes, BinaryenIndex numPasses) {
void BinaryenFunctionRunPasses(BinaryenFunctionRef func, BinaryenModuleRef module, const char** passes, BinaryenIndex numPasses) {
if (tracing) {
std::cout << " {\n";
std::cout << " const char* passes[] = { ";

View File

@ -340,7 +340,7 @@ BinaryenExpressionRef BinaryenLoop(BinaryenModuleRef module, const char* in, Bin
// 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);
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
@ -348,8 +348,8 @@ BinaryenExpressionRef BinaryenSwitch(BinaryenModuleRef module, const char **name
// 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 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
@ -368,8 +368,8 @@ BinaryenExpressionRef BinaryenCallIndirect(BinaryenModuleRef module, BinaryenExp
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);
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)
@ -608,11 +608,11 @@ void BinaryenRemoveFunction(BinaryenModuleRef module, const char* name);
typedef void* BinaryenImportRef;
WASM_DEPRECATED BinaryenImportRef BinaryenAddImport(BinaryenModuleRef module, const char* internalName, const char* externalModuleName, const char *externalBaseName, BinaryenFunctionTypeRef type);
BinaryenImportRef BinaryenAddFunctionImport(BinaryenModuleRef module, const char* internalName, const char* externalModuleName, const char *externalBaseName, BinaryenFunctionTypeRef functionType);
BinaryenImportRef BinaryenAddTableImport(BinaryenModuleRef module, const char* internalName, const char* externalModuleName, const char *externalBaseName);
BinaryenImportRef BinaryenAddMemoryImport(BinaryenModuleRef module, const char* internalName, const char* externalModuleName, const char *externalBaseName);
BinaryenImportRef BinaryenAddGlobalImport(BinaryenModuleRef module, const char* internalName, const char* externalModuleName, const char *externalBaseName, BinaryenType globalType);
WASM_DEPRECATED BinaryenImportRef BinaryenAddImport(BinaryenModuleRef module, const char* internalName, const char* externalModuleName, const char* externalBaseName, BinaryenFunctionTypeRef type);
BinaryenImportRef BinaryenAddFunctionImport(BinaryenModuleRef module, const char* internalName, const char* externalModuleName, const char* externalBaseName, BinaryenFunctionTypeRef functionType);
BinaryenImportRef BinaryenAddTableImport(BinaryenModuleRef module, const char* internalName, const char* externalModuleName, const char* externalBaseName);
BinaryenImportRef BinaryenAddMemoryImport(BinaryenModuleRef module, const char* internalName, const char* externalModuleName, const char* externalBaseName);
BinaryenImportRef BinaryenAddGlobalImport(BinaryenModuleRef module, const char* internalName, const char* externalModuleName, const char* externalBaseName, BinaryenType globalType);
void BinaryenRemoveImport(BinaryenModuleRef module, const char* internalName);
// Exports
@ -634,13 +634,13 @@ BinaryenGlobalRef BinaryenAddGlobal(BinaryenModuleRef module, const char* name,
// Function table. One per module
void BinaryenSetFunctionTable(BinaryenModuleRef module, BinaryenFunctionRef* funcs, BinaryenIndex numFuncs);
void BinaryenSetFunctionTable(BinaryenModuleRef module, const char** funcNames, BinaryenIndex numFuncNames);
// 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);
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
@ -693,7 +693,7 @@ void BinaryenSetDebugInfo(int on);
// Runs the specified passes on the module. Uses the currently set global
// optimize and shrink level.
void BinaryenModuleRunPasses(BinaryenModuleRef module, const char **passes, BinaryenIndex numPasses);
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,
@ -783,7 +783,7 @@ void BinaryenFunctionOptimize(BinaryenFunctionRef func, BinaryenModuleRef module
// Runs the specified passes on the function. Uses the currently set global
// optimize and shrink level.
void BinaryenFunctionRunPasses(BinaryenFunctionRef func, BinaryenModuleRef module, const char **passes, BinaryenIndex numPasses);
void BinaryenFunctionRunPasses(BinaryenFunctionRef func, BinaryenModuleRef module, const char** passes, BinaryenIndex numPasses);
// Sets the debug location of the specified `Expression` within the specified `Function`.
void BinaryenFunctionSetDebugLocation(BinaryenFunctionRef func, BinaryenExpressionRef expr, BinaryenIndex fileIndex, BinaryenIndex lineNumber, BinaryenIndex columnNumber);

View File

@ -0,0 +1,755 @@
/*
* Copyright 2018 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.
*/
//
// DataFlow IR is an SSA representation. It can be built from the main
// Binaryen IR.
//
// THe main initial use case was an IR that could easily be converted to
// Souper IR, and the design favors that.
//
#ifndef wasm_dataflow_graph_h
#define wasm_dataflow_graph_h
#include "wasm.h"
#include "ir/abstract.h"
#include "ir/iteration.h"
#include "ir/literal-utils.h"
#include "dataflow/node.h"
namespace wasm {
namespace DataFlow {
// Main logic to generate IR for a function. This is implemented as a
// visitor on the wasm, where visitors return a Node* that either
// contains the DataFlow IR for that expression, which can be a
// Bad node if not supported, or nullptr if not relevant (we only
// use the return value for internal expressions, that is, the
// value of a set_local or the condition of an if etc).
struct Graph : public UnifiedExpressionVisitor<Graph, Node*> {
// We only need one canonical bad node. It is never modified.
Node bad = Node(Node::Type::Bad);
// Connects a specific set to the data in its value.
std::unordered_map<SetLocal*, Node*> setNodeMap;
// Maps a control-flow expression to the conditions for it. Currently,
// this maps an if to the conditions for its arms
std::unordered_map<Expression*, std::vector<Node*>> expressionConditionMap;
// Maps each expression to its control-flow parent (or null if
// there is none). We only map expressions we need to know about,
// which are sets, set values, and control-flow constructs.
std::unordered_map<Expression*, Expression*> expressionParentMap;
// The same, for nodes. Note that we currently don't know the parents
// of nodes like phis that don't exist in wasm - we need to add more
// stuff to handle that. But we will know the parent of anything using
// that phi and storing to a local, which is probably enough anyhow
// for pc generation.
std::unordered_map<Node*, Expression*> nodeParentMap;
// All the sets, in order of appearance.
std::vector<SetLocal*> sets;
// The function being processed.
Function* func;
// The module we are working in.
Module* module;
// All of our nodes
std::vector<std::unique_ptr<Node>> nodes;
// Tracking state during building
// We need to track the parents of control flow nodes.
Expression* parent = nullptr;
// Tracks the state of locals in a control flow path:
// locals[i] = the node whose value it contains
// When we are in unreachable code (i.e., a path that does not
// need to be merged in anywhere), we set the length of this
// vector to 0 to indicate that.
typedef std::vector<Node*> Locals;
// The current local state in the control flow path being emitted.
Locals locals;
// The local states on branches to a specific target.
std::unordered_map<Name, std::vector<Locals>> breakStates;
// The local state in a control flow path, including a possible
// condition as well.
struct FlowState {
Locals locals;
Node* condition;
FlowState(Locals locals, Node* condition) : locals(locals), condition(condition) {}
};
// API
void build(Function* funcInit, Module* moduleInit) {
func = funcInit;
module = moduleInit;
auto numLocals = func->getNumLocals();
if (numLocals == 0) return; // nothing to do
// Set up initial local state IR.
setInReachable();
for (Index i = 0; i < numLocals; i++) {
if (!isRelevantType(func->getLocalType(i))) continue;
Node* node;
auto type = func->getLocalType(i);
if (func->isParam(i)) {
node = makeVar(type);
} else {
node = makeZero(type);
}
locals[i] = node;
}
// Process the function body, generating the rest of the IR.
visit(func->body);
}
// Makes a Var node, representing a value that could be anything.
Node* makeVar(wasm::Type type) {
if (isRelevantType(type)) {
return addNode(Node::makeVar(type));
} else {
return &bad;
}
}
// We create one node per constant value
std::unordered_map<Literal, Node*> constantNodes;
Node* makeConst(Literal value) {
auto iter = constantNodes.find(value);
if (iter!= constantNodes.end()) {
return iter->second;
}
// Create one for this literal.
Builder builder(*module);
auto* c = builder.makeConst(value);
auto* ret = addNode(Node::makeExpr(c, c));
constantNodes[value] = ret;
return ret;
}
Node* makeZero(wasm::Type type) {
return makeConst(LiteralUtils::makeLiteralZero(type));
}
// Add a new node to our list of owned nodes.
Node* addNode(Node* node) {
nodes.push_back(std::unique_ptr<Node>(node));
return node;
}
Node* makeZeroComp(Node* node, bool equal, Expression* origin) {
assert(!node->isBad());
Builder builder(*module);
auto type = node->getWasmType();
if (!isConcreteType(type)) return &bad;
auto* zero = makeZero(type);
auto* expr = builder.makeBinary(
Abstract::getBinary(type, equal ? Abstract::Eq : Abstract::Ne),
makeUse(node),
makeUse(zero)
);
auto* check = addNode(Node::makeExpr(expr, origin));
check->addValue(expandFromI1(node, origin));
check->addValue(zero);
return check;
}
void setInUnreachable() {
locals.clear();
}
void setInReachable() {
locals.resize(func->getNumLocals());
}
bool isInUnreachable() {
return isInUnreachable(locals);
}
bool isInUnreachable(const Locals& state) {
return state.empty();
}
bool isInUnreachable(const FlowState& state) {
return isInUnreachable(state.locals);
}
// Visiting.
Node* visitExpression(Expression* curr) {
// Control flow and get/set etc. are special. Aside from them, we just need
// to do something very generic.
if (auto* block = curr->dynCast<Block>()) {
return doVisitBlock(block);
} else if (auto* iff = curr->dynCast<If>()) {
return doVisitIf(iff);
} else if (auto* loop = curr->dynCast<Loop>()) {
return doVisitLoop(loop);
} else if (auto* get = curr->dynCast<GetLocal>()) {
return doVisitGetLocal(get);
} else if (auto* set = curr->dynCast<SetLocal>()) {
return doVisitSetLocal(set);
} else if (auto* br = curr->dynCast<Break>()) {
return doVisitBreak(br);
} else if (auto* sw = curr->dynCast<Switch>()) {
return doVisitSwitch(sw);
} else if (auto* c = curr->dynCast<Const>()) {
return doVisitConst(c);
} else if (auto* unary = curr->dynCast<Unary>()) {
return doVisitUnary(unary);
} else if (auto* binary = curr->dynCast<Binary>()) {
return doVisitBinary(binary);
} else if (auto* select = curr->dynCast<Select>()) {
return doVisitSelect(select);
} else if (auto* unreachable = curr->dynCast<Unreachable>()) {
return doVisitUnreachable(unreachable);
} else if (auto* drop = curr->dynCast<Drop>()) {
return doVisitDrop(drop);
} else {
return doVisitGeneric(curr);
}
}
Node* doVisitBlock(Block* curr) {
// TODO: handle super-deep nesting
auto* oldParent = parent;
expressionParentMap[curr] = oldParent;
parent = curr;
for (auto* child : curr->list) {
visit(child);
}
// Merge the outputs
// TODO handle conditions on these breaks
if (curr->name.is()) {
auto iter = breakStates.find(curr->name);
if (iter != breakStates.end()) {
auto& states = iter->second;
// Add the state flowing out
if (!isInUnreachable()) {
states.push_back(locals);
}
mergeBlock(states, locals);
}
}
parent = oldParent;
return &bad;
}
Node* doVisitIf(If* curr) {
auto* oldParent = parent;
expressionParentMap[curr] = oldParent;
parent = curr;
// Set up the condition.
Node* condition = visit(curr->condition);
assert(condition);
// Handle the contents.
auto initialState = locals;
visit(curr->ifTrue);
auto afterIfTrueState = locals;
if (curr->ifFalse) {
locals = initialState;
visit(curr->ifFalse);
auto afterIfFalseState = locals; // TODO: optimize
mergeIf(afterIfTrueState, afterIfFalseState, condition, curr, locals);
} else {
mergeIf(initialState, afterIfTrueState, condition, curr, locals);
}
parent = oldParent;
return &bad;
}
Node* doVisitLoop(Loop* curr) {
// As in Souper's LLVM extractor, we avoid loop phis, as we don't want
// our traces to represent a value that differs across loop iterations.
// For example,
// %b = block
// %x = phi %b, 1, %y
// %y = phi %b, 2, %x
// %z = eq %x %y
// infer %z
// Here %y refers to the previous iteration's %x.
// To do this, we set all locals to a Var at the loop entry, then process
// the inside of the loop. When that is done, we can see if a phi was
// actually needed for each local. If it was, we leave the Var (it
// represents an unknown value; analysis stops there), and if not, we
// can replace the Var with the fixed value.
// TODO: perhaps some more general uses of DataFlow will want loop phis?
// TODO: optimize stuff here
if (isInUnreachable()) {
return &bad; // none of this matters
}
if (!curr->name.is()) {
visit(curr->body);
return &bad; // no phis are possible
}
auto previous = locals;
auto numLocals = func->getNumLocals();
for (Index i = 0; i < numLocals; i++) {
locals[i] = makeVar(func->getLocalType(i));
}
auto vars = locals; // all the Vars we just created
// We may need to replace values later - only new nodes added from
// here are relevant.
auto firstNodeFromLoop = nodes.size();
// Process the loop body.
visit(curr->body);
// Find all incoming paths.
auto& breaks = breakStates[curr->name];
// Phis are possible, check for them.
for (Index i = 0; i < numLocals; i++) {
if (!isRelevantType(func->getLocalType(i))) continue;
bool needPhi = false;
// We replaced the proper value with a Var. If it's still that
// Var - or it's the original proper value, which can happen with
// constants - on all incoming paths, then a phi is not needed.
auto* var = vars[i];
auto* proper = previous[i];
for (auto& other : breaks) {
assert(!isInUnreachable(other));
auto& curr = *(other[i]);
if (curr != *var && curr != *proper) {
// A phi would be necessary here.
needPhi = true;
break;
}
}
if (needPhi) {
// Nothing to do - leave the Vars, the loop phis are
// unknown values to us.
} else {
// Undo the Var for this local: In every new node added for
// the loop body, replace references to the Var with the
// previous value (the value that is all we need instead of a phi).
for (auto j = firstNodeFromLoop; j < nodes.size(); j++) {
for (auto*& value : nodes[j].get()->values) {
if (value == var) {
value = proper;
}
}
}
// Also undo in the current local state, which is flowing out
// of the loop.
for (auto*& node : locals) {
if (node == var) {
node = proper;
}
}
}
}
return &bad;
}
Node* doVisitBreak(Break* curr) {
if (!isInUnreachable()) {
breakStates[curr->name].push_back(locals);
}
if (!curr->condition) {
setInUnreachable();
} else {
visit(curr->condition);
}
return &bad;
}
Node* doVisitSwitch(Switch* curr) {
visit(curr->condition);
if (!isInUnreachable()) {
std::unordered_set<Name> targets;
for (auto target : curr->targets) {
targets.insert(target);
}
targets.insert(curr->default_);
for (auto target : targets) {
breakStates[target].push_back(locals);
}
}
setInUnreachable();
return &bad;
}
Node* doVisitGetLocal(GetLocal* curr) {
if (!isRelevantLocal(curr->index) || isInUnreachable()) {
return &bad;
}
// We now know which IR node this get refers to
auto* node = locals[curr->index];
return node;
}
Node* doVisitSetLocal(SetLocal* curr) {
if (!isRelevantLocal(curr->index) || isInUnreachable()) {
return &bad;
}
assert(isConcreteType(curr->value->type));
sets.push_back(curr);
expressionParentMap[curr] = parent;
expressionParentMap[curr->value] = curr;
// Set the current node in the local state.
auto* node = visit(curr->value);
locals[curr->index] = setNodeMap[curr] = node;
// If we created a new node (and not just did a get of a set, which
// passes around an existing node), mark its parent.
if (nodeParentMap.find(node) == nodeParentMap.end()) {
nodeParentMap[node] = curr;
}
return &bad;
}
Node* doVisitConst(Const* curr) {
return makeConst(curr->value);
}
Node* doVisitUnary(Unary* curr) {
// First, check if we support this op.
switch (curr->op) {
case ClzInt32:
case ClzInt64:
case CtzInt32:
case CtzInt64:
case PopcntInt32:
case PopcntInt64: {
// These are ok as-is.
// Check if our child is supported.
auto* value = expandFromI1(visit(curr->value), curr);
if (value->isBad()) return value;
// Great, we are supported!
auto* ret = addNode(Node::makeExpr(curr, curr));
ret->addValue(value);
return ret;
}
case EqZInt32:
case EqZInt64: {
// These can be implemented using a binary.
// Check if our child is supported.
auto* value = expandFromI1(visit(curr->value), curr);
if (value->isBad()) return value;
// Great, we are supported!
return makeZeroComp(value, true, curr);
}
default: {
// Anything else is an unknown value.
return makeVar(curr->type);
}
}
}
Node* doVisitBinary(Binary *curr) {
// First, check if we support this op.
switch (curr->op) {
case AddInt32:
case AddInt64:
case SubInt32:
case SubInt64:
case MulInt32:
case MulInt64:
case DivSInt32:
case DivSInt64:
case DivUInt32:
case DivUInt64:
case RemSInt32:
case RemSInt64:
case RemUInt32:
case RemUInt64:
case AndInt32:
case AndInt64:
case OrInt32:
case OrInt64:
case XorInt32:
case XorInt64:
case ShlInt32:
case ShlInt64:
case ShrUInt32:
case ShrUInt64:
case ShrSInt32:
case ShrSInt64:
case RotLInt32:
case RotLInt64:
case RotRInt32:
case RotRInt64:
case EqInt32:
case EqInt64:
case NeInt32:
case NeInt64:
case LtSInt32:
case LtSInt64:
case LtUInt32:
case LtUInt64:
case LeSInt32:
case LeSInt64:
case LeUInt32:
case LeUInt64: {
// These are ok as-is.
// Check if our children are supported.
auto* left = expandFromI1(visit(curr->left), curr);
if (left->isBad()) return left;
auto* right = expandFromI1(visit(curr->right), curr);
if (right->isBad()) return right;
// Great, we are supported!
auto* ret = addNode(Node::makeExpr(curr, curr));
ret->addValue(left);
ret->addValue(right);
return ret;
}
case GtSInt32:
case GtSInt64:
case GeSInt32:
case GeSInt64:
case GtUInt32:
case GtUInt64:
case GeUInt32:
case GeUInt64: {
// These need to be flipped as Souper does not support redundant ops.
Builder builder(*module);
BinaryOp opposite;
switch (curr->op) {
case GtSInt32: opposite = LeSInt32; break;
case GtSInt64: opposite = LeSInt64; break;
case GeSInt32: opposite = LtSInt32; break;
case GeSInt64: opposite = LtSInt64; break;
case GtUInt32: opposite = LeUInt32; break;
case GtUInt64: opposite = LeUInt64; break;
case GeUInt32: opposite = LtUInt32; break;
case GeUInt64: opposite = LtUInt64; break;
default: WASM_UNREACHABLE();
}
auto* ret = visitBinary(builder.makeBinary(opposite, curr->right, curr->left));
// We just created a new binary node, but we need to set the origin properly
// to the original.
ret->origin = curr;
return ret;
}
default: {
// Anything else is an unknown value.
return makeVar(curr->type);
}
}
}
Node* doVisitSelect(Select* curr) {
auto* ifTrue = expandFromI1(visit(curr->ifTrue), curr);
if (ifTrue->isBad()) return ifTrue;
auto* ifFalse = expandFromI1(visit(curr->ifFalse), curr);
if (ifFalse->isBad()) return ifFalse;
auto* condition = ensureI1(visit(curr->condition), curr);
if (condition->isBad()) return condition;
// Great, we are supported!
auto* ret = addNode(Node::makeExpr(curr, curr));
ret->addValue(condition);
ret->addValue(ifTrue);
ret->addValue(ifFalse);
return ret;
}
Node* doVisitUnreachable(Unreachable* curr) {
setInUnreachable();
return &bad;
}
Node* doVisitDrop(Drop* curr) {
visit(curr->value);
// We need to know that the value's parent is a drop, indicating
// the value is not actually used here.
expressionParentMap[curr->value] = curr;
return &bad;
}
Node* doVisitGeneric(Expression* curr) {
// Just need to visit the nodes so we note all the gets
for (auto* child : ChildIterator(curr)) {
visit(child);
}
return makeVar(curr->type);
}
// Helpers.
bool isRelevantType(wasm::Type type) {
return isIntegerType(type);
}
bool isRelevantLocal(Index index) {
return isRelevantType(func->getLocalType(index));
}
// Merge local state for an if, also creating a block and conditions.
void mergeIf(Locals& aState, Locals& bState, Node* condition, Expression* expr, Locals& out) {
// Create the conditions (if we can).
Node* ifTrue;
Node* ifFalse;
if (!condition->isBad()) {
// Generate boolean (i1 returning) conditions for the two branches.
auto& conditions = expressionConditionMap[expr];
ifTrue = ensureI1(condition, nullptr);
conditions.push_back(ifTrue);
ifFalse = makeZeroComp(condition, true, nullptr);
conditions.push_back(ifFalse);
} else {
ifTrue = ifFalse = &bad;
}
// Finally, merge the state with that block. TODO optimize
std::vector<FlowState> states;
if (!isInUnreachable(aState)) {
states.emplace_back(aState, ifTrue);
}
if (!isInUnreachable(bState)) {
states.emplace_back(bState, ifFalse);
}
merge(states, out);
}
// Merge local state for a block
void mergeBlock(std::vector<Locals>& localses, Locals& out) {
// TODO: conditions
std::vector<FlowState> states;
for (auto& locals : localses) {
states.emplace_back(locals, &bad);
}
merge(states, out);
}
// Merge local state for multiple control flow paths, creating phis as needed.
void merge(std::vector<FlowState>& states, Locals& out) {
// We should only receive reachable states.
for (auto& state : states) {
assert(!isInUnreachable(state.locals));
}
Index numStates = states.size();
if (numStates == 0) {
// We were unreachable, and still are.
assert(isInUnreachable());
return;
}
// We may have just become reachable, if we were not before.
setInReachable();
// Just one thing to merge is trivial.
if (numStates == 1) {
out = states[0].locals;
return;
}
// We create a block if we need one.
Index numLocals = func->getNumLocals();
Node* block = nullptr;
for (Index i = 0; i < numLocals; i++) {
if (!isRelevantType(func->getLocalType(i))) continue;
// Process the inputs. If any is bad, the phi is bad.
bool bad = false;
for (auto& state : states) {
auto* node = state.locals[i];
if (node->isBad()) {
bad = true;
out[i] = node;
break;
}
}
if (bad) continue;
// Nothing is bad, proceed.
Node* first = nullptr;
for (auto& state : states) {
if (!first) {
first = out[i] = state.locals[i];
} else if (state.locals[i] != first) {
// We need to actually merge some stuff.
if (!block) {
block = addNode(Node::makeBlock());
for (Index index = 0; index < numStates; index++) {
auto* condition = states[index].condition;
if (!condition->isBad()) {
condition = addNode(Node::makeCond(block, index, condition));
}
block->addValue(condition);
}
}
auto* phi = addNode(Node::makePhi(block, i));
for (auto& state : states) {
auto* value = expandFromI1(state.locals[i], nullptr);
phi->addValue(value);
}
out[i] = phi;
break;
}
}
}
}
// If the node returns an i1, then we are called from a context that needs
// to use it normally as in wasm - extend it
Node* expandFromI1(Node* node, Expression* origin) {
if (!node->isBad() && node->returnsI1()) {
node = addNode(Node::makeZext(node, origin));
}
return node;
}
Node* ensureI1(Node* node, Expression* origin) {
if (!node->isBad() && !node->returnsI1()) {
node = makeZeroComp(node, false, origin);
}
return node;
}
// Given a node representing something that is set_local'd, return
// the set.
SetLocal* getSet(Node* node) {
auto iter = nodeParentMap.find(node);
if (iter == nodeParentMap.end()) return nullptr;
return iter->second->dynCast<SetLocal>();
}
// Given an expression, return the parent if such exists.
Expression* getParent(Expression* curr) {
auto iter = expressionParentMap.find(curr);
if (iter == expressionParentMap.end()) return nullptr;
return iter->second;
}
// Given an expression, return the set for it if such exists.
SetLocal* getSet(Expression* curr) {
auto* parent = getParent(curr);
return parent ? parent->dynCast<SetLocal>() : nullptr;
}
// Creates an expression that uses a node. Generally, a node represents
// a value in a local, so we create a get_local for it.
Expression* makeUse(Node* node) {
Builder builder(*module);
if (node->isPhi()) {
// The index is the wasm local that we assign to when implementing
// the phi; get from there.
auto index = node->index;
return builder.makeGetLocal(index, func->getLocalType(index));
} else if (node->isConst()) {
return builder.makeConst(node->expr->cast<Const>()->value);
} else if (node->isExpr()) {
// Find the set we are a value of.
auto index = getSet(node)->index;
return builder.makeGetLocal(index, func->getLocalType(index));
} else if (node->isZext()) {
// i1 zexts are a no-op for wasm
return makeUse(node->values[0]);
} else if (node->isVar()) {
// Nothing valid for us to read here.
// FIXME should we have a local index to get?
return Builder(*module).makeConst(LiteralUtils::makeLiteralZero(node->wasmType));
} else {
WASM_UNREACHABLE(); // TODO
}
}
};
} // namespace DataFlow
} // namespace wasm
#endif // wasm_dataflow_graph_h

View File

@ -0,0 +1,210 @@
/*
* Copyright 2018 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.
*/
//
// DataFlow IR is an SSA representation. It can be built from the main
// Binaryen IR.
//
// THe main initial use case was an IR that could easily be converted to
// Souper IR, and the design favors that.
//
#ifndef wasm_dataflow_node_h
#define wasm_dataflow_node_h
#include "wasm.h"
namespace wasm {
namespace DataFlow {
//
// The core IR representation in DataFlow: a Node.
//
// We reuse the Binaryen IR as much as possible: when things are identical between
// the two IRs, we just create an Expr node, which stores the opcode and other
// details, and we can emit them to Souper by reading the Binaryen Expression.
// Other node types here are special things from Souper IR that we can't
// represent that way.
//
// * Souper comparisons return an i1. We extend them immediately if they are
// going to be used as i32s or i64s.
// * When we use an Expression node, we just use its immediate fields, like the
// op in a binary, alignment etc. in a load, etc. We don't look into the
// pointers to child nodes. Instead, the DataFlow IR has its own pointers
// directly to DataFlow children. In particular, this means that it's easy
// to create an Expression with the info you need and not care about linking
// it up to other Expressions.
//
struct Node {
enum Type {
Var, // an unknown variable number (not to be confused with var/param/local in wasm)
Expr, // a value represented by a Binaryen Expression
Phi, // a phi from converging control flow
Cond, // a blockpc, representing one of the branchs for a Block
Block, // a source of phis
Zext, // zero-extend an i1 (from an op where Souper returns i1 but wasm does not,
// and so we need a special way to get back to an i32/i64 if we operate
// on that value instead of just passing it straight to Souper).
Bad // something we can't handle and should ignore
} type;
Node(Type type) : type(type) {}
// TODO: the others, if we need them
bool isVar() { return type == Var; }
bool isExpr() { return type == Expr; }
bool isPhi() { return type == Phi; }
bool isCond() { return type == Cond; }
bool isBlock() { return type == Block; }
bool isZext() { return type == Zext; }
bool isBad() { return type == Bad; }
bool isConst() { return type == Expr && expr->is<Const>(); }
union {
// For Var
wasm::Type wasmType;
// For Expr
Expression* expr;
// For Phi and Cond (the local index for phi, the block
// index for cond)
Index index;
};
// The wasm expression that we originate from (if such exists). A single
// wasm instruction may be turned into multiple dataflow IR nodes, and some
// nodes have no wasm origin (like phis).
Expression* origin = nullptr;
// Extra list of related nodes.
// For Expr, these are the Nodes for the inputs to the expression (e.g.
// a binary would have 2 in this vector here).
// For Phi, this is the block and then the list of values to pick from.
// For Cond, this is the block and node.
// For Block, this is the list of Conds. Note that that block does not
// depend on them - the Phis do, but we store them in the block so that
// we can avoid duplication.
// For Zext, this is the value we extend.
std::vector<Node*> values;
// Constructors
static Node* makeVar(wasm::Type wasmType) {
Node* ret = new Node(Var);
ret->wasmType = wasmType;
return ret;
}
static Node* makeExpr(Expression* expr, Expression* origin) {
Node* ret = new Node(Expr);
ret->expr = expr;
ret->origin = origin;
return ret;
}
static Node* makePhi(Node* block, Index index) {
Node* ret = new Node(Phi);
ret->addValue(block);
ret->index = index;
return ret;
}
static Node* makeCond(Node* block, Index index, Node* node) {
Node* ret = new Node(Cond);
ret->addValue(block);
ret->index = index;
ret->addValue(node);
return ret;
}
static Node* makeBlock() {
Node* ret = new Node(Block);
return ret;
}
static Node* makeZext(Node* child, Expression* origin) {
Node* ret = new Node(Zext);
ret->addValue(child);
ret->origin = origin;
return ret;
}
static Node* makeBad() {
Node* ret = new Node(Bad);
return ret;
}
// Helpers
void addValue(Node* value) {
values.push_back(value);
}
Node* getValue(Index i) {
return values.at(i);
}
// Gets the wasm type of the node. If there isn't a valid one,
// return unreachable.
wasm::Type getWasmType() {
switch (type) {
case Var: return wasmType;
case Expr: return expr->type;
case Phi: return getValue(1)->getWasmType();
case Zext: return getValue(0)->getWasmType();
case Bad: return unreachable;
default: WASM_UNREACHABLE();
}
}
bool operator==(const Node& other) {
if (type != other.type) return false;
switch (type) {
case Var:
case Block: return this == &other;
case Expr: {
if (!ExpressionAnalyzer::equal(expr, other.expr)) {
return false;
}
break;
}
case Cond: if (index != other.index) return false;
default: {}
}
if (values.size() != other.values.size()) return false;
for (Index i = 0; i < values.size(); i++) {
if (*(values[i]) != *(other.values[i])) return false;
}
return true;
}
bool operator!=(const Node& other) {
return !(*this == other);
}
// As mentioned above, comparisons return i1. This checks
// if an operation is of that sort.
bool returnsI1() {
if (isExpr()) {
if (auto* binary = expr->dynCast<Binary>()) {
return binary->isRelational();
} else if (auto* unary = expr->dynCast<Unary>()) {
return unary->isRelational();
}
}
return false;
}
};
} // namespace DataFlow
} // namespace wasm
#endif // wasm_dataflow_node

View File

@ -0,0 +1,106 @@
/*
* Copyright 2018 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.
*/
//
// DataFlow IR is an SSA representation. It can be built from the main
// Binaryen IR.
//
// THe main initial use case was an IR that could easily be converted to
// Souper IR, and the design favors that.
//
#ifndef wasm_dataflow_users_h
#define wasm_dataflow_users_h
#include "dataflow/graph.h"
namespace wasm {
namespace DataFlow {
// Calculates the users of each node.
// users[x] = { y, z, .. }
// where y, z etc. are nodes that use x, that is, x is in their
// values vector.
class Users {
typedef std::unordered_set<DataFlow::Node*> UserSet;
std::unordered_map<DataFlow::Node*, UserSet> users;
public:
void build(Graph& graph) {
for (auto& node : graph.nodes) {
for (auto* value : node->values) {
users[value].insert(node.get());
}
}
}
UserSet& getUsers(Node* node) {
auto iter = users.find(node);
if (iter == users.end()) {
static UserSet empty; // FIXME thread_local?
return empty;
}
return iter->second;
}
Index getNumUses(Node* node) {
auto& users = getUsers(node);
// A user may have more than one use
Index numUses = 0;
for (auto* user : users) {
#ifndef NDEBUG
bool found = false;
#endif
for (auto* value : user->values) {
if (value == node) {
numUses++;
#ifndef NDEBUG
found = true;
#endif
}
}
assert(found);
}
return numUses;
}
// Stops using all the values of this node. Called when a node is being
// removed.
void stopUsingValues(Node* node) {
for (auto* value : node->values) {
auto& users = getUsers(value);
users.erase(node);
}
}
// Adds a new user to a node. Called when we add or change a value of a node.
void addUser(Node* node, Node* newUser) {
users[node].insert(newUser);
}
// Remove all uses of a node. Called when a node is being removed.
void removeAllUsesOf(Node* node) {
users.erase(node);
}
};
} // namespace DataFlow
} // namespace wasm
#endif // wasm_dataflow_users

View File

@ -0,0 +1,145 @@
/*
* Copyright 2018 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.
*/
//
// DataFlow IR is an SSA representation. It can be built from the main
// Binaryen IR.
//
// THe main initial use case was an IR that could easily be converted to
// Souper IR, and the design favors that.
//
#ifndef wasm_dataflow_utils_h
#define wasm_dataflow_utils_h
#include "wasm.h"
#include "wasm-printing.h"
#include "dataflow/node.h"
namespace wasm {
namespace DataFlow {
inline std::ostream& dump(Node* node, std::ostream& o, size_t indent = 0) {
auto doIndent = [&]() {
for (size_t i = 0; i < indent; i++) o << ' ';
};
doIndent();
o << '[' << node << ' ';
switch (node->type) {
case Node::Type::Var: o << "var " << printType(node->wasmType) << ' ' << node; break;
case Node::Type::Expr: {
o << "expr ";
WasmPrinter::printExpression(node->expr, o, true);
break;
}
case Node::Type::Phi: o << "phi " << node->index; break;
case Node::Type::Cond: o << "cond " << node->index; break;
case Node::Type::Block: {
// don't print the conds - they would recurse
o << "block (" << node->values.size() << " conds)]\n";
return o;
}
case Node::Type::Zext: o << "zext"; break;
case Node::Type::Bad: o << "bad"; break;
default: WASM_UNREACHABLE();
}
if (!node->values.empty()) {
o << '\n';
for (auto* value : node->values) {
dump(value, o, indent + 1);
}
doIndent();
}
o << "] (origin: " << (void*)(node->origin) << ")\n";
return o;
}
inline std::ostream& dump(Graph& graph, std::ostream& o) {
for (auto& node : graph.nodes) {
o << "NODE " << node.get() << ": ";
dump(node.get(), o);
if (auto* set = graph.getSet(node.get())) {
o << " and that is set to local " << set->index << '\n';
}
}
return o;
}
// Checks if the inputs are all identical - something we could
// probably optimize. Returns false if irrelevant.
inline bool allInputsIdentical(Node* node) {
switch (node->type) {
case Node::Type::Expr: {
if (node->expr->is<Binary>()) {
return *(node->getValue(0)) == *(node->getValue(1));
} else if (node->expr->is<Select>()) {
return *(node->getValue(1)) == *(node->getValue(2));
}
break;
}
case Node::Type::Phi: {
auto* first = node->getValue(1);
// Check if any of the others are not equal
for (Index i = 2; i < node->values.size(); i++) {
auto* curr = node->getValue(i);
if (*first != *curr) {
return false;
}
}
return true;
}
default: {}
}
return false;
}
// Checks if the inputs are all constant - something we could
// probably optimize. Returns false if irrelevant.
inline bool allInputsConstant(Node* node) {
switch (node->type) {
case Node::Type::Expr: {
if (node->expr->is<Unary>()) {
return node->getValue(0)->isConst();
} else if (node->expr->is<Binary>()) {
return node->getValue(0)->isConst() &&
node->getValue(1)->isConst();
} else if (node->expr->is<Select>()) {
return node->getValue(0)->isConst() &&
node->getValue(1)->isConst() &&
node->getValue(2)->isConst();
}
break;
}
case Node::Type::Phi: {
// Check if any of the others are not equal
for (Index i = 1; i < node->values.size(); i++) {
if (!node->getValue(i)->isConst()) {
return false;
}
}
return true;
}
default: {}
}
return false;
}
} // namespace DataFlow
} // namespace wasm
#endif // wasm_dataflow_utils

View File

@ -257,7 +257,7 @@ bool ExpressionAnalyzer::flexibleEqual(Expression* left, Expression* right, Expr
break;
}
case Expression::Id::ConstId: {
if (!left->cast<Const>()->value.bitwiseEqual(right->cast<Const>()->value)) {
if (left->cast<Const>()->value != right->cast<Const>()->value) {
return false;
}
break;

View File

@ -18,6 +18,7 @@
#define wasm_ir_iteration_h
#include "wasm.h"
#include "wasm-traversal.h"
namespace wasm {
@ -56,147 +57,27 @@ class ChildIterator {
public:
std::vector<Expression*> children;
ChildIterator(Expression* expr) {
switch (expr->_id) {
case Expression::Id::BlockId: {
auto& list = expr->cast<Block>()->list;
for (auto* child : list) {
children.push_back(child);
ChildIterator(Expression* parent) {
struct Traverser : public PostWalker<Traverser> {
Expression* parent;
std::vector<Expression*>* children;
// We need to scan subchildren exactly once - just the parent.
bool scanned = false;
static void scan(Traverser* self, Expression** currp) {
if (!self->scanned) {
self->scanned = true;
PostWalker<Traverser, UnifiedExpressionVisitor<Traverser>>::scan(self, currp);
} else {
// This is one of the children. Do not scan further, just note it.
self->children->push_back(*currp);
}
break;
}
case Expression::Id::IfId: {
auto* iff = expr->cast<If>();
children.push_back(iff->condition);
children.push_back(iff->ifTrue);
if (iff->ifFalse) children.push_back(iff->ifFalse);
break;
}
case Expression::Id::LoopId: {
children.push_back(expr->cast<Loop>()->body);
break;
}
case Expression::Id::BreakId: {
auto* br = expr->cast<Break>();
if (br->value) children.push_back(br->value);
if (br->condition) children.push_back(br->condition);
break;
}
case Expression::Id::SwitchId: {
auto* br = expr->cast<Switch>();
if (br->value) children.push_back(br->value);
children.push_back(br->condition);
break;
}
case Expression::Id::CallId: {
auto& operands = expr->cast<Call>()->operands;
for (auto* child : operands) {
children.push_back(child);
}
break;
}
case Expression::Id::CallImportId: {
auto& operands = expr->cast<CallImport>()->operands;
for (auto* child : operands) {
children.push_back(child);
}
break;
}
case Expression::Id::CallIndirectId: {
auto* call = expr->cast<CallIndirect>();
auto& operands = call->operands;
for (auto* child : operands) {
children.push_back(child);
}
children.push_back(call->target);
break;
}
case Expression::Id::SetLocalId: {
children.push_back(expr->cast<SetLocal>()->value);
break;
}
case Expression::Id::SetGlobalId: {
children.push_back(expr->cast<SetGlobal>()->value);
break;
}
case Expression::Id::LoadId: {
children.push_back(expr->cast<Load>()->ptr);
break;
}
case Expression::Id::StoreId: {
auto* store = expr->cast<Store>();
children.push_back(store->ptr);
children.push_back(store->value);
break;
}
case Expression::Id::UnaryId: {
children.push_back(expr->cast<Unary>()->value);
break;
}
case Expression::Id::BinaryId: {
auto* binary = expr->cast<Binary>();
children.push_back(binary->left);
children.push_back(binary->right);
break;
}
case Expression::Id::SelectId: {
auto* select = expr->cast<Select>();
children.push_back(select->ifTrue);
children.push_back(select->ifFalse);
children.push_back(select->condition);
break;
}
case Expression::Id::DropId: {
children.push_back(expr->cast<Drop>()->value);
break;
}
case Expression::Id::ReturnId: {
auto* ret = expr->dynCast<Return>();
if (ret->value) children.push_back(ret->value);
break;
}
case Expression::Id::HostId: {
auto& operands = expr->cast<Host>()->operands;
for (auto* child : operands) {
children.push_back(child);
}
break;
}
case Expression::Id::AtomicRMWId: {
auto* atomic = expr->cast<AtomicRMW>();
children.push_back(atomic->ptr);
children.push_back(atomic->value);
break;
}
case Expression::Id::AtomicCmpxchgId: {
auto* atomic = expr->cast<AtomicCmpxchg>();
children.push_back(atomic->ptr);
children.push_back(atomic->expected);
children.push_back(atomic->replacement);
break;
}
case Expression::Id::AtomicWaitId: {
auto* atomic = expr->cast<AtomicWait>();
children.push_back(atomic->ptr);
children.push_back(atomic->expected);
children.push_back(atomic->timeout);
break;
}
case Expression::Id::AtomicWakeId: {
auto* atomic = expr->cast<AtomicWake>();
children.push_back(atomic->ptr);
children.push_back(atomic->wakeCount);
break;
}
case Expression::Id::GetLocalId:
case Expression::Id::GetGlobalId:
case Expression::Id::ConstId:
case Expression::Id::NopId:
case Expression::Id::UnreachableId: {
break; // no children
}
default: WASM_UNREACHABLE();
}
} traverser;
traverser.parent = parent;
traverser.children = &children;
traverser.walk(parent);
}
Iterator begin() const {

View File

@ -146,6 +146,42 @@ inline Index getZeroExtBits(Expression* curr) {
return Bits::getMaskedBits(curr->cast<Binary>()->right->cast<Const>()->value.geti32());
}
// Returns a falling-through value, that is, it looks through a tee_local
// and other operations that receive a value and let it flow through them.
inline Expression* getFallthrough(Expression* curr) {
// If the current node is unreachable, there is no value
// falling through.
if (curr->type == unreachable) {
return curr;
}
if (auto* set = curr->dynCast<SetLocal>()) {
if (set->isTee()) {
return getFallthrough(set->value);
}
} else if (auto* block = curr->dynCast<Block>()) {
// if no name, we can't be broken to, and then can look at the fallthrough
if (!block->name.is() && block->list.size() > 0) {
return getFallthrough(block->list.back());
}
} else if (auto* loop = curr->dynCast<Loop>()) {
return getFallthrough(loop->body);
} else if (auto* iff = curr->dynCast<If>()) {
if (iff->ifFalse) {
// Perhaps just one of the two actually returns.
if (iff->ifTrue->type == unreachable) {
return getFallthrough(iff->ifFalse);
} else if (iff->ifFalse->type == unreachable) {
return getFallthrough(iff->ifTrue);
}
}
} else if (auto* br = curr->dynCast<Break>()) {
if (br->condition && br->value) {
return getFallthrough(br->value);
}
}
return curr;
}
} // Properties
} // wasm

View File

@ -58,6 +58,7 @@ struct ExpressionAnalyzer {
using ExprComparer = std::function<bool(Expression*, Expression*)>;
static bool flexibleEqual(Expression* left, Expression* right, ExprComparer comparer);
// Compares two expressions for equivalence.
static bool equal(Expression* left, Expression* right) {
auto comparer = [](Expression* left, Expression* right) {
return false;
@ -65,6 +66,19 @@ struct ExpressionAnalyzer {
return flexibleEqual(left, right, comparer);
}
// A shallow comparison, ignoring child nodes.
static bool shallowEqual(Expression* left, Expression* right) {
auto comparer = [left, right](Expression* currLeft, Expression* currRight) {
if (currLeft == left && currRight == right) {
// these are the ones we want to compare
return false;
}
// otherwise, don't do the comparison, we don't care
return true;
};
return flexibleEqual(left, right, comparer);
}
// hash an expression, ignoring superficial details like specific internal names
static uint32_t hash(Expression* curr);
};

View File

@ -1129,9 +1129,11 @@ Module['Module'] = function(module) {
return Module['_BinaryenRemoveExport'](module, strToStack(externalName));
});
};
this['setFunctionTable'] = function(funcs) {
this['setFunctionTable'] = function(funcNames) {
return preserveStack(function() {
return Module['_BinaryenSetFunctionTable'](module, i32sToStack(funcs), funcs.length);
return Module['_BinaryenSetFunctionTable'](module, i32sToStack(
funcNames.map(strToStack)
), funcNames.length);
});
};
this['setMemory'] = function(initial, maximum, exportName, segments) {

View File

@ -71,9 +71,11 @@ public:
int64_t getInteger() const;
double getFloat() const;
int64_t getBits() const;
// Equality checks for the type and the bits, so a nan float would
// be compared bitwise (which means that a Literal containing a nan
// would be equal to itself, if the bits are equal).
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);
@ -130,6 +132,9 @@ public:
Literal rotL(const Literal& other) const;
Literal rotR(const Literal& other) const;
// Note that these functions perform equality checks based
// on the type of the literal, so that (unlike the == operator)
// a float nan would not be identical to itself.
Literal eq(const Literal& other) const;
Literal ne(const Literal& other) const;
Literal ltS(const Literal& other) const;

View File

@ -98,9 +98,12 @@ struct PassRunner {
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 setDebug(bool debug) {
options.debug = debug;
options.validateGlobally = debug; // validate everything by default if debugging
}
void setDebugInfo(bool debugInfo) {
options.debugInfo = debugInfo;
}
void setValidateGlobally(bool validate) {
options.validateGlobally = validate;

View File

@ -1,7 +1,7 @@
add_custom_command(
OUTPUT WasmIntrinsics.cpp
COMMAND python ${CMAKE_SOURCE_DIR}/scripts/embedwast.py ${CMAKE_SOURCE_DIR}/src/passes/wasm-intrinsics.wast ${CMAKE_CURRENT_BINARY_DIR}/WasmIntrinsics.cpp
DEPENDS ${CMAKE_SOURCE_DIR}/scripts/embedwast.py wasm-intrinsics.wast)
COMMAND python ${PROJECT_SOURCE_DIR}/scripts/embedwast.py ${PROJECT_SOURCE_DIR}/src/passes/wasm-intrinsics.wast ${CMAKE_CURRENT_BINARY_DIR}/WasmIntrinsics.cpp
DEPENDS ${PROJECT_SOURCE_DIR}/scripts/embedwast.py wasm-intrinsics.wast)
SET(passes_SOURCES
pass.cpp
@ -9,6 +9,7 @@ SET(passes_SOURCES
CodePushing.cpp
CodeFolding.cpp
ConstHoisting.cpp
DataFlowOpts.cpp
DeadCodeElimination.cpp
DuplicateFunctionElimination.cpp
ExtractFunction.cpp
@ -47,6 +48,7 @@ SET(passes_SOURCES
TrapMode.cpp
SafeHeap.cpp
SimplifyLocals.cpp
Souperify.cpp
SpillPointers.cpp
SSAify.cpp
Untee.cpp

View File

@ -0,0 +1,250 @@
/*
* Copyright 2018 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.
*/
//
// Optimize using the DataFlow SSA IR.
//
// This needs 'flatten' to be run before it, and you should run full
// regular opts afterwards to clean up the flattening. For example,
// you might use it like this:
//
// --flatten --dfo -Os
//
#include "wasm.h"
#include "pass.h"
#include "wasm-builder.h"
#include "ir/utils.h"
#include "dataflow/node.h"
#include "dataflow/graph.h"
#include "dataflow/users.h"
#include "dataflow/utils.h"
namespace wasm {
struct DataFlowOpts : public WalkerPass<PostWalker<DataFlowOpts>> {
bool isFunctionParallel() override { return true; }
Pass* create() override { return new DataFlowOpts; }
DataFlow::Users nodeUsers;
// The optimization work left to do: nodes that we need to look at.
std::unordered_set<DataFlow::Node*> workLeft;
DataFlow::Graph graph;
void doWalkFunction(Function* func) {
// Build the data-flow IR.
graph.build(func, getModule());
nodeUsers.build(graph);
// Propagate optimizations through the graph.
std::unordered_set<DataFlow::Node*> optimized; // which nodes we optimized
for (auto& node : graph.nodes) {
workLeft.insert(node.get()); // we should try to optimize each node
}
while (!workLeft.empty()) {
//std::cout << "\n\ndump before work iter\n";
//dump(graph, std::cout);
auto iter = workLeft.begin();
auto* node = *iter;
workLeft.erase(iter);
workOn(node);
}
// After updating the DataFlow IR, we can update the sets in
// the wasm.
// TODO: we also need phis, as a phi can flow directly into say
// a return or a call parameter.
for (auto* set : graph.sets) {
auto* node = graph.setNodeMap[set];
auto iter = optimized.find(node);
if (iter != optimized.end()) {
assert(node->isExpr()); // this is a set, where the node is defined
set->value = node->expr;
}
}
}
void workOn(DataFlow::Node* node) {
if (node->isConst()) return;
// If there are no uses, there is no point to work.
if (nodeUsers.getNumUses(node) == 0) return;
// Optimize: Look for nodes that we can easily convert into
// something simpler.
// TODO: we can expressionify and run full normal opts on that,
// then copy the result if it's smaller.
if (node->isPhi() && DataFlow::allInputsIdentical(node)) {
// Note we don't need to check for effects when replacing, as in
// flattened IR expression children are get_locals or consts.
auto* value = node->getValue(1);
if (value->isConst()) {
replaceAllUsesWith(node, value);
}
} else if (node->isExpr() && DataFlow::allInputsConstant(node)) {
assert(!node->isConst());
// If this is a concrete value (not e.g. an eqz of unreachable),
// it can definitely be precomputed into a constant.
if (isConcreteType(node->expr->type)) {
// This can be precomputed.
// TODO not just all-constant inputs? E.g. i32.mul of 0 and X.
optimizeExprToConstant(node);
}
}
}
void optimizeExprToConstant(DataFlow::Node* node) {
assert(node->isExpr());
assert(!node->isConst());
//std::cout << "will optimize an Expr of all constant inputs. before" << '\n';
//dump(node, std::cout);
auto* expr = node->expr;
// First, note that some of the expression's children may be
// get_locals that we inferred during SSA analysis as constant.
// We can apply those now.
for (Index i = 0; i < node->values.size(); i++) {
if (node->values[i]->isConst()) {
auto* currp = getIndexPointer(expr, i);
if (!(*currp)->is<Const>()) {
// Directly represent it as a constant.
auto* c = node->values[i]->expr->dynCast<Const>();
*currp = Builder(*getModule()).makeConst(c->value);
}
}
}
// Now we know that all our DataFlow inputs are constant, and all
// our Binaryen IR representations of them are constant too. RUn
// precompute, which will transform the expression into a constanat.
Module temp;
// XXX we should copy expr here, in principle, and definitely will need to
// when we do arbitrarily regenerated expressions
auto* func = Builder(temp).makeFunction("temp", std::vector<Type>{}, none, std::vector<Type>{}, expr);
PassRunner runner(&temp);
runner.setIsNested(true);
runner.add("precompute");
runner.runOnFunction(func);
// Get the optimized thing
auto* result = func->body;
// It may not be a constant, e.g. 0 / 0 does not optimize to 0
if (!result->is<Const>()) return;
// All good, copy it.
node->expr = Builder(*getModule()).makeConst(result->cast<Const>()->value);
assert(node->isConst());
// We no longer have values, and so do not use anything.
nodeUsers.stopUsingValues(node);
node->values.clear();
// Our contents changed, update our users.
replaceAllUsesWith(node, node);
}
// Replaces all uses of a node with another value. This both modifies
// the DataFlow IR to make the other users point to this one, and
// updates the underlying Binaryen IR as well.
// This can be used to "replace" a node with itself, which makes sense
// when the node contents have changed and so the users must be updated.
void replaceAllUsesWith(DataFlow::Node* node, DataFlow::Node* with) {
// Const nodes are trivial to replace, but other stuff is trickier -
// in particular phis.
assert(with->isConst()); // TODO
// All the users should be worked on later, as we will update them.
auto& users = nodeUsers.getUsers(node);
for (auto* user : users) {
// Add the user to the work left to do, as we are modifying it.
workLeft.insert(user);
// `with` is getting another user.
nodeUsers.addUser(with, user);
// Replacing in the DataFlow IR is simple - just replace it,
// in all the indexes it appears.
std::vector<Index> indexes;
for (Index i = 0; i < user->values.size(); i++) {
if (user->values[i] == node) {
user->values[i] = with;
indexes.push_back(i);
}
}
assert(!indexes.empty());
// Replacing in the Binaryen IR requires more care
switch (user->type) {
case DataFlow::Node::Type::Expr: {
auto* expr = user->expr;
for (auto index : indexes) {
*(getIndexPointer(expr, index)) = graph.makeUse(with);
}
break;
}
case DataFlow::Node::Type::Phi: {
// Nothing to do: a phi is not in the Binaryen IR.
// If the entire phi can become a constant, that will be
// propagated when we process that phi later.
break;
}
case DataFlow::Node::Type::Cond: {
// Nothing to do: a cond is not in the Binaryen IR.
// If the cond input is a constant, that might indicate
// useful optimizations are possible, which perhaps we
// should look into TODO
break;
}
case DataFlow::Node::Type::Zext: {
// Nothing to do: a zext is not in the Binaryen IR.
// If the cond input is a constant, that might indicate
// useful optimizations are possible, which perhaps we
// should look into TODO
break;
}
default: WASM_UNREACHABLE();
}
}
// No one is a user of this node after we replaced all the uses.
nodeUsers.removeAllUsesOf(node);
}
// Gets a pointer to the expression pointer in an expression.
// That is, given an index in the values() vector, get an
// Expression** that we can assign to so as to modify it.
Expression** getIndexPointer(Expression* expr, Index index) {
if (auto* unary = expr->dynCast<Unary>()) {
assert(index == 0);
return &unary->value;
} else if (auto* binary = expr->dynCast<Binary>()) {
if (index == 0) {
return &binary->left;
} else if (index == 1) {
return &binary->right;
} else {
WASM_UNREACHABLE();
}
} else if (auto* select = expr->dynCast<Select>()) {
if (index == 0) {
return &select->condition;
} else if (index == 1) {
return &select->ifTrue;
} else if (index == 2) {
return &select->ifFalse;
} else {
WASM_UNREACHABLE();
}
} else {
WASM_UNREACHABLE();
}
}
};
Pass *createDataFlowOptsPass() {
return new DataFlowOpts();
}
} // namespace wasm

View File

@ -68,7 +68,7 @@ struct DeadCodeElimination : public WalkerPass<PostWalker<DeadCodeElimination>>
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
// nodes, so we would not get to this place 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.

View File

@ -72,6 +72,10 @@ struct LegalizeJSInterface : public Pass {
}
}
if (illegalToLegal.size() > 0) {
for (auto& pair : illegalToLegal) {
module->removeImport(pair.first);
}
for (auto* im : newImports) {
module->addImport(im);
}

View File

@ -258,22 +258,6 @@ Index getMaxBits(Expression* curr, LocalInfoProvider* localInfoProvider) {
}
}
// looks through fallthrough operations, like tee_local, block fallthrough, etc.
// too and block fallthroughs, etc.
Expression* getFallthrough(Expression* curr) {
if (auto* set = curr->dynCast<SetLocal>()) {
if (set->isTee()) {
return getFallthrough(set->value);
}
} else if (auto* block = curr->dynCast<Block>()) {
// if no name, we can't be broken to, and then can look at the fallthrough
if (!block->name.is() && block->list.size() > 0) {
return getFallthrough(block->list.back());
}
}
return curr;
}
// Useful information about locals
struct LocalInfo {
static const Index kUnknown = Index(-1);
@ -316,7 +300,7 @@ struct LocalScanner : PostWalker<LocalScanner> {
auto type = getFunction()->getLocalType(curr->index);
if (type != i32 && type != i64) return;
// an integer var, worth processing
auto* value = getFallthrough(curr->value);
auto* value = Properties::getFallthrough(curr->value);
auto& info = localInfo[curr->index];
info.maxBits = std::max(info.maxBits, getMaxBits(value, this));
auto signExtBits = LocalInfo::kUnknown;
@ -421,7 +405,7 @@ struct OptimizeInstructions : public WalkerPass<PostWalker<OptimizeInstructions,
Index extraShifts;
auto bits = Properties::getAlmostSignExtBits(binary, extraShifts);
if (extraShifts == 0) {
if (auto* load = getFallthrough(ext)->dynCast<Load>()) {
if (auto* load = Properties::getFallthrough(ext)->dynCast<Load>()) {
// pattern match a load of 8 bits and a sign extend using a shl of 24 then shr_s of 24 as well, etc.
if (LoadUtils::canBeSigned(load) &&
((load->bytes == 1 && bits == 8) || (load->bytes == 2 && bits == 16))) {

View File

@ -316,7 +316,7 @@ private:
value = curr; // this is the first
first = false;
} else {
if (!value.bitwiseEqual(curr)) {
if (value != curr) {
// not the same, give up
value = Literal();
break;

File diff suppressed because it is too large Load Diff

View File

@ -47,10 +47,6 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> {
bool anotherCycle;
// Whether a value can flow in the current path. If so, then a br with value
// can be turned into a value, which will flow through blocks/ifs to the right place
bool valueCanFlow;
typedef std::vector<Expression**> Flows;
// list of breaks that are currently flowing. if they reach their target without
@ -73,14 +69,12 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> {
if (!br->condition) { // TODO: optimize?
// a break, let's see where it flows to
flows.push_back(currp);
self->valueCanFlow = true; // start optimistic
} else {
self->stopValueFlow();
}
} else if (curr->is<Return>()) {
flows.clear();
flows.push_back(currp);
self->valueCanFlow = true; // start optimistic
} else if (curr->is<If>()) {
auto* iff = curr->cast<If>();
if (iff->condition->type == unreachable) {
@ -90,10 +84,20 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> {
}
if (iff->ifFalse) {
assert(self->ifStack.size() > 0);
for (auto* flow : self->ifStack.back()) {
auto ifTrueFlows = std::move(self->ifStack.back());
self->ifStack.pop_back();
// we can flow values out in most cases, except if one arm
// has the none type - we will update the types later, but
// there is no way to emit a proper type for one arm being
// none and the other flowing a value; and there is no way
// to flow a value from a none.
if (iff->ifTrue->type == none || iff->ifFalse->type == none) {
self->removeValueFlow(ifTrueFlows);
self->stopValueFlow();
}
for (auto* flow : ifTrueFlows) {
flows.push_back(flow);
}
self->ifStack.pop_back();
} else {
// if without else stops the flow of values
self->stopValueFlow();
@ -108,17 +112,15 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> {
for (size_t i = 0; i < size; i++) {
auto* flow = (*flows[i])->dynCast<Break>();
if (flow && flow->name == name) {
if (!flow->value || self->valueCanFlow) {
if (!flow->value) {
// br => nop
ExpressionManipulator::nop<Break>(flow);
} else {
// br with value => value
*flows[i] = flow->value;
}
skip++;
self->anotherCycle = true;
if (!flow->value) {
// br => nop
ExpressionManipulator::nop<Break>(flow);
} else {
// br with value => value
*flows[i] = flow->value;
}
skip++;
self->anotherCycle = true;
} else if (skip > 0) {
flows[i - skip] = flows[i];
}
@ -148,18 +150,20 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> {
void stopFlow() {
flows.clear();
valueCanFlow = false;
}
void stopValueFlow() {
flows.erase(std::remove_if(flows.begin(), flows.end(), [&](Expression** currp) {
void removeValueFlow(Flows& currFlows) {
currFlows.erase(std::remove_if(currFlows.begin(), currFlows.end(), [&](Expression** currp) {
auto* curr = *currp;
if (auto* ret = curr->dynCast<Return>()) {
return ret->value;
}
return curr->cast<Break>()->value;
}), flows.end());
valueCanFlow = false;
}), currFlows.end());
}
void stopValueFlow() {
removeValueFlow(flows);
}
static void clear(RemoveUnusedBrs* self, Expression** currp) {
@ -447,7 +451,7 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> {
// return => nop
ExpressionManipulator::nop(flow);
anotherCycle = true;
} else if (valueCanFlow) {
} else {
// return with value => value
*flows[i] = flow->value;
anotherCycle = true;

View File

@ -0,0 +1,691 @@
/*
* Copyright 2018 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.
*/
//
// Souperify - convert to Souper IR in text form.
//
// This needs 'flatten' to be run before it, as it assumes the IR is in
// flat form. You may also want to optimize a little, e.g.
// --flatten --simplify-locals-nonesting --reorder-locals
// (as otherwise flattening introduces many copies; we do ignore boring
// copies here, but they end up as identical LHSes).
//
// See https://github.com/google/souper/issues/323
//
// TODO:
// * pcs and blockpcs for things other than ifs
// * Investigate 'inlining', adding in nodes through calls
// * Consider generalizing DataFlow IR for internal Binaryen use.
// * Automatic conversion of Binaryen IR opts to run on the DataFlow IR.
// This would subsume precompute-propagate, for example. Using DFIR we
// can "expand" the BIR into expressions that BIR opts can handle
// directly, without the need for *-propagate techniques.
//
#include "wasm.h"
#include "pass.h"
#include "wasm-builder.h"
#include "ir/local-graph.h"
#include "ir/utils.h"
#include "dataflow/node.h"
#include "dataflow/graph.h"
#include "dataflow/utils.h"
namespace wasm {
static int debug() {
static char* str = getenv("BINARYEN_DEBUG_SOUPERIFY");
static int ret = str ? atoi(str) : 0;
return ret;
}
namespace DataFlow {
// Internal helper to find all the uses of a set.
struct UseFinder {
// Gets a list of all the uses of an expression. As we are in flat IR,
// the expression must be the value of a set, and we seek the other sets
// (or rather, their values) that contain a get that uses that value.
// There may also be non-set uses of the value, for example in a drop
// or a return. We represent those with a nullptr, meaning "other".
std::vector<Expression*> getUses(Expression* origin, Graph& graph, LocalGraph& localGraph) {
if (debug() >= 2) {
std::cout << "getUses\n" << origin << '\n';
}
std::vector<Expression*> ret;
auto* set = graph.getSet(origin);
if (!set) {
// If the parent is not a set (a drop, call, return, etc.) then
// it is not something we need to track.
return ret;
}
addSetUses(set, graph, localGraph, ret);
return ret;
}
// There may be loops of sets with copies between them.
std::unordered_set<SetLocal*> seenSets;
void addSetUses(SetLocal* set, Graph& graph, LocalGraph& localGraph, std::vector<Expression*>& ret) {
// If already handled, nothing to do here.
if (seenSets.count(set)) return;
seenSets.insert(set);
// Find all the uses of that set.
auto& gets = localGraph.setInfluences[set];
if (debug() >= 2) {
std::cout << "addSetUses for " << set << ", " << gets.size() << " gets\n";
}
for (auto* get : gets) {
// Each of these relevant gets is either
// (1) a child of a set, which we can track, or
// (2) not a child of a set, e.g., a call argument or such
auto& sets = localGraph.getInfluences[get]; // TODO: iterator
// In flat IR, each get can influence at most 1 set.
assert(sets.size() <= 1);
if (sets.size() == 0) {
// This get is not the child of a set. Check if it is a drop,
// otherwise it is an actual use, and so an external use.
auto* parent = graph.getParent(get);
if (parent && parent->is<Drop>()) {
// Just ignore it.
} else {
ret.push_back(nullptr);
if (debug() >= 2) {
std::cout << "add nullptr\n";
}
}
} else {
// This get is the child of a set.
auto* subSet = *sets.begin();
// If this is a copy, we need to look through it: data-flow IR
// counts actual values, not copies, and in particular we need
// to look through the copies that implement a phi.
if (subSet->value == get) {
// Indeed a copy.
// TODO: this could be optimized and done all at once beforehand.
addSetUses(subSet, graph, localGraph, ret);
} else {
// Not a copy.
auto* value = subSet->value;
ret.push_back(value);
if (debug() >= 2) {
std::cout << "add a value\n" << value << '\n';
}
}
}
}
}
};
// Generates a trace: all the information to generate a Souper LHS
// for a specific set_local whose value we want to infer.
struct Trace {
Graph& graph;
Node* toInfer;
// Nodes we should exclude from being children of traces (but they
// may be the root we try to infer.
std::unordered_set<Node*>& excludeAsChildren;
// A limit on how deep we go - we don't want to create arbitrarily
// large traces.
size_t depthLimit = 10;
size_t totalLimit = 30;
bool bad = false;
std::vector<Node*> nodes;
std::unordered_set<Node*> addedNodes;
std::vector<Node*> pathConditions;
// When we need to (like when the depth is too deep), we replace
// expressions with other expressions, and track them here.
std::unordered_map<Node*, std::unique_ptr<Node>> replacements;
// The nodes that have additional external uses (only computed
// for the "work" nodes, not the descriptive nodes arriving for
// path conditions).
std::unordered_set<Node*> hasExternalUses;
// We add path conditions after the "work". We collect them here
// and then go through them at the proper time.
std::vector<Node*> conditionsToAdd;
// Whether we are at the adding-conditions stage (i.e., post
// adding the "work").
bool addingConditions = false;
// The local information graph. Used to check if a node has external uses.
LocalGraph& localGraph;
Trace(Graph& graph, Node* toInfer, std::unordered_set<Node*>& excludeAsChildren, LocalGraph& localGraph) : graph(graph), toInfer(toInfer), excludeAsChildren(excludeAsChildren), localGraph(localGraph) {
if (debug() >= 2) {
std::cout << "\nstart a trace (in " << graph.func->name << ")\n";
}
// Check if there is a depth limit override
auto* depthLimitStr = getenv("BINARYEN_SOUPERIFY_DEPTH_LIMIT");
if (depthLimitStr) {
depthLimit = atoi(depthLimitStr);
}
auto* totalLimitStr = getenv("BINARYEN_SOUPERIFY_TOTAL_LIMIT");
if (totalLimitStr) {
totalLimit = atoi(totalLimitStr);
}
// Pull in all the dependencies, starting from the value itself.
add(toInfer, 0);
if (bad) return;
// If we are trivial before adding pcs, we are still trivial, and
// can ignore this.
auto sizeBeforePathConditions = nodes.size();
// No input is uninteresting
if (sizeBeforePathConditions == 0) {
bad = true;
return;
}
// Just a var is uninteresting. TODO: others too?
if (sizeBeforePathConditions == 1 && nodes[0]->isVar()) {
bad = true;
return;
}
// Before adding the path conditions, we can now compute the
// actual number of uses of "work" nodes, the real computation done
// here and that we hope to replace, as opposed to path condition
// computation which is only descriptive and helps optimization of
// the work.
findExternalUses();
// We can now add conditions.
addingConditions = true;
for (auto* condition : conditionsToAdd) {
add(condition, 0);
}
// Add in path conditions based on the location of this node: e.g.
// if it is inside an if's true branch, we can add a path-condition
// for that.
auto iter = graph.nodeParentMap.find(toInfer);
if (iter != graph.nodeParentMap.end()) {
addPath(toInfer, iter->second);
}
}
Node* add(Node* node, size_t depth) {
depth++;
// If replaced, return the replacement.
auto iter = replacements.find(node);
if (iter != replacements.end()) {
return iter->second.get();
}
// If already added, nothing more to do.
if (addedNodes.find(node) != addedNodes.end()) {
return node;
}
switch (node->type) {
case Node::Type::Var: {
break; // nothing more to add
}
case Node::Type::Expr: {
// If this is a Const, it's not an instruction - nothing to add,
// it's just a value.
if (node->expr->is<Const>()) {
return node;
}
// If we've gone too deep, emit a var instead.
// Do the same if this is a node we should exclude from traces.
if (depth >= depthLimit || nodes.size() >= totalLimit ||
(node != toInfer && excludeAsChildren.find(node) != excludeAsChildren.end())) {
auto type = node->getWasmType();
assert(isConcreteType(type));
auto* var = Node::makeVar(type);
replacements[node] = std::unique_ptr<Node>(var);
node = var;
break;
}
// Add the dependencies.
assert(!node->expr->is<GetLocal>());
for (Index i = 0; i < node->values.size(); i++) {
add(node->getValue(i), depth);
}
break;
}
case Node::Type::Phi: {
auto* block = add(node->getValue(0), depth);
assert(block);
auto size = block->values.size();
// First, add the conditions for the block
for (Index i = 0; i < size; i++) {
// a condition may be bad, but conditions are not necessary -
// we can proceed without the extra condition information
auto* condition = block->getValue(i);
if (!condition->isBad()) {
if (!addingConditions) {
// Too early, queue it for later.
conditionsToAdd.push_back(condition);
} else {
add(condition, depth);
}
}
}
// Then, add the phi values
for (Index i = 1; i < size + 1; i++) {
add(node->getValue(i), depth);
}
break;
}
case Node::Type::Cond: {
add(node->getValue(0), depth); // add the block
add(node->getValue(1), depth); // add the node
break;
}
case Node::Type::Block: {
break; // nothing more to add
}
case Node::Type::Zext: {
add(node->getValue(0), depth);
break;
}
case Node::Type::Bad: {
bad = true;
return nullptr;
}
default: WASM_UNREACHABLE();
}
// Assert on no cycles
assert(addedNodes.find(node) == addedNodes.end());
nodes.push_back(node);
addedNodes.insert(node);
return node;
}
void addPath(Node* node, Expression* curr) {
// We track curr and parent, which are always in the state of parent
// being the parent of curr.
auto* parent = graph.expressionParentMap.at(curr);
while (parent) {
auto iter = graph.expressionConditionMap.find(parent);
if (iter != graph.expressionConditionMap.end()) {
// Given the block, add a proper path-condition
addPathTo(parent, curr, iter->second);
}
curr = parent;
parent = graph.expressionParentMap.at(parent);
}
}
// curr is a child of parent, and parent has a Block which we are
// give as 'node'. Add a path condition for reaching the child.
void addPathTo(Expression* parent, Expression* curr, std::vector<Node*> conditions) {
if (auto* iff = parent->dynCast<If>()) {
Index index;
if (curr == iff->ifTrue) {
index = 0;
} else if (curr == iff->ifFalse) {
index = 1;
} else {
WASM_UNREACHABLE();
}
auto* condition = conditions[index];
// Add the condition itself as an instruction in the trace -
// the pc uses it as its input.
add(condition, 0);
// Add it as a pc, which we will emit directly.
pathConditions.push_back(condition);
} else {
WASM_UNREACHABLE();
}
}
bool isBad() {
return bad;
}
static bool isTraceable(Node* node) {
if (!node->origin) {
// Ignore artificial etc. nodes.
// TODO: perhaps require all the nodes for an origin appear, so we
// don't try to compute an internal part of one, like the
// extra artificial != 0 of a select?
return false;
}
if (node->isExpr()) {
// Consider only the simple computational nodes.
auto* expr = node->expr;
return expr->is<Unary>() || expr->is<Binary>() || expr->is<Select>();
}
return false;
}
void findExternalUses() {
// Find all the wasm code represented in this trace.
std::unordered_set<Expression*> origins;
for (auto& node : nodes) {
if (auto* origin = node->origin) {
if (debug() >= 2) {
std::cout << "note origin " << origin << '\n';
}
origins.insert(origin);
}
}
for (auto& node : nodes) {
if (node == toInfer) continue;
if (auto* origin = node->origin) {
auto uses = UseFinder().getUses(origin, graph, localGraph);
for (auto* use : uses) {
// A non-set use (a drop or return etc.) is definitely external.
// Otherwise, check if internal or external.
if (use == nullptr || origins.count(use) == 0) {
if (debug() >= 2) {
std::cout << "found external use for\n";
dump(node, std::cout);
std::cout << " due to " << use << '\n';
}
hasExternalUses.insert(node);
break;
}
}
}
}
}
};
// Emits a trace, which is basically a Souper LHS.
struct Printer {
Graph& graph;
Trace& trace;
// Each Node in a trace has an index, from 0.
std::unordered_map<Node*, Index> indexing;
bool printedHasExternalUses = false;
Printer(Graph& graph, Trace& trace) : graph(graph), trace(trace) {
std::cout << "\n; start LHS (in " << graph.func->name << ")\n";
// Index the nodes.
for (auto* node : trace.nodes) {
if (!node->isCond()) { // pcs and blockpcs are not instructions and do not need to be indexed
auto index = indexing.size();
indexing[node] = index;
}
}
// Print them out.
for (auto* node : trace.nodes) {
print(node);
}
// Print out pcs.
for (auto* condition : trace.pathConditions) {
printPathCondition(condition);
}
// Finish up
std::cout << "infer %" << indexing[trace.toInfer] << "\n\n";
}
Node* getMaybeReplaced(Node* node) {
auto iter = trace.replacements.find(node);
if (iter != trace.replacements.end()) {
return iter->second.get();
}
return node;
}
void print(Node* node) {
// The node may have been replaced during trace building, if so then
// print the proper replacement.
node = getMaybeReplaced(node);
assert(node);
switch (node->type) {
case Node::Type::Var: {
std::cout << "%" << indexing[node] << ":" << printType(node->wasmType) << " = var";
break; // nothing more to add
}
case Node::Type::Expr: {
if (debug()) {
std::cout << "; ";
WasmPrinter::printExpression(node->expr, std::cout, true);
std::cout << '\n';
}
std::cout << "%" << indexing[node] << " = ";
printExpression(node);
break;
}
case Node::Type::Phi: {
auto* block = node->getValue(0);
auto size = block->values.size();
std::cout << "%" << indexing[node] << " = phi %" << indexing[block];
for (Index i = 1; i < size + 1; i++) {
std::cout << ", ";
printInternal(node->getValue(i));
}
break;
}
case Node::Type::Cond: {
std::cout << "blockpc %" << indexing[node->getValue(0)] << ' ' << node->index << ' ';
printInternal(node->getValue(1));
std::cout << " 1:i1";
break;
}
case Node::Type::Block: {
std::cout << "%" << indexing[node] << " = block " << node->values.size();
break;
}
case Node::Type::Zext: {
auto* child = node->getValue(0);
std::cout << "%" << indexing[node] << ':' << printType(child->getWasmType());
std::cout << " = zext ";
printInternal(child);
break;
}
case Node::Type::Bad: {
std::cout << "!!!BAD!!!";
WASM_UNREACHABLE();
}
default: WASM_UNREACHABLE();
}
if (node->isExpr() || node->isPhi()) {
if (node->origin != trace.toInfer->origin && trace.hasExternalUses.count(node) > 0) {
std::cout << " (hasExternalUses)";
printedHasExternalUses = true;
}
}
std::cout << '\n';
if (debug() && (node->isExpr() || node->isPhi())) {
warnOnSuspiciousValues(node);
}
}
void print(Literal value) {
std::cout << value.getInteger() << ':' << printType(value.type);
}
void printInternal(Node* node) {
node = getMaybeReplaced(node);
assert(node);
if (node->isConst()) {
print(node->expr->cast<Const>()->value);
} else {
std::cout << "%" << indexing[node];
}
}
// Emit an expression
void printExpression(Node* node) {
assert(node->isExpr());
// TODO use a Visitor here?
auto* curr = node->expr;
if (auto* c = curr->dynCast<Const>()) {
print(c->value);
} else if (auto* unary = curr->dynCast<Unary>()) {
switch (unary->op) {
case ClzInt32:
case ClzInt64: std::cout << "ctlz"; break;
case CtzInt32:
case CtzInt64: std::cout << "cttz"; break;
case PopcntInt32:
case PopcntInt64: std::cout << "ctpop"; break;
default: WASM_UNREACHABLE();
}
std::cout << ' ';
auto* value = node->getValue(0);
printInternal(value);
} else if (auto* binary = curr->dynCast<Binary>()) {
switch (binary->op) {
case AddInt32:
case AddInt64: std::cout << "add"; break;
case SubInt32:
case SubInt64: std::cout << "sub"; break;
case MulInt32:
case MulInt64: std::cout << "mul"; break;
case DivSInt32:
case DivSInt64: std::cout << "sdiv"; break;
case DivUInt32:
case DivUInt64: std::cout << "udiv"; break;
case RemSInt32:
case RemSInt64: std::cout << "srem"; break;
case RemUInt32:
case RemUInt64: std::cout << "urem"; break;
case AndInt32:
case AndInt64: std::cout << "and"; break;
case OrInt32:
case OrInt64: std::cout << "or"; break;
case XorInt32:
case XorInt64: std::cout << "xor"; break;
case ShlInt32:
case ShlInt64: std::cout << "shl"; break;
case ShrUInt32:
case ShrUInt64: std::cout << "lshr"; break;
case ShrSInt32:
case ShrSInt64: std::cout << "ashr"; break;
case RotLInt32:
case RotLInt64: std::cout << "rotl"; break;
case RotRInt32:
case RotRInt64: std::cout << "rotr"; break;
case EqInt32:
case EqInt64: std::cout << "eq"; break;
case NeInt32:
case NeInt64: std::cout << "ne"; break;
case LtSInt32:
case LtSInt64: std::cout << "slt"; break;
case LtUInt32:
case LtUInt64: std::cout << "ult"; break;
case LeSInt32:
case LeSInt64: std::cout << "sle"; break;
case LeUInt32:
case LeUInt64: std::cout << "ule"; break;
default: WASM_UNREACHABLE();
}
std::cout << ' ';
auto* left = node->getValue(0);
printInternal(left);
std::cout << ", ";
auto* right = node->getValue(1);
printInternal(right);
} else if (curr->is<Select>()) {
std::cout << "select ";
printInternal(node->getValue(0));
std::cout << ", ";
printInternal(node->getValue(1));
std::cout << ", ";
printInternal(node->getValue(2));
} else {
WASM_UNREACHABLE();
}
}
void printPathCondition(Node* condition) {
std::cout << "pc ";
printInternal(condition);
std::cout << " 1:i1\n";
}
// Checks if a value looks suspiciously optimizable.
void warnOnSuspiciousValues(Node* node) {
assert(debug());
// If the node has no uses, it's not interesting enough to be
// suspicious. TODO
// If an input was replaced with a var, then we should not
// look into it, it's not suspiciously trivial.
for (auto* value : node->values) {
if (value != getMaybeReplaced(value)) {
return;
}
}
if (allInputsIdentical(node)) {
std::cout << "^^ suspicious identical inputs! missing optimization in " << graph.func->name << "? ^^\n";
return;
}
if (!node->isPhi() && allInputsConstant(node)) {
std::cout << "^^ suspicious constant inputs! missing optimization in " << graph.func->name << "? ^^\n";
return;
}
}
};
} // namespace DataFlow
struct Souperify : public WalkerPass<PostWalker<Souperify>> {
// Not parallel, for now - could parallelize and combine outputs at the end.
// If Souper is thread-safe, we could also run it in parallel.
bool singleUseOnly;
Souperify(bool singleUseOnly) : singleUseOnly(singleUseOnly) {}
void doWalkFunction(Function* func) {
std::cout << "\n; function: " << func->name << '\n';
// Build the data-flow IR.
DataFlow::Graph graph;
graph.build(func, getModule());
if (debug() >= 2) dump(graph, std::cout);
// Build the local graph data structure.
LocalGraph localGraph(func);
localGraph.computeInfluences();
// If we only want single-use nodes, exclude all the others.
std::unordered_set<DataFlow::Node*> excludeAsChildren;
if (singleUseOnly) {
for (auto& nodePtr : graph.nodes) {
auto* node = nodePtr.get();
if (node->origin) {
// TODO: work for identical origins could be saved
auto uses = DataFlow::UseFinder().getUses(node->origin, graph, localGraph);
if (debug() >= 2) {
std::cout << "following node has " << uses.size() << " uses\n";
dump(node, std::cout);
}
if (uses.size() > 1) {
excludeAsChildren.insert(node);
}
}
}
}
// Emit possible traces.
for (auto& nodePtr : graph.nodes) {
auto* node = nodePtr.get();
// Trace
if (DataFlow::Trace::isTraceable(node)) {
DataFlow::Trace trace(graph, node, excludeAsChildren, localGraph);
if (!trace.isBad()) {
DataFlow::Printer printer(graph, trace);
if (singleUseOnly) {
assert(!printer.printedHasExternalUses);
}
}
}
}
}
};
Pass *createSouperifyPass() {
return new Souperify(false);
}
Pass *createSouperifySingleUsePass() {
return new Souperify(true);
}
} // namespace wasm

View File

@ -71,32 +71,6 @@ Pass* createGenerateStackIRPass() {
return new GenerateStackIR();
}
// Print (for debugging purposes)
struct PrintStackIR : public WalkerPass<PostWalker<PrintStackIR>> {
// Not parallel: this pass is just for testing and debugging; keep the output
// sorted by function order.
bool isFunctionParallel() override { return false; }
Pass* create() override { return new PrintStackIR; }
bool modifiesBinaryenIR() override { return false; }
void doWalkFunction(Function* func) {
std::cout << func->name << ":\n";
if (func->stackIR) {
std::cout << *func->stackIR;
} else {
std::cout << " (no stack ir)";
}
std::cout << '\n';
}
};
Pass* createPrintStackIRPass() {
return new PrintStackIR();
}
// Optimize
class StackIROptimizer {

View File

@ -72,6 +72,7 @@ void PassRegistry::registerPasses() {
registerPass("code-folding", "fold code, merging duplicates", createCodeFoldingPass);
registerPass("const-hoisting", "hoist repeated constants to a local", createConstHoistingPass);
registerPass("dce", "removes unreachable code", createDeadCodeEliminationPass);
registerPass("dfo", "optimizes using the DataFlow SSA IR", createDataFlowOptsPass);
registerPass("duplicate-function-elimination", "removes duplicate functions", createDuplicateFunctionEliminationPass);
registerPass("extract-function", "leaves just one function (useful for debugging)", createExtractFunctionPass);
registerPass("flatten", "flattens out code, removing nesting", createFlattenPass);
@ -117,9 +118,11 @@ void PassRegistry::registerPasses() {
registerPass("safe-heap", "instrument loads and stores to check for invalid behavior", createSafeHeapPass);
registerPass("simplify-locals", "miscellaneous locals-related optimizations", createSimplifyLocalsPass);
registerPass("simplify-locals-nonesting", "miscellaneous locals-related optimizations (no nesting at all; preserves flatness)", createSimplifyLocalsNoNestingPass);
registerPass("simplify-locals-notee", "miscellaneous locals-related optimizations", createSimplifyLocalsNoTeePass);
registerPass("simplify-locals-nostructure", "miscellaneous locals-related optimizations", createSimplifyLocalsNoStructurePass);
registerPass("simplify-locals-notee-nostructure", "miscellaneous locals-related optimizations", createSimplifyLocalsNoTeeNoStructurePass);
registerPass("simplify-locals-notee", "miscellaneous locals-related optimizations (no tees)", createSimplifyLocalsNoTeePass);
registerPass("simplify-locals-nostructure", "miscellaneous locals-related optimizations (no structure)", createSimplifyLocalsNoStructurePass);
registerPass("simplify-locals-notee-nostructure", "miscellaneous locals-related optimizations (no tees or structure)", createSimplifyLocalsNoTeeNoStructurePass);
registerPass("souperify", "emit Souper IR in text form", createSouperifyPass);
registerPass("souperify-single-use", "emit Souper IR in text form (single-use nodes only)", createSouperifySingleUsePass);
registerPass("spill-pointers", "spill pointers to the C stack (useful for Boehm-style GC)", createSpillPointersPass);
registerPass("ssa", "ssa-ify variables so that they have a single assignment", createSSAifyPass);
registerPass("trap-mode-clamp", "replace trapping operations with clamping semantics", createTrapModeClamp);

View File

@ -27,6 +27,7 @@ Pass* createCoalesceLocalsWithLearningPass();
Pass* createCodeFoldingPass();
Pass* createCodePushingPass();
Pass* createConstHoistingPass();
Pass* createDataFlowOptsPass();
Pass* createDeadCodeEliminationPass();
Pass* createDuplicateFunctionEliminationPass();
Pass* createExtractFunctionPass();
@ -76,6 +77,8 @@ Pass* createSimplifyLocalsNoNestingPass();
Pass* createSimplifyLocalsNoTeePass();
Pass* createSimplifyLocalsNoStructurePass();
Pass* createSimplifyLocalsNoTeeNoStructurePass();
Pass* createSouperifyPass();
Pass* createSouperifySingleUsePass();
Pass* createSpillPointersPass();
Pass* createSSAifyPass();
Pass* createTrapModeClamp();

View File

@ -62,16 +62,21 @@ inline std::ostream& printText(std::ostream &o, const char *str) {
return o << '"';
}
inline std::ostream& printOpening(std::ostream &o, const char *str, bool major=false) {
o << '(';
major ? prepareMajorColor(o) : prepareColor(o);
inline std::ostream& printMajor(std::ostream &o, const char *str, bool major=false) {
prepareMajorColor(o);
o << str;
restoreNormalColor(o);
return o;
}
inline std::ostream& printMinorOpening(std::ostream &o, const char *str) {
o << '(';
inline std::ostream& printMedium(std::ostream &o, const char *str, bool major=false) {
prepareColor(o);
o << str;
restoreNormalColor(o);
return o;
}
inline std::ostream& printMinor(std::ostream &o, const char *str) {
prepareMinorColor(o);
o << str;
restoreNormalColor(o);

View File

@ -138,7 +138,7 @@ struct ShellExternalInterface final : ModuleInstance::ExternalInterface {
Literal callImport(Import *import, LiteralList& arguments) override {
if (import->module == SPECTEST && import->base == PRINT) {
for (auto argument : arguments) {
std::cout << argument << '\n';
std::cout << '(' << argument << ')' << '\n';
}
return Literal();
} else if (import->module == ENV && import->base == EXIT) {

View File

@ -78,7 +78,7 @@ struct ExecutionResults {
abort();
}
std::cout << "[fuzz-exec] comparing " << name << '\n';
if (!results[name].bitwiseEqual(other.results[name])) {
if (results[name] != other.results[name]) {
std::cout << "not identical!\n";
abort();
}

View File

@ -42,6 +42,7 @@ int main(int argc, const char *argv[]) {
std::string outputSourceMapUrl;
bool emitBinary = true;
bool debugInfo = false;
bool legalizeJavaScriptFFI = true;
unsigned numReservedFunctionPointers = 0;
uint64_t globalBase;
Options options("wasm-emscripten-finalize",
@ -77,9 +78,16 @@ int main(int argc, const char *argv[]) {
[&globalBase](Options*, const std::string&argument ) {
globalBase = std::stoull(argument);
})
.add("--input-source-map", "-ism", "Consume source map from the specified file",
Options::Arguments::One,
[&inputSourceMapFilename](Options *o, const std::string& argument) { inputSourceMapFilename = argument; })
.add("--no-legalize-javascript-ffi", "-nj", "Do not legalize (i64->i32, "
"f32->f64) the imports and exports for interfacing with JS",
Options::Arguments::Zero,
[&legalizeJavaScriptFFI](Options *o, const std::string& ) {
legalizeJavaScriptFFI = false;
})
.add("--output-source-map", "-osm", "Emit source map to the specified file",
Options::Arguments::One,
[&outputSourceMapFilename](Options *o, const std::string& argument) { outputSourceMapFilename = argument; })
@ -133,10 +141,21 @@ int main(int argc, const char *argv[]) {
uint32_t dataSize = dataEndConst->value.geti32() - globalBase;
std::vector<Name> initializerFunctions;
initializerFunctions.push_back("__wasm_call_ctors");
if (wasm.getFunctionOrNull("__wasm_call_ctors")) {
initializerFunctions.push_back("__wasm_call_ctors");
}
EmscriptenGlueGenerator generator(wasm);
generator.fixInvokeFunctionNames();
if (legalizeJavaScriptFFI) {
PassRunner passRunner(&wasm);
passRunner.setDebug(options.debug);
passRunner.setDebugInfo(debugInfo);
passRunner.add("legalize-js-interface");
passRunner.run();
}
generator.generateRuntimeFunctions();
generator.generateMemoryGrowthFunction();
generator.generateDynCallThunks();

View File

@ -106,7 +106,7 @@ struct Mergeable {
}
}
for (auto& section : wasm.userSections) {
if (section.name == "dylink") {
if (section.name == BinaryConsts::UserSections::Dylink) {
WasmBinaryBuilder builder(wasm, section.data, false);
totalMemorySize = std::max(totalMemorySize, builder.getU32LEB());
totalTableSize = std::max(totalTableSize, builder.getU32LEB());

View File

@ -201,14 +201,14 @@ static void run_asserts(Name moduleName, size_t* i, bool* checked, Module* wasm,
->dynCast<Const>()
->value;
std::cerr << "seen " << result << ", expected " << expected << '\n';
if (!expected.bitwiseEqual(result)) {
if (expected != result) {
std::cout << "unexpected, should be identical\n";
abort();
}
} else {
Literal expected;
std::cerr << "seen " << result << ", expected " << expected << '\n';
if (!expected.bitwiseEqual(result)) {
if (expected != result) {
std::cout << "unexpected, should be identical\n";
abort();
}

View File

@ -15,21 +15,21 @@
*/
//
// wasm2asm console tool
// wasm2js console tool
//
#include "support/colors.h"
#include "support/command-line.h"
#include "support/file.h"
#include "wasm-s-parser.h"
#include "wasm2asm.h"
#include "wasm2js.h"
using namespace cashew;
using namespace wasm;
int main(int argc, const char *argv[]) {
Wasm2AsmBuilder::Flags builderFlags;
Options options("wasm2asm", "Transform .wast files to asm.js");
Wasm2JSBuilder::Flags builderFlags;
Options options("wasm2js", "Transform .wasm/.wast files to asm.js");
options
.add("--output", "-o", "Output file (stdout if not specified)",
Options::Arguments::One,
@ -55,28 +55,49 @@ int main(int argc, const char *argv[]) {
options.parse(argc, argv);
if (options.debug) builderFlags.debug = true;
auto input(
read_file<std::vector<char>>(options.extra["infile"], Flags::Text, options.debug ? Flags::Debug : Flags::Release));
Element* root;
Module wasm;
Ref asmjs;
try {
if (options.debug) std::cerr << "s-parsing..." << std::endl;
SExpressionParser parser(input.data());
root = parser.root;
// If the input filename ends in `.wasm`, then parse it in binary form,
// otherwise assume it's a `*.wast` file and go from there.
//
// Note that we're not using the built-in `ModuleReader` which will also do
// similar logic here because when testing JS files we use the
// `--allow-asserts` flag which means we need to parse the extra
// s-expressions that come at the end of the `*.wast` file after the module
// is defined.
auto &input = options.extra["infile"];
std::string suffix(".wasm");
if (input.size() >= suffix.size() &&
input.compare(input.size() - suffix.size(), suffix.size(), suffix) == 0) {
ModuleReader reader;
reader.setDebug(options.debug);
reader.read(input, wasm, "");
if (options.debug) std::cerr << "w-parsing..." << std::endl;
SExpressionWasmBuilder builder(wasm, *(*root)[0]);
if (options.debug) std::cerr << "asming..." << std::endl;
Wasm2JSBuilder wasm2js(builderFlags);
asmjs = wasm2js.processWasm(&wasm);
if (options.debug) std::cerr << "asming..." << std::endl;
Wasm2AsmBuilder wasm2asm(builderFlags);
asmjs = wasm2asm.processWasm(&wasm);
} else {
auto input(
read_file<std::vector<char>>(options.extra["infile"], Flags::Text, options.debug ? Flags::Debug : Flags::Release));
if (options.debug) std::cerr << "s-parsing..." << std::endl;
SExpressionParser parser(input.data());
root = parser.root;
if (options.extra["asserts"] == "1") {
if (options.debug) std::cerr << "asserting..." << std::endl;
flattenAppend(asmjs, wasm2asm.processAsserts(&wasm, *root, builder));
if (options.debug) std::cerr << "w-parsing..." << std::endl;
SExpressionWasmBuilder builder(wasm, *(*root)[0]);
if (options.debug) std::cerr << "asming..." << std::endl;
Wasm2JSBuilder wasm2js(builderFlags);
asmjs = wasm2js.processWasm(&wasm);
if (options.extra["asserts"] == "1") {
if (options.debug) std::cerr << "asserting..." << std::endl;
flattenAppend(asmjs, wasm2js.processAsserts(&wasm, *root, builder));
}
}
} catch (ParseException& p) {
p.dump(std::cerr);

View File

@ -340,6 +340,8 @@ namespace UserSections {
extern const char* Name;
extern const char* SourceMapUrl;
extern const char* Dylink;
enum Subsection {
NameFunction = 1,
NameLocal = 2,
@ -713,7 +715,9 @@ public:
void writeNames();
void writeSourceMapUrl();
void writeSymbolMap();
void writeUserSections();
void writeEarlyUserSections();
void writeLateUserSections();
void writeUserSection(const UserSection& section);
void writeSourceMapProlog();
void writeSourceMapEpilog();
@ -721,6 +725,7 @@ public:
// helpers
void writeInlineString(const char* name);
void writeEscapedName(const char* name);
void writeInlineBuffer(const char* data, size_t size);
struct Buffer {

View File

@ -30,9 +30,13 @@ struct WasmPrinter {
static std::ostream& printModule(Module* module);
static std::ostream& printExpression(Expression* expression, std::ostream& o, bool minify = false, bool full = false);
static std::ostream& printStackInst(StackInst* inst, std::ostream& o, Function* func=nullptr);
static std::ostream& printStackIR(StackIR* ir, std::ostream& o, Function* func=nullptr);
};
}
} // namespace wasm
namespace std {
@ -44,6 +48,14 @@ inline std::ostream& operator<<(std::ostream& o, wasm::Expression& expression) {
return wasm::WasmPrinter::printExpression(&expression, o);
}
inline std::ostream& operator<<(std::ostream& o, wasm::StackInst& inst) {
return wasm::WasmPrinter::printStackInst(&inst, o);
}
inline std::ostream& operator<<(std::ostream& o, wasm::StackIR& ir) {
return wasm::WasmPrinter::printStackIR(&ir, o);
}
} // namespace std
#endif // wasm_wasm_printing_h

View File

@ -70,51 +70,6 @@ public:
// e.g. wasm has no unreachable blocks, they must be none
};
} // namespace wasm
namespace std {
inline std::ostream& operator<<(std::ostream& o, wasm::StackInst& inst) {
switch (inst.op) {
case wasm::StackInst::Basic: {
std::cout << wasm::getExpressionName(inst.origin) << " (" << wasm::printType(inst.type) << ')';
break;
}
case wasm::StackInst::BlockBegin:
case wasm::StackInst::IfBegin:
case wasm::StackInst::LoopBegin: {
std::cout << wasm::getExpressionName(inst.origin);
break;
}
case wasm::StackInst::BlockEnd:
case wasm::StackInst::IfEnd:
case wasm::StackInst::LoopEnd: {
std::cout << "end (" << wasm::printType(inst.type) << ')';
break;
}
case wasm::StackInst::IfElse: {
std::cout << "else";
break;
}
default: WASM_UNREACHABLE();
}
return o;
}
inline std::ostream& operator<<(std::ostream& o, wasm::StackIR& insts) {
wasm::Index index = 0;
for (wasm::Index i = 0; i < insts.size(); i++) {
auto* inst = insts[i];
if (!inst) continue;
std::cout << index++ << ' ' << *inst << '\n';
}
return o;
}
} // namespace std
namespace wasm {
//
// StackWriter: Writes out binary format stack machine code for a Binaryen IR expression
//

View File

@ -213,7 +213,7 @@ struct OverriddenVisitor {
// separate visit* per node
template<typename SubType, typename ReturnType = void>
struct UnifiedExpressionVisitor : public Visitor<SubType> {
struct UnifiedExpressionVisitor : public Visitor<SubType, ReturnType> {
// called on each node
ReturnType visitExpression(Expression* curr) { return ReturnType(); }

View File

@ -81,26 +81,14 @@ int64_t Literal::getBits() const {
bool Literal::operator==(const Literal& other) const {
if (type != other.type) return false;
switch (type) {
case Type::none: return true;
case Type::i32: return i32 == other.i32;
case Type::f32: return getf32() == other.getf32();
case Type::i64: return i64 == other.i64;
case Type::f64: return getf64() == other.getf64();
default: abort();
}
if (type == none) return true;
return getBits() == other.getBits();
}
bool Literal::operator!=(const Literal& other) const {
return !(*this == other);
}
bool Literal::bitwiseEqual(const Literal& other) const {
if (type != other.type) return false;
if (type == none) return true;
return getBits() == other.getBits();
}
uint32_t Literal::NaNPayload(float f) {
assert(std::isnan(f) && "expected a NaN");
// SEEEEEEE EFFFFFFF FFFFFFFF FFFFFFFF
@ -168,7 +156,6 @@ void Literal::printDouble(std::ostream& o, double d) {
}
std::ostream& operator<<(std::ostream& o, Literal literal) {
o << '(';
prepareMinorColor(o) << printType(literal.type) << ".const ";
switch (literal.type) {
case none: o << "?"; break;
@ -179,7 +166,7 @@ std::ostream& operator<<(std::ostream& o, Literal literal) {
default: WASM_UNREACHABLE();
}
restoreNormalColor(o);
return o << ')';
return o;
}
Literal Literal::countLeadingZeroes() const {

View File

@ -39,6 +39,9 @@ void WasmBinaryWriter::prepare() {
void WasmBinaryWriter::write() {
writeHeader();
writeEarlyUserSections();
if (sourceMap) {
writeSourceMapProlog();
}
@ -62,7 +65,7 @@ void WasmBinaryWriter::write() {
writeSourceMapEpilog();
}
writeUserSections();
writeLateUserSections();
finishUp();
}
@ -452,13 +455,13 @@ void WasmBinaryWriter::writeNames() {
for (auto& import : wasm->imports) {
if (import->kind == ExternalKind::Function) {
o << U32LEB(emitted);
writeInlineString(import->name.str);
writeEscapedName(import->name.str);
emitted++;
}
}
for (auto& curr : wasm->functions) {
o << U32LEB(emitted);
writeInlineString(curr->name.str);
writeEscapedName(curr->name.str);
emitted++;
}
assert(emitted == mappedFunctions.size());
@ -535,17 +538,33 @@ void WasmBinaryWriter::writeSourceMapEpilog() {
*sourceMap << "\"}";
}
void WasmBinaryWriter::writeUserSections() {
void WasmBinaryWriter::writeEarlyUserSections() {
// The dylink section must be the first in the module, per
// the spec, to allow simple parsing by loaders.
for (auto& section : wasm->userSections) {
auto start = startSection(0);
writeInlineString(section.name.c_str());
for (size_t i = 0; i < section.data.size(); i++) {
o << uint8_t(section.data[i]);
if (section.name == BinaryConsts::UserSections::Dylink) {
writeUserSection(section);
}
finishSection(start);
}
}
void WasmBinaryWriter::writeLateUserSections() {
for (auto& section : wasm->userSections) {
if (section.name != BinaryConsts::UserSections::Dylink) {
writeUserSection(section);
}
}
}
void WasmBinaryWriter::writeUserSection(const UserSection& section) {
auto start = startSection(0);
writeInlineString(section.name.c_str());
for (size_t i = 0; i < section.data.size(); i++) {
o << uint8_t(section.data[i]);
}
finishSection(start);
}
void WasmBinaryWriter::writeDebugLocation(Expression* curr, Function* func) {
auto& debugLocations = func->debugLocations;
auto iter = debugLocations.find(curr);
@ -565,6 +584,35 @@ void WasmBinaryWriter::writeInlineString(const char* name) {
}
}
static bool isHexDigit(char ch) {
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
}
static int decodeHexNibble(char ch) {
return ch <= '9' ? ch & 15 : (ch & 15) + 9;
}
void WasmBinaryWriter::writeEscapedName(const char* name) {
if (!strpbrk(name, "\\")) {
writeInlineString(name);
return;
}
// decode escaped by escapeName (see below) function names
std::string unescaped;
int32_t size = strlen(name);
for (int32_t i = 0; i < size;) {
char ch = name[i++];
// support only `\xx` escapes; ignore invalid or unsupported escapes
if (ch != '\\' || i + 1 >= size || !isHexDigit(name[i]) || !isHexDigit(name[i + 1])) {
unescaped.push_back(ch);
continue;
}
unescaped.push_back(char((decodeHexNibble(name[i]) << 4) | decodeHexNibble(name[i + 1])));
i += 2;
}
writeInlineString(unescaped.c_str());
}
void WasmBinaryWriter::writeInlineBuffer(const char* data, size_t size) {
o << U32LEB(size);
for (size_t i = 0; i < size; i++) {
@ -1152,6 +1200,8 @@ void WasmBinaryBuilder::readSourceMapHeader() {
}
} else if (matching && name[pos] == ch) {
++pos;
} else if (matching) {
matching = false;
}
}
skipWhitespace();
@ -1515,6 +1565,42 @@ void WasmBinaryBuilder::readTableElements() {
}
}
static bool isIdChar(char ch) {
return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
ch == '!' || ch == '#' || ch == '$' || ch == '%' || ch == '&' || ch == '\'' || ch == '*' ||
ch == '+' || ch == '-' || ch == '.' || ch == '/' || ch == ':' || ch == '<' || ch == '=' ||
ch == '>' || ch == '?' || ch == '@' || ch == '^' || ch == '_' || ch == '`' || ch == '|' ||
ch == '~';
}
static char formatNibble(int nibble) {
return nibble < 10 ? '0' + nibble : 'a' - 10 + nibble;
}
static void escapeName(Name &name) {
bool allIdChars = true;
for (const char *p = name.str; allIdChars && *p; p++) {
allIdChars = isIdChar(*p);
}
if (allIdChars) {
return;
}
// encode name, if at least one non-idchar (per WebAssembly spec) was found
std::string escaped;
for (const char *p = name.str; *p; p++) {
char ch = *p;
if (isIdChar(ch)) {
escaped.push_back(ch);
continue;
}
// replace non-idchar with `\xx` escape
escaped.push_back('\\');
escaped.push_back(formatNibble(ch >> 4));
escaped.push_back(formatNibble(ch & 15));
}
name = escaped;
}
void WasmBinaryBuilder::readNames(size_t payloadLen) {
if (debug) std::cerr << "== readNames" << std::endl;
auto sectionPos = pos;
@ -1533,6 +1619,7 @@ void WasmBinaryBuilder::readNames(size_t payloadLen) {
for (size_t i = 0; i < num; i++) {
auto index = getU32LEB();
auto rawName = getInlineString();
escapeName(rawName);
auto name = rawName;
// De-duplicate names by appending .1, .2, etc.
for (int i = 1; !usedNames.insert(name).second; ++i) {

View File

@ -599,13 +599,9 @@ struct FixInvokeFunctionNamesWalker : public PostWalker<FixInvokeFunctionNamesWa
if (newname == curr->base)
return;
if (curr->base != curr->name) {
Fatal() << "Import name and function name do not match: '" << curr->base << "' '" << curr->name << "'";
}
assert(importRenames.count(curr->name) == 0);
importRenames[curr->name] = newname;
// Either rename of remove the existing import
// Either rename or remove the existing import
if (wasm.getImportOrNull(newname) || !newImports.insert(newname).second) {
toRemove.push_back(curr->name);
} else {
@ -724,6 +720,12 @@ std::string EmscriptenGlueGenerator::generateEmscriptenMetadata(
meta << "]";
}
// Avoid adding duplicate imports to `declares' or `invokeFuncs`. Even
// though we might import the same function multiple times (i.e. with
// different sigs) we only need to list is in the metadata once.
std::set<std::string> declares;
std::set<std::string> invokeFuncs;
// We use the `base` rather than the `name` of the imports here and below
// becasue this is the externally visible name that the embedder (JS) will
// see.
@ -735,7 +737,8 @@ std::string EmscriptenGlueGenerator::generateEmscriptenMetadata(
!import->base.startsWith(EMSCRIPTEN_ASM_CONST.str) &&
!import->base.startsWith("invoke_") &&
!import->base.startsWith("jsCall_")) {
meta << maybeComma() << '"' << import->base.str << '"';
if (declares.insert(import->base.str).second)
meta << maybeComma() << '"' << import->base.str << '"';
}
}
meta << "]";
@ -769,7 +772,8 @@ std::string EmscriptenGlueGenerator::generateEmscriptenMetadata(
commaFirst = true;
for (const auto& import : wasm.imports) {
if (import->base.startsWith("invoke_")) {
meta << maybeComma() << '"' << import->base.str << '"';
if (invokeFuncs.insert(import->base.str).second)
meta << maybeComma() << '"' << import->base.str << '"';
}
}
meta << "]";

View File

@ -29,6 +29,8 @@ namespace BinaryConsts {
namespace UserSections {
const char* Name = "name";
const char* SourceMapUrl = "sourceMappingURL";
const char* Dylink = "dylink";
}
}

View File

@ -19,8 +19,8 @@
// infrastructure.
//
#ifndef wasm_wasm2asm_h
#define wasm_wasm2asm_h
#ifndef wasm_wasm2js_h
#define wasm_wasm2js_h
#include <cmath>
#include <numeric>
@ -45,8 +45,8 @@ using namespace cashew;
IString ASM_FUNC("asmFunc"),
ABORT_FUNC("abort"),
FUNCTION_TABLE("FUNCTION_TABLE"),
NO_RESULT("wasm2asm$noresult"), // no result at all
EXPRESSION_RESULT("wasm2asm$expresult"); // result in an expression, no temp var
NO_RESULT("wasm2js$noresult"), // no result at all
EXPRESSION_RESULT("wasm2js$expresult"); // result in an expression, no temp var
// Appends extra to block, flattening out if extra is a block as well
void flattenAppend(Ref ast, Ref extra) {
@ -73,8 +73,9 @@ enum class NameScope {
Max,
};
static uint64_t constOffset(Table::Segment &segment) {
auto* c = segment.offset->dynCast<Const>();
template<typename T>
static uint64_t constOffset(const T& segment) {
auto* c = segment.offset->template dynCast<Const>();
if (!c) {
Fatal() << "non-constant offsets aren't supported yet\n";
abort();
@ -83,7 +84,7 @@ static uint64_t constOffset(Table::Segment &segment) {
}
//
// Wasm2AsmBuilder - converts a WebAssembly module into asm.js
// Wasm2JSBuilder - converts a WebAssembly module into asm.js
//
// In general, asm.js => wasm is very straightforward, as can
// be seen in asm2wasm.h. Just a single pass, plus a little
@ -111,7 +112,7 @@ static uint64_t constOffset(Table::Segment &segment) {
// can easily show bad behavior here, with many unnecessary
// temp vars. We could rely on optimization passes like
// Emscripten's eliminate/registerize pair, but we want
// wasm2asm to be fairly fast to run, as it might run on
// wasm2js to be fairly fast to run, as it might run on
// the client.
//
// The approach taken here therefore performs 2 passes on
@ -129,7 +130,7 @@ static uint64_t constOffset(Table::Segment &segment) {
// optimizing away unnecessary forwarding.
class Wasm2AsmBuilder {
class Wasm2JSBuilder {
MixedArena allocator;
public:
@ -139,7 +140,7 @@ public:
bool allowAsserts = false;
};
Wasm2AsmBuilder(Flags f) : flags(f) {}
Wasm2JSBuilder(Flags f) : flags(f) {}
Ref processWasm(Module* wasm, Name funcName = ASM_FUNC);
Ref processFunction(Module* wasm, Function* func);
@ -166,7 +167,7 @@ public:
frees[type].pop_back();
} else {
size_t index = temps[type]++;
ret = IString((std::string("wasm2asm_") + printType(type) + "$" +
ret = IString((std::string("wasm2js_") + printType(type) + "$" +
std::to_string(index)).c_str(), false);
}
if (func->localIndices.find(ret) == func->localIndices.end()) {
@ -268,6 +269,8 @@ private:
bool almostASM = false;
void addEsmImports(Ref ast, Module* wasm);
void addEsmExportsAndInstantiate(Ref ast, Module* wasm, Name funcName);
void addBasics(Ref ast);
void addImport(Ref ast, Import* import);
void addTables(Ref ast, Module* wasm);
@ -294,12 +297,12 @@ private:
Element& e,
Name testFuncName,
Name asmModule);
Wasm2AsmBuilder() = delete;
Wasm2AsmBuilder(const Wasm2AsmBuilder &) = delete;
Wasm2AsmBuilder &operator=(const Wasm2AsmBuilder&) = delete;
Wasm2JSBuilder() = delete;
Wasm2JSBuilder(const Wasm2JSBuilder &) = delete;
Wasm2JSBuilder &operator=(const Wasm2JSBuilder&) = delete;
};
Ref Wasm2AsmBuilder::processWasm(Module* wasm, Name funcName) {
Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) {
PassRunner runner(wasm);
runner.add<AutoDrop>();
// First up remove as many non-JS operations we can, including things like
@ -328,8 +331,10 @@ Ref Wasm2AsmBuilder::processWasm(Module* wasm, Name funcName) {
#endif
Ref ret = ValueBuilder::makeToplevel();
addEsmImports(ret, wasm);
Ref asmFunc = ValueBuilder::makeFunction(funcName);
ret[1]->push_back(asmFunc);
addEsmExportsAndInstantiate(ret, wasm, funcName);
ValueBuilder::appendArgumentToFunction(asmFunc, GLOBAL);
ValueBuilder::appendArgumentToFunction(asmFunc, ENV);
ValueBuilder::appendArgumentToFunction(asmFunc, BUFFER);
@ -397,7 +402,207 @@ Ref Wasm2AsmBuilder::processWasm(Module* wasm, Name funcName) {
return ret;
}
void Wasm2AsmBuilder::addBasics(Ref ast) {
void Wasm2JSBuilder::addEsmImports(Ref ast, Module *wasm) {
std::unordered_map<Name, Name> nameMap;
for (auto& import : wasm->imports) {
// Only function imports are supported for now, but eventually imported
// memories can probably be supported at least.
switch (import->kind) {
case ExternalKind::Function: break;
default:
Fatal() << "non-function imports aren't supported yet\n";
abort();
}
// Right now codegen requires a flat namespace going into the module,
// meaning we don't importing the same name from multiple namespaces yet.
if (nameMap.count(import->base) && nameMap[import->base] != import->module) {
Fatal() << "the name " << import->base << " cannot be imported from "
<< "two different modules yet\n";
abort();
}
nameMap[import->base] = import->module;
std::ostringstream out;
out << "import { "
<< import->base.str
<< " } from '"
<< import->module.str
<< "'";
std::string os = out.str();
IString name(os.c_str(), false);
flattenAppend(ast, ValueBuilder::makeName(name));
}
}
static std::string base64Encode(std::vector<char> &data) {
std::string ret;
size_t i = 0;
const char* alphabet =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
while (i + 3 <= data.size()) {
int bits =
(((int) data[i + 0]) << 16) |
(((int) data[i + 1]) << 8) |
(((int) data[i + 2]) << 0);
ret += alphabet[(bits >> 18) & 0x3f];
ret += alphabet[(bits >> 12) & 0x3f];
ret += alphabet[(bits >> 6) & 0x3f];
ret += alphabet[(bits >> 0) & 0x3f];
i += 3;
}
if (i + 2 == data.size()) {
int bits =
(((int) data[i + 0]) << 8) |
(((int) data[i + 1]) << 0);
ret += alphabet[(bits >> 10) & 0x3f];
ret += alphabet[(bits >> 4) & 0x3f];
ret += alphabet[(bits << 2) & 0x3f];
ret += '=';
} else if (i + 1 == data.size()) {
int bits =(int) data[i + 0];
ret += alphabet[(bits >> 2) & 0x3f];
ret += alphabet[(bits << 4) & 0x3f];
ret += '=';
ret += '=';
} else {
assert(i == data.size());
}
return ret;
}
void Wasm2JSBuilder::addEsmExportsAndInstantiate(Ref ast, Module *wasm, Name funcName) {
// Create an initial `ArrayBuffer` and populate it with static data.
// Currently we use base64 encoding to encode static data and we decode it at
// instantiation time.
//
// Note that the translation here expects that the lower values of this memory
// can be used for conversions, so make sure there's at least one page.
{
auto pages = wasm->memory.initial == 0 ? 1 : wasm->memory.initial.addr;
std::ostringstream out;
out << "const mem" << funcName.str << " = new ArrayBuffer("
<< pages * Memory::kPageSize
<< ")";
std::string os = out.str();
IString name(os.c_str(), false);
flattenAppend(ast, ValueBuilder::makeName(name));
}
if (wasm->memory.segments.size() > 0) {
auto expr = R"(
function(mem) {
const _mem = new Uint8Array(mem);
return function(offset, s) {
if (typeof Buffer === 'undefined') {
const bytes = atob(s);
for (let i = 0; i < bytes.length; i++)
_mem[offset + i] = bytes.charCodeAt(i);
} else {
const bytes = Buffer.from(s, 'base64');
for (let i = 0; i < bytes.length; i++)
_mem[offset + i] = bytes[i];
}
}
}
)";
// const assign$name = ($expr)(mem$name);
std::ostringstream out;
out << "const assign" << funcName.str
<< " = (" << expr << ")(mem" << funcName.str << ")";
std::string os = out.str();
IString name(os.c_str(), false);
flattenAppend(ast, ValueBuilder::makeName(name));
}
for (auto& seg : wasm->memory.segments) {
std::ostringstream out;
out << "assign" << funcName.str << "("
<< constOffset(seg)
<< ", \""
<< base64Encode(seg.data)
<< "\")";
std::string os = out.str();
IString name(os.c_str(), false);
flattenAppend(ast, ValueBuilder::makeName(name));
}
// Actually invoke the `asmFunc` generated function, passing in all global
// values followed by all imports (imported via addEsmImports above)
std::ostringstream construct;
construct << "const ret" << funcName.str << " = " << funcName.str << "({"
<< "Math,"
<< "Int8Array,"
<< "Uint8Array,"
<< "Int16Array,"
<< "Uint16Array,"
<< "Int32Array,"
<< "Uint32Array,"
<< "Float32Array,"
<< "Float64Array,"
<< "NaN,"
<< "Infinity"
<< "}, {";
construct << "abort:function() { throw new Error('abort'); }";
for (auto& import : wasm->imports) {
switch (import->kind) {
case ExternalKind::Function: break;
default: continue;
}
construct << "," << import->base.str;
}
construct << "},mem" << funcName.str << ")";
std::string sconstruct = construct.str();
IString name(sconstruct.c_str(), false);
flattenAppend(ast, ValueBuilder::makeName(name));
if (flags.allowAsserts) {
return;
}
// And now that we have our returned instance, export all our functions
// that are hanging off it.
for (auto& exp : wasm->exports) {
switch (exp->kind) {
case ExternalKind::Function:
case ExternalKind::Memory:
break;
// Exported globals and function tables aren't supported yet
default:
continue;
}
std::ostringstream export_name;
for (auto *ptr = exp->name.str; *ptr; ptr++) {
if (*ptr == '-') {
export_name << '_';
} else {
export_name << *ptr;
}
}
std::ostringstream out;
out << "export const "
<< fromName(exp->name, NameScope::Top).str
<< " = ret"
<< funcName.str
<< "."
<< fromName(exp->name, NameScope::Top).str;
std::string os = out.str();
IString name(os.c_str(), false);
flattenAppend(ast, ValueBuilder::makeName(name));
}
}
void Wasm2JSBuilder::addBasics(Ref ast) {
// heaps, var HEAP8 = new global.Int8Array(buffer); etc
auto addHeap = [&](IString name, IString view) {
Ref theVar = ValueBuilder::makeVar();
@ -473,7 +678,7 @@ void Wasm2AsmBuilder::addBasics(Ref ast) {
);
}
void Wasm2AsmBuilder::addImport(Ref ast, Import* import) {
void Wasm2JSBuilder::addImport(Ref ast, Import* import) {
Ref theVar = ValueBuilder::makeVar();
ast->push_back(theVar);
Ref module = ValueBuilder::makeName(ENV); // TODO: handle nested module imports
@ -486,7 +691,7 @@ void Wasm2AsmBuilder::addImport(Ref ast, Import* import) {
);
}
void Wasm2AsmBuilder::addTables(Ref ast, Module* wasm) {
void Wasm2JSBuilder::addTables(Ref ast, Module* wasm) {
std::map<std::string, std::vector<IString>> tables; // asm.js tables, sig => contents of table
for (Table::Segment& seg : wasm->table.segments) {
for (size_t i = 0; i < seg.data.size(); i++) {
@ -521,7 +726,7 @@ void Wasm2AsmBuilder::addTables(Ref ast, Module* wasm) {
}
}
void Wasm2AsmBuilder::addExports(Ref ast, Module* wasm) {
void Wasm2JSBuilder::addExports(Ref ast, Module* wasm) {
Ref exports = ValueBuilder::makeObject();
for (auto& export_ : wasm->exports) {
if (export_->kind == ExternalKind::Function) {
@ -576,7 +781,7 @@ void Wasm2AsmBuilder::addExports(Ref ast, Module* wasm) {
ast->push_back(ValueBuilder::makeStatement(ValueBuilder::makeReturn(exports)));
}
void Wasm2AsmBuilder::addGlobal(Ref ast, Global* global) {
void Wasm2JSBuilder::addGlobal(Ref ast, Global* global) {
if (auto* const_ = global->init->dynCast<Const>()) {
Ref theValue;
switch (const_->type) {
@ -620,7 +825,7 @@ static bool expressionEndsInReturn(Expression *e) {
return expressionEndsInReturn((*stats)[stats->size()-1]);
}
Ref Wasm2AsmBuilder::processFunction(Module* m, Function* func) {
Ref Wasm2JSBuilder::processFunction(Module* m, Function* func) {
if (flags.debug) {
static int fns = 0;
std::cerr << "processFunction " << (fns++) << " " << func->name
@ -704,11 +909,11 @@ Ref Wasm2AsmBuilder::processFunction(Module* m, Function* func) {
return ret;
}
void Wasm2AsmBuilder::scanFunctionBody(Expression* curr) {
void Wasm2JSBuilder::scanFunctionBody(Expression* curr) {
struct ExpressionScanner : public PostWalker<ExpressionScanner> {
Wasm2AsmBuilder* parent;
Wasm2JSBuilder* parent;
ExpressionScanner(Wasm2AsmBuilder* parent) : parent(parent) {}
ExpressionScanner(Wasm2JSBuilder* parent) : parent(parent) {}
// Visitors
@ -792,26 +997,26 @@ void Wasm2AsmBuilder::scanFunctionBody(Expression* curr) {
ExpressionScanner(this).walk(curr);
}
Ref Wasm2AsmBuilder::processFunctionBody(Module* m, Function* func, IString result) {
Ref Wasm2JSBuilder::processFunctionBody(Module* m, Function* func, IString result) {
struct ExpressionProcessor : public Visitor<ExpressionProcessor, Ref> {
Wasm2AsmBuilder* parent;
Wasm2JSBuilder* parent;
IString result;
Function* func;
Module* module;
MixedArena allocator;
ExpressionProcessor(Wasm2AsmBuilder* parent, Module* m, Function* func)
ExpressionProcessor(Wasm2JSBuilder* parent, Module* m, Function* func)
: parent(parent), func(func), module(m) {}
// A scoped temporary variable.
struct ScopedTemp {
Wasm2AsmBuilder* parent;
Wasm2JSBuilder* parent;
Type type;
IString temp;
bool needFree;
// @param possible if provided, this is a variable we can use as our temp. it has already been
// allocated in a higher scope, and we can just assign to it as our result is
// going there anyhow.
ScopedTemp(Type type, Wasm2AsmBuilder* parent, Function* func,
ScopedTemp(Type type, Wasm2JSBuilder* parent, Function* func,
IString possible = NO_RESULT) : parent(parent), type(type) {
assert(possible != EXPRESSION_RESULT);
if (possible == NO_RESULT) {
@ -1839,16 +2044,11 @@ Ref Wasm2AsmBuilder::processFunctionBody(Module* m, Function* func, IString resu
return ExpressionProcessor(this, m, func).visit(func->body, result);
}
static void makeInstantiation(Ref ret, Name funcName, Name moduleName, bool first) {
Name buffer("__array_buffer");
static void makeHelpers(Ref ret, Name funcName, Name moduleName, bool first) {
if (first) {
// TODO: nan and infinity shouldn't be needed once literal asm.js code isn't
// generated
flattenAppend(ret, ValueBuilder::makeName(R"(
var __array_buffer = new ArrayBuffer(65536)
var HEAP32 = new Int32Array(__array_buffer);
var HEAPF32 = new Float32Array(__array_buffer);
var HEAPF64 = new Float64Array(__array_buffer);
var nan = NaN;
var infinity = Infinity;
)"));
@ -1885,48 +2085,12 @@ static void makeInstantiation(Ref ret, Name funcName, Name moduleName, bool firs
return (isNaN(a) && isNaN(b)) || (ai1 == bi1 && ai2 == bi2);
}
function i64Equal(actual_lo, actual_hi, expected_lo, expected_hi) {
return actual_lo == (expected_lo | 0) && actual_hi == (expected_hi | 0);
}
)"));
}
Ref lib = ValueBuilder::makeObject();
auto insertItem = [&](IString item) {
ValueBuilder::appendToObject(lib, item, ValueBuilder::makeName(item));
};
insertItem(MATH);
insertItem(INT8ARRAY);
insertItem(INT16ARRAY);
insertItem(INT32ARRAY);
insertItem(UINT8ARRAY);
insertItem(UINT16ARRAY);
insertItem(UINT32ARRAY);
insertItem(FLOAT32ARRAY);
insertItem(FLOAT64ARRAY);
// TODO: these shouldn't be necessary once we don't generate literal asm.js
// code
insertItem("Infinity");
insertItem("NaN");
Ref env = ValueBuilder::makeObject();
Ref abortFunc = ValueBuilder::makeFunction("abort");
abortFunc[3]->push_back(ValueBuilder::makeCall("unreachable"));
ValueBuilder::appendToObject(env, "abort", abortFunc);
Ref printFunc = ValueBuilder::makeFunction("print");
abortFunc[3]->push_back(ValueBuilder::makeCall("console_log"));
ValueBuilder::appendToObject(env, "print", printFunc);
Ref call = ValueBuilder::makeCall(IString(funcName), lib, env,
ValueBuilder::makeName(buffer));
Ref module = ValueBuilder::makeVar();
ValueBuilder::appendToVar(module, moduleName, call);
flattenAppend(ret, module);
// 64-bit numbers get a different ABI w/ wasm2asm, and in general you can't
// actually export them from wasm at the boundary. We hack around this though
// to get the spec tests working.
flattenAppend(ret, ValueBuilder::makeName(R"(
function i64Equal(actual_lo, actual_hi, expected_lo, expected_hi) {
return actual_lo == (expected_lo | 0) && actual_hi == (expected_hi | 0);
}
)"));
}
static void prefixCalls(Ref asmjs, Name asmModule) {
@ -1961,7 +2125,7 @@ static void prefixCalls(Ref asmjs, Name asmModule) {
}
}
Ref Wasm2AsmBuilder::makeAssertReturnFunc(SExpressionWasmBuilder& sexpBuilder,
Ref Wasm2JSBuilder::makeAssertReturnFunc(SExpressionWasmBuilder& sexpBuilder,
Module* wasm,
Builder& wasmBuilder,
Element& e,
@ -2026,7 +2190,7 @@ Ref Wasm2AsmBuilder::makeAssertReturnFunc(SExpressionWasmBuilder& sexpBuilder,
return jsFunc;
}
Ref Wasm2AsmBuilder::makeAssertReturnNanFunc(SExpressionWasmBuilder& sexpBuilder,
Ref Wasm2JSBuilder::makeAssertReturnNanFunc(SExpressionWasmBuilder& sexpBuilder,
Module* wasm,
Builder& wasmBuilder,
Element& e,
@ -2048,7 +2212,7 @@ Ref Wasm2AsmBuilder::makeAssertReturnNanFunc(SExpressionWasmBuilder& sexpBuilder
return jsFunc;
}
Ref Wasm2AsmBuilder::makeAssertTrapFunc(SExpressionWasmBuilder& sexpBuilder,
Ref Wasm2JSBuilder::makeAssertTrapFunc(SExpressionWasmBuilder& sexpBuilder,
Module* wasm,
Builder& wasmBuilder,
Element& e,
@ -2092,14 +2256,14 @@ Ref Wasm2AsmBuilder::makeAssertTrapFunc(SExpressionWasmBuilder& sexpBuilder,
return outerFunc;
}
void Wasm2AsmBuilder::setNeedsAlmostASM(const char *reason) {
void Wasm2JSBuilder::setNeedsAlmostASM(const char *reason) {
if (!almostASM) {
almostASM = true;
std::cerr << "Switching to \"almost asm\" mode, reason: " << reason << std::endl;
}
}
void Wasm2AsmBuilder::addMemoryGrowthFuncs(Ref ast) {
void Wasm2JSBuilder::addMemoryGrowthFuncs(Ref ast) {
Ref growMemoryFunc = ValueBuilder::makeFunction(WASM_GROW_MEMORY);
ValueBuilder::appendArgumentToFunction(growMemoryFunc, IString("pagesToAdd"));
@ -2250,7 +2414,7 @@ void Wasm2AsmBuilder::addMemoryGrowthFuncs(Ref ast) {
ast->push_back(currentMemoryFunc);
}
bool Wasm2AsmBuilder::isAssertHandled(Element& e) {
bool Wasm2JSBuilder::isAssertHandled(Element& e) {
return e.isList() && e.size() >= 2 && e[0]->isStr()
&& (e[0]->str() == Name("assert_return") ||
e[0]->str() == Name("assert_return_nan") ||
@ -2259,26 +2423,28 @@ bool Wasm2AsmBuilder::isAssertHandled(Element& e) {
&& (*e[1])[0]->str() == Name("invoke");
}
Ref Wasm2AsmBuilder::processAsserts(Module* wasm,
Ref Wasm2JSBuilder::processAsserts(Module* wasm,
Element& root,
SExpressionWasmBuilder& sexpBuilder) {
Builder wasmBuilder(sexpBuilder.getAllocator());
Ref ret = ValueBuilder::makeBlock();
Name asmModule = ASM_MODULE;
makeInstantiation(ret, ASM_FUNC, asmModule, true);
std::stringstream asmModuleS;
asmModuleS << "ret" << ASM_FUNC.c_str();
Name asmModule(asmModuleS.str().c_str());
makeHelpers(ret, ASM_FUNC, asmModule, true);
for (size_t i = 1; i < root.size(); ++i) {
Element& e = *root[i];
if (e.isList() && e.size() >= 1 && e[0]->isStr() && e[0]->str() == Name("module")) {
std::stringstream funcNameS;
funcNameS << ASM_FUNC.c_str() << i;
std::stringstream moduleNameS;
moduleNameS << ASM_MODULE.c_str() << i;
moduleNameS << "ret" << ASM_FUNC.c_str() << i;
Name funcName(funcNameS.str().c_str());
asmModule = Name(moduleNameS.str().c_str());
Module wasm;
SExpressionWasmBuilder builder(wasm, e);
flattenAppend(ret, processWasm(&wasm, funcName));
makeInstantiation(ret, funcName, asmModule, false);
makeHelpers(ret, funcName, asmModule, false);
continue;
}
if (!isAssertHandled(e)) {
@ -2319,4 +2485,4 @@ Ref Wasm2AsmBuilder::processAsserts(Module* wasm,
} // namespace wasm
#endif // wasm_wasm2asm_h
#endif // wasm_wasm2js_h

View File

@ -31,3 +31,6 @@ function asmFunc(global, env, buffer) {
};
}
const memasmFunc = new ArrayBuffer(65536);
const retasmFunc = asmFunc({Math,Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,NaN,Infinity}, {abort:function() { throw new Error('abort'); }},memasmFunc);

View File

@ -250,7 +250,7 @@ function test_core() {
// Function table. One per module
module.setFunctionTable([ sinker ]);
module.setFunctionTable([ Binaryen.getFunctionInfo(sinker).name ]);
// Memory. One per module

View File

@ -1456,9 +1456,20 @@ getExpressionInfo(f64.const)={"id":15,"type":4,"value":9.5}
}
imports[0] = BinaryenAddFunctionImport(the_module, "an-imported", "module", "base", functionTypes[1]);
exports[0] = BinaryenAddFunctionExport(the_module, "kitchen()sinker", "kitchen_sinker");
BinaryenFunctionGetName(functions[0]);
BinaryenFunctionGetType(functions[0]);
BinaryenFunctionGetNumParams(functions[0]);
BinaryenFunctionGetParam(functions[0], 0);
BinaryenFunctionGetParam(functions[0], 1);
BinaryenFunctionGetParam(functions[0], 2);
BinaryenFunctionGetParam(functions[0], 3);
BinaryenFunctionGetResult(functions[0]);
BinaryenFunctionGetNumVars(functions[0]);
BinaryenFunctionGetVar(functions[0], 0);
BinaryenFunctionGetBody(functions[0]);
{
BinaryenFunctionRef funcs[] = { functions[0] };
BinaryenSetFunctionTable(the_module, funcs, 1);
const char* funcNames[] = { "kitchen()sinker" };
BinaryenSetFunctionTable(the_module, funcNames, 1);
}
expressions[256] = BinaryenConst(the_module, BinaryenLiteralInt32(10));
{

Binary file not shown.

View File

@ -0,0 +1,11 @@
(module
(type $0 (func))
(export "$zoo (.bar)" (func $1))
(func $foo\20\28.bar\29 (; 0 ;) (type $0)
(nop)
)
(func $1 (; 1 ;) (type $0)
(call $foo\20\28.bar\29)
)
)

View File

@ -0,0 +1,4 @@
(module
(func $foo\20\28.bar\29)
(func "$zoo (.bar)" (call $foo\20\28.bar\29))
)

View File

@ -0,0 +1,10 @@
(module
(type $0 (func))
(export "$zoo (.bar)" (func $1))
(func $foo\20\28.bar\29 (; 0 ;) (type $0)
(nop)
)
(func $1 (; 1 ;) (type $0)
(call $foo\20\28.bar\29)
)
)

View File

@ -0,0 +1,11 @@
(module
(type $0 (func))
(export "$zoo (.bar)" (func $1))
(func $foo\20\28.bar\29 (; 0 ;) (type $0)
(nop)
)
(func $1 (; 1 ;) (type $0)
(call $foo\20\28.bar\29)
)
)

View File

@ -0,0 +1,11 @@
(module
(type $0 (func))
(export "$zoo (.bar)" (func $1))
(func $0 (; 0 ;) (type $0)
(nop)
)
(func $1 (; 1 ;) (type $0)
(call $0)
)
)

View File

@ -253,12 +253,12 @@ void test_core() {
BinaryenAddFunctionExport(module, "kitchen()sinker", "kitchen_sinker");
// Function table. One per module
BinaryenFunctionRef functions[] = { sinker };
BinaryenSetFunctionTable(module, functions, 1);
const char* funcNames[] = { BinaryenFunctionGetName(sinker) };
BinaryenSetFunctionTable(module, funcNames, 1);
// Memory. One per module
const char *segments[] = { "hello, world" };
const char* segments[] = { "hello, world" };
BinaryenExpressionRef segmentOffsets[] = { BinaryenConst(module, BinaryenLiteralInt32(10)) };
BinaryenIndex segmentSizes[] = { 12 };
BinaryenSetMemory(module, 1, 256, "mem", segments, segmentOffsets, segmentSizes, 1);

View File

@ -1400,9 +1400,10 @@ int main() {
}
imports[0] = BinaryenAddFunctionImport(the_module, "an-imported", "module", "base", functionTypes[1]);
exports[0] = BinaryenAddFunctionExport(the_module, "kitchen()sinker", "kitchen_sinker");
BinaryenFunctionGetName(functions[0]);
{
BinaryenFunctionRef funcs[] = { functions[0] };
BinaryenSetFunctionTable(the_module, funcs, 1);
const char* funcNames[] = { "kitchen()sinker" };
BinaryenSetFunctionTable(the_module, funcNames, 1);
}
expressions[254] = BinaryenConst(the_module, BinaryenLiteralInt32(10));
{

View File

@ -5,24 +5,23 @@
(import "env" "table" (table 0 0 anyfunc))
(import "env" "memoryBase" (global $memoryBase i32))
(import "env" "tableBase" (global $tableBase i32))
(import "env" "illegalImportResult" (func $illegalImportResult (result i64)))
(import "env" "illegalImportResult" (func $legalimport$illegalImportResult (result i32)))
(global $tempRet0 (mut i32) (i32.const 0))
(export "illegalResult" (func $legalstub$illegalResult))
(export "imports" (func $imports))
(func $illegalResult (; 2 ;) (result i64)
(func $illegalResult (; 1 ;) (result i64)
(return
(i64.const 8589934593)
)
)
(func $imports (; 3 ;) (result i32)
(func $imports (; 2 ;) (result i32)
(return
(i32.wrap/i64
(call $legalfunc$illegalImportResult)
)
)
)
(func $legalstub$illegalResult (; 4 ;) (result i32)
(func $legalstub$illegalResult (; 3 ;) (result i32)
(local $0 i64)
(set_local $0
(call $illegalResult)
@ -39,7 +38,7 @@
(get_local $0)
)
)
(func $legalfunc$illegalImportResult (; 5 ;) (result i64)
(func $legalfunc$illegalImportResult (; 4 ;) (result i64)
(i64.or
(i64.extend_u/i32
(call $legalimport$illegalImportResult)

View File

@ -5,24 +5,23 @@
(import "env" "table" (table 0 0 anyfunc))
(import "env" "memoryBase" (global $memoryBase i32))
(import "env" "tableBase" (global $tableBase i32))
(import "env" "illegalImportResult" (func $illegalImportResult (result i64)))
(import "env" "illegalImportResult" (func $legalimport$illegalImportResult (result i32)))
(global $tempRet0 (mut i32) (i32.const 0))
(export "illegalResult" (func $legalstub$illegalResult))
(export "imports" (func $imports))
(func $illegalResult (; 2 ;) (result i64)
(func $illegalResult (; 1 ;) (result i64)
(return
(i64.const 8589934593)
)
)
(func $imports (; 3 ;) (result i32)
(func $imports (; 2 ;) (result i32)
(return
(i32.wrap/i64
(call $legalfunc$illegalImportResult)
)
)
)
(func $legalstub$illegalResult (; 4 ;) (result i32)
(func $legalstub$illegalResult (; 3 ;) (result i32)
(local $0 i64)
(set_local $0
(call $illegalResult)
@ -39,7 +38,7 @@
(get_local $0)
)
)
(func $legalfunc$illegalImportResult (; 5 ;) (result i64)
(func $legalfunc$illegalImportResult (; 4 ;) (result i64)
(i64.or
(i64.extend_u/i32
(call $legalimport$illegalImportResult)

View File

@ -5,24 +5,23 @@
(import "env" "table" (table 0 0 anyfunc))
(import "env" "memoryBase" (global $memoryBase i32))
(import "env" "tableBase" (global $tableBase i32))
(import "env" "illegalImportResult" (func $illegalImportResult (result i64)))
(import "env" "illegalImportResult" (func $legalimport$illegalImportResult (result i32)))
(global $tempRet0 (mut i32) (i32.const 0))
(export "illegalResult" (func $legalstub$illegalResult))
(export "imports" (func $imports))
(func $illegalResult (; 2 ;) (result i64)
(func $illegalResult (; 1 ;) (result i64)
(return
(i64.const 8589934593)
)
)
(func $imports (; 3 ;) (result i32)
(func $imports (; 2 ;) (result i32)
(return
(i32.wrap/i64
(call $legalfunc$illegalImportResult)
)
)
)
(func $legalstub$illegalResult (; 4 ;) (result i32)
(func $legalstub$illegalResult (; 3 ;) (result i32)
(local $0 i64)
(set_local $0
(call $illegalResult)
@ -39,7 +38,7 @@
(get_local $0)
)
)
(func $legalfunc$illegalImportResult (; 5 ;) (result i64)
(func $legalfunc$illegalImportResult (; 4 ;) (result i64)
(i64.or
(i64.extend_u/i32
(call $legalimport$illegalImportResult)

View File

@ -0,0 +1,32 @@
(module
(type $0 (func (param i32) (result i32)))
(type $1 (func (result i32)))
(type $2 (func))
(import "env" "puts" (func $puts1 (param i32) (result i32)))
(import "env" "puts" (func $puts2 (param i64) (result i32)))
(import "env" "invoke_ffd" (func $invoke_ffd (param i32 f32 f64) (result f32)))
(import "env" "invoke_ffd" (func $invoke_ffd2 (param i32 f64 f64) (result f32)))
(global $global$0 (mut i32) (i32.const 66128))
(global $global$1 i32 (i32.const 66128))
(global $global$2 i32 (i32.const 581))
(table 1 1 anyfunc)
(memory $0 2)
(data (i32.const 568) "Hello, world\00")
(export "memory" (memory $0))
(export "__wasm_call_ctors" (func $__wasm_call_ctors))
(export "main" (func $main))
(export "__heap_base" (global $global$1))
(export "__data_end" (global $global$2))
(func $main (; 1 ;) (type $1) (result i32)
(drop
(call $puts1
(i32.const 568)
)
)
(i32.const 0)
)
(func $__wasm_call_ctors (; 2 ;) (type $2)
)
;; custom section "linking", size 3
)

View File

@ -0,0 +1,104 @@
(module
(type $0 (func (param i32) (result i32)))
(type $1 (func (result i32)))
(type $2 (func))
(type $FUNCSIG$ii (func (param i32) (result i32)))
(type $FUNCSIG$ij (func (param i64) (result i32)))
(type $FUNCSIG$fifd (func (param i32 f32 f64) (result f32)))
(type $FUNCSIG$fidd (func (param i32 f64 f64) (result f32)))
(type $legaltype$puts2 (func (param i32 i32) (result i32)))
(type $legaltype$invoke_ffd (func (param i32 f64 f64) (result f64)))
(type $legaltype$invoke_ffd2 (func (param i32 f64 f64) (result f64)))
(import "env" "puts" (func $puts1 (param i32) (result i32)))
(import "env" "puts" (func $legalimport$puts2 (param i32 i32) (result i32)))
(import "env" "invoke_ffd" (func $legalimport$invoke_ffd (param i32 f64 f64) (result f64)))
(import "env" "invoke_ffd" (func $legalimport$invoke_ffd2 (param i32 f64 f64) (result f64)))
(global $global$0 (mut i32) (i32.const 66128))
(global $global$1 i32 (i32.const 66128))
(global $global$2 i32 (i32.const 581))
(table 1 1 anyfunc)
(memory $0 2)
(data (i32.const 568) "Hello, world\00")
(export "memory" (memory $0))
(export "__wasm_call_ctors" (func $__wasm_call_ctors))
(export "main" (func $main))
(export "__heap_base" (global $global$1))
(export "__data_end" (global $global$2))
(export "stackSave" (func $stackSave))
(export "stackAlloc" (func $stackAlloc))
(export "stackRestore" (func $stackRestore))
(export "__growWasmMemory" (func $__growWasmMemory))
(func $main (; 4 ;) (type $1) (result i32)
(drop
(call $puts1
(i32.const 568)
)
)
(i32.const 0)
)
(func $__wasm_call_ctors (; 5 ;) (type $2)
(nop)
)
(func $legalfunc$puts2 (; 6 ;) (param $0 i64) (result i32)
(call $legalimport$puts2
(i32.wrap/i64
(get_local $0)
)
(i32.wrap/i64
(i64.shr_u
(get_local $0)
(i64.const 32)
)
)
)
)
(func $legalfunc$invoke_ffd (; 7 ;) (param $0 i32) (param $1 f32) (param $2 f64) (result f32)
(f32.demote/f64
(call $legalimport$invoke_ffd
(get_local $0)
(f64.promote/f32
(get_local $1)
)
(get_local $2)
)
)
)
(func $legalfunc$invoke_ffd2 (; 8 ;) (param $0 i32) (param $1 f64) (param $2 f64) (result f32)
(f32.demote/f64
(call $legalimport$invoke_ffd2
(get_local $0)
(get_local $1)
(get_local $2)
)
)
)
(func $stackSave (; 9 ;) (result i32)
(get_global $global$0)
)
(func $stackAlloc (; 10 ;) (param $0 i32) (result i32)
(local $1 i32)
(set_global $global$0
(tee_local $1
(i32.and
(i32.sub
(get_global $global$0)
(get_local $0)
)
(i32.const -16)
)
)
)
(get_local $1)
)
(func $stackRestore (; 11 ;) (param $0 i32)
(set_global $global$0
(get_local $0)
)
)
(func $__growWasmMemory (; 12 ;) (param $newSize i32) (result i32)
(grow_memory
(get_local $newSize)
)
)
)
;; METADATA: { "asmConsts": {},"staticBump": 13, "initializers": ["__wasm_call_ctors"], "declares": ["puts"], "externs": [], "implementedFunctions": ["___wasm_call_ctors","_main","_stackSave","_stackAlloc","_stackRestore","___growWasmMemory"], "exports": ["memory","__wasm_call_ctors","main","__heap_base","__data_end","stackSave","stackAlloc","stackRestore","__growWasmMemory"], "invokeFuncs": ["invoke_ffd"] }

View File

@ -1,5 +1,30 @@
$stacky-help:
(no stack ir)
(module
(type $0 (func (param i32) (result i32)))
(export "stacky-help" (func $stacky-help))
(func $stacky-help (; 0 ;) (type $0) (param $0 i32) (result i32)
(local $1 i32)
(i32.add
(call $stacky-help
(i32.const 0)
)
(block (result i32)
(set_local $1
(call $stacky-help
(i32.const 1)
)
)
(drop
(call $stacky-help
(i32.const 2)
)
)
(i32.eqz
(get_local $1)
)
)
)
)
)
(module
(type $0 (func (param i32) (result i32)))
(export "stacky-help" (func $stacky-help))

View File

@ -1,5 +1,17 @@
$0:
(no stack ir)
(module
(type $0 (func (param i32 i32 i32 i64) (result i64)))
(export "func" (func $0))
(func $0 (; 0 ;) (type $0) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 i64) (result i64)
(local $4 i32)
(block $label$1
(set_local $3
(i64.const 2147483647)
)
(nop)
)
(i64.const 2147483647)
)
)
(module
(type $0 (func (param i32 i32 i32 i64) (result i64)))
(export "func" (func $0))

View File

@ -1,16 +1,21 @@
$stacky-help:
0 const (i32)
1 call (i32)
2 const (i32)
3 call (i32)
4 set_local (none)
5 const (i32)
6 call (i32)
7 drop (none)
8 get_local (i32)
9 unary (i32)
10 binary (i32)
(module
(type $0 (func (param i32) (result i32)))
(export "stacky-help" (func $stacky-help))
(func $stacky-help (; 0 ;) (; has Stack IR ;) (type $0) (param $0 i32) (result i32)
(local $1 i32)
i32.const 0
call $stacky-help
i32.const 1
call $stacky-help
set_local $1
i32.const 2
call $stacky-help
drop
get_local $1
i32.eqz
i32.add
)
)
(module
(type $0 (func (param i32) (result i32)))
(export "stacky-help" (func $stacky-help))

View File

@ -1,14 +1,19 @@
$stacky-help:
0 const (i32)
1 call (i32)
2 const (i32)
3 call (i32)
4 const (i32)
5 call (i32)
6 drop (none)
7 unary (i32)
8 binary (i32)
(module
(type $0 (func (param i32) (result i32)))
(export "stacky-help" (func $stacky-help))
(func $stacky-help (; 0 ;) (; has Stack IR ;) (type $0) (param $0 i32) (result i32)
(local $1 i32)
i32.const 0
call $stacky-help
i32.const 1
call $stacky-help
i32.const 2
call $stacky-help
drop
i32.eqz
i32.add
)
)
(module
(type $0 (func (param i32) (result i32)))
(export "stacky-help" (func $stacky-help))

View File

@ -1,14 +1,19 @@
$stacky-help:
0 const (i32)
1 call (i32)
2 const (i32)
3 call (i32)
4 const (i32)
5 call (i32)
6 drop (none)
7 unary (i32)
8 binary (i32)
(module
(type $0 (func (param i32) (result i32)))
(export "stacky-help" (func $stacky-help))
(func $stacky-help (; 0 ;) (; has Stack IR ;) (type $0) (param $0 i32) (result i32)
(local $1 i32)
i32.const 0
call $stacky-help
i32.const 1
call $stacky-help
i32.const 2
call $stacky-help
drop
i32.eqz
i32.add
)
)
(module
(type $0 (func (param i32) (result i32)))
(export "stacky-help" (func $stacky-help))

View File

@ -0,0 +1,50 @@
(module
(type $0 (func))
(type $1 (func (param i32 i32) (result i32)))
(type $2 (func (param i64 i32) (result f64)))
(type $3 (func (param f64) (result f64)))
(memory $0 (shared 1 1))
(export "one" (func $0))
(export "two" (func $1))
(export "use-var" (func $2))
(export "bad1" (func $3))
(export "only-dfo" (func $4))
(func $0 (; 0 ;) (; has Stack IR ;) (type $0)
(loop $label$2
(block $label$3
(br_if $label$3
(i32.load
(i32.const 3060)
)
)
)
)
(unreachable)
)
(func $1 (; 1 ;) (; has Stack IR ;) (type $1) (param $0 i32) (param $1 i32) (result i32)
(i32.const 0)
)
(func $2 (; 2 ;) (; has Stack IR ;) (type $2) (param $0 i64) (param $1 i32) (result f64)
(block $label$6
(loop $label$8
(br_if $label$8
(get_local $1)
)
)
(unreachable)
)
)
(func $3 (; 3 ;) (; has Stack IR ;) (type $0)
(i32.store
(i32.const 1)
(i32.const -16384)
)
)
(func $4 (; 4 ;) (; has Stack IR ;) (type $3) (param $0 f64) (result f64)
(local $1 i32)
(local $2 i32)
(loop $label$1
(br $label$1)
)
)
)

View File

@ -0,0 +1,187 @@
(module
(memory $0 (shared 1 1))
(func "one"
(loop $label$2
(br_if $label$2
(block $label$3 (result i32)
(drop
(br_if $label$3
(i32.const 0)
(i32.load
(i32.const 3060)
)
)
)
(i32.const 0)
)
)
)
(unreachable)
)
(func "two" (param $var$0 i32) (param $var$1 i32) (result i32)
(nop)
(nop)
(nop)
(nop)
(nop)
(if
(i32.const 0)
(i32.store8
(i32.const 8)
(block $label$2 (result i32)
(drop
(br_if $label$2
(i32.const 1)
(i32.const 0)
)
)
(if
(i32.const 0)
(drop
(br_if $label$2
(i32.const 1)
(i32.const 1)
)
)
)
(block $label$4
(br_if $label$4
(i32.const 0)
)
(br_if $label$4
(i32.const 0)
)
(drop
(br_if $label$2
(i32.const 1)
(i32.const 0)
)
)
)
(i32.const 6704)
)
)
)
(nop)
(i32.const 0)
)
(func "use-var" (param $var$0 i64) (param $var$1 i32) (result f64)
(local $var$2 i32)
(block $label$1
(br_table $label$1 $label$1 $label$1 $label$1 $label$1 $label$1 $label$1 $label$1 $label$1 $label$1
(i32.wrap/i64
(if (result i64)
(i32.const 0)
(i64.const 1)
(if (result i64)
(if (result i32)
(i32.const 0)
(unreachable)
(block $label$6 (result i32)
(block $label$7
(loop $label$8
(br_if $label$8
(br_if $label$6
(tee_local $var$2
(block $label$9 (result i32)
(get_local $var$1)
)
)
(i32.const 0)
)
)
(loop $label$10
(if
(i32.const 0)
(set_local $var$2
(get_local $var$1)
)
)
)
(drop
(i32.eqz
(get_local $var$2)
)
)
)
)
(unreachable)
)
)
(unreachable)
(i64.const 1)
)
)
)
)
)
(unreachable)
)
(func "bad1"
(local $var$2 i32)
(local $var$4 i32)
(block $label$1
(loop $label$2
(set_local $var$4
(if (result i32)
(i32.const 0)
(block (result i32)
(set_local $var$4
(tee_local $var$2
(i32.xor
(i32.const 0)
(i32.const -1)
)
)
)
(i32.const 0)
)
(block (result i32)
(set_local $var$4
(tee_local $var$2
(i32.xor
(i32.const 0)
(i32.const -1)
)
)
)
(i32.const 0)
)
)
)
(i32.store
(i32.const 1)
(i32.shl
(get_local $var$2)
(i32.const 14)
)
)
)
)
)
(func "only-dfo" (param $var$0 f64) (result f64)
(local $var$1 i32)
(local $var$2 i32)
(loop $label$1
(if
(tee_local $var$1
(tee_local $var$2
(get_local $var$1)
)
)
(if
(get_local $var$2)
(i64.atomic.store32 offset=3
(i32.and
(get_local $var$1) ;; only dfo can figure out that this is 0
(i32.const 15)
)
(i64.const -32768)
)
)
)
(br $label$1)
)
)
)

View File

@ -0,0 +1,41 @@
(module
(type $0 (func))
(type $1 (func (result f64)))
(type $2 (func (param i32 f64 f64) (result i32)))
(type $3 (func (param i64)))
(type $4 (func (param f64) (result i32)))
(export "if-select" (func $0))
(export "unreachable-body-update-zext" (func $1))
(export "ssa-const" (func $2))
(export "if-nothing" (func $3))
(export "only-dfo" (func $4))
(func $0 (; 0 ;) (; has Stack IR ;) (type $0)
(nop)
)
(func $1 (; 1 ;) (; has Stack IR ;) (type $1) (result f64)
(unreachable)
)
(func $2 (; 2 ;) (; has Stack IR ;) (type $2) (param $0 i32) (param $1 f64) (param $2 f64) (result i32)
(unreachable)
)
(func $3 (; 3 ;) (; has Stack IR ;) (type $3) (param $0 i64)
(unreachable)
)
(func $4 (; 4 ;) (; has Stack IR ;) (type $4) (param $0 f64) (result i32)
(local $1 i32)
(loop $label$1
(if
(i32.eqz
(get_local $1)
)
(block
(set_local $1
(i32.const -2147483648)
)
(br $label$1)
)
)
)
(i32.const -2766)
)
)

View File

@ -0,0 +1,112 @@
(module
(func "if-select"
(local $var$0 i32)
(nop)
(drop
(if (result i32)
(select
(i32.const 65473)
(i32.const 1)
(get_local $var$0)
)
(i32.const -2405046)
(i32.const 1)
)
)
)
(func "unreachable-body-update-zext" (result f64)
(if
(i32.eqz
(i32.const 0)
)
(unreachable)
)
(f64.const -9223372036854775808)
)
(func "ssa-const" (param $var$0 i32) (param $var$1 f64) (param $var$2 f64) (result i32)
(block $label$1 (result i32)
(block $label$2
(if
(i32.const 1)
(block
(drop
(loop $label$5 (result i64)
(if (result i64)
(i32.const 0)
(i64.load offset=22
(i32.and
(br_if $label$1
(i32.const 0)
(i32.const 0)
)
(i32.const 15)
)
(i64.const 1)
)
(i64.const 1)
)
)
)
)
)
(unreachable)
)
)
)
(func "if-nothing" (param $var$0 i64)
(local $var$1 i32)
(local $var$2 i32)
(block $label$1
(loop $label$2
(block $label$3
(block $label$4
(br_if $label$3
(i32.eqz
(if (result i32)
(i32.const 0)
(i32.const 0)
(get_local $var$2)
)
)
)
(unreachable)
)
(unreachable)
)
)
(unreachable)
)
)
(func "only-dfo" (param $var$0 f64) (result i32)
(local $var$1 i32)
(local $var$2 i32)
(local $var$3 i32)
(local $var$4 i32)
(loop $label$1
(set_local $var$3
(tee_local $var$1
(tee_local $var$2
(get_local $var$1)
)
)
)
(if
(i32.eqz
(get_local $var$4)
)
(block
(set_local $var$4
(select
(get_local $var$3)
(i32.const -2147483648)
(get_local $var$2)
)
)
(br $label$1)
)
)
)
(i32.const -2766)
)
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
[fuzz-exec] note result: $a => (i32.const -69)
[fuzz-exec] note result: $b => (i32.const -31768)
[fuzz-exec] note result: $c => (i64.const -69)
[fuzz-exec] note result: $d => (i64.const -31768)
[fuzz-exec] note result: $e => (i64.const -2146649112)
[fuzz-exec] note result: $a => i32.const -69
[fuzz-exec] note result: $b => i32.const -31768
[fuzz-exec] note result: $c => i64.const -69
[fuzz-exec] note result: $d => i64.const -31768
[fuzz-exec] note result: $e => i64.const -2146649112
[fuzz-exec] 5 results noted
(module
(type $0 (func (result i32)))
@ -38,11 +38,11 @@
)
)
)
[fuzz-exec] note result: $a => (i32.const -69)
[fuzz-exec] note result: $b => (i32.const -31768)
[fuzz-exec] note result: $c => (i64.const -69)
[fuzz-exec] note result: $d => (i64.const -31768)
[fuzz-exec] note result: $e => (i64.const -2146649112)
[fuzz-exec] note result: $a => i32.const -69
[fuzz-exec] note result: $b => i32.const -31768
[fuzz-exec] note result: $c => i64.const -69
[fuzz-exec] note result: $d => i64.const -31768
[fuzz-exec] note result: $e => i64.const -2146649112
[fuzz-exec] 5 results noted
[fuzz-exec] comparing $a
[fuzz-exec] comparing $b

View File

@ -1,5 +1,5 @@
[fuzz-exec] note result: $func_0 => (none.const ?)
[fuzz-exec] note result: $func_1 => (none.const ?)
[fuzz-exec] note result: $func_0 => none.const ?
[fuzz-exec] note result: $func_1 => none.const ?
[fuzz-exec] 2 results noted
(module
(type $0 (func (result i64)))
@ -23,8 +23,8 @@
)
)
)
[fuzz-exec] note result: $func_0 => (none.const ?)
[fuzz-exec] note result: $func_1 => (none.const ?)
[fuzz-exec] note result: $func_0 => none.const ?
[fuzz-exec] note result: $func_1 => none.const ?
[fuzz-exec] 2 results noted
[fuzz-exec] comparing $func_0
[fuzz-exec] comparing $func_1

View File

@ -11,6 +11,7 @@
(type $9 (func (param i32 i64 f32 f64)))
(type $10 (func (param i32 i64 f32)))
(type $11 (func (param i32 i64 f64 i32)))
(type $12 (func (result f64)))
(memory $0 0)
(export "load-off-2" (func $load-off-2))
(func $f (; 0 ;) (type $0) (param $i1 i32) (param $i2 i64)
@ -2906,6 +2907,96 @@
)
)
)
(func $getFallthrough (; 71 ;) (type $1)
(local $x0 i32)
(local $x1 i32)
(local $x2 i32)
(local $x3 i32)
(local $x4 i32)
(local $x5 i32)
(local $x6 i32)
(local $x7 i32)
(set_local $x0
(i32.const 1)
)
(drop
(get_local $x0)
)
(set_local $x1
(tee_local $x2
(i32.const 1)
)
)
(drop
(get_local $x1)
)
(set_local $x3
(loop $loop-in (result i32)
(i32.const 1)
)
)
(drop
(get_local $x3)
)
(set_local $x4
(if (result i32)
(i32.const 1)
(i32.const 2)
(i32.const 3)
)
)
(drop
(i32.and
(get_local $x4)
(i32.const 7)
)
)
(set_local $x5
(if (result i32)
(i32.const 1)
(unreachable)
(i32.const 3)
)
)
(drop
(get_local $x5)
)
(set_local $x6
(if (result i32)
(i32.const 1)
(i32.const 3)
(unreachable)
)
)
(drop
(get_local $x6)
)
(drop
(block $out (result i32)
(set_local $x7
(br_if $out
(i32.const 1)
(i32.const 1)
)
)
(drop
(get_local $x7)
)
(unreachable)
)
)
)
(func $tee-with-unreachable-value (; 72 ;) (type $12) (result f64)
(local $var$0 i32)
(block $label$1 (result f64)
(tee_local $var$0
(br_if $label$1
(f64.const 1)
(unreachable)
)
)
)
)
)
(module
(type $0 (func))

View File

@ -3479,6 +3479,53 @@
)
)
)
(func $getFallthrough ;; unit tests for Properties::getFallthrough
(local $x0 i32)
(local $x1 i32)
(local $x2 i32)
(local $x3 i32)
(local $x4 i32)
(local $x5 i32)
(local $x6 i32)
(local $x7 i32)
;; the trivial case
(set_local $x0 (i32.const 1))
(drop (i32.and (get_local $x0) (i32.const 7)))
;; tees
(set_local $x1 (tee_local $x2 (i32.const 1)))
(drop (i32.and (get_local $x1) (i32.const 7)))
;; loop
(set_local $x3 (loop (result i32) (i32.const 1)))
(drop (i32.and (get_local $x3) (i32.const 7)))
;; if - two sides, can't
(set_local $x4 (if (result i32) (i32.const 1) (i32.const 2) (i32.const 3)))
(drop (i32.and (get_local $x4) (i32.const 7)))
;; if - one side, can
(set_local $x5 (if (result i32) (i32.const 1) (unreachable) (i32.const 3)))
(drop (i32.and (get_local $x5) (i32.const 7)))
;; if - one side, can
(set_local $x6 (if (result i32) (i32.const 1) (i32.const 3) (unreachable)))
(drop (i32.and (get_local $x6) (i32.const 7)))
;; br_if with value
(drop
(block $out (result i32)
(set_local $x7 (br_if $out (i32.const 1) (i32.const 1)))
(drop (i32.and (get_local $x7) (i32.const 7)))
(unreachable)
)
)
)
(func $tee-with-unreachable-value (result f64)
(local $var$0 i32)
(block $label$1 (result f64)
(tee_local $var$0
(br_if $label$1 ;; the f64 does not actually flow through this, it's unreachable (and the type is wrong - but unchecked)
(f64.const 1)
(unreachable)
)
)
)
)
)
(module
(import "env" "memory" (memory $0 (shared 256 256)))

View File

@ -741,9 +741,7 @@
)
(if (result i32)
(i32.const 6)
(br $outval
(i32.const 7)
)
(i32.const 7)
(i32.const 8)
)
)
@ -1871,4 +1869,57 @@
)
(i32.const 0)
)
(func $if-flow-1 (; 75 ;) (type $2) (result i32)
(if (result i32)
(i32.const 0)
(i32.const 1)
(i32.const 2)
)
)
(func $if-flow-2 (; 76 ;) (type $2) (result i32)
(if (result i32)
(i32.const 0)
(unreachable)
(i32.const 2)
)
)
(func $if-flow-3 (; 77 ;) (type $2) (result i32)
(if (result i32)
(i32.const 0)
(i32.const 1)
(unreachable)
)
)
(func $if-flow-4 (; 78 ;) (type $2) (result i32)
(if
(return
(i32.const 0)
)
(return
(i32.const 1)
)
(return
(i32.const 2)
)
)
)
(func $iff-flow-fuzz-bug (; 79 ;) (type $2) (result i32)
(loop $label$1
(br_if $label$1
(i32.eqz
(i32.const 1)
)
)
(loop $label$2
(unreachable)
(if
(i32.const 0)
(nop)
(return
(i32.const 0)
)
)
)
)
)
)

View File

@ -1494,5 +1494,51 @@
)
(i32.const 0)
)
(func $if-flow-1 (result i32)
(if
(i32.const 0)
(return (i32.const 1))
(return (i32.const 2))
)
)
(func $if-flow-2 (result i32)
(if
(i32.const 0)
(unreachable)
(return (i32.const 2))
)
)
(func $if-flow-3 (result i32)
(if
(i32.const 0)
(return (i32.const 1))
(unreachable)
)
)
(func $if-flow-4 (result i32)
(if
(return (i32.const 0))
(return (i32.const 1))
(return (i32.const 2))
)
)
(func $iff-flow-fuzz-bug (result i32)
(loop $label$1
(if
(i32.const 1)
(loop $label$2
(unreachable)
(if ;; a loop that is never reached at the end of a loop
(i32.const 0)
(nop)
(return
(i32.const 0)
)
)
)
)
(br $label$1)
)
)
)

View File

@ -1,25 +1,28 @@
$0:
0 block
1 block
2 loop
3 block
4 unreachable (unreachable)
5 unreachable (unreachable)
6 end (none)
7 unreachable (unreachable)
8 set_local (unreachable)
9 unreachable (unreachable)
10 unreachable (unreachable)
11 end (none)
12 unreachable (unreachable)
13 unreachable (unreachable)
14 unreachable (unreachable)
15 end (none)
16 unreachable (unreachable)
17 break (unreachable)
18 unreachable (unreachable)
19 end (none)
(module
(type $0 (func (param i64)))
(func $0 (; 0 ;) (; has Stack IR ;) (type $0) (param $var$0 i64)
block $label$1
block $label$2
loop $label$3
block $label$4
unreachable
unreachable
end
unreachable
tee_local $var$0
unreachable
unreachable
end
unreachable
unreachable
unreachable
end
unreachable
br_if $label$1
unreachable
end
)
)
(module
(type $0 (func (param i64)))
(func $0 (; 0 ;) (; has Stack IR ;) (type $0) (param $var$0 i64)

View File

@ -430,4 +430,33 @@
)
)
)
(func $fuzz-nan (; 18 ;) (type $2)
(local $0 f64)
(local $1 f64)
(block $block
(br_if $block
(i32.const 0)
)
(loop $loop
(set_local $1
(get_local $0)
)
(set_local $0
(f64.const -nan:0xfffffffffff87)
)
(br_if $loop
(i32.const 1)
)
)
)
(set_local $0
(get_local $1)
)
(if
(i32.const 0)
(drop
(get_local $0)
)
)
)
)

View File

@ -248,5 +248,34 @@
)
)
)
(func $fuzz-nan
(local $0 f64)
(local $1 f64)
(block $block
(br_if $block
(i32.const 0)
)
(loop $loop
(set_local $1
(get_local $0)
)
(set_local $0
(f64.const -nan:0xfffffffffff87)
)
(br_if $loop
(i32.const 1)
)
)
)
(set_local $0 ;; make them equal
(get_local $1)
)
(if
(i32.const 0)
(set_local $1 ;; we can drop this
(get_local $0)
)
)
)
)

View File

@ -1,4 +1,4 @@
[fuzz-exec] note result: $func_0 => (i32.const 16384)
[fuzz-exec] note result: $func_0 => i32.const 16384
[fuzz-exec] 1 results noted
(module
(type $0 (func (result i32)))
@ -125,7 +125,7 @@
)
)
)
[fuzz-exec] note result: $func_0 => (i32.const 16384)
[fuzz-exec] note result: $func_0 => i32.const 16384
[fuzz-exec] 1 results noted
[fuzz-exec] comparing $func_0
[fuzz-exec] 1 results match

View File

@ -12,10 +12,6 @@
(import "env" "memoryBase" (global $memoryBase i32))
(import "env" "tableBase" (global $tableBase i32))
(import "env" "STACKTOP" (global $STACKTOP$asm2wasm$import i32))
(import "env" "illegalImport" (func $illegalImport (param f64 i64 i32)))
(import "env" "illegalImportResult" (func $illegalImportResult (result i64)))
(import "env" "_fabsf" (func $_fabsf (param f32) (result f32)))
(import "env" "do_i64" (func $do_i64 (result i64)))
(import "env" "abort" (func $abort))
(import "env" "illegalImport" (func $legalimport$illegalImport (param f64 i32 i32 i32)))
(import "env" "illegalImportResult" (func $legalimport$illegalImportResult (result i32)))
@ -30,7 +26,7 @@
(export "keepAlive" (func $keepAlive))
(export "getTempRet0" (func $getTempRet0))
(export "setTempRet0" (func $setTempRet0))
(func $loads (; 9 ;)
(func $loads (; 5 ;)
(local $i i32)
(local $f f32)
(local $d f64)
@ -145,7 +141,7 @@
)
)
)
(func $stores (; 10 ;)
(func $stores (; 6 ;)
(local $i i32)
(local $f f32)
(local $d f64)
@ -238,7 +234,7 @@
(get_local $d)
)
)
(func $test (; 11 ;)
(func $test (; 7 ;)
(local $i i32)
(local $j i64)
(local $f f32)
@ -284,7 +280,7 @@
)
)
)
(func $i64u-div (; 12 ;) (param $0 i64) (param $1 i64) (result i64)
(func $i64u-div (; 8 ;) (param $0 i64) (param $1 i64) (result i64)
(if (result i64)
(i64.eqz
(get_local $1)
@ -296,7 +292,7 @@
)
)
)
(func $i64s-div (; 13 ;) (param $0 i64) (param $1 i64) (result i64)
(func $i64s-div (; 9 ;) (param $0 i64) (param $1 i64) (result i64)
(if (result i64)
(i64.eqz
(get_local $1)
@ -321,7 +317,7 @@
)
)
)
(func $i64u-rem (; 14 ;) (param $0 i64) (param $1 i64) (result i64)
(func $i64u-rem (; 10 ;) (param $0 i64) (param $1 i64) (result i64)
(if (result i64)
(i64.eqz
(get_local $1)
@ -333,7 +329,7 @@
)
)
)
(func $i64s-rem (; 15 ;) (param $0 i64) (param $1 i64) (result i64)
(func $i64s-rem (; 11 ;) (param $0 i64) (param $1 i64) (result i64)
(if (result i64)
(i64.eqz
(get_local $1)
@ -345,7 +341,7 @@
)
)
)
(func $f32-to-int64 (; 16 ;) (param $0 f32) (result i64)
(func $f32-to-int64 (; 12 ;) (param $0 f32) (result i64)
(if (result i64)
(f32.ne
(get_local $0)
@ -371,7 +367,7 @@
)
)
)
(func $f64-to-int64 (; 17 ;) (param $0 f64) (result i64)
(func $f64-to-int64 (; 13 ;) (param $0 f64) (result i64)
(if (result i64)
(f64.ne
(get_local $0)
@ -397,7 +393,7 @@
)
)
)
(func $f32-to-uint64 (; 18 ;) (param $0 f32) (result i64)
(func $f32-to-uint64 (; 14 ;) (param $0 f32) (result i64)
(if (result i64)
(f32.ne
(get_local $0)
@ -423,7 +419,7 @@
)
)
)
(func $f64-to-uint64 (; 19 ;) (param $0 f64) (result i64)
(func $f64-to-uint64 (; 15 ;) (param $0 f64) (result i64)
(if (result i64)
(f64.ne
(get_local $0)
@ -449,7 +445,7 @@
)
)
)
(func $test64 (; 20 ;)
(func $test64 (; 16 ;)
(local $x i64)
(local $y i64)
(local $z i32)
@ -721,7 +717,7 @@
)
)
)
(func $imports (; 21 ;) (result i64)
(func $imports (; 17 ;) (result i64)
(call $legalfunc$illegalImport
(f64.const -3.13159)
(i64.const 94489280523)
@ -731,7 +727,7 @@
(call $legalfunc$illegalImportResult)
)
)
(func $arg (; 22 ;) (param $x i64)
(func $arg (; 18 ;) (param $x i64)
(i64.store
(i32.const 100)
(get_local $x)
@ -740,7 +736,7 @@
(get_local $x)
)
)
(func $illegalParam (; 23 ;) (param $a i32) (param $x i64) (param $b f64)
(func $illegalParam (; 19 ;) (param $a i32) (param $x i64) (param $b f64)
(i64.store
(i32.const 100)
(get_local $x)
@ -751,17 +747,17 @@
(f64.const 12.34)
)
)
(func $result (; 24 ;) (result i64)
(func $result (; 20 ;) (result i64)
(return
(i64.const 8589934593)
)
)
(func $illegalResult (; 25 ;) (result i64)
(func $illegalResult (; 21 ;) (result i64)
(return
(i64.const 8589934593)
)
)
(func $call1 (; 26 ;) (param $x i64) (result i64)
(func $call1 (; 22 ;) (param $x i64) (result i64)
(local $y i64)
(set_local $y
(call $call1
@ -772,7 +768,7 @@
(get_local $y)
)
)
(func $call2 (; 27 ;) (param $x i64) (result i64)
(func $call2 (; 23 ;) (param $x i64) (result i64)
(drop
(call $call2
(call $call2
@ -784,12 +780,12 @@
(i64.const 245127260211081)
)
)
(func $returnCastConst (; 28 ;) (result i64)
(func $returnCastConst (; 24 ;) (result i64)
(return
(i64.const 0)
)
)
(func $ifValue64 (; 29 ;) (param $$4 i64) (param $$6 i64) (result i64)
(func $ifValue64 (; 25 ;) (param $$4 i64) (param $$6 i64) (result i64)
(local $$$0 i64)
(local $$9 i64)
(local $$10 i64)
@ -822,7 +818,7 @@
(get_local $$$0)
)
)
(func $ifValue32 (; 30 ;) (param $$4 i32) (param $$6 i32) (result i32)
(func $ifValue32 (; 26 ;) (param $$4 i32) (param $$6 i32) (result i32)
(local $$$0 i32)
(local $$9 i32)
(local $$10 i32)
@ -855,7 +851,7 @@
(get_local $$$0)
)
)
(func $switch64 (; 31 ;) (param $$a444 i64) (result i32)
(func $switch64 (; 27 ;) (param $$a444 i64) (result i32)
(local $$waka i32)
(local $2 i64)
(block $switch
@ -906,7 +902,7 @@
(get_local $$waka)
)
)
(func $unreachable_leftovers (; 32 ;) (param $$0 i32) (param $$1 i32) (param $$2 i32)
(func $unreachable_leftovers (; 28 ;) (param $$0 i32) (param $$1 i32) (param $$2 i32)
(local $label i32)
(block $label$break$L1
(if
@ -942,7 +938,7 @@
)
(return)
)
(func $switch64TOOMUCH (; 33 ;) (param $$a444 i64) (result i32)
(func $switch64TOOMUCH (; 29 ;) (param $$a444 i64) (result i32)
(local $$waka i32)
(local $2 i64)
(local $3 i32)
@ -1074,7 +1070,7 @@
(i32.const 44)
)
)
(func $_memchr (; 34 ;) (param $$src i32) (param $$c i32) (param $$n i32) (result i32)
(func $_memchr (; 30 ;) (param $$src i32) (param $$c i32) (param $$n i32) (result i32)
(local $$0 i32)
(local $$1 i32)
(local $$2 i32)
@ -1652,7 +1648,7 @@
(get_local $$cond)
)
)
(func $switch64_big_condition1 (; 35 ;) (param $$x i64)
(func $switch64_big_condition1 (; 31 ;) (param $$x i64)
(local $1 i64)
(block $switch
(block $switch-default
@ -1687,7 +1683,7 @@
(return)
)
)
(func $switch64_big_condition2 (; 36 ;) (param $$x i64)
(func $switch64_big_condition2 (; 32 ;) (param $$x i64)
(local $1 i64)
(block $switch
(block $switch-case
@ -1719,7 +1715,7 @@
)
)
)
(func $keepAlive (; 37 ;)
(func $keepAlive (; 33 ;)
(call $loads)
(call $loads)
(call $stores)
@ -1827,7 +1823,7 @@
(i64.const 0)
)
)
(func $__emscripten_dceable_type_decls (; 38 ;)
(func $__emscripten_dceable_type_decls (; 34 ;)
(drop
(call $legalfunc$_fabsf
(f32.const 0)
@ -1837,7 +1833,7 @@
(call $legalfunc$do_i64)
)
)
(func $legalstub$illegalParam (; 39 ;) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 f64)
(func $legalstub$illegalParam (; 35 ;) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 f64)
(call $illegalParam
(get_local $0)
(i64.or
@ -1854,7 +1850,7 @@
(get_local $3)
)
)
(func $legalstub$illegalResult (; 40 ;) (result i32)
(func $legalstub$illegalResult (; 36 ;) (result i32)
(local $0 i64)
(set_local $0
(call $illegalResult)
@ -1871,7 +1867,7 @@
(get_local $0)
)
)
(func $legalfunc$illegalImport (; 41 ;) (param $0 f64) (param $1 i64) (param $2 i32)
(func $legalfunc$illegalImport (; 37 ;) (param $0 f64) (param $1 i64) (param $2 i32)
(call $legalimport$illegalImport
(get_local $0)
(i32.wrap/i64
@ -1886,7 +1882,7 @@
(get_local $2)
)
)
(func $legalfunc$illegalImportResult (; 42 ;) (result i64)
(func $legalfunc$illegalImportResult (; 38 ;) (result i64)
(i64.or
(i64.extend_u/i32
(call $legalimport$illegalImportResult)
@ -1899,7 +1895,7 @@
)
)
)
(func $legalfunc$_fabsf (; 43 ;) (param $0 f32) (result f32)
(func $legalfunc$_fabsf (; 39 ;) (param $0 f32) (result f32)
(f32.demote/f64
(call $legalimport$_fabsf
(f64.promote/f32
@ -1908,7 +1904,7 @@
)
)
)
(func $legalfunc$do_i64 (; 44 ;) (result i64)
(func $legalfunc$do_i64 (; 40 ;) (result i64)
(i64.or
(i64.extend_u/i32
(call $legalimport$do_i64)
@ -1921,10 +1917,10 @@
)
)
)
(func $getTempRet0 (; 45 ;) (result i32)
(func $getTempRet0 (; 41 ;) (result i32)
(get_global $tempRet0)
)
(func $setTempRet0 (; 46 ;) (param $0 i32)
(func $setTempRet0 (; 42 ;) (param $0 i32)
(set_global $tempRet0
(get_local $0)
)

View File

@ -12,10 +12,6 @@
(import "env" "memoryBase" (global $memoryBase i32))
(import "env" "tableBase" (global $tableBase i32))
(import "env" "STACKTOP" (global $STACKTOP$asm2wasm$import i32))
(import "env" "illegalImport" (func $illegalImport (param f64 i64 i32)))
(import "env" "illegalImportResult" (func $illegalImportResult (result i64)))
(import "env" "_fabsf" (func $_fabsf (param f32) (result f32)))
(import "env" "do_i64" (func $do_i64 (result i64)))
(import "env" "abort" (func $abort))
(import "env" "illegalImport" (func $legalimport$illegalImport (param f64 i32 i32 i32)))
(import "env" "illegalImportResult" (func $legalimport$illegalImportResult (result i32)))
@ -30,7 +26,7 @@
(export "keepAlive" (func $keepAlive))
(export "getTempRet0" (func $getTempRet0))
(export "setTempRet0" (func $setTempRet0))
(func $loads (; 9 ;)
(func $loads (; 5 ;)
(local $i i32)
(local $f f32)
(local $d f64)
@ -145,7 +141,7 @@
)
)
)
(func $stores (; 10 ;)
(func $stores (; 6 ;)
(local $i i32)
(local $f f32)
(local $d f64)
@ -238,7 +234,7 @@
(get_local $d)
)
)
(func $test (; 11 ;)
(func $test (; 7 ;)
(local $i i32)
(local $j i64)
(local $f f32)
@ -284,7 +280,7 @@
)
)
)
(func $test64 (; 12 ;)
(func $test64 (; 8 ;)
(local $x i64)
(local $y i64)
(local $z i32)
@ -556,7 +552,7 @@
)
)
)
(func $imports (; 13 ;) (result i64)
(func $imports (; 9 ;) (result i64)
(call $legalfunc$illegalImport
(f64.const -3.13159)
(i64.const 94489280523)
@ -566,7 +562,7 @@
(call $legalfunc$illegalImportResult)
)
)
(func $arg (; 14 ;) (param $x i64)
(func $arg (; 10 ;) (param $x i64)
(i64.store
(i32.const 100)
(get_local $x)
@ -575,7 +571,7 @@
(get_local $x)
)
)
(func $illegalParam (; 15 ;) (param $a i32) (param $x i64) (param $b f64)
(func $illegalParam (; 11 ;) (param $a i32) (param $x i64) (param $b f64)
(i64.store
(i32.const 100)
(get_local $x)
@ -586,17 +582,17 @@
(f64.const 12.34)
)
)
(func $result (; 16 ;) (result i64)
(func $result (; 12 ;) (result i64)
(return
(i64.const 8589934593)
)
)
(func $illegalResult (; 17 ;) (result i64)
(func $illegalResult (; 13 ;) (result i64)
(return
(i64.const 8589934593)
)
)
(func $call1 (; 18 ;) (param $x i64) (result i64)
(func $call1 (; 14 ;) (param $x i64) (result i64)
(local $y i64)
(set_local $y
(call $call1
@ -607,7 +603,7 @@
(get_local $y)
)
)
(func $call2 (; 19 ;) (param $x i64) (result i64)
(func $call2 (; 15 ;) (param $x i64) (result i64)
(drop
(call $call2
(call $call2
@ -619,12 +615,12 @@
(i64.const 245127260211081)
)
)
(func $returnCastConst (; 20 ;) (result i64)
(func $returnCastConst (; 16 ;) (result i64)
(return
(i64.const 0)
)
)
(func $ifValue64 (; 21 ;) (param $$4 i64) (param $$6 i64) (result i64)
(func $ifValue64 (; 17 ;) (param $$4 i64) (param $$6 i64) (result i64)
(local $$$0 i64)
(local $$9 i64)
(local $$10 i64)
@ -657,7 +653,7 @@
(get_local $$$0)
)
)
(func $ifValue32 (; 22 ;) (param $$4 i32) (param $$6 i32) (result i32)
(func $ifValue32 (; 18 ;) (param $$4 i32) (param $$6 i32) (result i32)
(local $$$0 i32)
(local $$9 i32)
(local $$10 i32)
@ -690,7 +686,7 @@
(get_local $$$0)
)
)
(func $switch64 (; 23 ;) (param $$a444 i64) (result i32)
(func $switch64 (; 19 ;) (param $$a444 i64) (result i32)
(local $$waka i32)
(local $2 i64)
(block $switch
@ -741,7 +737,7 @@
(get_local $$waka)
)
)
(func $unreachable_leftovers (; 24 ;) (param $$0 i32) (param $$1 i32) (param $$2 i32)
(func $unreachable_leftovers (; 20 ;) (param $$0 i32) (param $$1 i32) (param $$2 i32)
(local $label i32)
(block $label$break$L1
(if
@ -777,7 +773,7 @@
)
(return)
)
(func $switch64TOOMUCH (; 25 ;) (param $$a444 i64) (result i32)
(func $switch64TOOMUCH (; 21 ;) (param $$a444 i64) (result i32)
(local $$waka i32)
(local $2 i64)
(local $3 i32)
@ -909,7 +905,7 @@
(i32.const 44)
)
)
(func $_memchr (; 26 ;) (param $$src i32) (param $$c i32) (param $$n i32) (result i32)
(func $_memchr (; 22 ;) (param $$src i32) (param $$c i32) (param $$n i32) (result i32)
(local $$0 i32)
(local $$1 i32)
(local $$2 i32)
@ -1487,7 +1483,7 @@
(get_local $$cond)
)
)
(func $switch64_big_condition1 (; 27 ;) (param $$x i64)
(func $switch64_big_condition1 (; 23 ;) (param $$x i64)
(local $1 i64)
(block $switch
(block $switch-default
@ -1522,7 +1518,7 @@
(return)
)
)
(func $switch64_big_condition2 (; 28 ;) (param $$x i64)
(func $switch64_big_condition2 (; 24 ;) (param $$x i64)
(local $1 i64)
(block $switch
(block $switch-case
@ -1554,7 +1550,7 @@
)
)
)
(func $keepAlive (; 29 ;)
(func $keepAlive (; 25 ;)
(call $loads)
(call $loads)
(call $stores)
@ -1662,7 +1658,7 @@
(i64.const 0)
)
)
(func $__emscripten_dceable_type_decls (; 30 ;)
(func $__emscripten_dceable_type_decls (; 26 ;)
(drop
(call $legalfunc$_fabsf
(f32.const 0)
@ -1672,7 +1668,7 @@
(call $legalfunc$do_i64)
)
)
(func $legalstub$illegalParam (; 31 ;) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 f64)
(func $legalstub$illegalParam (; 27 ;) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 f64)
(call $illegalParam
(get_local $0)
(i64.or
@ -1689,7 +1685,7 @@
(get_local $3)
)
)
(func $legalstub$illegalResult (; 32 ;) (result i32)
(func $legalstub$illegalResult (; 28 ;) (result i32)
(local $0 i64)
(set_local $0
(call $illegalResult)
@ -1706,7 +1702,7 @@
(get_local $0)
)
)
(func $legalfunc$illegalImport (; 33 ;) (param $0 f64) (param $1 i64) (param $2 i32)
(func $legalfunc$illegalImport (; 29 ;) (param $0 f64) (param $1 i64) (param $2 i32)
(call $legalimport$illegalImport
(get_local $0)
(i32.wrap/i64
@ -1721,7 +1717,7 @@
(get_local $2)
)
)
(func $legalfunc$illegalImportResult (; 34 ;) (result i64)
(func $legalfunc$illegalImportResult (; 30 ;) (result i64)
(i64.or
(i64.extend_u/i32
(call $legalimport$illegalImportResult)
@ -1734,7 +1730,7 @@
)
)
)
(func $legalfunc$_fabsf (; 35 ;) (param $0 f32) (result f32)
(func $legalfunc$_fabsf (; 31 ;) (param $0 f32) (result f32)
(f32.demote/f64
(call $legalimport$_fabsf
(f64.promote/f32
@ -1743,7 +1739,7 @@
)
)
)
(func $legalfunc$do_i64 (; 36 ;) (result i64)
(func $legalfunc$do_i64 (; 32 ;) (result i64)
(i64.or
(i64.extend_u/i32
(call $legalimport$do_i64)
@ -1756,10 +1752,10 @@
)
)
)
(func $getTempRet0 (; 37 ;) (result i32)
(func $getTempRet0 (; 33 ;) (result i32)
(get_global $tempRet0)
)
(func $setTempRet0 (; 38 ;) (param $0 i32)
(func $setTempRet0 (; 34 ;) (param $0 i32)
(set_global $tempRet0
(get_local $0)
)

View File

@ -12,10 +12,6 @@
(import "env" "memoryBase" (global $memoryBase i32))
(import "env" "tableBase" (global $tableBase i32))
(import "env" "STACKTOP" (global $STACKTOP$asm2wasm$import i32))
(import "env" "illegalImport" (func $illegalImport (param f64 i64 i32)))
(import "env" "illegalImportResult" (func $illegalImportResult (result i64)))
(import "env" "_fabsf" (func $_fabsf (param f32) (result f32)))
(import "env" "do_i64" (func $do_i64 (result i64)))
(import "env" "abort" (func $abort))
(import "env" "illegalImport" (func $legalimport$illegalImport (param f64 i32 i32 i32)))
(import "env" "illegalImportResult" (func $legalimport$illegalImportResult (result i32)))
@ -30,7 +26,7 @@
(export "keepAlive" (func $keepAlive))
(export "getTempRet0" (func $getTempRet0))
(export "setTempRet0" (func $setTempRet0))
(func $loads (; 9 ;)
(func $loads (; 5 ;)
(local $i i32)
(local $f f32)
(local $d f64)
@ -145,7 +141,7 @@
)
)
)
(func $stores (; 10 ;)
(func $stores (; 6 ;)
(local $i i32)
(local $f f32)
(local $d f64)
@ -238,7 +234,7 @@
(get_local $d)
)
)
(func $test (; 11 ;)
(func $test (; 7 ;)
(local $i i32)
(local $j i64)
(local $f f32)
@ -284,7 +280,7 @@
)
)
)
(func $i64u-div (; 12 ;) (param $0 i64) (param $1 i64) (result i64)
(func $i64u-div (; 8 ;) (param $0 i64) (param $1 i64) (result i64)
(if (result i64)
(i64.eqz
(get_local $1)
@ -296,7 +292,7 @@
)
)
)
(func $i64s-div (; 13 ;) (param $0 i64) (param $1 i64) (result i64)
(func $i64s-div (; 9 ;) (param $0 i64) (param $1 i64) (result i64)
(if (result i64)
(i64.eqz
(get_local $1)
@ -321,7 +317,7 @@
)
)
)
(func $i64u-rem (; 14 ;) (param $0 i64) (param $1 i64) (result i64)
(func $i64u-rem (; 10 ;) (param $0 i64) (param $1 i64) (result i64)
(if (result i64)
(i64.eqz
(get_local $1)
@ -333,7 +329,7 @@
)
)
)
(func $i64s-rem (; 15 ;) (param $0 i64) (param $1 i64) (result i64)
(func $i64s-rem (; 11 ;) (param $0 i64) (param $1 i64) (result i64)
(if (result i64)
(i64.eqz
(get_local $1)
@ -345,7 +341,7 @@
)
)
)
(func $f32-to-int64 (; 16 ;) (param $0 f32) (result i64)
(func $f32-to-int64 (; 12 ;) (param $0 f32) (result i64)
(if (result i64)
(f32.ne
(get_local $0)
@ -371,7 +367,7 @@
)
)
)
(func $f64-to-int64 (; 17 ;) (param $0 f64) (result i64)
(func $f64-to-int64 (; 13 ;) (param $0 f64) (result i64)
(if (result i64)
(f64.ne
(get_local $0)
@ -397,7 +393,7 @@
)
)
)
(func $f32-to-uint64 (; 18 ;) (param $0 f32) (result i64)
(func $f32-to-uint64 (; 14 ;) (param $0 f32) (result i64)
(if (result i64)
(f32.ne
(get_local $0)
@ -423,7 +419,7 @@
)
)
)
(func $f64-to-uint64 (; 19 ;) (param $0 f64) (result i64)
(func $f64-to-uint64 (; 15 ;) (param $0 f64) (result i64)
(if (result i64)
(f64.ne
(get_local $0)
@ -449,7 +445,7 @@
)
)
)
(func $test64 (; 20 ;)
(func $test64 (; 16 ;)
(local $x i64)
(local $y i64)
(local $z i32)
@ -721,7 +717,7 @@
)
)
)
(func $imports (; 21 ;) (result i64)
(func $imports (; 17 ;) (result i64)
(call $legalfunc$illegalImport
(f64.const -3.13159)
(i64.const 94489280523)
@ -731,7 +727,7 @@
(call $legalfunc$illegalImportResult)
)
)
(func $arg (; 22 ;) (param $x i64)
(func $arg (; 18 ;) (param $x i64)
(i64.store
(i32.const 100)
(get_local $x)
@ -740,7 +736,7 @@
(get_local $x)
)
)
(func $illegalParam (; 23 ;) (param $a i32) (param $x i64) (param $b f64)
(func $illegalParam (; 19 ;) (param $a i32) (param $x i64) (param $b f64)
(i64.store
(i32.const 100)
(get_local $x)
@ -751,17 +747,17 @@
(f64.const 12.34)
)
)
(func $result (; 24 ;) (result i64)
(func $result (; 20 ;) (result i64)
(return
(i64.const 8589934593)
)
)
(func $illegalResult (; 25 ;) (result i64)
(func $illegalResult (; 21 ;) (result i64)
(return
(i64.const 8589934593)
)
)
(func $call1 (; 26 ;) (param $x i64) (result i64)
(func $call1 (; 22 ;) (param $x i64) (result i64)
(local $y i64)
(set_local $y
(call $call1
@ -772,7 +768,7 @@
(get_local $y)
)
)
(func $call2 (; 27 ;) (param $x i64) (result i64)
(func $call2 (; 23 ;) (param $x i64) (result i64)
(drop
(call $call2
(call $call2
@ -784,12 +780,12 @@
(i64.const 245127260211081)
)
)
(func $returnCastConst (; 28 ;) (result i64)
(func $returnCastConst (; 24 ;) (result i64)
(return
(i64.const 0)
)
)
(func $ifValue64 (; 29 ;) (param $$4 i64) (param $$6 i64) (result i64)
(func $ifValue64 (; 25 ;) (param $$4 i64) (param $$6 i64) (result i64)
(local $$$0 i64)
(local $$9 i64)
(local $$10 i64)
@ -822,7 +818,7 @@
(get_local $$$0)
)
)
(func $ifValue32 (; 30 ;) (param $$4 i32) (param $$6 i32) (result i32)
(func $ifValue32 (; 26 ;) (param $$4 i32) (param $$6 i32) (result i32)
(local $$$0 i32)
(local $$9 i32)
(local $$10 i32)
@ -855,7 +851,7 @@
(get_local $$$0)
)
)
(func $switch64 (; 31 ;) (param $$a444 i64) (result i32)
(func $switch64 (; 27 ;) (param $$a444 i64) (result i32)
(local $$waka i32)
(local $2 i64)
(block $switch
@ -906,7 +902,7 @@
(get_local $$waka)
)
)
(func $unreachable_leftovers (; 32 ;) (param $$0 i32) (param $$1 i32) (param $$2 i32)
(func $unreachable_leftovers (; 28 ;) (param $$0 i32) (param $$1 i32) (param $$2 i32)
(local $label i32)
(block $label$break$L1
(if
@ -942,7 +938,7 @@
)
(return)
)
(func $switch64TOOMUCH (; 33 ;) (param $$a444 i64) (result i32)
(func $switch64TOOMUCH (; 29 ;) (param $$a444 i64) (result i32)
(local $$waka i32)
(local $2 i64)
(local $3 i32)
@ -1074,7 +1070,7 @@
(i32.const 44)
)
)
(func $_memchr (; 34 ;) (param $$src i32) (param $$c i32) (param $$n i32) (result i32)
(func $_memchr (; 30 ;) (param $$src i32) (param $$c i32) (param $$n i32) (result i32)
(local $$0 i32)
(local $$1 i32)
(local $$2 i32)
@ -1652,7 +1648,7 @@
(get_local $$cond)
)
)
(func $switch64_big_condition1 (; 35 ;) (param $$x i64)
(func $switch64_big_condition1 (; 31 ;) (param $$x i64)
(local $1 i64)
(block $switch
(block $switch-default
@ -1687,7 +1683,7 @@
(return)
)
)
(func $switch64_big_condition2 (; 36 ;) (param $$x i64)
(func $switch64_big_condition2 (; 32 ;) (param $$x i64)
(local $1 i64)
(block $switch
(block $switch-case
@ -1719,7 +1715,7 @@
)
)
)
(func $keepAlive (; 37 ;)
(func $keepAlive (; 33 ;)
(call $loads)
(call $loads)
(call $stores)
@ -1827,7 +1823,7 @@
(i64.const 0)
)
)
(func $__emscripten_dceable_type_decls (; 38 ;)
(func $__emscripten_dceable_type_decls (; 34 ;)
(drop
(call $legalfunc$_fabsf
(f32.const 0)
@ -1837,7 +1833,7 @@
(call $legalfunc$do_i64)
)
)
(func $legalstub$illegalParam (; 39 ;) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 f64)
(func $legalstub$illegalParam (; 35 ;) (param $0 i32) (param $1 i32) (param $2 i32) (param $3 f64)
(call $illegalParam
(get_local $0)
(i64.or
@ -1854,7 +1850,7 @@
(get_local $3)
)
)
(func $legalstub$illegalResult (; 40 ;) (result i32)
(func $legalstub$illegalResult (; 36 ;) (result i32)
(local $0 i64)
(set_local $0
(call $illegalResult)
@ -1871,7 +1867,7 @@
(get_local $0)
)
)
(func $legalfunc$illegalImport (; 41 ;) (param $0 f64) (param $1 i64) (param $2 i32)
(func $legalfunc$illegalImport (; 37 ;) (param $0 f64) (param $1 i64) (param $2 i32)
(call $legalimport$illegalImport
(get_local $0)
(i32.wrap/i64
@ -1886,7 +1882,7 @@
(get_local $2)
)
)
(func $legalfunc$illegalImportResult (; 42 ;) (result i64)
(func $legalfunc$illegalImportResult (; 38 ;) (result i64)
(i64.or
(i64.extend_u/i32
(call $legalimport$illegalImportResult)
@ -1899,7 +1895,7 @@
)
)
)
(func $legalfunc$_fabsf (; 43 ;) (param $0 f32) (result f32)
(func $legalfunc$_fabsf (; 39 ;) (param $0 f32) (result f32)
(f32.demote/f64
(call $legalimport$_fabsf
(f64.promote/f32
@ -1908,7 +1904,7 @@
)
)
)
(func $legalfunc$do_i64 (; 44 ;) (result i64)
(func $legalfunc$do_i64 (; 40 ;) (result i64)
(i64.or
(i64.extend_u/i32
(call $legalimport$do_i64)
@ -1921,10 +1917,10 @@
)
)
)
(func $getTempRet0 (; 45 ;) (result i32)
(func $getTempRet0 (; 41 ;) (result i32)
(get_global $tempRet0)
)
(func $setTempRet0 (; 46 ;) (param $0 i32)
(func $setTempRet0 (; 42 ;) (param $0 i32)
(set_global $tempRet0
(get_local $0)
)

View File

@ -1,53 +0,0 @@
function asmFunc(global, env, buffer) {
"use asm";
var HEAP8 = new global.Int8Array(buffer);
var HEAP16 = new global.Int16Array(buffer);
var HEAP32 = new global.Int32Array(buffer);
var HEAPU8 = new global.Uint8Array(buffer);
var HEAPU16 = new global.Uint16Array(buffer);
var HEAPU32 = new global.Uint32Array(buffer);
var HEAPF32 = new global.Float32Array(buffer);
var HEAPF64 = new global.Float64Array(buffer);
var Math_imul = global.Math.imul;
var Math_fround = global.Math.fround;
var Math_abs = global.Math.abs;
var Math_clz32 = global.Math.clz32;
var Math_min = global.Math.min;
var Math_max = global.Math.max;
var Math_floor = global.Math.floor;
var Math_ceil = global.Math.ceil;
var Math_sqrt = global.Math.sqrt;
var abort = env.abort;
var nan = global.NaN;
var infinity = global.Infinity;
var print = env.print;
var i64toi32_i32$HIGH_BITS = 0;
function $0(i) {
i = i | 0;
var wasm2asm_i32$0 = 0;
print(HEAPU8[i >> 0] | 0 | 0);
print(HEAPU8[(i + 1 | 0) >> 0] | 0 | 0);
print(HEAPU8[(i + 2 | 0) >> 0] | 0 | 0);
print(HEAPU8[(i + 25 | 0) >> 0] | 0 | 0);
print(HEAPU16[i >> 1] | 0 | 0);
print((wasm2asm_i32$0 = i, HEAPU8[wasm2asm_i32$0 >> 0] | 0 | 0 | (HEAPU8[(wasm2asm_i32$0 + 1 | 0) >> 0] | 0 | 0) << 8) | 0);
print((wasm2asm_i32$0 = i, HEAPU8[(wasm2asm_i32$0 + 1 | 0) >> 0] | 0 | 0 | (HEAPU8[(wasm2asm_i32$0 + 2 | 0) >> 0] | 0 | 0) << 8) | 0);
print(HEAPU16[(i + 2 | 0) >> 1] | 0 | 0);
print((wasm2asm_i32$0 = i, HEAPU8[(wasm2asm_i32$0 + 25 | 0) >> 0] | 0 | 0 | (HEAPU8[(wasm2asm_i32$0 + 26 | 0) >> 0] | 0 | 0) << 8) | 0);
print(HEAPU32[i >> 2] | 0 | 0);
print((wasm2asm_i32$0 = i, HEAPU8[(wasm2asm_i32$0 + 1 | 0) >> 0] | 0 | 0 | (HEAPU8[(wasm2asm_i32$0 + 2 | 0) >> 0] | 0 | 0) << 8 | (HEAPU8[(wasm2asm_i32$0 + 3 | 0) >> 0] | 0 | 0) << 16 | (HEAPU8[(wasm2asm_i32$0 + 4 | 0) >> 0] | 0 | 0) << 24) | 0);
print((wasm2asm_i32$0 = i, HEAPU8[(wasm2asm_i32$0 + 2 | 0) >> 0] | 0 | 0 | (HEAPU8[(wasm2asm_i32$0 + 3 | 0) >> 0] | 0 | 0) << 8 | (HEAPU8[(wasm2asm_i32$0 + 4 | 0) >> 0] | 0 | 0) << 16 | (HEAPU8[(wasm2asm_i32$0 + 5 | 0) >> 0] | 0 | 0) << 24) | 0);
print((wasm2asm_i32$0 = i, HEAPU8[(wasm2asm_i32$0 + 25 | 0) >> 0] | 0 | 0 | (HEAPU8[(wasm2asm_i32$0 + 26 | 0) >> 0] | 0 | 0) << 8 | (HEAPU8[(wasm2asm_i32$0 + 27 | 0) >> 0] | 0 | 0) << 16 | (HEAPU8[(wasm2asm_i32$0 + 28 | 0) >> 0] | 0 | 0) << 24) | 0);
}
function $1(i) {
i = i | 0;
HEAPU32[(i + 4294967295 | 0) >> 2] | 0;
}
return {
good: $0,
bad: $1
};
}

Some files were not shown because too many files have changed in this diff Show More