├── .gitignore ├── .gitmodules ├── AUTHORS ├── CONTRIBUTORS ├── Contributing.md ├── LICENSE ├── README.md ├── bin ├── asm2wasm ├── binaryen-shell ├── clang ├── clang++ ├── d8 ├── natives_blob.bin ├── s2wasm ├── sexpr-wasm ├── snapshot_blob.bin ├── wacc ├── wacc++ └── wasm2asm ├── scripts ├── build-all.sh ├── build-binaryen.sh ├── build-d8.sh ├── build-llvm.sh ├── build-sexpr-wasm.sh ├── clang.sha ├── llvm.sha ├── run.py └── update-llvm.sh └── test ├── fizzbuzz.c ├── hello.c ├── raytrace.cc ├── run-tests.py ├── wasm.cc ├── wasm.h └── wasm.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | /third_party/llvm 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/sexpr-wasm-prototype"] 2 | path = third_party/sexpr-wasm-prototype 3 | url = https://github.com/webassembly/sexpr-wasm-prototype 4 | [submodule "third_party/v8-native-prototype"] 5 | path = third_party/v8-native-prototype 6 | url = https://github.com/webassembly/v8-native-prototype 7 | [submodule "third_party/binaryen"] 8 | path = third_party/binaryen 9 | url = https://github.com/WebAssembly/binaryen.git 10 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of authors for copyright purposes. 2 | # This file is distinct from the CONTRIBUTORS files. 3 | # See the latter for an explanation. 4 | 5 | # Names should be added to this file as: 6 | # Name or Organization 7 | # The email address is not required for organizations. 8 | 9 | Google Inc. 10 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # People who have agreed to one of the CLAs and can contribute patches. 2 | # The AUTHORS file lists the copyright holders; this file 3 | # lists people. For example, Google employees are listed here 4 | # but not in AUTHORS, because Google holds the copyright. 5 | # 6 | # https://developers.google.com/open-source/cla/individual 7 | # https://developers.google.com/open-source/cla/corporate 8 | # 9 | # Names should be added to this file as: 10 | # Name 11 | 12 | Ben Smith 13 | Nick Bray 14 | -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to WebAssembly 2 | 3 | Interested in participating? Please follow 4 | [the same contributing guidelines as the design repository][]. 5 | 6 | [the same contributing guidelines as the design repository]: https://github.com/WebAssembly/design/blob/master/Contributing.md 7 | 8 | Also, please be sure to read [the README.md](README.md) for this repository. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2015 the repository authors, see AUTHORS file. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/WebAssembly/wasm-e2e.svg?branch=master)](https://travis-ci.org/WebAssembly/wasm-e2e) 2 | 3 | # wasm-e2e 4 | 5 | **WARNING** 6 | wasm-e2e isn't kept up to date anymore. We're keeping it here for historical purpose only. Don't rely on it! 7 | **WARNING** 8 | 9 | This repository contains tools and scripts to compile C/C++ to a WebAssembly 10 | binary format, and run it. 11 | 12 | Currently this includes: 13 | - LLVM: for compiling C source to the ".s" format, which is the current 14 | output of WebAssembly by the LLVM backend. 15 | - binaryen: different tools, including `s2wasm` for converting `.s` to the 16 | S-expression format as defined in the [spec repo][]. 17 | - sexpr-wasm: for converting the S-expression format to the v8-native binary 18 | format. 19 | - v8-native: for running the binary. 20 | 21 | [spec repo]: https://github.com/WebAssembly/spec 22 | 23 | At this time, the contents of this repository are under development and known 24 | to be incomplet and inkorrect. 25 | 26 | Participation is welcome, though many of the changes will need to be landed in 27 | other repositories. 28 | 29 | ## Cloning/Updating 30 | 31 | Clone the repo, update the submodules, and update LLVM: 32 | 33 | ``` 34 | $ git clone https://github.com/WebAssembly/wasm-e2e 35 | $ cd wasm-e2e 36 | $ git submodule update --init 37 | $ ./scripts/update-llvm.sh 38 | ``` 39 | 40 | LLVM is not included as a submodule because it is quite large and requires a 41 | repo to be installed inside another repo. The `update-llvm.sh` script clones 42 | with `--depth 5000` to reduce the download size. 43 | 44 | Updating the repo is almost the same: 45 | 46 | ``` 47 | $ git pull origin master 48 | $ git submodule update 49 | $ ./scripts/update-llvm.sh 50 | ``` 51 | 52 | ## Building 53 | 54 | To build all the tools: 55 | 56 | ``` 57 | $ ./scripts/build-all.sh 58 | ``` 59 | 60 | You can also build each component separately: 61 | - LLVM/Clang: `./scripts/build-llvm.sh` 62 | - v8-native/d8: `./scripts/build-d8.sh` 63 | - sexpr-wasm: `./scripts/build-sexpr-wasm.sh` 64 | 65 | ## Running 66 | 67 | Use the `./script/run.py` Python script to compile and run a C source file: 68 | 69 | ``` 70 | $ ./scripts/run.py test/hello.c 71 | Hello, world! 72 | 73 | $ scripts/run.py test/fizzbuzz.c 74 | 1 75 | 2 76 | fizz 77 | 4 78 | buzz 79 | fizz 80 | 7 81 | 8 82 | fizz 83 | buzz 84 | 11 85 | fizz 86 | 13 87 | 14 88 | fizzbuzz 89 | 16 90 | 17 91 | fizz 92 | 19 93 | ``` 94 | 95 | The script compiles the C source to v8-native binary format. 96 | -------------------------------------------------------------------------------- /bin/asm2wasm: -------------------------------------------------------------------------------- 1 | ../third_party/binaryen/bin/asm2wasm -------------------------------------------------------------------------------- /bin/binaryen-shell: -------------------------------------------------------------------------------- 1 | ../third_party/binaryen/bin/binaryen-shell -------------------------------------------------------------------------------- /bin/clang: -------------------------------------------------------------------------------- 1 | ../third_party/llvm/build/bin/clang -------------------------------------------------------------------------------- /bin/clang++: -------------------------------------------------------------------------------- 1 | ../third_party/llvm/build/bin/clang++ -------------------------------------------------------------------------------- /bin/d8: -------------------------------------------------------------------------------- 1 | ../third_party/v8-native-prototype/v8/v8/out/Release/d8 -------------------------------------------------------------------------------- /bin/natives_blob.bin: -------------------------------------------------------------------------------- 1 | ../third_party/v8-native-prototype/v8/v8/out/Release/natives_blob.bin -------------------------------------------------------------------------------- /bin/s2wasm: -------------------------------------------------------------------------------- 1 | ../third_party/binaryen/bin/s2wasm -------------------------------------------------------------------------------- /bin/sexpr-wasm: -------------------------------------------------------------------------------- 1 | ../third_party/sexpr-wasm-prototype/out/sexpr-wasm -------------------------------------------------------------------------------- /bin/snapshot_blob.bin: -------------------------------------------------------------------------------- 1 | ../third_party/v8-native-prototype/v8/v8/out/Release/snapshot_blob.bin -------------------------------------------------------------------------------- /bin/wacc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SCRIPT_DIR="$(dirname "$0")" 3 | ${SCRIPT_DIR}/clang -target wasm32-unknown-unknown -O2 -S -c $* 4 | -------------------------------------------------------------------------------- /bin/wacc++: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SCRIPT_DIR="$(dirname "$0")" 3 | ${SCRIPT_DIR}/clang++ -target wasm32-unknown-unknown -O2 -S -c $* 4 | -------------------------------------------------------------------------------- /bin/wasm2asm: -------------------------------------------------------------------------------- 1 | ../third_party/binaryen/bin/wasm2asm -------------------------------------------------------------------------------- /scripts/build-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o nounset 3 | set -o errexit 4 | 5 | SCRIPT_DIR="$(dirname "$0")" 6 | 7 | ${SCRIPT_DIR}/build-llvm.sh 8 | ${SCRIPT_DIR}/build-d8.sh 9 | ${SCRIPT_DIR}/build-sexpr-wasm.sh 10 | ${SCRIPT_DIR}/build-binaryen.sh 11 | -------------------------------------------------------------------------------- /scripts/build-binaryen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o nounset 3 | set -o errexit 4 | 5 | SCRIPT_DIR="$(dirname "$0")" 6 | ROOT_DIR="$(dirname "${SCRIPT_DIR}")" 7 | 8 | cd "${ROOT_DIR}/third_party/binaryen" 9 | cmake -G Ninja 10 | ninja 11 | -------------------------------------------------------------------------------- /scripts/build-d8.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o nounset 3 | set -o errexit 4 | 5 | sync=YES 6 | config=Release 7 | 8 | while [[ $# > 0 ]]; do 9 | flag="$1" 10 | case $flag in 11 | --no-sync) 12 | sync=NO 13 | ;; 14 | --debug) 15 | config=Debug 16 | ;; 17 | *) 18 | echo "unknown arg ${flag}" 19 | ;; 20 | esac 21 | shift 22 | done 23 | 24 | SCRIPT_DIR="$(dirname "$0")" 25 | ROOT_DIR="$(dirname "${SCRIPT_DIR}")" 26 | 27 | cd ${ROOT_DIR}/third_party/v8-native-prototype 28 | 29 | # copied from v8-native-prototype/install-dependencies.sh, but this fetches a 30 | # shallow clone. 31 | if [[ ! -d depot_tools ]]; then 32 | echo "Cloning depot_tools" 33 | git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git 34 | fi 35 | 36 | export PATH=$PWD/depot_tools:$PATH 37 | 38 | if [[ ${sync} = "YES" ]]; then 39 | if [[ ! -d v8 ]]; then 40 | echo "Fetching v8" 41 | mkdir v8 42 | pushd v8 43 | fetch --no-history v8 44 | ln -fs $PWD/.. v8/third_party/wasm 45 | popd 46 | else 47 | pushd v8/v8 48 | git fetch origin 49 | git checkout origin/master 50 | popd 51 | fi 52 | 53 | pushd v8 54 | gclient update 55 | popd 56 | fi 57 | 58 | # Don't use the CC from the environment; v8 doesn't seem to build properly with 59 | # it. 60 | unset CC 61 | cd v8/v8 62 | GYP_GENERATORS=ninja build/gyp_v8 -Dv8_wasm=1 -Dwerror= 63 | time ninja -C out/${config} d8 64 | -------------------------------------------------------------------------------- /scripts/build-llvm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o nounset 3 | set -o errexit 4 | 5 | SCRIPT_DIR="$(dirname "$0")" 6 | ROOT_DIR="$(dirname "${SCRIPT_DIR}")" 7 | LLVM_DIR="${ROOT_DIR}/third_party/llvm" 8 | BUILD_DIR="${LLVM_DIR}/build" 9 | CMAKE_FLAGS="\ 10 | -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ 11 | -DLLVM_BUILD_TESTS=ON \ 12 | -DCMAKE_BUILD_TYPE=Debug \ 13 | -DLLVM_ENABLE_ASSERTIONS=ON \ 14 | -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly \ 15 | -DLLVM_TARGETS_TO_BUILD=X86" 16 | 17 | if [[ ! -d ${BUILD_DIR} ]]; then 18 | mkdir -p ${BUILD_DIR} 19 | (cd ${BUILD_DIR} && cmake -G Ninja .. ${CMAKE_FLAGS}) 20 | fi 21 | 22 | pushd ${BUILD_DIR} 23 | time ninja bin/clang 24 | popd 25 | -------------------------------------------------------------------------------- /scripts/build-sexpr-wasm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o nounset 3 | set -o errexit 4 | 5 | SCRIPT_DIR="$(dirname "$0")" 6 | ROOT_DIR="$(dirname "${SCRIPT_DIR}")" 7 | 8 | make -C "${ROOT_DIR}/third_party/sexpr-wasm-prototype" 9 | -------------------------------------------------------------------------------- /scripts/clang.sha: -------------------------------------------------------------------------------- 1 | 157d401f5d70ed850010a3c88902e03d1a9a3d2e 2 | -------------------------------------------------------------------------------- /scripts/llvm.sha: -------------------------------------------------------------------------------- 1 | d10549743ae65fcc4420a5b07606f97f5cac4bae 2 | -------------------------------------------------------------------------------- /scripts/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | import os 5 | import os.path 6 | import shutil 7 | import subprocess 8 | import sys 9 | import tempfile 10 | 11 | SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 12 | REPO_ROOT_DIR = os.path.dirname(SCRIPT_DIR) 13 | BIN_DIR = os.path.join(REPO_ROOT_DIR, 'bin') 14 | 15 | CLANG = os.path.join(BIN_DIR, 'clang') 16 | WACC = os.path.join(BIN_DIR, 'wacc') 17 | LINKER = os.path.join(BIN_DIR, 's2wasm') 18 | SEXPR_WASM = os.path.join(BIN_DIR, 'sexpr-wasm') 19 | D8 = os.path.join(BIN_DIR, 'd8') 20 | WASM_JS = os.path.join(REPO_ROOT_DIR, 'test', 'wasm.js') 21 | 22 | 23 | class Error(Exception): 24 | pass 25 | 26 | 27 | def call_and_suppress_stderr(cmd): 28 | process = subprocess.Popen(cmd, 29 | stderr=subprocess.PIPE) 30 | _, stderr = process.communicate() 31 | if process.returncode != 0: 32 | raise Error(stderr) 33 | 34 | 35 | def main(args): 36 | parser = argparse.ArgumentParser() 37 | parser.add_argument('in_file', help='input file (C or WAST)') 38 | parser.add_argument('--js-env', help='JS/WASM environment file') 39 | parser.add_argument('--native-env', help='native environment file') 40 | parser.add_argument('--dump', help='directory to store intermediate files') 41 | options = parser.parse_args(args) 42 | 43 | if not options.js_env and not options.native_env: 44 | options.js_env = WASM_JS 45 | 46 | path_parts = os.path.splitext(os.path.basename(options.in_file)) 47 | basename = path_parts[0] 48 | extension = path_parts[1] 49 | if not options.dump: 50 | clean_temp_dir = True 51 | temp_dir = tempfile.mkdtemp(prefix='wasm-e2e-run-') 52 | else: 53 | clean_temp_dir = False 54 | temp_dir = options.dump 55 | if not os.path.exists(temp_dir): 56 | os.makedirs(temp_dir) 57 | 58 | try: 59 | if options.js_env: 60 | if extension == '.c': 61 | s_file = os.path.join(temp_dir, basename + '.s') 62 | wast_file = os.path.join(temp_dir, basename + '.wast') 63 | subprocess.check_call([WACC, '-fno-builtin', options.in_file, '-o', s_file, '-I', '.']) 64 | subprocess.check_call([LINKER, s_file, '-o', wast_file]) 65 | elif extension == '.wast': 66 | wast_file = options.in_file 67 | else: 68 | raise Exception('unimplemented input file type: %s' % options.in_file) 69 | 70 | wasm_file = os.path.join(temp_dir, basename + '.wasm') 71 | 72 | subprocess.check_call([SEXPR_WASM, wast_file, '-o', wasm_file]) 73 | call_and_suppress_stderr([D8, '--expose-wasm', options.js_env, '--', wasm_file]) 74 | 75 | if options.native_env: 76 | executable_file = os.path.join(temp_dir, basename) 77 | subprocess.check_call([CLANG, '-O2', options.in_file, options.native_env, 78 | '-o', executable_file, '-I', '.']) 79 | subprocess.check_call([executable_file]) 80 | 81 | finally: 82 | if clean_temp_dir: 83 | shutil.rmtree(temp_dir) 84 | 85 | 86 | if __name__ == '__main__': 87 | try: 88 | sys.exit(main(sys.argv[1:])) 89 | except (Error, OSError, subprocess.CalledProcessError) as e: 90 | sys.stderr.write(str(e) + '\n') 91 | sys.exit(1) 92 | -------------------------------------------------------------------------------- /scripts/update-llvm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o nounset 3 | set -o errexit 4 | 5 | SCRIPT_DIR="$(dirname "$0")" 6 | ROOT_DIR="$(dirname "${SCRIPT_DIR}")" 7 | LLVM_DIR="${ROOT_DIR}/third_party/llvm" 8 | CLANG_DIR="${LLVM_DIR}/tools/clang" 9 | LLVM_SHA_FILE=${SCRIPT_DIR}/llvm.sha 10 | CLANG_SHA_FILE=${SCRIPT_DIR}/clang.sha 11 | LLVM_GIT_URL="http://llvm.org/git/" 12 | LLVM_SVN_URL="https://llvm.org/svn/llvm-project/" 13 | SVN_FILE="${LLVM_DIR}/.git/svn/.metadata" 14 | DEPTH=5000 15 | SYNC_TO_HEAD=NO 16 | LLVM_USERNAME=NONE 17 | 18 | while [[ $# > 0 ]]; do 19 | flag="$1" 20 | case $flag in 21 | --head) 22 | SYNC_TO_HEAD=YES 23 | ;; 24 | --llvm-username) 25 | LLVM_USERNAME="$2" 26 | shift # Pass over $2 value. 27 | ;; 28 | -h|--help) 29 | echo "Create or update LLVM-related repositories." 30 | echo " --head Sync to origin/master" 31 | echo " --llvm-username=a@b.com LLVM project username for commits" 32 | exit 0 33 | ;; 34 | *) 35 | echo "unknown arg ${flag}" 36 | exit 1 37 | ;; 38 | esac 39 | shift 40 | done 41 | 42 | if [[ ${SYNC_TO_HEAD} = "YES" ]]; then 43 | LLVM_SHA=origin/master 44 | CLANG_SHA=origin/master 45 | else 46 | LLVM_SHA=`cat ${LLVM_SHA_FILE}` 47 | CLANG_SHA=`cat ${CLANG_SHA_FILE}` 48 | fi 49 | 50 | fetch_or_clone() { 51 | local dir=$1 52 | local git_url=${LLVM_GIT_URL}$2.git 53 | local svn_url=${LLVM_SVN_URL}$3/trunk 54 | 55 | if [[ -d ${dir} ]]; then 56 | # Try to extend the depth if needed. 57 | if [[ `git -C ${dir} log --pretty=oneline | wc -l` -lt ${DEPTH} ]]; then 58 | git -C ${dir} fetch origin --depth ${DEPTH} 59 | else 60 | git -C ${dir} fetch origin 61 | fi 62 | if [[ -e ${SVN_FILE} ]]; then 63 | # The git repo has SVN history, keep the tags in sync. 64 | pushd ${dir} 65 | git checkout master 66 | git svn rebase -l 67 | popd 68 | fi 69 | else 70 | git -C "$(dirname ${dir})" clone --depth ${DEPTH} $git_url 71 | # The upstream repository is in Subversion, use git pull --rebase instead 72 | # of git pull to avoid generating a non-linear history in the clone: 73 | # llvm.org/docs/GettingStarted.html#git-mirror 74 | git config branch.master.rebase true 75 | if [[ "${LLVM_USERNAME}" != "NONE" ]]; then 76 | # Initialize the SVN history: 77 | # llvm.org/docs/GettingStarted.html#for-developers-to-work-with-git-svn 78 | pushd ${dir} 79 | git svn init $svn_url --username=${LLVM_USERNAME} 80 | git config svn-remote.svn.fetch :refs/remotes/origin/master 81 | git svn rebase -l 82 | popd 83 | fi 84 | fi 85 | } 86 | 87 | fetch_or_clone ${LLVM_DIR} llvm llvm 88 | fetch_or_clone ${CLANG_DIR} clang cfe 89 | 90 | git -C ${LLVM_DIR} checkout ${LLVM_SHA} 91 | git -C ${CLANG_DIR} checkout ${CLANG_SHA} 92 | 93 | # If running w/ --head, also update llvm.sha and clang.sha to the new revisions. 94 | if [[ ${SYNC_TO_HEAD} = "YES" ]]; then 95 | git -C ${LLVM_DIR} rev-parse HEAD > ${LLVM_SHA_FILE} 96 | git -C ${CLANG_DIR} rev-parse HEAD > ${CLANG_SHA_FILE} 97 | fi 98 | -------------------------------------------------------------------------------- /test/fizzbuzz.c: -------------------------------------------------------------------------------- 1 | #include "test/wasm.h" 2 | 3 | int main() { 4 | int i; 5 | for (i = 1; i < 20; ++i) { 6 | if (i % 15 == 0) 7 | puts("fizzbuzz"); 8 | else if (i % 5 == 0) 9 | puts("buzz"); 10 | else if (i % 3 == 0) 11 | puts("fizz"); 12 | else 13 | print(i); 14 | } 15 | return 0; 16 | } 17 | /** STDOUT /// 18 | 1 19 | 2 20 | fizz 21 | 4 22 | buzz 23 | fizz 24 | 7 25 | 8 26 | fizz 27 | buzz 28 | 11 29 | fizz 30 | 13 31 | 14 32 | fizzbuzz 33 | 16 34 | 17 35 | fizz 36 | 19 37 | /// STDOUT **/ 38 | -------------------------------------------------------------------------------- /test/hello.c: -------------------------------------------------------------------------------- 1 | #include "test/wasm.h" 2 | 3 | int main() { 4 | puts("Hello, world!\n"); 5 | return 0; 6 | } 7 | /** STDOUT /// 8 | Hello, world! 9 | 10 | /// STDOUT **/ 11 | -------------------------------------------------------------------------------- /test/raytrace.cc: -------------------------------------------------------------------------------- 1 | #include "test/wasm.h" 2 | 3 | #define WIDTH 256 4 | #define HEIGHT 256 5 | 6 | typedef unsigned char byte; 7 | 8 | unsigned int buffer[WIDTH * HEIGHT]; 9 | 10 | 11 | static unsigned int f2b(float value) { 12 | if (value < 0.0f) { 13 | value = 0.0f; 14 | } 15 | if(value > 1.0f) { 16 | value = 1.0f; 17 | } 18 | return (int)(value * 255); 19 | } 20 | 21 | // Convert a linear color value to a gamma-space byte. 22 | // Square root approximates gamma-correct rendering. 23 | static unsigned int l2g(float value) { 24 | return f2b(sqrtF32(value)); 25 | } 26 | 27 | static unsigned int packColor(float r, float g, float b, float a) { 28 | return f2b(a) << 24 | l2g(b) << 16 | l2g(g) << 8 | l2g(r); 29 | } 30 | 31 | class Vec3 { 32 | public: 33 | Vec3(): x(0.0f), y(0.0f), z(0.0f) {} 34 | Vec3(float x, float y, float z) : x(x), y(y), z(z) {} 35 | 36 | float x; 37 | float y; 38 | float z; 39 | 40 | void add(const Vec3& other) { 41 | x += other.x; 42 | y += other.y; 43 | z += other.z; 44 | } 45 | 46 | void scaledAdd(const Vec3& other, float scale) { 47 | x += other.x * scale; 48 | y += other.y * scale; 49 | z += other.z * scale; 50 | } 51 | 52 | void scaledAdd(const Vec3& other, const Vec3& scale) { 53 | x += other.x * scale.x; 54 | y += other.y * scale.y; 55 | z += other.z * scale.z; 56 | } 57 | 58 | void sub(const Vec3& other) { 59 | x -= other.x; 60 | y -= other.y; 61 | z -= other.z; 62 | } 63 | 64 | void scale(float other) { 65 | x *= other; 66 | y *= other; 67 | z *= other; 68 | } 69 | 70 | void scale(const Vec3& other) { 71 | x *= other.x; 72 | y *= other.y; 73 | z *= other.z; 74 | } 75 | 76 | float dot(const Vec3& other) const { 77 | return x * other.x + y * other.y + z * other.z; 78 | } 79 | 80 | float nlDot(const Vec3& other) const { 81 | float value = dot(other); 82 | if (value < 0.0f) { 83 | value = 0.0f; 84 | } 85 | return value; 86 | } 87 | 88 | float length() const { 89 | return sqrtF32(x * x + y * y + z * z); 90 | } 91 | 92 | void normalize() { 93 | scale(1.0f / length()); 94 | } 95 | 96 | void blend(const Vec3& other, float amt, Vec3* out) { 97 | float keep = 1.0f - amt; 98 | *out = Vec3(x * keep + other.x * amt, y * keep + other.y * amt, z * keep + other.z * amt); 99 | } 100 | 101 | }; 102 | 103 | // TODO return structures. 104 | static void sampleEnv(const Vec3& dir, Vec3* out) { 105 | float amt = dir.y * 0.5f + 0.5f; 106 | Vec3(0.1f, 1.0f, 0.1f).blend(Vec3(0.1f, 0.1f, 1.0f), amt, out); 107 | } 108 | 109 | class Intersection { 110 | public: 111 | Vec3 pos; 112 | Vec3 normal; 113 | }; 114 | 115 | static int intersect(const Vec3& pos, const Vec3& dir, Vec3* normal) { 116 | // The sphere. 117 | const float radius = 4.0f; 118 | // TODO movement. 119 | Vec3 center(0.0f, 0.0f, -6.0f); 120 | 121 | Vec3 offset(pos); 122 | offset.sub(center); 123 | 124 | float dot = dir.dot(offset); 125 | float partial = dot * dot + radius * radius - offset.dot(offset); 126 | if (partial >= 0.0f) { 127 | float d = -dot - sqrtF32(partial); 128 | if (d >= 0.0f) { 129 | Vec3 n(pos); 130 | n.scaledAdd(dir, d); 131 | n.sub(center); 132 | n.normalize(); 133 | *normal = n; 134 | return 1; 135 | } 136 | } 137 | return 0; 138 | } 139 | 140 | Vec3 light; 141 | Vec3 normal; 142 | Vec3 pos; 143 | Vec3 dir; 144 | 145 | static void emitImage(unsigned int* p, int width, int height) { 146 | light = Vec3(20.0f, 20.0f, 15.0f); 147 | light.normalize(); 148 | 149 | for (int j = 0; j < height; j++) { 150 | const float y = 0.5f - j / (float)height; 151 | for (int i = 0; i < width; i++) { 152 | const float x = i / (float)width - 0.5f; 153 | 154 | pos = Vec3(x, y, 0.0f); 155 | dir = Vec3(x, y, -0.5f); 156 | dir.normalize(); 157 | 158 | // Compute the half vector; 159 | Vec3 half(dir); 160 | half.scale(-1.0f); 161 | half.add(light); 162 | half.normalize(); 163 | 164 | // Light accumulation 165 | Vec3 color(0.0f, 0.0f, 0.0f); 166 | 167 | // Surface diffuse. 168 | Vec3 diffuseColor(0.7f, 0.7f, 0.7f); 169 | 170 | Vec3 env; 171 | if (intersect(pos, dir, &normal)) { 172 | float ambientScale = 0.2f; 173 | sampleEnv(normal, &env); 174 | env.scale(ambientScale); 175 | color.scaledAdd(diffuseColor, env); 176 | 177 | float diffuse = normal.nlDot(light); 178 | color.scaledAdd(diffuseColor, diffuse); 179 | 180 | float specular = normal.nlDot(half); 181 | // Take it to the 64th power, manually. 182 | specular = specular * specular; 183 | specular = specular * specular; 184 | specular = specular * specular; 185 | specular = specular * specular; 186 | specular = specular * specular; 187 | specular = specular * specular; 188 | 189 | specular = specular * 0.6f; 190 | 191 | color.scaledAdd(Vec3(1.0f, 1.0f, 1.0f), specular); 192 | } else { 193 | sampleEnv(dir, &env); 194 | color.add(env); 195 | } 196 | unsigned int pixel = packColor(color.x, color.y, color.z, 255); 197 | *p++ = pixel; 198 | } 199 | } 200 | } 201 | 202 | int main() { 203 | emitImage(buffer, WIDTH, HEIGHT); 204 | flipBuffer((void*)buffer, WIDTH, HEIGHT); 205 | return 0; 206 | } 207 | -------------------------------------------------------------------------------- /test/run-tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | import difflib 4 | import fnmatch 5 | import multiprocessing 6 | import os 7 | import Queue 8 | import re 9 | import shlex 10 | import shutil 11 | import subprocess 12 | import sys 13 | import tempfile 14 | import threading 15 | import time 16 | 17 | 18 | SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 19 | REPO_ROOT_DIR = os.path.dirname(SCRIPT_DIR) 20 | DEFAULT_EXE = os.path.join(REPO_ROOT_DIR, 'scripts', 'run.py') 21 | DEFAULT_TIMEOUT = 10 # seconds 22 | SLOW_TIMEOUT_MULTIPLIER = 2 23 | 24 | 25 | class Error(Exception): 26 | pass 27 | 28 | 29 | def AsList(value): 30 | if value is None: 31 | return [] 32 | elif type(value) is list: 33 | return value 34 | else: 35 | return [value] 36 | 37 | 38 | def Indent(s, spaces): 39 | return ''.join(' '*spaces + l for l in s.splitlines(1)) 40 | 41 | 42 | def DiffLines(expected, actual): 43 | expected_lines = expected.splitlines(1) 44 | actual_lines = actual.splitlines(1) 45 | return list(difflib.unified_diff(expected_lines, actual_lines, 46 | fromfile='expected', tofile='actual')) 47 | 48 | 49 | def FindTestFiles(directory, ext, filter_pattern_re): 50 | tests = [] 51 | for root, dirs, files in os.walk(directory): 52 | for f in files: 53 | path = os.path.join(root, f) 54 | if os.path.splitext(f)[1] == ext: 55 | tests.append(os.path.relpath(path, SCRIPT_DIR)) 56 | tests.sort() 57 | return [test for test in tests if re.match(filter_pattern_re, test)] 58 | 59 | 60 | class TestInfo(object): 61 | def __init__(self): 62 | self.name = '' 63 | self.filename = '' 64 | self.header = [] 65 | self.input_file = '' 66 | self.input_ = [] 67 | self.expected_stdout = '' 68 | self.expected_stderr = '' 69 | self.exe = None 70 | self.flags = [] 71 | self.expected_error = 0 72 | self.slow = False 73 | self.skip = False 74 | 75 | def Parse(self, filename): 76 | self.name = filename 77 | self.filename = os.path.join(SCRIPT_DIR, filename) 78 | 79 | with open(self.filename) as f: 80 | seen_keys = set() 81 | state = 'header' 82 | empty = True 83 | header_lines = [] 84 | input_lines = [] 85 | stdout_lines = [] 86 | stderr_lines = [] 87 | for line in f.readlines(): 88 | empty = False 89 | m = re.match(r'\s*/\*\* (STDOUT|STDERR) ///$', line) 90 | if m: 91 | directive = m.group(1) 92 | if directive == 'STDERR': 93 | state = 'stderr' 94 | continue 95 | elif directive == 'STDOUT': 96 | state = 'stdout' 97 | continue 98 | else: 99 | m = re.match(r'\s*///(.*)$', line) 100 | if m: 101 | directive = m.group(1).strip() 102 | if state == 'header': 103 | key, value = directive.split(':', 1) 104 | key = key.strip() 105 | value = value.strip() 106 | if key in seen_keys: 107 | raise Error('%s already set' % key) 108 | seen_keys.add(key) 109 | if key == 'EXE': 110 | self.exe = value 111 | elif key == 'STDIN_FILE': 112 | self.input_file = value 113 | elif key == 'FLAGS': 114 | self.flags = shlex.split(value) 115 | elif key == 'ERROR': 116 | self.expected_error = int(value) 117 | elif key == 'SLOW': 118 | self.slow = True 119 | elif key == 'SKIP': 120 | self.skip = True 121 | else: 122 | raise Error('Unknown directive: %s' % key) 123 | elif state in ('stdout', 'stderr'): 124 | if not re.match(r'%s \*\*/$' % state.upper(), directive): 125 | raise Error('Bad directive in %s block: %s' % ( 126 | state, directive)) 127 | state = 'none' 128 | else: 129 | raise Error('Unexpected directive: %s' % directive) 130 | elif state == 'header': 131 | state = 'input' 132 | 133 | if state == 'header': 134 | header_lines.append(line) 135 | if state == 'input': 136 | if self.input_file: 137 | raise Error('Can\'t have input_file and input') 138 | input_lines.append(line) 139 | elif state == 'stderr': 140 | stderr_lines.append(line) 141 | elif state == 'stdout': 142 | stdout_lines.append(line) 143 | if empty: 144 | raise Error('empty test file') 145 | 146 | self.header = ''.join(header_lines) 147 | self.input_ = ''.join(input_lines) 148 | self.expected_stdout = ''.join(stdout_lines) 149 | self.expected_stderr = ''.join(stderr_lines) 150 | 151 | def GetExecutable(self, override_exe): 152 | # If the test overrides the executable, we assume that it knows best, so we 153 | # don't allow the commandline to override it. 154 | if self.exe: 155 | exe = os.path.join(REPO_ROOT_DIR, self.exe) 156 | elif override_exe: 157 | exe = override_exe 158 | else: 159 | exe = DEFAULT_EXE 160 | return os.path.abspath(os.path.join(SCRIPT_DIR, exe)) 161 | 162 | def GetCommand(self, filename, override_exe=None): 163 | cmd = [self.GetExecutable(override_exe)] 164 | cmd += self.flags 165 | cmd += [filename] 166 | return cmd 167 | 168 | def Run(self, timeout, temp_dir, override_exe=None): 169 | if self.input_file: 170 | file_path = os.path.join(temp_dir, self.input_file) 171 | else: 172 | file_path = os.path.join(temp_dir, self.name) 173 | file_dir = os.path.dirname(file_path) 174 | try: 175 | os.makedirs(file_dir) 176 | except OSError as e: 177 | if not os.path.isdir(file_dir): 178 | raise 179 | 180 | if self.slow: 181 | timeout *= SLOW_TIMEOUT_MULTIPLIER 182 | 183 | process = None 184 | # Cheesy way to be able to set is_timeout from inside KillProcess 185 | is_timeout = [False] 186 | def KillProcess(timeout=True): 187 | if process: 188 | try: 189 | os.killpg(os.getpgid(process.pid), 15) 190 | except OSError: 191 | pass 192 | is_timeout[0] = timeout 193 | 194 | file_ = open(file_path, 'w') 195 | try: 196 | # add an empty line for each header line so the line numbers match 197 | if self.input_file: 198 | with open(self.input_file) as input_file: 199 | file_.write(input_file.read()) 200 | else: 201 | file_.write('\n' * self.header.count('\n')) 202 | file_.write(self.input_) 203 | file_.flush() 204 | # make filenames relative to temp_dir so the autogenerated name is not 205 | # included in the error output. 206 | rel_file_path = os.path.relpath(file_.name, temp_dir) 207 | cmd = self.GetCommand(rel_file_path, override_exe) 208 | try: 209 | start_time = time.time() 210 | # http://stackoverflow.com/a/10012262: subprocess with a timeout 211 | # http://stackoverflow.com/a/22582602: kill subprocess and children 212 | process = subprocess.Popen(cmd, cwd=temp_dir, stdout=subprocess.PIPE, 213 | stderr=subprocess.PIPE, 214 | preexec_fn=os.setsid) 215 | timer = threading.Timer(timeout, KillProcess) 216 | try: 217 | timer.start() 218 | stdout, stderr = process.communicate() 219 | finally: 220 | timer.cancel() 221 | if is_timeout[0]: 222 | raise Error('TIMEOUT\nSTDOUT:\n%s\nSTDERR:\n%s\n' % (stdout, stderr)) 223 | duration = time.time() - start_time 224 | except OSError as e: 225 | raise Error(str(e)) 226 | finally: 227 | KillProcess(False) 228 | finally: 229 | file_.close() 230 | 231 | return stdout, stderr, process.returncode, duration 232 | 233 | def Rebase(self, stdout, stderr): 234 | with open(self.filename, 'w') as f: 235 | f.write(self.header) 236 | f.write(self.input_) 237 | if stderr: 238 | f.write('/** STDERR ///\n') 239 | f.write(stderr) 240 | f.write('/// STDERR **/\n') 241 | if stdout: 242 | f.write('/** STDOUT ///\n') 243 | f.write(stdout) 244 | f.write('/// STDOUT **/\n') 245 | 246 | def Diff(self, stdout, stderr): 247 | msg = '' 248 | if self.expected_stderr != stderr: 249 | diff_lines = DiffLines(self.expected_stderr, stderr) 250 | msg += 'STDERR MISMATCH:\n' + ''.join(diff_lines) 251 | 252 | if self.expected_stdout != stdout: 253 | diff_lines = DiffLines(self.expected_stdout, stdout) 254 | msg += 'STDOUT MISMATCH:\n' + ''.join(diff_lines) 255 | 256 | if msg: 257 | raise Error(msg) 258 | 259 | 260 | class Status(object): 261 | def __init__(self, verbose): 262 | self.verbose = verbose 263 | self.start_time = None 264 | self.last_length = 0 265 | self.last_finished = None 266 | self.passed = 0 267 | self.failed = 0 268 | self.total = 0 269 | self.failed_tests = [] 270 | 271 | def Start(self, total): 272 | self.total = total 273 | self.start_time = time.time() 274 | 275 | def Passed(self, info, duration): 276 | self.passed += 1 277 | if self.verbose: 278 | sys.stderr.write('+ %s (%.3fs)\n' % (info.name, duration)) 279 | else: 280 | self.Clear() 281 | self._PrintShortStatus(info) 282 | sys.stderr.flush() 283 | 284 | def Failed(self, info, error_msg): 285 | self.failed += 1 286 | self.failed_tests.append(info) 287 | self.Clear() 288 | sys.stderr.write('- %s\n%s\n' % (info.name, Indent(error_msg, 2))) 289 | 290 | def Skipped(self, info): 291 | if self.verbose: 292 | sys.stderr.write('. %s (skipped)\n' % info.name) 293 | 294 | def Timeout(self): 295 | self._PrintShortStatus(self.last_finished) 296 | 297 | def Print(self): 298 | self._PrintShortStatus(None) 299 | sys.stderr.write('\n') 300 | 301 | def _PrintShortStatus(self, info): 302 | total_duration = time.time() - self.start_time 303 | name = info.name if info else '' 304 | if self.total: 305 | percent = 100 * (self.passed + self.failed) / self.total 306 | else: 307 | percent = 100 308 | status = '[+%d|-%d|%%%d] (%.2fs) %s\r' % (self.passed, self.failed, 309 | percent, total_duration, name) 310 | self.last_length = len(status) 311 | self.last_finished = info 312 | sys.stderr.write(status) 313 | 314 | def Clear(self): 315 | if not self.verbose: 316 | sys.stderr.write('%s\r' % (' ' * self.last_length)) 317 | 318 | 319 | def GetAllTestInfo(test_names, status): 320 | infos = [] 321 | for test_name in test_names: 322 | info = TestInfo() 323 | try: 324 | info.Parse(test_name) 325 | infos.append(info) 326 | except Error as e: 327 | status.Failed(info, str(e)) 328 | 329 | return infos 330 | 331 | 332 | def main(args): 333 | parser = argparse.ArgumentParser() 334 | parser.add_argument('-e', '--executable', help='override executable.') 335 | parser.add_argument('-v', '--verbose', help='print more diagnotic messages.', 336 | action='store_true') 337 | parser.add_argument('-l', '--list', help='list all tests.', 338 | action='store_true') 339 | parser.add_argument('--list-exes', 340 | help='list all executables needed for the tests.', 341 | action='store_true') 342 | parser.add_argument('-r', '--rebase', 343 | help='rebase a test to its current output.', 344 | action='store_true') 345 | parser.add_argument('-j', '--jobs', help='number of jobs to use to run tests', 346 | type=int, default=multiprocessing.cpu_count()) 347 | parser.add_argument('-t', '--timeout', type=float, default=DEFAULT_TIMEOUT, 348 | help='per test timeout in seconds') 349 | parser.add_argument('patterns', metavar='pattern', nargs='*', 350 | help='test patterns.') 351 | options = parser.parse_args(args) 352 | 353 | if options.patterns: 354 | pattern_re = '|'.join(fnmatch.translate('*%s*' % p) 355 | for p in options.patterns) 356 | else: 357 | pattern_re = '.*' 358 | 359 | test_names = FindTestFiles(SCRIPT_DIR, '.c', pattern_re) 360 | if options.list: 361 | for test_name in test_names: 362 | print test_name 363 | return 0 364 | if not test_names: 365 | print 'no tests match that filter' 366 | return 1 367 | 368 | if options.executable: 369 | if not os.path.exists(options.executable): 370 | parser.error('executable %s does not exist' % options.executable) 371 | options.executable = os.path.abspath(options.executable) 372 | 373 | isatty = os.isatty(1) 374 | 375 | status = Status(options.verbose) 376 | infos = GetAllTestInfo(test_names, status) 377 | 378 | if options.list_exes: 379 | exes = set([info.exe for info in infos]) 380 | if None in exes: 381 | exes.remove(None) 382 | exes.add(os.path.relpath(DEFAULT_EXE, os.getcwd())) 383 | print '\n'.join(exes) 384 | return 0 385 | 386 | inq = multiprocessing.Queue() 387 | test_count = 0 388 | for info in infos: 389 | if info.skip: 390 | status.Skipped(info) 391 | continue 392 | inq.put(info) 393 | test_count += 1 394 | 395 | outq = multiprocessing.Queue() 396 | num_proc = options.jobs 397 | processes = [] 398 | status.Start(test_count) 399 | 400 | def Worker(i, options, inq, outq): 401 | try: 402 | while True: 403 | try: 404 | info = inq.get(False) 405 | try: 406 | out = info.Run(options.timeout, temp_dir, options.executable) 407 | except Exception as e: 408 | outq.put((info, e)) 409 | continue 410 | outq.put((info, out)) 411 | except Queue.Empty: 412 | # Seems this can be fired even when the queue isn't actually empty. 413 | # Double-check, via inq.empty() 414 | if inq.empty(): 415 | break 416 | except KeyboardInterrupt: 417 | pass 418 | 419 | temp_dir = tempfile.mkdtemp(prefix='wasm-e2e-') 420 | try: 421 | for i, p in enumerate(range(num_proc)): 422 | proc = multiprocessing.Process(target=Worker, 423 | args=(i, options, inq, outq)) 424 | processes.append(proc) 425 | proc.start() 426 | 427 | finished_tests = 0 428 | while finished_tests < test_count: 429 | try: 430 | info, result = outq.get(True, 0.01) 431 | except Queue.Empty: 432 | status.Timeout() 433 | continue 434 | 435 | finished_tests += 1 436 | try: 437 | if isinstance(result, Exception): 438 | raise result 439 | 440 | stdout, stderr, returncode, duration = result 441 | if returncode != info.expected_error: 442 | # This test has already failed, but diff it anyway. 443 | msg = 'expected error code %d, got %d.' % (info.expected_error, 444 | returncode) 445 | try: 446 | info.Diff(stdout, stderr) 447 | except Error as e: 448 | msg += '\n' + str(e) 449 | raise Error(msg) 450 | else: 451 | if options.rebase: 452 | info.Rebase(stdout, stderr) 453 | else: 454 | info.Diff(stdout, stderr) 455 | status.Passed(info, duration) 456 | except Exception as e: 457 | status.Failed(info, str(e)) 458 | except KeyboardInterrupt: 459 | for proc in processes: 460 | proc.join() 461 | finally: 462 | for proc in processes: 463 | proc.terminate() 464 | proc.join() 465 | shutil.rmtree(temp_dir) 466 | 467 | status.Clear() 468 | 469 | ret = 0 470 | 471 | if status.failed: 472 | sys.stderr.write('**** FAILED %s\n' % ('*' * (80 - 14))) 473 | for info in status.failed_tests: 474 | sys.stderr.write('- %s\n' % info.name) 475 | ret = 1 476 | 477 | status.Print() 478 | return ret 479 | 480 | 481 | if __name__ == '__main__': 482 | try: 483 | sys.exit(main(sys.argv[1:])) 484 | except Error as e: 485 | sys.stderr.write(str(e) + '\n') 486 | sys.exit(1) 487 | -------------------------------------------------------------------------------- /test/wasm.cc: -------------------------------------------------------------------------------- 1 | #include "test/wasm.h" 2 | 3 | #include 4 | 5 | extern "C" void print(int value) { 6 | printf("%d\n", value); 7 | } 8 | 9 | static void writeByte(unsigned char value) { 10 | printf("%3d", value); 11 | } 12 | 13 | extern "C" void flipBuffer(const void* vp, int w, int h) { 14 | const unsigned char* p = (const unsigned char*)vp; 15 | printf("P3\n"); 16 | printf("%d %d\n", w, h); 17 | printf("255\n"); 18 | printf("# This is a PPM file, redirect stdout to view.\n"); 19 | for (int j = 0; j < h; j++) { 20 | for (int i = 0; i < w; i++) { 21 | if (i != 0) printf(" "); 22 | writeByte(*p++); 23 | printf(" "); 24 | writeByte(*p++); 25 | printf(" "); 26 | writeByte(*p++); 27 | p++; 28 | } 29 | printf("\n"); 30 | printf("\n"); 31 | } 32 | printf("# This is a PPM file, redirect stdout to view.\n"); 33 | } 34 | -------------------------------------------------------------------------------- /test/wasm.h: -------------------------------------------------------------------------------- 1 | #ifdef __cplusplus 2 | extern "C" { 3 | #endif 4 | 5 | int puts(const char*); 6 | void print(int); 7 | float sqrtF32(float) asm("llvm.sqrt.f32"); 8 | void flipBuffer(const void*, int, int); 9 | 10 | #ifdef __cplusplus 11 | } 12 | #endif 13 | -------------------------------------------------------------------------------- /test/wasm.js: -------------------------------------------------------------------------------- 1 | var m; 2 | var mem8; 3 | 4 | function puts(p) { 5 | var s = ''; 6 | while (mem8[p] != 0) 7 | s += String.fromCharCode(mem8[p++]); 8 | print(s); 9 | } 10 | 11 | function writeByte(b) { 12 | var s = b.toString(); 13 | while (s.length < 3) { 14 | s = " " + s; 15 | } 16 | write(s); 17 | } 18 | 19 | function flipBuffer(p, w, h) { 20 | print("P3"); 21 | print(w + " " + h); 22 | print("255"); 23 | print("# This is a PPM file, redirect stdout to view."); 24 | for (var j = 0; j < h; j++) { 25 | for (var i = 0; i < w; i++) { 26 | if (i != 0) write(" "); 27 | writeByte(mem8[p++]); 28 | write(" "); 29 | writeByte(mem8[p++]); 30 | write(" "); 31 | writeByte(mem8[p++]); 32 | p++; 33 | } 34 | print(); 35 | print(); 36 | } 37 | print("# This is a PPM file, redirect stdout to view."); 38 | } 39 | 40 | var ffi = { 41 | print: print, 42 | puts: puts, 43 | flipBuffer: flipBuffer 44 | }; 45 | 46 | m = _WASMEXP_.instantiateModule(readbuffer(arguments[0]), ffi); 47 | mem8 = new Uint8Array(m.memory); 48 | m.main(); 49 | --------------------------------------------------------------------------------