├── .gitignore ├── AUTHORS ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── astutil.js ├── base.js ├── compilerutil.js ├── compiletests.js ├── d8_common.js ├── d8_main.js ├── d8_system.wasm ├── d8_tests.js ├── demos ├── draw.wasm ├── raytrace.wasm ├── simple.wasm └── threading.wasm ├── index.html ├── js ├── ast.js └── backend.js ├── main.js ├── system.js ├── system.wasm ├── tests.html ├── tests.js ├── third_party ├── peg-0.8.0.js ├── qunit-1.18.0.css ├── qunit-1.18.0.js └── require.js ├── tools └── httpd.py ├── v8 └── backend.js ├── wasm.pegjs ├── wasm ├── ast.js ├── dce.js ├── desugar.js ├── opinfo.js ├── semantic.js ├── tojs.js ├── traverse.js └── typeinfo.js └── wassembler.css /.gitignore: -------------------------------------------------------------------------------- 1 | # python 2 | *.pyc 3 | *.pyo 4 | 5 | # emacs 6 | *~ 7 | \#*\# 8 | .\#* 9 | 10 | out/ 11 | 12 | # OSX 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /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 | Nick Bray 13 | -------------------------------------------------------------------------------- /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 [yyyy] [name of copyright owner] 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 | # WASM Assembler 2 | 3 | This is a quick and dirty prototype for turning a textual language into JS and/or bytecode. 4 | 5 | ## Running in the Browser 6 | 7 | `./tools/httpd.py` will serve the current directory at http://localhost:7777/ 8 | 9 | ## Running on the Command Line 10 | 11 | [Get V8.](https://code.google.com/p/v8-wiki/wiki/UsingGit) 12 | 13 | [Patch V8.](https://github.com/WebAssembly/v8-native-prototype) 14 | 15 | `path/to/patched/d8 d8_main.js -- demos/simple.wasm` 16 | 17 | ## Design 18 | 19 | ### Goals 20 | 21 | * Human-understandable text can be recovered from bytecode. 22 | * A complete program can be specified in a single file. 23 | 24 | ### Non-goals 25 | 26 | * Hand writing large amounts of textual WASM. 27 | * Inline assembly. Textual WASM can be compiled and linked into a C program, instead. 28 | 29 | ### Compiler Pipeline 30 | 31 | parse => semantic pass => desugar => backend 32 | 33 | * parse: translate the textual format into an AST. 34 | * semantic pass: resolve names and check types. 35 | * desugar: convert user-friendly “sugar” into concepts that can be directly represented in the bytecode. For example converting i8 types into i32 types. 36 | * backend: generate JS or bytecode. 37 | 38 | ## Design Notes 39 | 40 | ### Indirect Function Calls 41 | 42 | Indirect function calls are strange because there are no pointer types. The callsite must declare the function signature. 43 | 44 | TODO: how is the value of a function pointer determined? It should likely be symbolic in the bytecode, to maximize implementation flexibility. On the other hand, this means function pointers could have implementation-defined values and there would need to be some sort of "relocation" information for memory, even when statically linking. On the other hand, dynamic linking would be complicated if function pointers had fixed values. There are many design tradeoffs, here. 45 | 46 | TODO: should JS be able to invoke function pointers? How? 47 | 48 | TODO: what datatype is a function pointer? What if memory pointers are 64 bit? 49 | 50 | ### Unsigned Types 51 | 52 | There is currently no distinction between signed and unsigned types. Sign-sensitive binary operations (div/mod/compare) will require two textual variants (TBD). 53 | 54 | It may be possible to make signedness distinctions in the FFI boundary, otherwise JS will need to `arg>>>0` every value it wishes to be unsigned. 55 | 56 | Adding unsigned types would be complicated because LLVM does not make a distinction between signed and unsigned types in its IR. 57 | 58 | ### Small Integer Types 59 | 60 | There is currently no direct support for i8 and i16 types. 61 | 62 | TODO: what's the size and performance cost of emulating i8 operations in terms of i32? 63 | 64 | TODO: what does emulating i8 and i16 types buy if we already need to support i32, i64, f16?, f32, f64, and several SIMD types? 65 | 66 | TODO: does this cause an impediance mismatch with small integer SIMD types? 67 | 68 | ### Hiding Implementation Details 69 | 70 | This prototype does not expose the heap to JS as an array buffer - instead it uses methods on the WASM module instance, such as `_copyOut`, to do bulk data transfer. The upside is that it is easier for the WASM heap to behave as if it were not an array buffer - unmapped memory pages, for example. The downside is that fine-grained interactions with the heap, such as traversing pointer-based datastructures, are more expensive from JS. This interface also works best when data sizes are known apriori - JS should explicitly be given the length of null terminated strings, rather than calculating the string's length itself, for example. 71 | 72 | TODO: is this the correct tradeoff? 73 | 74 | ### FFI Binding 75 | 76 | Generally, non-trivial FFI calls will need to know which WASM module instance they are being invoked on behalf of. FFI calls will need to copy data in and out of the instance's memory space, keep a lookup table of JS objects assosiated with a particular instance, etc. Historically, asm.js bound FFI functions to an instance using closures. Threaded applications are more complicated, however, because FFI functions must be bound to the instance for each thread / JS isolate. How the FFI functions get loaded into each isolate and how they are bound TBD. 77 | 78 | Currently this prototype binds the WASM module instance to "this" for all FFI calls. 79 | 80 | 81 | ## Things to Investigate 82 | 83 | * FFI / system interface design. 84 | * Shared memory and threads. 85 | * Goto / CFG support. 86 | * Dynamic linking. 87 | * SIMD. 88 | * 64-bit pointers. 89 | -------------------------------------------------------------------------------- /astutil.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | var makeASTBuilder = function(config) { 3 | var exports = {}; 4 | for (var i in config) { 5 | var decl = config[i]; 6 | if (decl.name in exports) { 7 | throw Error("Attempted to redefine " + decl.name); 8 | } 9 | 10 | decl.fieldIndex = {}; 11 | for (var i in decl.fields) { 12 | var field = decl.fields[i]; 13 | if (field.name in decl.fieldIndex) { 14 | throw Error("Attempted to redefine " + decl.name + "." + field.name); 15 | } 16 | decl.fieldIndex[field.name] = field; 17 | } 18 | 19 | exports[decl.name] = (function(decl) { 20 | return function(args){ 21 | for (var name in args) { 22 | if (!(name in decl.fieldIndex)) { 23 | throw Error("Unknown AST field: " + decl.name + "." + name); 24 | } 25 | } 26 | var node = {type: decl.name}; 27 | for (var i in decl.fields) { 28 | var field = decl.fields[i]; 29 | var value = args[field.name]; 30 | if (value === undefined) { 31 | value = field.defaultValue; 32 | } 33 | if (value === undefined) { 34 | throw Error("Undefined AST field: " + decl.name + "." + field.name); 35 | } 36 | node[field.name] = value; 37 | } 38 | return node; 39 | } 40 | })(decl); 41 | } 42 | return exports; 43 | }; 44 | 45 | var index = function(keynames, table, rowfilter, rowrewrite) { 46 | var finalkeyname = keynames.pop(); 47 | var out = {}; 48 | nextrow: for (var i = 0; i < table.length; i++) { 49 | var row = table[i]; 50 | if (rowfilter && !rowfilter(row)) continue nextrow; 51 | var current = out; 52 | for (var j = 0; j < keynames.length; j++) { 53 | var key = row[keynames[j]]; 54 | if (key === undefined) throw Error("bad key " + keynames[j] + "?"); 55 | if (key === null) continue nextrow; 56 | if (!(key in current)) { 57 | current[key] = {}; 58 | } 59 | current = current[key]; 60 | } 61 | var key = row[finalkeyname]; 62 | if (key === undefined) throw Error("bad key " + finalkeyname + "?"); 63 | if (key === null) continue nextrow; 64 | if (key in current) throw Error("tried to redefine " + key + " @" + i); 65 | if (rowrewrite) { 66 | row = rowrewrite(row); 67 | } 68 | current[key] = row; 69 | } 70 | return out; 71 | }; 72 | 73 | return { 74 | makeASTBuilder: makeASTBuilder, 75 | index: index, 76 | }; 77 | }); 78 | -------------------------------------------------------------------------------- /base.js: -------------------------------------------------------------------------------- 1 | define( 2 | ["wasm/ast", "wasm/semantic", "wasm/dce", "wasm/tojs", "js/backend"], 3 | function(wast, semantic, dce, tojs, js_backend) { 4 | 5 | // Promisified XMLHttpRequest. 6 | var getURL = function(url) { 7 | return new Promise(function(resolve, reject) { 8 | var req = new XMLHttpRequest(); 9 | req.onload = function() { 10 | if (req.status == 200) { 11 | resolve(req.response); 12 | } else { 13 | reject(Error("getURL " + url + " - reponse " + req.status)); 14 | } 15 | }; 16 | req.onerror = function() { 17 | reject(Error("getURL " + url + " - network error")); 18 | }; 19 | req.open("get", url); 20 | req.send(); 21 | }); 22 | }; 23 | 24 | var createParser = function(grammar, status) { 25 | var parser = null; 26 | try { 27 | parser = PEG.buildParser(grammar); 28 | } catch (e) { 29 | status.error(e.message, e); 30 | } 31 | return parser; 32 | }; 33 | 34 | // Scrape a parsed module the the names of the externs it declares. 35 | // This is used to infer which externs are provided by the system. 36 | var getExternNames = function(module) { 37 | var names = []; 38 | for (var i = 0; i < module.decls.length; i++) { 39 | var decl = module.decls[i]; 40 | if (decl.type == "Extern") { 41 | names.push(decl.name.text); 42 | } 43 | } 44 | return names; 45 | }; 46 | 47 | var parse = function(text, parser, status) { 48 | var result = null; 49 | try { 50 | result = parser.parse(text, {wast: wast}); 51 | } catch (e) { 52 | status.error(e.message, e); 53 | } 54 | return result; 55 | }; 56 | 57 | var frontend = function(systemWASMSrc, filename, text, parser, status, reportAST) { 58 | status.setFilename("system.wasm"); 59 | var system = parse(systemWASMSrc, parser, status); 60 | if (status.num_errors > 0) { 61 | return null; 62 | } 63 | 64 | var system_externs = getExternNames(system); 65 | 66 | status.setFilename(filename); 67 | var parsed = parse(text, parser, status); 68 | if (status.num_errors > 0) { 69 | return null; 70 | } 71 | 72 | parsed = wast.ParsedModule({ 73 | decls: system.decls.concat(parsed.decls), 74 | }); 75 | 76 | if (reportAST) reportAST(parsed); 77 | 78 | var module = semantic.processModule(parsed, status); 79 | if (status.num_errors > 0) { 80 | return null; 81 | } 82 | status.setFilename(""); 83 | 84 | if (reportAST) reportAST(module); 85 | 86 | module = dce.process(module); 87 | 88 | if (reportAST) reportAST(module); 89 | 90 | // HACK 91 | module.system_externs = system_externs; 92 | 93 | return module; 94 | }; 95 | 96 | var astToJSSrc = function(module, systemJSSrc, config) { 97 | var translated = tojs.translate(module, systemJSSrc, config.use_shared_memory); 98 | var src = js_backend.generateExpr(translated) + "()"; 99 | return src; 100 | }; 101 | 102 | // Eval JS src, but convert any errors into a compiler error. 103 | var evalJSSrc = function(src, status) { 104 | // Compile the module. (Does not bind.) 105 | try { 106 | return eval(src); 107 | } catch (e) { 108 | status.error("JS compile - " + e.message); 109 | return null; 110 | } 111 | }; 112 | 113 | var astToCompiledJS = function(module, systemJSSrc, config, status, reportSrc) { 114 | var src = astToJSSrc(module, systemJSSrc, config); 115 | if (reportSrc) reportSrc(src); 116 | return evalJSSrc(src, status); 117 | } 118 | 119 | // Display errors and keep track if any error have occured. 120 | var Status = function(logger) { 121 | this.logger = logger; 122 | this.filename = ""; 123 | this.num_errors = 0; 124 | }; 125 | 126 | Status.prototype.setFilename = function(filename) { 127 | this.filename = filename 128 | }; 129 | 130 | Status.prototype.error = function(message, pos) { 131 | var prefix = "ERROR"; 132 | if (this.filename) { 133 | prefix += " " + this.filename; 134 | } 135 | if (pos != undefined) { 136 | prefix += " " + pos.line + ":" + pos.column; 137 | } 138 | prefix += ": "; 139 | this.logger(prefix + message); 140 | this.num_errors += 1; 141 | }; 142 | 143 | return { 144 | getURL: getURL, 145 | createParser: createParser, 146 | frontend: frontend, 147 | astToJSSrc: astToJSSrc, 148 | evalJSSrc: evalJSSrc, 149 | astToCompiledJS: astToCompiledJS, 150 | Status: Status, 151 | }; 152 | }); 153 | -------------------------------------------------------------------------------- /compilerutil.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | var CodeWriter = function() { 3 | this.margins = []; 4 | this.margin = ""; 5 | this.output = ""; 6 | this.dirty = false; 7 | }; 8 | 9 | CodeWriter.prototype.getOutput = function() { 10 | return this.output; 11 | }; 12 | 13 | CodeWriter.prototype.out = function(text) { 14 | if (typeof text != "string") { 15 | throw Error(text); 16 | } 17 | if (!this.dirty) { 18 | this.output += this.margin; 19 | this.dirty = true; 20 | } 21 | this.output += text; 22 | return this; 23 | }; 24 | 25 | CodeWriter.prototype.eol = function() { 26 | this.output += "\n"; 27 | this.dirty = false; 28 | return this; 29 | }; 30 | 31 | CodeWriter.prototype.indent = function() { 32 | this.margins.push(this.margin); 33 | this.margin += " "; 34 | return this; 35 | }; 36 | 37 | CodeWriter.prototype.dedent = function() { 38 | this.margin = this.margins.pop(); 39 | return this; 40 | }; 41 | 42 | var checkRange = function(value, min, max) { 43 | if (typeof value != "number" || value < min || value > max) throw Error(value + "?"); 44 | } 45 | 46 | var BinaryWriter = function() { 47 | this.data = new DataView(new ArrayBuffer(1024)); 48 | this.pos = 0; 49 | }; 50 | 51 | BinaryWriter.prototype.expect = function(size) { 52 | var n = this.data.buffer.byteLength; 53 | if (this.pos + size <= n) return; 54 | while (this.pos + size > n) { 55 | n *= 2; 56 | } 57 | // Reallocate 58 | var replace = new DataView(new ArrayBuffer(n)); 59 | // Copy 60 | new Uint8Array(replace.buffer).set(new Uint8Array(this.data.buffer)); 61 | // Replace 62 | this.data = replace; 63 | }; 64 | 65 | BinaryWriter.prototype.getOutput = function() { 66 | return this.data.buffer.slice(0, this.pos); 67 | }; 68 | 69 | BinaryWriter.prototype.i8 = function(data) { 70 | checkRange(data, -0x80, 0x7f); 71 | this.expect(1); 72 | this.data.setInt8(this.pos, data, true); 73 | this.pos += 1; 74 | }; 75 | 76 | BinaryWriter.prototype.u8 = function(data) { 77 | checkRange(data, 0x0, 0xff); 78 | this.expect(1); 79 | this.data.setUint8(this.pos, data, true); 80 | this.pos += 1; 81 | }; 82 | 83 | BinaryWriter.prototype.i16 = function(data) { 84 | checkRange(data, -0x8000, 0x7fff); 85 | this.expect(2); 86 | this.data.setInt16(this.pos, data, true); 87 | this.pos += 2; 88 | }; 89 | 90 | BinaryWriter.prototype.u16 = function(data) { 91 | checkRange(data, 0x0, 0xffff); 92 | this.expect(2); 93 | this.data.setUint16(this.pos, data, true); 94 | this.pos += 2; 95 | }; 96 | 97 | BinaryWriter.prototype.i32 = function(data) { 98 | checkRange(data, -0x80000000, 0x7fffffff); 99 | this.expect(4); 100 | this.data.setInt32(this.pos, data, true); 101 | this.pos += 4; 102 | }; 103 | 104 | BinaryWriter.prototype.u32 = function(data) { 105 | checkRange(data, 0x0, 0xffffffff); 106 | this.expect(4); 107 | this.data.setUint32(this.pos, data, true); 108 | this.pos += 4; 109 | }; 110 | 111 | BinaryWriter.prototype.i64 = function(data) { 112 | checkRange(data, -0x8000000000000, 0x7ffffffffffff); // Approximate i64 113 | this.expect(8); 114 | this.data.setInt32(this.pos, data|0, true); 115 | this.data.setInt32(this.pos + 4, (data/0x100000000)|0, true); 116 | this.pos += 8; 117 | }; 118 | 119 | BinaryWriter.prototype.u64 = function(data) { 120 | checkRange(data, 0x0, 0xfffffffffffff); // Approximate i64 121 | this.expect(8); 122 | this.data.setInt32(this.pos, data|0, true); 123 | this.data.setInt32(this.pos + 4, (data/0x100000000)|0, true); 124 | this.pos += 8; 125 | }; 126 | 127 | BinaryWriter.prototype.f32 = function(data) { 128 | this.expect(4); 129 | this.data.setFloat32(this.pos, data, true); 130 | this.pos += 4; 131 | }; 132 | 133 | BinaryWriter.prototype.f64 = function(data) { 134 | this.expect(8); 135 | this.data.setFloat64(this.pos, data, true); 136 | this.pos += 8; 137 | }; 138 | 139 | BinaryWriter.prototype.zeros = function(count) { 140 | checkRange(count, 0x0, 0xffffffff); 141 | this.expect(count); 142 | this.pos += count; 143 | }; 144 | 145 | BinaryWriter.prototype.allocU32 = function() { 146 | this.expect(4); 147 | var temp = this.pos; 148 | this.pos += 4; 149 | return temp; 150 | }; 151 | 152 | BinaryWriter.prototype.patchU32 = function(pos, data) { 153 | checkRange(pos, 0x0, 0xffffffff); 154 | checkRange(data, 0x0, 0xffffffff); 155 | this.data.setUint32(pos, data, true); 156 | return this; 157 | }; 158 | 159 | BinaryWriter.prototype.utf8 = function(s) { 160 | var size = 0; 161 | for (var i = 0; i < s.length; i++) { 162 | var c = s.charCodeAt(i); 163 | if (c > 127) { 164 | // HACK real no unicode support, yet. 165 | throw c; 166 | } 167 | this.u8(c); 168 | size += 1; 169 | } 170 | return size; 171 | }; 172 | 173 | BinaryWriter.prototype.bytes = function(buffer) { 174 | var size = buffer.byteLength; 175 | this.expect(size); 176 | (new Uint8Array(this.data.buffer, this.pos, size)).set(new Uint8Array(buffer)); 177 | this.pos += size; 178 | }; 179 | 180 | return { 181 | CodeWriter: CodeWriter, 182 | BinaryWriter: BinaryWriter, 183 | }; 184 | }); 185 | -------------------------------------------------------------------------------- /d8_common.js: -------------------------------------------------------------------------------- 1 | if (!this.WASM) { 2 | throw "You need to patch v8 to support WASM."; 3 | } 4 | 5 | // Minimal require.js polyfill for d8. 6 | var cache = {}; 7 | var resolving = null; 8 | 9 | function resolve(name) { 10 | if (name in cache) { 11 | return cache[name]; 12 | } 13 | cache[name] = {}; 14 | resolving = name; 15 | load(name + ".js"); 16 | return cache[name]; 17 | } 18 | 19 | function define(deps, module) { 20 | var name = resolving; 21 | var resolved = []; 22 | for (var i = 0; i < deps.length; i++) { 23 | resolved.push(resolve(deps[i])); 24 | } 25 | var result = module.apply(module, resolved); 26 | cache[name] = result; 27 | } 28 | 29 | load("third_party/peg-0.8.0.js"); 30 | 31 | // Run the assembler. 32 | var base = resolve("base"); 33 | var desugar = resolve("wasm/desugar"); 34 | var wasm_backend_v8 = resolve("v8/backend"); 35 | 36 | var sources = { 37 | grammar: read("wasm.pegjs"), 38 | systemWASM: read("d8_system.wasm"), 39 | systemJS: read("system.js"), 40 | }; 41 | -------------------------------------------------------------------------------- /d8_main.js: -------------------------------------------------------------------------------- 1 | load("d8_common.js"); 2 | 3 | function compile(filename) { 4 | var status = new base.Status(function(message) { 5 | print(message); 6 | }); 7 | var parser = base.createParser(sources.grammar, status); 8 | 9 | var text = read(filename); 10 | 11 | var module = base.frontend(sources.systemWASM, filename, text, parser, status); 12 | if (status.num_errors > 0) { 13 | return null; 14 | } 15 | 16 | module = desugar.process(module); 17 | 18 | var compiled = base.astToCompiledJS(module, sources.systemJS, {}, status); 19 | if (status.num_errors > 0) { 20 | return null; 21 | } 22 | // Generate binary encoding 23 | var buffer = wasm_backend_v8.generate(module); 24 | print("bytes:", new Uint8Array(buffer)); 25 | print("num bytes:", buffer.byteLength); 26 | print(); 27 | 28 | // Instantiate 29 | var foreign = { 30 | sinF32: function(value) { 31 | return Math.fround(Math.sin(value)); 32 | }, 33 | printI32: function(value) { 34 | print("print", value); 35 | }, 36 | flipBuffer: function(ptr) { 37 | print("flip", ptr); 38 | }, 39 | }; 40 | var instanceJS = compiled(foreign); 41 | var instanceV8 = WASM.instantiateModule(buffer, foreign); 42 | 43 | print("JS result:", instanceJS.main()); 44 | print("V8 result:", instanceV8.main()); 45 | } 46 | 47 | if (arguments.length != 1) { 48 | print("Usage: d8 d8_main.js -- file.wasm"); 49 | // TODO exit code. 50 | } else { 51 | var filename = arguments[0]; 52 | compile(filename); 53 | } 54 | -------------------------------------------------------------------------------- /d8_system.wasm: -------------------------------------------------------------------------------- 1 | config { 2 | memory.fixed: 1048576 3 | } 4 | 5 | memory { 6 | _current_break: zero 4; 7 | } 8 | 9 | func sbrk(amt i32) i32 { 10 | var temp i32 = loadI32(_current_break); 11 | storeI32(_current_break, temp + amt); 12 | // Note: _end is a special memory label generated by the assembler that 13 | // points to the end of statically reserved memory. Currently we can't 14 | // initialize memory with symbols, so do relative addressing instead. 15 | return _end + temp; 16 | } 17 | 18 | import func powF32(f32, f32) f32; 19 | import func sinF32(f32) f32; 20 | import func cosF32(f32) f32; 21 | 22 | import func powF64(f64, f64) f64; 23 | import func sinF64(f64) f64; 24 | import func cosF64(f64) f64; 25 | 26 | // Needed for tests. 27 | import func atomicLoadI32(i32) i32; 28 | import func atomicStoreI32(i32, i32) void; 29 | import func atomicCompareExchangeI32(i32, i32, i32) i32; 30 | 31 | -------------------------------------------------------------------------------- /d8_tests.js: -------------------------------------------------------------------------------- 1 | load("d8_common.js"); 2 | 3 | 4 | var status = new base.Status(function(message) { 5 | throw Error(message); 6 | }); 7 | var parser = base.createParser(sources.grammar, status); 8 | 9 | 10 | var compiletests = resolve("compiletests"); 11 | 12 | function haltTest() { 13 | throw Error("halt test"); 14 | } 15 | 16 | var externs = { 17 | addI8: function(a, b) { 18 | return (a + b) << 24 >> 24; 19 | }, 20 | }; 21 | 22 | var testCount = 0; 23 | var jsTestPass = 0; 24 | var jsTestFail = 0; 25 | var v8TestPass = 0; 26 | var v8TestFail = 0; 27 | 28 | function makeAssert() { 29 | var assert = { 30 | equal: function(a, b) { 31 | if (a != b) { 32 | print (" " + a + " != " + b); 33 | assert.num_errors += 1; 34 | } 35 | }, 36 | ok: function(value) { 37 | if (!value) { 38 | print (" " + "not ok"); 39 | assert.num_errors += 1; 40 | } 41 | }, 42 | notOk: function(value) { 43 | if (value) { 44 | print (" " + "ok"); 45 | assert.num_errors += 1; 46 | } 47 | }, 48 | num_errors: 0, 49 | }; 50 | return assert; 51 | } 52 | 53 | function runTest(test) { 54 | testCount += 1; 55 | var status = new base.Status(function(message) { 56 | print(message); 57 | }); 58 | 59 | print(" common"); 60 | var ast = base.frontend(sources.systemWASM, "test", test.source, parser, status); 61 | if (status.num_errors > 0) { 62 | haltTest(); 63 | } 64 | ast = desugar.process(ast); 65 | 66 | if (test.js) { 67 | print(" JS"); 68 | 69 | var moduleJS = base.astToCompiledJS(ast, sources.systemJS, {}, status); 70 | if (status.num_errors > 0) { 71 | haltTest(); 72 | } 73 | 74 | var instanceJS = moduleJS(externs); 75 | var assert = makeAssert(); 76 | test.verify(instanceJS, assert); 77 | if (assert.num_errors > 0) { 78 | jsTestFail += 1; 79 | } else { 80 | jsTestPass += 1; 81 | } 82 | } 83 | 84 | if (test.v8_encode) { 85 | print(" V8 encode"); 86 | var buffer = wasm_backend_v8.generate(ast); 87 | if (test.v8_run) { 88 | print(" V8 run"); 89 | WASM.verifyModule(buffer); 90 | var instanceV8 = WASM.instantiateModule(buffer, externs); 91 | var assert = makeAssert(); 92 | test.verify(instanceV8, assert); 93 | if (assert.num_errors > 0) { 94 | v8TestFail += 1; 95 | } else { 96 | v8TestPass += 1; 97 | } 98 | } 99 | } 100 | } 101 | 102 | function main() { 103 | for (var m = 0; m < compiletests.testDefinitions.length; m++) { 104 | var module = compiletests.testDefinitions[m]; 105 | for (var t = 0; t < module.tests.length; t++) { 106 | var test = module.tests[t]; 107 | print(module.name + " / " + test.name); 108 | runTest(test); 109 | } 110 | } 111 | 112 | print(); 113 | print(testCount, "tests"); 114 | print("JS", jsTestPass, "pass,", jsTestFail, "fail"); 115 | print("V8", v8TestPass, "pass,", v8TestFail, "fail"); 116 | } 117 | 118 | main() 119 | -------------------------------------------------------------------------------- /demos/draw.wasm: -------------------------------------------------------------------------------- 1 | import func flipBuffer(i32) void; 2 | 3 | memory { 4 | align 4; 5 | phase: zero 4; 6 | 7 | align 4; 8 | frame_buffer: zero 4; 9 | 10 | bitmap: hex 11 | 0000000000000000 12 | 0000ff0000ff0000 13 | 0000ff0000ff0000 14 | 0000000000000000 15 | ff000000000000ff 16 | 00ff00000000ff00 17 | 0000ffffffff0000 18 | 0000000000000000; 19 | } 20 | 21 | func sampleBitmap(x f32, y f32) i32 { 22 | var xi i32 = i32(x * 8.0f); 23 | if (xi < 0) { return 0; } 24 | if (xi >= 8) { return 0; } 25 | var yi i32 = i32(y * 8.0f); 26 | if (yi < 0) { return 0; } 27 | if (yi >= 8) { return 0; } 28 | return i32(loadI8(bitmap + yi * 8 + xi)); 29 | } 30 | 31 | func shade(x f32, y f32, t f32) i32 { 32 | var xOff f32 = x - 0.5f; 33 | var yOff f32 = y - 0.5f; 34 | var rsq f32 = xOff * xOff + yOff * yOff; 35 | var radius f32 = sqrtF32(rsq); 36 | var r f32 = cosF32(radius * 100.0f - t * 3.0f); 37 | var g f32 = x; 38 | var b f32 = y; 39 | var a f32 = 1.0f; 40 | if (sampleBitmap(x, y)) { 41 | r = 1.0f - r; 42 | g = 1.0f - g; 43 | b = 1.0f - b; 44 | } 45 | return packColor(r, g, b, a); 46 | } 47 | 48 | // Convert [0.0, 1.0] to [0, 255]. 49 | func f2b(v f32) i32 { 50 | var vi i32 = i32(v * 255.0f); 51 | if (vi < 0) { 52 | vi = 0; 53 | } 54 | if(vi > 255) { 55 | vi = 255; 56 | } 57 | return vi; 58 | } 59 | 60 | // Convert a linear color value to a gamma-space byte. 61 | // Square root approximates gamma-correct rendering. 62 | func l2g(v f32) i32 { 63 | return f2b(sqrtF32(v)); 64 | } 65 | 66 | func packColor(r f32, g f32, b f32, a f32) i32 { 67 | return f2b(a) << 24 | l2g(b) << 16 | l2g(g) << 8 | l2g(r); 68 | } 69 | 70 | func renderBuffer(buffer i32, p f32) void { 71 | var y i32 = 0; 72 | while (y < 256) { 73 | var yAmt f32 = f32(y) / 256.0f; 74 | var x i32 = 0; 75 | while (x < 256) { 76 | var xAmt f32 = f32(x) / 256.0f; 77 | var color i32 = shade(xAmt, yAmt, p); 78 | storeI32(buffer + (y * 256 + x) * 4, color); 79 | x = x + 1; 80 | } 81 | y = y + 1; 82 | } 83 | } 84 | 85 | func render() void { 86 | var buffer i32 = loadI32(frame_buffer); 87 | var p f32 = loadF32(phase); 88 | renderBuffer(buffer, p); 89 | flipBuffer(buffer); 90 | } 91 | 92 | export func frame(dt f32) void { 93 | storeF32(phase, loadF32(phase) + dt); 94 | render(); 95 | } 96 | 97 | export func main() void { 98 | // Allocate the frame buffer. 99 | storeI32(frame_buffer, sbrk(256 * 256 * 4)); 100 | 101 | // Render the first frame. 102 | render(); 103 | } -------------------------------------------------------------------------------- /demos/raytrace.wasm: -------------------------------------------------------------------------------- 1 | import func flipBuffer(i32) void; 2 | 3 | // Debugging hack. 4 | import func printI32(i32) void; 5 | 6 | memory { 7 | width: zero 4; 8 | height: zero 4; 9 | frame_buffer: zero 4; 10 | pos: zero 12; 11 | dir: zero 12; 12 | light: zero 12; 13 | half: zero 12; 14 | intersection: zero 24; 15 | color: zero 12; 16 | phase: zero 4; 17 | } 18 | 19 | // Convert [0.0, 1.0] to [0, 255]. 20 | func f2b(v f32) i32 { 21 | var vi i32 = i32(v * 255.0f); 22 | if (vi < 0) { 23 | vi = 0; 24 | } 25 | if(vi > 255) { 26 | vi = 255; 27 | } 28 | return vi; 29 | } 30 | 31 | // Convert a linear color value to a gamma-space byte. 32 | // Square root approximates gamma-correct rendering. 33 | func l2g(v f32) i32 { 34 | return f2b(sqrtF32(v)); 35 | } 36 | 37 | func packColor(r f32, g f32, b f32, a f32) i32 { 38 | return f2b(a) << 24 | l2g(b) << 16 | l2g(g) << 8 | l2g(r); 39 | } 40 | 41 | func vecStore(x f32, y f32, z f32, ptr i32) void { 42 | storeF32(ptr, x); 43 | storeF32(ptr + 4, y); 44 | storeF32(ptr + 8, z); 45 | } 46 | 47 | func vecAdd(a i32, b i32, out i32) void { 48 | vecStore(loadF32(a) + loadF32(b), loadF32(a + 4) + loadF32(b + 4), loadF32(a + 8) + loadF32(b + 8), out); 49 | } 50 | 51 | func vecScale(a i32, scale f32, out i32) void { 52 | vecStore(loadF32(a) * scale, loadF32(a + 4) * scale, loadF32(a + 8) * scale, out); 53 | } 54 | 55 | func vecNormalize(ptr i32) void { 56 | var x f32 = loadF32(ptr); 57 | var y f32 = loadF32(ptr + 4); 58 | var z f32 = loadF32(ptr + 8); 59 | var invLen f32 = 1.0f / sqrtF32(x * x + y * y + z * z); 60 | //printF32(x); 61 | //printF32(y); 62 | //printF32(z); 63 | //printF32(invLen); 64 | vecStore(x * invLen, y * invLen, z * invLen, ptr); 65 | } 66 | 67 | func vecLen(ptr i32) f32 { 68 | var x f32 = loadF32(ptr); 69 | var y f32 = loadF32(ptr + 4); 70 | var z f32 = loadF32(ptr + 8); 71 | return sqrtF32(x * x + y * y + z * z); 72 | } 73 | 74 | func vecDot(a i32, b i32) f32 { 75 | return loadF32(a) * loadF32(b) + loadF32(a + 4) * loadF32(b + 4) + loadF32(a + 8) * loadF32(b + 8); 76 | } 77 | 78 | func vecNLDot(a i32, b i32) f32 { 79 | // Don't use maxF32, yet, because the V8 backend doesn't implement it. 80 | var value f32 = vecDot(a, b); 81 | if (value < 0.0f) { 82 | value = 0.0f; 83 | } 84 | return value; 85 | } 86 | 87 | func sampleEnv(dir i32, ptr i32) void { 88 | var y f32 = loadF32(dir + 4); 89 | var amt f32 = y * 0.5f + 0.5f; 90 | var keep f32 = 1.0f - amt; 91 | vecStore(keep * 0.1f + amt * 0.1f, keep * 1.0f + amt * 0.1f, keep * 0.1f + amt * 1.0f, ptr); 92 | } 93 | 94 | func intersect(pos i32, dir i32, intersection i32) i32 { 95 | var px f32 = loadF32(pos); 96 | var py f32 = loadF32(pos+4); 97 | var pz f32 = loadF32(pos+8); 98 | 99 | var vx f32 = loadF32(dir); 100 | var vy f32 = loadF32(dir + 4); 101 | var vz f32 = loadF32(dir + 8); 102 | 103 | 104 | // The sphere. 105 | var radius f32 = 4.0f; 106 | var cx f32 = 0.0f; 107 | var cy f32 = sinF32(loadF32(phase)); 108 | var cz f32 = -6.0f; 109 | 110 | // Calculate the position relative to the center of the sphere. 111 | var ox f32 = px - cx; 112 | var oy f32 = py - cy; 113 | var oz f32 = pz - cz; 114 | 115 | var dot f32 = vx * ox + vy * oy + vz * oz; 116 | 117 | var partial f32 = dot * dot + radius * radius - (ox * ox + oy * oy + oz * oz); 118 | if (partial >= 0.0f) { 119 | var d f32 = -dot - sqrtF32(partial); 120 | if (d >= 0.0f) { 121 | var normal i32 = intersection + 12; 122 | vecStore(px + vx * d - cx, py + vy * d - cy, pz + vz * d - cz, normal); 123 | vecNormalize(normal); 124 | return 1; 125 | } 126 | } 127 | return 0; 128 | } 129 | 130 | func renderFrame() i32 { 131 | var w i32 = loadI32(width); 132 | var h i32 = loadI32(height); 133 | var buffer i32 = loadI32(frame_buffer); 134 | 135 | vecStore(20.0f, 20.0f, 15.0f, light); 136 | vecNormalize(light); 137 | 138 | var j i32 = 0; 139 | while (j < h) { 140 | var y f32 = 0.5f - f32(j) / f32(h); 141 | var i i32 = 0; 142 | while (i < w) { 143 | var x f32 = f32(i) / f32(w) - 0.5f; 144 | vecStore(x, y, 0.0f, pos); 145 | vecStore(x, y, -0.5f, dir); 146 | vecNormalize(dir); 147 | 148 | // Compute the half vector; 149 | vecScale(dir, -1.0f, half); 150 | vecAdd(half, light, half); 151 | vecNormalize(half); 152 | 153 | // Light accumulation 154 | var r f32 = 0.0f; 155 | var g f32 = 0.0f; 156 | var b f32 = 0.0f; 157 | 158 | // Surface diffuse. 159 | var dr f32 = 0.7f; 160 | var dg f32 = 0.7f; 161 | var db f32 = 0.7f; 162 | 163 | if (intersect(pos, dir, intersection)) { 164 | sampleEnv(intersection + 12, color); 165 | var ambientScale f32 = 0.2f; 166 | r = r + dr * loadF32(color) * ambientScale; 167 | g = g + dg * loadF32(color + 4) * ambientScale; 168 | b = b + db * loadF32(color + 8) * ambientScale; 169 | 170 | var diffuse f32 = vecNLDot(intersection + 12, light); 171 | var specular f32 = vecNLDot(intersection + 12, half); 172 | // Take it to the 64th power, manually. 173 | specular = specular * specular; 174 | specular = specular * specular; 175 | specular = specular * specular; 176 | specular = specular * specular; 177 | specular = specular * specular; 178 | specular = specular * specular; 179 | 180 | specular = specular * 0.6f; 181 | 182 | r = r + dr * diffuse + specular; 183 | g = g + dg * diffuse + specular; 184 | b = b + db * diffuse + specular; 185 | } else { 186 | sampleEnv(dir, color); 187 | r = loadF32(color); 188 | g = loadF32(color + 4); 189 | b = loadF32(color + 8); 190 | } 191 | storeI32(buffer + (j * w + i) * 4, packColor(r, g, b, 1.0f)); 192 | i = i + 1; 193 | } 194 | j = j + 1; 195 | } 196 | return buffer; 197 | } 198 | 199 | export func checksum(ptr i32, size i32) i32 { 200 | printI32(ptr); 201 | printI32(size); 202 | var sum i32 = 0; 203 | var current i32 = ptr; 204 | while (current < ptr + size) { 205 | //printI32(sum); // TODO why does this cause a crash? 206 | sum = sum + loadI32(current); 207 | current = current + 4; 208 | } 209 | return sum; 210 | } 211 | 212 | export func frame(dt f32) void { 213 | storeF32(phase, loadF32(phase) + dt); 214 | flipBuffer(renderFrame()); 215 | } 216 | 217 | export func init(w i32, h i32) void { 218 | storeI32(width, w); 219 | storeI32(height, h); 220 | storeI32(frame_buffer, sbrk(w * h * 4)); 221 | } 222 | 223 | export func main() void { 224 | init(256, 256); 225 | flipBuffer(renderFrame()); 226 | // Print the checksum. 227 | printI32(checksum(loadI32(frame_buffer), loadI32(width) * loadI32(height) * 4)); 228 | } -------------------------------------------------------------------------------- /demos/simple.wasm: -------------------------------------------------------------------------------- 1 | memory { 2 | temp: zero 4; 3 | scale: hex 02000000; 4 | } 5 | 6 | func ifelse(cond i32, a i32, b i32) i32 { 7 | storeI32(temp, 10); 8 | if (cond) { 9 | return a > b; 10 | } else { 11 | return a < b; 12 | } 13 | } 14 | 15 | export func main() i32 { 16 | storeI32(temp, 0); 17 | var i i32 = 0; 18 | done: while (i < 10) { 19 | i = i + 1; 20 | if (i >= 7) { 21 | break done; 22 | } 23 | } 24 | return (i + ifelse(0, 1, 2) * 2) * loadI32(temp) + loadI32(scale); 25 | } 26 | 27 | //export func check() i32 { 28 | // var i i32 = 0; 29 | // var total i32 = 0; 30 | // while (i < 1024 * 1024) { 31 | // total = total | loadI32(i); 32 | // i = i + 4; 33 | // } 34 | // return total; 35 | //} -------------------------------------------------------------------------------- /demos/threading.wasm: -------------------------------------------------------------------------------- 1 | memory { 2 | // "hello, world\0" 3 | message: hex 68656c6c6f2c20776f726c6400; 4 | no_threads: string "Threads are not enabled."; 5 | } 6 | 7 | func strlen(ptr i32) i32 { 8 | var curr i32 = ptr; 9 | while (i32(loadI8(curr))) { 10 | curr = curr + 1; 11 | } 12 | return curr - ptr; 13 | } 14 | 15 | export func thread_main(context i32) void { 16 | consoleI32(context); 17 | consoleI32(12345); 18 | } 19 | 20 | export func main() void { 21 | consoleString(message, strlen(message)); 22 | if (threadingSupported()) { 23 | threadCreate(thread_main, 0); 24 | } else { 25 | consoleString(no_threads, strlen(no_threads)); 26 | } 27 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Interactive Wassembler 6 | 7 | 8 | 9 | 10 | 11 |

Interactive Wassembler

12 |
13 |
14 | Demos: draw.wasm | raytrace.wasm | threading.wasm 15 |
16 |
17 | 18 | 19 | 20 | 21 | 22 | Repo 23 | Tests 24 |
25 |
26 | 27 | 28 |
29 |
30 | 31 | 32 |
33 |
34 | 35 |
36 |
37 |

Help

38 |
    39 |
  • The first text area is editable. Hit "Eval" to recompile.
  • 40 |
  • Supported types: i32, f32, f64, and void.
  • 41 |
  • Semi-supported types: i64 - wrong answers for bit values. i8 and i16 - no constants, yet.
  • 42 |
  • Basic arithmetic binary operators and comparisons are supported.
  • 43 |
  • if, while, and return are supported.
  • 44 |
  • memory name #size align #alignment; statically allocates memory.
  • 45 |
  • loadI32(addr) and storeI32(addr, value) are memory accesses, and there are variants for other types.
  • 46 |
  • Prefixing a function with "export" makes it visible to JS.
  • 47 |
  • import func name(argtypes) returntype; declares a binding to a JS function.
  • 48 |
49 |
50 |
51 | 52 | 59 | 60 | -------------------------------------------------------------------------------- /js/ast.js: -------------------------------------------------------------------------------- 1 | define(["astutil"], function(astutil) { 2 | var schema = [ 3 | { 4 | name: "ConstNum", 5 | fields: [ 6 | {name: "value"}, 7 | ], 8 | }, 9 | { 10 | name: "GetName", 11 | fields: [ 12 | {name: "name"}, 13 | ], 14 | }, 15 | { 16 | name: "GetAttr", 17 | fields: [ 18 | {name: "expr"}, 19 | {name: "attr"}, 20 | ], 21 | }, 22 | { 23 | name: "GetIndex", 24 | fields: [ 25 | {name: "expr"}, 26 | {name: "index"}, 27 | ], 28 | }, 29 | { 30 | name: "BinaryOp", 31 | fields: [ 32 | {name: "left"}, 33 | {name: "op"}, 34 | {name: "right"}, 35 | ], 36 | }, 37 | { 38 | name: "PrefixOp", 39 | fields: [ 40 | {name: "op"}, 41 | {name: "expr"}, 42 | ], 43 | }, 44 | { 45 | name: "Call", 46 | fields: [ 47 | {name: "expr"}, 48 | {name: "args"}, 49 | ], 50 | }, 51 | { 52 | name: "New", 53 | fields: [ 54 | {name: "expr"}, 55 | {name: "args"}, 56 | ], 57 | }, 58 | { 59 | name: "CreateArray", 60 | fields: [ 61 | {name: "args"}, 62 | ], 63 | }, 64 | { 65 | name: "KeyValue", 66 | fields: [ 67 | {name: "key"}, 68 | {name: "value"}, 69 | ], 70 | }, 71 | { 72 | name: "CreateObject", 73 | fields: [ 74 | {name: "args"}, 75 | ], 76 | }, 77 | { 78 | name: "Assign", 79 | fields: [ 80 | {name: "target"}, 81 | {name: "value"}, 82 | ], 83 | }, 84 | { 85 | name: "Label", 86 | fields: [ 87 | {name: "name"}, 88 | {name: "stmt"}, 89 | ], 90 | }, 91 | { 92 | name: "Break", 93 | fields: [ 94 | {name: "name"}, 95 | ], 96 | }, 97 | { 98 | name: "Return", 99 | fields: [ 100 | {name: "expr"}, 101 | ], 102 | }, 103 | { 104 | name: "If", 105 | fields: [ 106 | {name: "cond"}, 107 | {name: "t"}, 108 | {name: "f"}, 109 | ], 110 | }, 111 | { 112 | name: "While", 113 | fields: [ 114 | {name: "cond"}, 115 | {name: "body"}, 116 | ], 117 | }, 118 | { 119 | name: "VarDecl", 120 | fields: [ 121 | {name: "name"}, 122 | {name: "expr"}, 123 | ], 124 | }, 125 | { 126 | name: "FunctionExpr", 127 | fields: [ 128 | {name: "params"}, 129 | {name: "body"}, 130 | ], 131 | }, 132 | { 133 | name: "InjectSource", 134 | fields: [ 135 | {name: "source"}, 136 | ], 137 | }, 138 | ]; 139 | 140 | return astutil.makeASTBuilder(schema); 141 | }); 142 | -------------------------------------------------------------------------------- /js/backend.js: -------------------------------------------------------------------------------- 1 | define(["compilerutil"], function(compilerutil) { 2 | var binOpPrec = { 3 | "*": 14, 4 | "/": 14, 5 | "%": 14, 6 | "+": 13, 7 | "-": 13, 8 | "<<": 12, 9 | ">>": 12, 10 | ">>>": 12, 11 | "<": 11, 12 | "<=": 11, 13 | ">": 11, 14 | ">=": 11, 15 | "==": 10, 16 | "!=": 10, 17 | "===": 10, 18 | "!==": 10, 19 | "&": 9, 20 | "^": 8, 21 | "|": 7, 22 | }; 23 | 24 | var exprPrec = function(expr) { 25 | switch(expr.type) { 26 | case "FunctionExpr": 27 | case "GetName": 28 | case "ConstNum": 29 | case "CreateArray": 30 | return 19; 31 | case "CreateObject": 32 | return 19; 33 | case "GetIndex": 34 | case "GetAttr": 35 | case "New": 36 | return 18; 37 | case "BinaryOp": 38 | return binOpPrec[expr.op]; 39 | case "PrefixOp": 40 | return 15; 41 | case "Call": 42 | return 17; 43 | case "Assign": 44 | return 3; 45 | default: 46 | console.log(expr); 47 | throw expr.type; 48 | } 49 | }; 50 | 51 | var JSGenerator = function() { 52 | this.writer = new compilerutil.CodeWriter(); 53 | }; 54 | 55 | JSGenerator.prototype.generateExpr = function(expr, contextPrec) { 56 | var prec = exprPrec(expr); 57 | 58 | if (prec < contextPrec) { 59 | this.writer.out("("); 60 | } 61 | 62 | switch(expr.type) { 63 | case "ConstNum": 64 | this.writer.out(expr.value.toString()); 65 | break; 66 | case "GetName": 67 | this.writer.out(expr.name); 68 | break; 69 | case "GetAttr": 70 | this.generateExpr(expr.expr, prec); 71 | this.writer.out(".").out(expr.attr); 72 | break; 73 | case "PrefixOp": 74 | this.writer.out(expr.op); 75 | this.generateExpr(expr.expr, prec); 76 | break; 77 | case "BinaryOp": 78 | this.generateExpr(expr.left, prec); 79 | this.writer.out(" ").out(expr.op).out(" "); 80 | this.generateExpr(expr.right, prec + 1); 81 | break; 82 | case "Call": 83 | this.generateExpr(expr.expr, prec); 84 | this.writer.out("("); 85 | for (var i = 0; i < expr.args.length; i++) { 86 | if (i != 0) { 87 | this.writer.out(", "); 88 | } 89 | this.generateExpr(expr.args[i], 0); 90 | } 91 | this.writer.out(")"); 92 | break; 93 | case "New": 94 | this.writer.out("new "); 95 | this.generateExpr(expr.expr, prec); 96 | this.writer.out("("); 97 | for (var i = 0; i < expr.args.length; i++) { 98 | if (i != 0) { 99 | this.writer.out(", "); 100 | } 101 | this.generateExpr(expr.args[i], 0); 102 | } 103 | this.writer.out(")"); 104 | break; 105 | case "GetIndex": 106 | this.generateExpr(expr.expr, prec); 107 | this.writer.out("["); 108 | this.generateExpr(expr.index, 0); 109 | this.writer.out("]"); 110 | // TODO coerce return type 111 | break; 112 | case "Assign": 113 | this.generateExpr(expr.target, prec + 1); 114 | this.writer.out(" = "); 115 | this.generateExpr(expr.value, prec); 116 | break; 117 | case "FunctionExpr": 118 | this.writer.out("function("); 119 | for (var i = 0; i < expr.params.length; i++) { 120 | if (i != 0) { 121 | this.writer.out(", "); 122 | } 123 | this.writer.out(expr.params[i]); 124 | } 125 | this.writer.out(") {").eol().indent(); 126 | this.generateBlock(expr.body); 127 | this.writer.dedent().out("}"); 128 | break; 129 | case "CreateArray": 130 | this.writer.out("[").eol().indent(); 131 | for (var i = 0; i < expr.args.length; i++) { 132 | if (i != 0) { 133 | this.writer.out(",").eol(); 134 | } 135 | var arg = expr.args[i]; 136 | this.generateExpr(arg, 0); 137 | } 138 | this.writer.eol(); 139 | this.writer.dedent().out("]"); 140 | break; 141 | case "CreateObject": 142 | this.writer.out("{").eol().indent(); 143 | for (var i = 0; i < expr.args.length; i++) { 144 | if (i != 0) { 145 | this.writer.out(",").eol(); 146 | } 147 | var arg = expr.args[i]; 148 | this.writer.out(arg.key).out(": "); 149 | this.generateExpr(arg.value, 0); 150 | } 151 | this.writer.eol(); 152 | this.writer.dedent().out("}"); 153 | break; 154 | default: 155 | console.log(expr); 156 | throw expr.type; 157 | } 158 | 159 | if (prec < contextPrec) { 160 | this.writer.out(")"); 161 | } 162 | }; 163 | 164 | JSGenerator.prototype.generateStmt = function(stmt) { 165 | switch (stmt.type) { 166 | case "VarDecl": 167 | this.writer.out("var ").out(stmt.name); 168 | if (stmt.expr) { 169 | this.writer.out(" = "); 170 | this.generateExpr(stmt.expr, 0); 171 | } 172 | this.writer.out(";").eol(); 173 | break; 174 | case "Return": 175 | this.writer.out("return"); 176 | if (stmt.expr) { 177 | this.writer.out(" "); 178 | this.generateExpr(stmt.expr, 0); 179 | } 180 | this.writer.out(";").eol(); 181 | break; 182 | case "Label": 183 | this.writer.out(stmt.name).out(": "); 184 | this.generateStmt(stmt.stmt); 185 | break; 186 | case "Break": 187 | this.writer.out("break ").out(stmt.name); 188 | this.writer.out(";").eol(); 189 | break; 190 | case "If": 191 | this.writer.out("if ("); 192 | this.generateExpr(stmt.cond, 0); 193 | this.writer.out(") {").eol(); 194 | this.writer.indent(); 195 | this.generateBlock(stmt.t); 196 | this.writer.dedent(); 197 | if (stmt.f) { 198 | this.writer.out("} else {").eol(); 199 | this.writer.indent(); 200 | this.generateBlock(stmt.f); 201 | this.writer.dedent(); 202 | } 203 | this.writer.out("}").eol(); 204 | break; 205 | case "While": 206 | this.writer.out("while ("); 207 | this.generateExpr(stmt.cond, 0); 208 | this.writer.out(") {").eol(); 209 | this.writer.indent(); 210 | this.generateBlock(stmt.body); 211 | this.writer.dedent(); 212 | this.writer.out("}").eol(); 213 | break; 214 | case "InjectSource": 215 | var lines = stmt.source.split("\n"); 216 | while (lines.length && lines[lines.length - 1] == "") { 217 | lines.pop(); 218 | } 219 | for (var i = 0; i < lines.length; i++) { 220 | if (lines[i]) { 221 | this.writer.out(lines[i]); 222 | } 223 | this.writer.eol(); 224 | } 225 | break; 226 | default: 227 | this.generateExpr(stmt, 0); 228 | this.writer.out(";").eol(); 229 | } 230 | }; 231 | 232 | JSGenerator.prototype.generateBlock = function(block) { 233 | for (var i = 0; i < block.length; i++) { 234 | this.generateStmt(block[i]); 235 | } 236 | }; 237 | 238 | var generateExpr = function(expr) { 239 | var gen = new JSGenerator(); 240 | gen.writer.out("("); 241 | gen.generateExpr(expr, 0); 242 | gen.writer.out(")"); 243 | return gen.writer.getOutput(); 244 | }; 245 | 246 | return { 247 | generateExpr: generateExpr, 248 | }; 249 | }); -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | define( 2 | ["base", "wasm/desugar", "v8/backend"], 3 | function(base, desugar, wasm_backend_v8) { 4 | 5 | var queryDefaults = { 6 | file: "draw.wasm", 7 | }; 8 | 9 | var parseQuery = function(defaults) { 10 | var raw = document.location.search.substring(1); 11 | var parts = raw.split("&"); 12 | var query = {}; 13 | for (var key in defaults) { 14 | query[key] = defaults[key]; 15 | } 16 | for (var i = 0; i < parts.length; i++) { 17 | var keyvalue = parts[i].split("="); 18 | var key = decodeURIComponent(keyvalue[0]); 19 | var value = decodeURIComponent(keyvalue[1]); 20 | query[key] = value; 21 | } 22 | return query; 23 | }; 24 | 25 | var sanitizeFileName = function(filename) { 26 | var parts = filename.split("."); 27 | var ext = parts.pop(); 28 | var base = parts.join("_"); 29 | base = base.replace(/\W+/g, '_'); 30 | ext = ext.replace(/\W+/g, '_'); 31 | if (base) { 32 | return base + "." + ext; 33 | } else { 34 | return ext; 35 | } 36 | }; 37 | 38 | var setupUI = function(code, parse) { 39 | setText("code", code); 40 | 41 | var reeval = function() { 42 | parse(getText("code")); 43 | }; 44 | 45 | document.getElementById("animate").onclick = pumpAnimation; 46 | document.getElementById("eval").onclick = reeval; 47 | document.getElementById("show").onclick = updateVisibility; 48 | document.getElementById("native").onclick = reeval; 49 | document.getElementById("shared_memory").onclick = reeval; 50 | 51 | document.getElementById("native").disabled = window.WASM === undefined; 52 | document.getElementById("shared_memory").disabled = window.SharedArrayBuffer === undefined; 53 | 54 | updateVisibility(); 55 | reeval(); 56 | }; 57 | 58 | var last = 0; 59 | 60 | var pumpPending = false; 61 | var pumpAnimationWrapper = function() { 62 | pumpPending = false; 63 | pumpAnimation(); 64 | } 65 | var pumpAnimation = function() { 66 | if (!pumpPending && document.getElementById("animate").checked && instance && instance.frame !== undefined) { 67 | if (last == 0) { 68 | last = Date.now(); 69 | } 70 | var current = Date.now(); 71 | var dt = (current - last) / 1000; 72 | last = current; 73 | instance.frame(dt); 74 | pumpPending = true; 75 | requestAnimationFrame(pumpAnimationWrapper); 76 | } else { 77 | last = 0; 78 | } 79 | }; 80 | 81 | var updateVisibility = function() { 82 | var display = "none"; 83 | if (document.getElementById("show").checked) { 84 | display = "inline-block"; 85 | } 86 | document.getElementById("intermediate").style.display = display; 87 | }; 88 | 89 | var getText = function(pane) { 90 | return document.getElementById(pane).value; 91 | }; 92 | 93 | var setText = function(pane, text) { 94 | document.getElementById(pane).value = text; 95 | }; 96 | 97 | var appendText = function(pane, text) { 98 | document.getElementById(pane).value += text; 99 | }; 100 | 101 | var systemJSSrc; 102 | var systemWASMSrc; 103 | var instance = null; 104 | var srcURL = null; 105 | 106 | var makeExterns = function() { 107 | var c = document.getElementById("canvas"); 108 | var ctx = c.getContext("2d"); 109 | var imageData = ctx.getImageData(0, 0, c.width, c.height); 110 | 111 | var externs = { 112 | flipBuffer: function(ptr) { 113 | var out = imageData.data.buffer; 114 | externs._instance._copyOut(ptr, out.byteLength, out, 0); 115 | ctx.putImageData(imageData, 0, 0); 116 | }, 117 | printI32: function(value) { 118 | appendText("terminal", "printI32: " + value + "\n"); 119 | }, 120 | printF32: function(value) { 121 | appendText("terminal", "printF32: " + value + "\n"); 122 | }, 123 | printF64: function(value) { 124 | appendText("terminal", "printF64: " + value + "\n"); 125 | }, 126 | 127 | powF32: function(base, exponent) { 128 | return Math.fround(Math.pow(base, exponent)); 129 | }, 130 | sinF32: function(value) { 131 | return Math.fround(Math.sin(value)); 132 | }, 133 | cosF32: function(value) { 134 | return Math.fround(Math.cos(value)); 135 | }, 136 | powF64: function(base, exponent) { 137 | return Math.pow(base, exponent); 138 | }, 139 | sinF64: function(value) { 140 | return Math.sin(value); 141 | }, 142 | cosF64: function(value) { 143 | return Math.cos(value); 144 | }, 145 | }; 146 | return externs; 147 | }; 148 | 149 | var exampleFile = null; 150 | 151 | var parser; 152 | 153 | var status = new base.Status(function(message) { 154 | appendText("terminal", message + "\n"); 155 | }); 156 | 157 | var reevaluate = function(text) { 158 | console.log("Re-evaluating."); 159 | var start = performance.now(); 160 | // Clear the outputs. 161 | setText("ast", ""); 162 | setText("generated", ""); 163 | setText("terminal", ""); 164 | 165 | instance = null; 166 | if (srcURL) { 167 | URL.revokeObjectURL(srcURL); 168 | srcURL = null; 169 | } 170 | 171 | status = new base.Status(function(message) { 172 | appendText("terminal", message + "\n"); 173 | }); 174 | 175 | var reportAST = function(ast) { 176 | //setText("ast", JSON.stringify(ast, null, " ")); 177 | }; 178 | 179 | var reportSrc = function(src) { 180 | setText("generated", src); 181 | }; 182 | 183 | var substart = performance.now(); 184 | var module = base.frontend(systemWASMSrc, exampleFile, text, parser, status, reportAST); 185 | if (status.num_errors > 0) { 186 | return null; 187 | } 188 | 189 | module = desugar.process(module); 190 | console.log("front end", performance.now() - substart); 191 | 192 | 193 | var config = { 194 | //use_native: false, 195 | use_native: document.getElementById("native").checked, 196 | use_shared_memory: document.getElementById("shared_memory").checked, 197 | }; 198 | 199 | var substart = performance.now(); 200 | var src = base.astToJSSrc(module, systemJSSrc, config); 201 | console.log("gen JS", performance.now() - substart); 202 | 203 | if (reportSrc) reportSrc(src); 204 | 205 | var substart = performance.now(); 206 | var compiled = base.evalJSSrc(src, status); 207 | if (status.num_errors > 0) { 208 | return null; 209 | } 210 | console.log("eval JS", performance.now() - substart); 211 | 212 | // Generate binary encoding 213 | var substart = performance.now(); 214 | var buffer = wasm_backend_v8.generate(module); 215 | //console.log(new Uint8Array(buffer)); 216 | //console.log(buffer.byteLength); 217 | console.log("gen v8", performance.now() - substart); 218 | 219 | var externs = makeExterns(); 220 | 221 | console.log("total compile time", performance.now() - start); 222 | 223 | if (config.use_native) { 224 | var start = performance.now(); 225 | instance = WASM.instantiateModule(buffer, externs); 226 | console.log("instantiate time", performance.now() - start); 227 | 228 | instance._copyOut = function(srcOff, size, dst, dstOff) { 229 | var buffer = instance.memory; 230 | var end = srcOff + size; 231 | if (end < srcOff || srcOff > buffer.byteLength || srcOff < 0 || end > buffer.byteLength || end < 0) { 232 | throw Error("Range [" + srcOff + ", " + end + ") is out of bounds. [0, " + buffer.byteLength + ")"); 233 | } 234 | new Uint8Array(dst, dstOff, size).set(new Uint8Array(buffer, srcOff, size)); 235 | }; 236 | } else { 237 | if (config.use_shared_memory) { 238 | srcURL = URL.createObjectURL(new Blob([src], {type: 'text/javascript'})); 239 | } 240 | 241 | // Bind the module. 242 | try { 243 | var start = performance.now(); 244 | instance = compiled(externs, srcURL); 245 | console.log("instantiate time", performance.now() - start); 246 | } catch (e) { 247 | appendText("terminal", "binding failed - " + e.message); 248 | return; 249 | } 250 | } 251 | externs._instance = instance; 252 | 253 | // Run main. 254 | appendText("terminal", "running main...\n\n"); 255 | try { 256 | var start = performance.now(); 257 | var result = instance.main(); 258 | console.log("main time", performance.now() - start); 259 | } catch (e) { 260 | appendText("terminal", "\nruntime error: " + e.message); 261 | return; 262 | } 263 | appendText("terminal", "\nresult: " + result); 264 | 265 | pumpAnimation(); 266 | }; 267 | 268 | var run = function() { 269 | var query = parseQuery(queryDefaults); 270 | exampleFile = "demos/" + sanitizeFileName(query.file); 271 | 272 | Promise.all([ 273 | base.getURL("wasm.pegjs"), 274 | base.getURL(exampleFile), 275 | base.getURL("system.js"), 276 | base.getURL("system.wasm"), 277 | ]).then(function(values) { 278 | status.setFilename("wasm.pegjs"); 279 | parser = base.createParser(values[0], status); 280 | status.setFilename(""); 281 | if (status.num_errors > 0) { 282 | return; 283 | } 284 | 285 | var code = values[1]; 286 | systemJSSrc = values[2]; 287 | systemWASMSrc = values[3]; 288 | setupUI(code, reevaluate); 289 | }, function(err){ 290 | setText("code", err); 291 | }); 292 | }; 293 | 294 | return {run: run}; 295 | }); 296 | -------------------------------------------------------------------------------- /system.js: -------------------------------------------------------------------------------- 1 | var createSystem = function(buffer, srcURL) { 2 | var system = {}; 3 | 4 | // Intrinstics. 5 | 6 | // Math 7 | system.powF32 = function(base, exponent) { 8 | return Math.fround(Math.pow(base, exponent)); 9 | }; 10 | system.sinF32 = function(value) { 11 | return Math.fround(Math.sin(value)); 12 | }; 13 | system.cosF32 = function(value) { 14 | return Math.fround(Math.cos(value)); 15 | }; 16 | system.powF64 = function(base, exponent) { 17 | return Math.pow(base, exponent); 18 | }; 19 | system.sinF64 = function(value) { 20 | return Math.sin(value); 21 | }; 22 | system.cosF64 = function(value) { 23 | return Math.cos(value); 24 | }; 25 | 26 | system.threadingSupported = function() { 27 | return threading_supported; 28 | }; 29 | 30 | // Atomics. 31 | if (threading_supported) { 32 | var I32 = new SharedInt32Array(buffer); 33 | var a = Atomics; 34 | } else { 35 | var I32 = new Int32Array(buffer); 36 | // Polyfill 37 | var a = { 38 | load: function(view, addr) { 39 | return view[addr]; 40 | }, 41 | store: function(view, addr, value) { 42 | view[addr] = value; 43 | }, 44 | compareExchange: function(view, addr, expected, value) { 45 | var actual = view[addr]; 46 | if (actual === expected) { 47 | view[addr] = value; 48 | } 49 | return actual; 50 | }, 51 | }; 52 | } 53 | 54 | system.atomicLoadI32 = function(addr) { 55 | return a.load(I32, addr >> 2); 56 | }; 57 | 58 | system.atomicStoreI32 = function(addr, value) { 59 | a.store(I32, addr >> 2, value); 60 | }; 61 | 62 | system.atomicCompareExchangeI32 = function(addr, expected, value) { 63 | return a.compareExchange(I32, addr >> 2, expected, value); 64 | }; 65 | 66 | // Libc-like stuff. 67 | 68 | var is_main_thread = false; 69 | system.initMainThread = function() { 70 | is_main_thread = true; 71 | }; 72 | 73 | // Threading 74 | if (threading_supported) { 75 | system.threadCreate = function(f, context) { 76 | var worker = new Worker(srcURL); 77 | worker.postMessage({buffer: buffer, f: f, context: context}, [buffer]); 78 | }; 79 | } 80 | 81 | system.consoleI32 = function(value) { 82 | console.log(value); 83 | }; 84 | 85 | system.consoleString = function(ptr, size) { 86 | var buffer = new ArrayBuffer(size); 87 | instance._copyOut(ptr, size, buffer); 88 | var values = new Uint8Array(buffer); 89 | var s = ""; 90 | // TODO UTF8 support. 91 | for (var i = 0; i < values.length; i++) { 92 | s += String.fromCharCode(values[i]); 93 | } 94 | console.log(s); 95 | }; 96 | 97 | return system; 98 | }; 99 | 100 | var augmentInstance = function(instance, buffer) { 101 | if (threading_supported) { 102 | instance._copyOut = function(srcOff, size, dst, dstOff) { 103 | var end = srcOff + size; 104 | if (end < srcOff || srcOff > buffer.byteLength || srcOff < 0 || end > buffer.byteLength || end < 0) { 105 | throw Error("Range [" + srcOff + ", " + end + ") is out of bounds. [0, " + buffer.byteLength + ")"); 106 | } 107 | new Uint8Array(dst, dstOff, size).set(new SharedUint8Array(buffer, srcOff, size)); 108 | }; 109 | } else { 110 | instance._copyOut = function(srcOff, size, dst, dstOff) { 111 | var end = srcOff + size; 112 | if (end < srcOff || srcOff > buffer.byteLength || srcOff < 0 || end > buffer.byteLength || end < 0) { 113 | throw Error("Range [" + srcOff + ", " + end + ") is out of bounds. [0, " + buffer.byteLength + ")"); 114 | } 115 | new Uint8Array(dst, dstOff, size).set(new Uint8Array(buffer, srcOff, size)); 116 | }; 117 | } 118 | }; 119 | 120 | var instance; 121 | 122 | var is_browser = typeof window === 'object'; 123 | var is_worker = typeof importScripts === 'function'; 124 | 125 | if (!is_worker) { 126 | return function(foreign, srcURL) { 127 | var buffer = createMemory(); 128 | var system = createSystem(buffer, srcURL); 129 | var wrapped_foreign = wrap_foreign(system, foreign); 130 | instance = module(stdlib, wrapped_foreign, buffer); 131 | augmentInstance(instance, buffer); 132 | system.initMainThread(); 133 | return instance; 134 | } 135 | } else { 136 | var init = function(evt) { 137 | self.removeEventListener("message", init, false); 138 | 139 | var buffer = evt.data.buffer; 140 | 141 | // HACK 142 | var foreign = {}; 143 | var srcURL = null; 144 | 145 | var system = createSystem(buffer, srcURL); 146 | var wrapped_foreign = wrap_foreign(system, foreign); 147 | instance = module(stdlib, wrapped_foreign, buffer); 148 | augmentInstance(instance, buffer); 149 | if (instance.threadStart) { 150 | instance.threadStart(evt.data.f, evt.data.context); 151 | } 152 | self.close(); 153 | }; 154 | self.addEventListener("message", init, false); 155 | } 156 | -------------------------------------------------------------------------------- /system.wasm: -------------------------------------------------------------------------------- 1 | // This file is effectively prepended to any .wasm file being compiled. 2 | // Imports in this file are provided by the system. 3 | 4 | config { 5 | memory.fixed: 1048576 6 | } 7 | 8 | memory { 9 | _current_break: zero 4; 10 | } 11 | 12 | func sbrk(amt i32) i32 { 13 | var temp i32 = loadI32(_current_break); 14 | storeI32(_current_break, temp + amt); 15 | // Note: _end is a special memory label generated by the assembler that 16 | // points to the end of statically reserved memory. Currently we can't 17 | // initialize memory with symbols, so do relative addressing instead. 18 | return _end + temp; 19 | } 20 | 21 | import func powF32(f32, f32) f32; 22 | import func sinF32(f32) f32; 23 | import func cosF32(f32) f32; 24 | 25 | import func powF64(f64, f64) f64; 26 | import func sinF64(f64) f64; 27 | import func cosF64(f64) f64; 28 | 29 | import func threadingSupported() i32; 30 | 31 | import func atomicLoadI32(i32) i32; 32 | import func atomicStoreI32(i32, i32) void; 33 | import func atomicCompareExchangeI32(i32, i32, i32) i32; 34 | 35 | import func threadCreate(i32, i32) void; 36 | 37 | // HACK unconditional, user facing export. 38 | // Really, we'd only want to export this to the system if it was used. 39 | //export func threadStart(f i32, context i32) void { 40 | // (i32)void(f)(context); 41 | //} 42 | 43 | import func consoleI32(i32) void; 44 | import func consoleString(i32, i32) void; -------------------------------------------------------------------------------- /tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wassembler Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests.js: -------------------------------------------------------------------------------- 1 | define(["base", "wasm/desugar", "v8/backend", "compiletests"], function(base, desugar, wasm_backend_v8, compiletests) { 2 | var parser = null; 3 | var systemJSSrc = null; 4 | var systemWASMSrc = null; 5 | 6 | var externs = { 7 | addI8: function(a, b) { 8 | return (a + b) << 24 >> 24; 9 | }, 10 | }; 11 | 12 | var createNormal = function(text, assert, suppress_v8) { 13 | var status = new base.Status(function(message) { assert.ok(false, message); }); 14 | 15 | var ast = base.frontend(systemWASMSrc, "test", text, parser, status); 16 | assert.notEqual(ast, null, "frontend"); 17 | 18 | ast = desugar.process(ast); 19 | 20 | var module = base.astToCompiledJS(ast, systemJSSrc, {}, status); 21 | assert.notEqual(module, null, "backend"); 22 | 23 | if (!suppress_v8) { 24 | // Smoke test the V8 backend. 25 | wasm_backend_v8.generate(ast); 26 | } 27 | 28 | return module(externs); 29 | }; 30 | 31 | var defineTests = function(mode_name, create) { 32 | for (var m = 0; m < compiletests.testDefinitions.length; m++) { 33 | var module = compiletests.testDefinitions[m]; 34 | QUnit.module(mode_name + " " + module.name); 35 | for (var t = 0; t < module.tests.length; t++) { 36 | var test = module.tests[t]; 37 | (function(test) { 38 | QUnit.test(test.name, function(assert) { 39 | var m = create(test.source, assert, !test.v8); 40 | test.verify(m, assert); 41 | }); 42 | })(test); 43 | } 44 | } 45 | } 46 | 47 | defineTests("polyfill", createNormal); 48 | 49 | var run = function() { 50 | Promise.all([ 51 | base.getURL("wasm.pegjs"), 52 | base.getURL("system.js"), 53 | base.getURL("system.wasm"), 54 | ]).then(function(values) { 55 | var status = new base.Status(function(message) { throw message; }); 56 | parser = base.createParser(values[0], status); 57 | systemJSSrc = values[1]; 58 | systemWASMSrc = values[2]; 59 | QUnit.start(); 60 | }); 61 | }; 62 | 63 | return { 64 | run: run, 65 | }; 66 | }); -------------------------------------------------------------------------------- /third_party/qunit-1.18.0.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * QUnit 1.18.0 3 | * http://qunitjs.com/ 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license 7 | * http://jquery.org/license 8 | * 9 | * Date: 2015-04-03T10:23Z 10 | */ 11 | 12 | /** Font Family and Sizes */ 13 | 14 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 15 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 16 | } 17 | 18 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 19 | #qunit-tests { font-size: smaller; } 20 | 21 | 22 | /** Resets */ 23 | 24 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 25 | margin: 0; 26 | padding: 0; 27 | } 28 | 29 | 30 | /** Header */ 31 | 32 | #qunit-header { 33 | padding: 0.5em 0 0.5em 1em; 34 | 35 | color: #8699A4; 36 | background-color: #0D3349; 37 | 38 | font-size: 1.5em; 39 | line-height: 1em; 40 | font-weight: 400; 41 | 42 | border-radius: 5px 5px 0 0; 43 | } 44 | 45 | #qunit-header a { 46 | text-decoration: none; 47 | color: #C2CCD1; 48 | } 49 | 50 | #qunit-header a:hover, 51 | #qunit-header a:focus { 52 | color: #FFF; 53 | } 54 | 55 | #qunit-testrunner-toolbar label { 56 | display: inline-block; 57 | padding: 0 0.5em 0 0.1em; 58 | } 59 | 60 | #qunit-banner { 61 | height: 5px; 62 | } 63 | 64 | #qunit-testrunner-toolbar { 65 | padding: 0.5em 1em 0.5em 1em; 66 | color: #5E740B; 67 | background-color: #EEE; 68 | overflow: hidden; 69 | } 70 | 71 | #qunit-userAgent { 72 | padding: 0.5em 1em 0.5em 1em; 73 | background-color: #2B81AF; 74 | color: #FFF; 75 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 76 | } 77 | 78 | #qunit-modulefilter-container { 79 | float: right; 80 | padding: 0.2em; 81 | } 82 | 83 | .qunit-url-config { 84 | display: inline-block; 85 | padding: 0.1em; 86 | } 87 | 88 | .qunit-filter { 89 | display: block; 90 | float: right; 91 | margin-left: 1em; 92 | } 93 | 94 | /** Tests: Pass/Fail */ 95 | 96 | #qunit-tests { 97 | list-style-position: inside; 98 | } 99 | 100 | #qunit-tests li { 101 | padding: 0.4em 1em 0.4em 1em; 102 | border-bottom: 1px solid #FFF; 103 | list-style-position: inside; 104 | } 105 | 106 | #qunit-tests > li { 107 | display: none; 108 | } 109 | 110 | #qunit-tests li.running, 111 | #qunit-tests li.pass, 112 | #qunit-tests li.fail, 113 | #qunit-tests li.skipped { 114 | display: list-item; 115 | } 116 | 117 | #qunit-tests.hidepass li.running, 118 | #qunit-tests.hidepass li.pass { 119 | visibility: hidden; 120 | position: absolute; 121 | width: 0px; 122 | height: 0px; 123 | padding: 0; 124 | border: 0; 125 | margin: 0; 126 | } 127 | 128 | #qunit-tests li strong { 129 | cursor: pointer; 130 | } 131 | 132 | #qunit-tests li.skipped strong { 133 | cursor: default; 134 | } 135 | 136 | #qunit-tests li a { 137 | padding: 0.5em; 138 | color: #C2CCD1; 139 | text-decoration: none; 140 | } 141 | 142 | #qunit-tests li p a { 143 | padding: 0.25em; 144 | color: #6B6464; 145 | } 146 | #qunit-tests li a:hover, 147 | #qunit-tests li a:focus { 148 | color: #000; 149 | } 150 | 151 | #qunit-tests li .runtime { 152 | float: right; 153 | font-size: smaller; 154 | } 155 | 156 | .qunit-assert-list { 157 | margin-top: 0.5em; 158 | padding: 0.5em; 159 | 160 | background-color: #FFF; 161 | 162 | border-radius: 5px; 163 | } 164 | 165 | .qunit-collapsed { 166 | display: none; 167 | } 168 | 169 | #qunit-tests table { 170 | border-collapse: collapse; 171 | margin-top: 0.2em; 172 | } 173 | 174 | #qunit-tests th { 175 | text-align: right; 176 | vertical-align: top; 177 | padding: 0 0.5em 0 0; 178 | } 179 | 180 | #qunit-tests td { 181 | vertical-align: top; 182 | } 183 | 184 | #qunit-tests pre { 185 | margin: 0; 186 | white-space: pre-wrap; 187 | word-wrap: break-word; 188 | } 189 | 190 | #qunit-tests del { 191 | background-color: #E0F2BE; 192 | color: #374E0C; 193 | text-decoration: none; 194 | } 195 | 196 | #qunit-tests ins { 197 | background-color: #FFCACA; 198 | color: #500; 199 | text-decoration: none; 200 | } 201 | 202 | /*** Test Counts */ 203 | 204 | #qunit-tests b.counts { color: #000; } 205 | #qunit-tests b.passed { color: #5E740B; } 206 | #qunit-tests b.failed { color: #710909; } 207 | 208 | #qunit-tests li li { 209 | padding: 5px; 210 | background-color: #FFF; 211 | border-bottom: none; 212 | list-style-position: inside; 213 | } 214 | 215 | /*** Passing Styles */ 216 | 217 | #qunit-tests li li.pass { 218 | color: #3C510C; 219 | background-color: #FFF; 220 | border-left: 10px solid #C6E746; 221 | } 222 | 223 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 224 | #qunit-tests .pass .test-name { color: #366097; } 225 | 226 | #qunit-tests .pass .test-actual, 227 | #qunit-tests .pass .test-expected { color: #999; } 228 | 229 | #qunit-banner.qunit-pass { background-color: #C6E746; } 230 | 231 | /*** Failing Styles */ 232 | 233 | #qunit-tests li li.fail { 234 | color: #710909; 235 | background-color: #FFF; 236 | border-left: 10px solid #EE5757; 237 | white-space: pre; 238 | } 239 | 240 | #qunit-tests > li:last-child { 241 | border-radius: 0 0 5px 5px; 242 | } 243 | 244 | #qunit-tests .fail { color: #000; background-color: #EE5757; } 245 | #qunit-tests .fail .test-name, 246 | #qunit-tests .fail .module-name { color: #000; } 247 | 248 | #qunit-tests .fail .test-actual { color: #EE5757; } 249 | #qunit-tests .fail .test-expected { color: #008000; } 250 | 251 | #qunit-banner.qunit-fail { background-color: #EE5757; } 252 | 253 | /*** Skipped tests */ 254 | 255 | #qunit-tests .skipped { 256 | background-color: #EBECE9; 257 | } 258 | 259 | #qunit-tests .qunit-skipped-label { 260 | background-color: #F4FF77; 261 | display: inline-block; 262 | font-style: normal; 263 | color: #366097; 264 | line-height: 1.8em; 265 | padding: 0 0.5em; 266 | margin: -0.4em 0.4em -0.4em 0; 267 | } 268 | 269 | /** Result */ 270 | 271 | #qunit-testresult { 272 | padding: 0.5em 1em 0.5em 1em; 273 | 274 | color: #2B81AF; 275 | background-color: #D2E0E6; 276 | 277 | border-bottom: 1px solid #FFF; 278 | } 279 | #qunit-testresult .module-name { 280 | font-weight: 700; 281 | } 282 | 283 | /** Fixture */ 284 | 285 | #qunit-fixture { 286 | position: absolute; 287 | top: -10000px; 288 | left: -10000px; 289 | width: 1000px; 290 | height: 1000px; 291 | } 292 | -------------------------------------------------------------------------------- /tools/httpd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright 2008, Google Inc. 3 | # All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are 7 | # met: 8 | # 9 | # * Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # * Redistributions in binary form must reproduce the above 12 | # copyright notice, this list of conditions and the following disclaimer 13 | # in the documentation and/or other materials provided with the 14 | # distribution. 15 | # * Neither the name of Google Inc. nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | """A tiny web server. 33 | 34 | This is intended to be used for testing, and 35 | only run from within the 36 | googleclient/native_client 37 | """ 38 | 39 | 40 | import BaseHTTPServer 41 | import logging 42 | import os 43 | import SimpleHTTPServer 44 | import SocketServer 45 | import sys 46 | 47 | logging.getLogger().setLevel(logging.INFO) 48 | 49 | # Using 'localhost' means that we only accept connections 50 | # via the loop back interface. 51 | SERVER_PORT = 7777 52 | SERVER_HOST = '' 53 | 54 | # We only run from the native_client directory, so that not too much 55 | # is exposed via this HTTP server. Everything in the directory is 56 | # served, so there should never be anything potentially sensitive in 57 | # the serving directory, especially if the machine might be a 58 | # multi-user machine and not all users are trusted. We only serve via 59 | # the loopback interface. 60 | 61 | # the sole purpose of this class is to make the BaseHTTPServer threaded 62 | class ThreadedServer(SocketServer.ThreadingMixIn, 63 | BaseHTTPServer.HTTPServer): 64 | pass 65 | 66 | 67 | def Run(server_address, 68 | server_class=ThreadedServer, 69 | handler_class=SimpleHTTPServer.SimpleHTTPRequestHandler): 70 | httpd = server_class(server_address, handler_class) 71 | logging.info('started server on port %d', httpd.server_address[1]) 72 | httpd.serve_forever() 73 | # enddef 74 | 75 | 76 | if __name__ == '__main__': 77 | if len(sys.argv) > 1: 78 | Run((SERVER_HOST, int(sys.argv[1]))) 79 | else: 80 | Run((SERVER_HOST, SERVER_PORT)) 81 | # endif 82 | # endif 83 | -------------------------------------------------------------------------------- /v8/backend.js: -------------------------------------------------------------------------------- 1 | define(["astutil", "compilerutil", "wasm/ast", "wasm/opinfo"], function(astutil, compilerutil, wast, opinfo) { 2 | var types = { 3 | "void": 0, 4 | "i32": 1, 5 | "i64": 2, 6 | "f32": 3, 7 | "f64": 4, 8 | }; 9 | 10 | // log2(num_bytes) | sgn << 2 11 | var mem_types = { 12 | "u8": 0, 13 | "u16": 1, 14 | "u32": 2, 15 | "u64": 3, 16 | "i8": 4, 17 | "i16": 5, 18 | "i32": 6, 19 | "i64": 7, 20 | "f32": 0, // Ignored 21 | "f64": 0, // Ignored 22 | }; 23 | 24 | var localTypeForMemType = { 25 | "i8": "i32", 26 | "i16": "i32", 27 | "i32": "i32", 28 | "i64": "i64", 29 | "f32": "f32", 30 | "f64": "f64", 31 | }; 32 | 33 | var ops = { 34 | if1: {bytecode: 0x01}, 35 | if2: {bytecode: 0x02}, 36 | block: {bytecode: 0x03}, 37 | 38 | loop: {bytecode: 0x06}, 39 | continue_: {bytecode: 0x07}, 40 | break_: {bytecode: 0x08}, 41 | return_: {bytecode: 0x09}, 42 | 43 | i8const: {bytecode: 0x10}, 44 | i32const: {bytecode: 0x11}, 45 | i64const: {bytecode: 0x12}, 46 | f64const: {bytecode: 0x13}, 47 | f32const: {bytecode: 0x14}, 48 | 49 | getlocal: {bytecode: 0x15}, 50 | setlocal: {bytecode: 0x16}, 51 | 52 | getglobal: {bytecode: 0x17}, 53 | setglobal: {bytecode: 0x18}, 54 | 55 | callfunc: {bytecode: 0x19}, 56 | callindirect: {bytecode: 0x1a}, 57 | 58 | i32fromf32: {bytecode: 0x9d}, 59 | i32fromf64: {bytecode: 0x9e}, 60 | 61 | i32fromi64: {bytecode: 0xa1}, 62 | 63 | i64fromf32: {bytecode: 0xa2}, 64 | i64fromf64: {bytecode: 0xa3}, 65 | 66 | i64fromi32: {bytecode: 0xa6}, 67 | 68 | 69 | f32fromi32: {bytecode: 0xa8}, 70 | f32fromu32: {bytecode: 0xa9}, 71 | f32fromi64: {bytecode: 0xaa}, 72 | f32fromu64: {bytecode: 0xab}, 73 | f32fromf64: {bytecode: 0xac}, 74 | 75 | f64fromi32: {bytecode: 0xae}, 76 | f64fromu32: {bytecode: 0xaf}, 77 | f64fromi64: {bytecode: 0xb0}, 78 | f64fromu64: {bytecode: 0xb1}, 79 | f64fromf32: {bytecode: 0xb2}, 80 | }; 81 | 82 | var unaryOpEncodingTable = [ 83 | {optype: "i32", op: "boolnot", bytecode: 0x5a}, 84 | {optype: "f32", op: "neg", bytecode: 0x7c}, 85 | {optype: "f32", op: "sqrt", bytecode: 0x82}, 86 | {optype: "f64", op: "neg", bytecode: 0x90}, 87 | {optype: "f64", op: "sqrt", bytecode: 0x96}, 88 | ]; 89 | 90 | var binaryOpEncodingTable = [ 91 | {optype: "i32", op: opinfo.binaryOps.add, bytecode: 0x40}, 92 | {optype: "i32", op: opinfo.binaryOps.sub, bytecode: 0x41}, 93 | {optype: "i32", op: opinfo.binaryOps.mul, bytecode: 0x42}, 94 | {optype: "i32", op: opinfo.binaryOps.sdiv, bytecode: 0x43}, 95 | {optype: "i32", op: opinfo.binaryOps.udiv, bytecode: 0x44}, 96 | {optype: "i32", op: opinfo.binaryOps.srem, bytecode: 0x45}, 97 | {optype: "i32", op: opinfo.binaryOps.urem, bytecode: 0x46}, 98 | {optype: "i32", op: opinfo.binaryOps.and, bytecode: 0x47}, 99 | {optype: "i32", op: opinfo.binaryOps.ior, bytecode: 0x48}, 100 | {optype: "i32", op: opinfo.binaryOps.xor, bytecode: 0x49}, 101 | {optype: "i32", op: opinfo.binaryOps.shl, bytecode: 0x4a}, 102 | {optype: "i32", op: opinfo.binaryOps.shr, bytecode: 0x4b}, 103 | {optype: "i32", op: opinfo.binaryOps.sar, bytecode: 0x4c}, 104 | {optype: "i32", op: opinfo.binaryOps.eq, bytecode: 0x4d}, 105 | {optype: "i32", op: opinfo.binaryOps.ne, bytecode: 0x4e}, 106 | {optype: "i32", op: opinfo.binaryOps.slt, bytecode: 0x4f}, 107 | {optype: "i32", op: opinfo.binaryOps.sle, bytecode: 0x50}, 108 | {optype: "i32", op: opinfo.binaryOps.ult, bytecode: 0x51}, 109 | {optype: "i32", op: opinfo.binaryOps.ule, bytecode: 0x52}, 110 | {optype: "i32", op: opinfo.binaryOps.sgt, bytecode: 0x53}, 111 | {optype: "i32", op: opinfo.binaryOps.sge, bytecode: 0x54}, 112 | {optype: "i32", op: opinfo.binaryOps.ugt, bytecode: 0x55}, 113 | {optype: "i32", op: opinfo.binaryOps.uge, bytecode: 0x56}, 114 | 115 | {optype: "i64", op: opinfo.binaryOps.add, bytecode: 0x5b}, 116 | {optype: "i64", op: opinfo.binaryOps.sub, bytecode: 0x5c}, 117 | {optype: "i64", op: opinfo.binaryOps.mul, bytecode: 0x5d}, 118 | {optype: "i64", op: opinfo.binaryOps.sdiv, bytecode: 0x5e}, 119 | {optype: "i64", op: opinfo.binaryOps.udiv, bytecode: 0x5f}, 120 | {optype: "i64", op: opinfo.binaryOps.srem, bytecode: 0x60}, 121 | {optype: "i64", op: opinfo.binaryOps.urem, bytecode: 0x61}, 122 | {optype: "i64", op: opinfo.binaryOps.and, bytecode: 0x62}, 123 | {optype: "i64", op: opinfo.binaryOps.ior, bytecode: 0x63}, 124 | {optype: "i64", op: opinfo.binaryOps.xor, bytecode: 0x64}, 125 | {optype: "i64", op: opinfo.binaryOps.shl, bytecode: 0x65}, 126 | {optype: "i64", op: opinfo.binaryOps.shr, bytecode: 0x66}, 127 | {optype: "i64", op: opinfo.binaryOps.sar, bytecode: 0x67}, 128 | {optype: "i64", op: opinfo.binaryOps.eq, bytecode: 0x68}, 129 | {optype: "i64", op: opinfo.binaryOps.ne, bytecode: 0x69}, 130 | {optype: "i64", op: opinfo.binaryOps.slt, bytecode: 0x6a}, 131 | {optype: "i64", op: opinfo.binaryOps.sle, bytecode: 0x6b}, 132 | {optype: "i64", op: opinfo.binaryOps.ult, bytecode: 0x6c}, 133 | {optype: "i64", op: opinfo.binaryOps.ule, bytecode: 0x6d}, 134 | {optype: "i64", op: opinfo.binaryOps.sgt, bytecode: 0x6e}, 135 | {optype: "i64", op: opinfo.binaryOps.sge, bytecode: 0x6f}, 136 | {optype: "i64", op: opinfo.binaryOps.ugt, bytecode: 0x70}, 137 | {optype: "i64", op: opinfo.binaryOps.uge, bytecode: 0x71}, 138 | 139 | {optype: "f32", op: opinfo.binaryOps.add, bytecode: 0x75}, 140 | {optype: "f32", op: opinfo.binaryOps.sub, bytecode: 0x76}, 141 | {optype: "f32", op: opinfo.binaryOps.mul, bytecode: 0x77}, 142 | {optype: "f32", op: opinfo.binaryOps.div, bytecode: 0x78}, 143 | {optype: "f32", op: opinfo.binaryOps.min, bytecode: 0x79}, 144 | {optype: "f32", op: opinfo.binaryOps.max, bytecode: 0x7a}, 145 | 146 | {optype: "f32", op: opinfo.binaryOps.eq, bytecode: 0x83}, 147 | {optype: "f32", op: opinfo.binaryOps.ne, bytecode: 0x84}, 148 | {optype: "f32", op: opinfo.binaryOps.lt, bytecode: 0x85}, 149 | {optype: "f32", op: opinfo.binaryOps.le, bytecode: 0x86}, 150 | {optype: "f32", op: opinfo.binaryOps.gt, bytecode: 0x87}, 151 | {optype: "f32", op: opinfo.binaryOps.ge, bytecode: 0x88}, 152 | 153 | {optype: "f64", op: opinfo.binaryOps.add, bytecode: 0x89}, 154 | {optype: "f64", op: opinfo.binaryOps.sub, bytecode: 0x8a}, 155 | {optype: "f64", op: opinfo.binaryOps.mul, bytecode: 0x8b}, 156 | {optype: "f64", op: opinfo.binaryOps.div, bytecode: 0x8c}, 157 | {optype: "f64", op: opinfo.binaryOps.min, bytecode: 0x8d}, 158 | {optype: "f64", op: opinfo.binaryOps.max, bytecode: 0x8e}, 159 | 160 | {optype: "f64", op: opinfo.binaryOps.eq, bytecode: 0x97}, 161 | {optype: "f64", op: opinfo.binaryOps.ne, bytecode: 0x98}, 162 | {optype: "f64", op: opinfo.binaryOps.lt, bytecode: 0x99}, 163 | {optype: "f64", op: opinfo.binaryOps.le, bytecode: 0x9a}, 164 | {optype: "f64", op: opinfo.binaryOps.gt, bytecode: 0x9b}, 165 | {optype: "f64", op: opinfo.binaryOps.ge, bytecode: 0x9c}, 166 | ]; 167 | 168 | var unaryOpMap = astutil.index(["optype", "op"], unaryOpEncodingTable); 169 | var binOpMap = astutil.index(["optype", "op"], binaryOpEncodingTable); 170 | 171 | 172 | var loadOpEncodingTable = [ 173 | {resulttype: "i32", addrtype: "i32", bytecode: 0x20}, 174 | {resulttype: "i64", addrtype: "i32", bytecode: 0x21}, 175 | {resulttype: "f32", addrtype: "i32", bytecode: 0x22}, 176 | {resulttype: "f64", addrtype: "i32", bytecode: 0x23}, 177 | {resulttype: "i32", addrtype: "i64", bytecode: 0x24}, 178 | {resulttype: "i64", addrtype: "i64", bytecode: 0x25}, 179 | {resulttype: "f32", addrtype: "i64", bytecode: 0x26}, 180 | {resulttype: "f64", addrtype: "i64", bytecode: 0x27}, 181 | ]; 182 | 183 | var storeOpEncodingTable = [ 184 | {resulttype: "i32", addrtype: "i32", bytecode: 0x30}, 185 | {resulttype: "i64", addrtype: "i32", bytecode: 0x31}, 186 | {resulttype: "f32", addrtype: "i32", bytecode: 0x32}, 187 | {resulttype: "f64", addrtype: "i32", bytecode: 0x33}, 188 | {resulttype: "i32", addrtype: "i64", bytecode: 0x34}, 189 | {resulttype: "i64", addrtype: "i64", bytecode: 0x35}, 190 | {resulttype: "f32", addrtype: "i64", bytecode: 0x36}, 191 | {resulttype: "f64", addrtype: "i64", bytecode: 0x37}, 192 | ]; 193 | 194 | 195 | var loadOpMap = astutil.index(["addrtype", "resulttype"], loadOpEncodingTable); 196 | var storeOpMap = astutil.index(["addrtype", "resulttype"], storeOpEncodingTable); 197 | 198 | var BinaryGenerator = function() { 199 | this.writer = new compilerutil.BinaryWriter(); 200 | }; 201 | 202 | BinaryGenerator.prototype.generateLocalRef = function(local) { 203 | this.writer.u8(local.remappedIndex); 204 | }; 205 | 206 | BinaryGenerator.prototype.generateFuncRef = function(func) { 207 | this.writer.u8(this.funcID[func.index]); 208 | }; 209 | 210 | BinaryGenerator.prototype.generateExternRef = function(func) { 211 | this.writer.u8(this.externID[func.index]); 212 | }; 213 | 214 | BinaryGenerator.prototype.generateExpr = function(expr) { 215 | switch (expr.type) { 216 | case "ConstI32": 217 | // Coerce because the value could have been specified as a big unsigned number. 218 | var value = expr.value | 0; 219 | if (-128 <= value && value <= 127) { 220 | // A more compact encoding for smaller numbers. 221 | this.writer.u8(ops.i8const.bytecode); 222 | this.writer.u8(value); 223 | } else { 224 | this.writer.u8(ops.i32const.bytecode); 225 | this.writer.i32(value); 226 | } 227 | break; 228 | case "ConstI64": 229 | this.writer.u8(ops.i64const.bytecode); 230 | this.writer.i64(expr.value); 231 | break; 232 | case "ConstF32": 233 | this.writer.u8(ops.f32const.bytecode); 234 | this.writer.f32(expr.value); 235 | break; 236 | case "ConstF64": 237 | this.writer.u8(ops.f64const.bytecode); 238 | this.writer.f64(expr.value); 239 | break; 240 | case "GetLocal": 241 | this.writer.u8(ops.getlocal.bytecode); 242 | this.generateLocalRef(expr.local); 243 | break; 244 | case "GetTls": 245 | throw Error("TLS not supported in V8 backend."); 246 | case "GetFunction": 247 | this.writer.u8(ops.i32const.bytecode); 248 | this.writer.i32(this.funcID[expr.func.index]); 249 | break; 250 | case "Load": 251 | var decl = loadOpMap["i32"][localTypeForMemType[expr.mtype]]; 252 | this.writer.u8(decl.bytecode); 253 | this.generateMemType(expr.mtype); 254 | this.generateExpr(expr.address); 255 | break; 256 | case "Store": 257 | var decl = storeOpMap["i32"][localTypeForMemType[expr.mtype]]; 258 | this.writer.u8(decl.bytecode); 259 | this.generateMemType(expr.mtype); 260 | this.generateExpr(expr.address); 261 | this.generateExpr(expr.value); 262 | break; 263 | case "Coerce": 264 | var src = expr.expr.etype; 265 | var dst = expr.mtype; 266 | switch (dst) { 267 | case "i8": 268 | switch (src) { 269 | case "i32": 270 | this.generateExpr(expr.expr); // HACK 271 | break; 272 | default: 273 | throw Error(dst + "<=" + src); 274 | } 275 | break; 276 | case "i16": 277 | switch (src) { 278 | case "i32": 279 | this.generateExpr(expr.expr); // HACK 280 | break; 281 | default: 282 | throw Error(dst + "<=" + src); 283 | } 284 | break; 285 | case "i32": 286 | switch (src) { 287 | case "i64": 288 | this.writer.u8(ops.i32fromi64.bytecode); 289 | this.generateExpr(expr.expr); 290 | break; 291 | case "f32": 292 | this.writer.u8(ops.i32fromf32.bytecode); 293 | this.generateExpr(expr.expr); 294 | break; 295 | case "f64": 296 | this.writer.u8(ops.i32fromf64.bytecode); 297 | this.generateExpr(expr.expr); 298 | break; 299 | default: 300 | throw Error(dst + "<=" + src); 301 | } 302 | break; 303 | case "i64": 304 | switch (src) { 305 | case "i32": 306 | this.writer.u8(ops.i64fromi32.bytecode); 307 | this.generateExpr(expr.expr); 308 | break; 309 | case "f32": 310 | this.writer.u8(ops.i64fromf32.bytecode); 311 | this.generateExpr(expr.expr); 312 | break; 313 | case "f64": 314 | this.writer.u8(ops.i64fromf64.bytecode); 315 | this.generateExpr(expr.expr); 316 | break; 317 | default: 318 | throw Error(dst + "<=" + src); 319 | } 320 | break; 321 | case "f32": 322 | switch (src) { 323 | case "i32": 324 | this.writer.u8(ops.f32fromi32.bytecode); 325 | this.generateExpr(expr.expr); 326 | break; 327 | case "i64": 328 | this.writer.u8(ops.f32fromi64.bytecode); 329 | this.generateExpr(expr.expr); 330 | break; 331 | case "f64": 332 | this.writer.u8(ops.f32fromf64.bytecode); 333 | this.generateExpr(expr.expr); 334 | break; 335 | default: 336 | throw Error(dst + "<=" + src); 337 | } 338 | break; 339 | case "f64": 340 | switch (src) { 341 | case "i32": 342 | this.writer.u8(ops.f64fromi32.bytecode); 343 | this.generateExpr(expr.expr); 344 | break; 345 | case "i64": 346 | this.writer.u8(ops.f64fromi64.bytecode); 347 | this.generateExpr(expr.expr); 348 | break; 349 | case "f32": 350 | this.writer.u8(ops.f64fromf32.bytecode); 351 | this.generateExpr(expr.expr); 352 | break; 353 | default: 354 | throw Error(dst + "<=" + src); 355 | } 356 | break; 357 | default: 358 | throw Error(dst + "<=" + src); 359 | } 360 | break; 361 | case "UnaryOp": 362 | if (!(expr.optype in unaryOpMap)) throw Error(expr.optype); 363 | var map = unaryOpMap[expr.optype]; 364 | if (!(expr.op in map)) throw Error(expr.optype + "." + expr.op); 365 | var op = map[expr.op]; 366 | this.writer.u8(op.bytecode); 367 | this.generateExpr(expr.expr); 368 | break; 369 | case "BinaryOp": 370 | if (!(expr.optype in binOpMap)) throw Error(expr.optype); 371 | var map = binOpMap[expr.optype]; 372 | if (!(expr.op in map)) throw Error(expr.optype + "." + expr.op); 373 | var op = map[expr.op]; 374 | 375 | this.writer.u8(op.bytecode); 376 | this.generateExpr(expr.left); 377 | this.generateExpr(expr.right); 378 | break; 379 | case "CallExternal": 380 | this.writer.u8(ops.callfunc.bytecode); 381 | this.generateExternRef(expr.func); 382 | // Number of arguments infered from target signature. 383 | for (var i in expr.args) { 384 | this.generateExpr(expr.args[i]); 385 | } 386 | break; 387 | case "CallDirect": 388 | this.writer.u8(ops.callfunc.bytecode); 389 | this.generateFuncRef(expr.func); 390 | // Number of arguments infered from target signature. 391 | for (var i in expr.args) { 392 | this.generateExpr(expr.args[i]); 393 | } 394 | break; 395 | case "CallIndirect": 396 | this.writer.u8(ops.callindirect.bytecode); 397 | var ft = expr.ftype; 398 | // TODO is there a better way to determine the signature of the function? 399 | this.generateSignature(ft.paramTypes, ft.returnType); 400 | this.generateExpr(expr.expr); 401 | // Number of arguments infered from target signature. 402 | for (var i in expr.args) { 403 | this.generateExpr(expr.args[i]); 404 | } 405 | break; 406 | case "Return": 407 | // Count infered from the function signature. 408 | this.writer.u8(ops.return_.bytecode); 409 | if (expr.expr) { 410 | this.generateExpr(expr.expr); 411 | } 412 | break; 413 | case "Break": 414 | this.writer.u8(ops.break_.bytecode); 415 | this.writer.u8(expr.depth); 416 | break; 417 | default: 418 | throw Error(expr.type); 419 | }; 420 | }; 421 | 422 | BinaryGenerator.prototype.generateStmt = function(expr) { 423 | switch (expr.type) { 424 | case "If": 425 | if (expr.f) { 426 | this.writer.u8(ops.if2.bytecode); 427 | this.generateExpr(expr.cond); 428 | this.generateBlock(expr.t); 429 | this.generateBlock(expr.f); 430 | } else { 431 | this.writer.u8(ops.if1.bytecode); 432 | this.generateExpr(expr.cond); 433 | this.generateBlock(expr.t); 434 | } 435 | break; 436 | case "Loop": 437 | this.writer.u8(ops.loop.bytecode); 438 | this.generateBlock(expr.body, true); // Loops have an implicit block, do not output a bytecode. 439 | break; 440 | case "SetLocal": 441 | this.writer.u8(ops.setlocal.bytecode); 442 | this.generateLocalRef(expr.local); 443 | this.generateExpr(expr.value); 444 | break; 445 | case "SetTls": 446 | throw Error("TLS not supported in V8 backend."); 447 | default: 448 | this.generateExpr(expr); 449 | }; 450 | }; 451 | 452 | BinaryGenerator.prototype.generateBlock = function(block, supress_bytecode) { 453 | if (!supress_bytecode) { 454 | this.writer.u8(ops.block.bytecode); 455 | } 456 | this.writer.u8(block.length); 457 | for (var i in block) { 458 | this.generateStmt(block[i]); 459 | }; 460 | }; 461 | 462 | BinaryGenerator.prototype.generateFunc = function(func) { 463 | this.func = func; 464 | this.generateBlock(func.body); 465 | }; 466 | 467 | BinaryGenerator.prototype.generateType = function(t) { 468 | if (!(t in types)) { 469 | throw Error(t); 470 | } 471 | this.writer.u8(types[t]); 472 | }; 473 | 474 | BinaryGenerator.prototype.generateMemType = function(t) { 475 | if (!(t in mem_types)) { 476 | throw Error(t); 477 | } 478 | this.writer.u8(mem_types[t]); 479 | }; 480 | 481 | 482 | BinaryGenerator.prototype.generateSignature = function(argTypes, returnType) { 483 | //if (returnType == "void") { 484 | // this.writer.u8(0); 485 | //} else { 486 | // this.writer.u8(1); 487 | // this.generateType(returnType); 488 | //} 489 | 490 | this.writer.u8(argTypes.length); 491 | this.generateType(returnType); 492 | for (var i in argTypes) { 493 | this.generateType(argTypes[i]); 494 | } 495 | }; 496 | 497 | BinaryGenerator.prototype.generateStringRef = function(value) { 498 | if (typeof value !== "string") { 499 | throw Error(value); 500 | } 501 | if (!(value in this.strings)) { 502 | this.strings[value] = []; 503 | } 504 | this.strings[value].push(this.writer.allocU32()); 505 | }; 506 | 507 | BinaryGenerator.prototype.generateStringTable = function() { 508 | for (var s in this.strings) { 509 | var refs = this.strings[s]; 510 | for (var i = 0; i < refs.length; i++) { 511 | this.writer.patchU32(refs[i], this.writer.pos); 512 | } 513 | var size = this.writer.utf8(s); 514 | this.writer.u8(0); // Null terminate strings. 515 | } 516 | }; 517 | 518 | BinaryGenerator.prototype.generateModule = function(module) { 519 | this.module = module; 520 | 521 | this.strings = {}; 522 | 523 | // Header 524 | var num_address_space_bits = Math.ceil(Math.log2(module.config.memory.fixed)); 525 | this.writer.u8(num_address_space_bits); 526 | this.writer.u8(1); // Export memory to polyfill _copyOut. 527 | this.writer.u16(0); // No globals. 528 | this.writer.u16(module.funcs.length + module.externs.length); 529 | this.writer.u16(module.memory.length); 530 | 531 | var funcBegin = {}; 532 | var funcEnd = {}; 533 | var dataBegin = {}; 534 | 535 | var uid = 0; 536 | 537 | this.externID = {}; 538 | this.funcID = {}; 539 | 540 | for (var i in module.externs) { 541 | var extern = module.externs[i]; 542 | var ft = extern.ftype; 543 | this.externID[i] = uid; 544 | uid += 1; 545 | 546 | this.generateSignature(ft.paramTypes, ft.returnType); 547 | this.generateStringRef(extern.name.text); 548 | 549 | this.writer.u32(0); // No offset 550 | this.writer.u32(0); // No offset 551 | 552 | this.writer.u16(0); // No i32 553 | this.writer.u16(0); // No i64 554 | this.writer.u16(0); // No f32 555 | this.writer.u16(0); // No f64 556 | 557 | this.writer.u8(0); 558 | this.writer.u8(1); // Not an extern. 559 | }; 560 | 561 | for (var f = 0; f < module.funcs.length; f++) { 562 | var func = module.funcs[f]; 563 | 564 | var localIndex = 0; 565 | 566 | // Parameters are assigned the first indexes. 567 | for(var p = 0; p < func.params.length; p++) { 568 | var param = func.params[p]; 569 | var l = param.local; 570 | l.param = true; 571 | l.remappedIndex = localIndex; 572 | localIndex += 1; 573 | } 574 | 575 | // Bucket locals by type. 576 | var i32Locals = []; 577 | var i64Locals = []; 578 | var f32Locals = []; 579 | var f64Locals = []; 580 | for (var i in func.locals) { 581 | var l = func.locals[i]; 582 | // Parameters are not considered locals. 583 | if (l.param) continue; 584 | 585 | switch (l.ltype) { 586 | case "i32": 587 | i32Locals.push(l); 588 | break; 589 | case "i64": 590 | i64Locals.push(l); 591 | break; 592 | case "f32": 593 | f32Locals.push(l); 594 | break; 595 | case "f64": 596 | f64Locals.push(l); 597 | break; 598 | default: 599 | throw Error(l.ltype); 600 | } 601 | } 602 | 603 | // Recalculate index, when bucked by types. 604 | for (var i in i32Locals) { 605 | i32Locals[i].remappedIndex = localIndex; 606 | localIndex += 1; 607 | } 608 | for (var i in i64Locals) { 609 | i64Locals[i].remappedIndex = localIndex; 610 | localIndex += 1; 611 | } 612 | for (var i in f32Locals) { 613 | f32Locals[i].remappedIndex = localIndex; 614 | localIndex += 1; 615 | } 616 | for (var i in f64Locals) { 617 | f64Locals[i].remappedIndex = localIndex; 618 | localIndex += 1; 619 | } 620 | 621 | this.funcID[f] = uid; 622 | uid += 1; 623 | 624 | var argTypes = []; 625 | for(var j in func.params) { 626 | argTypes.push(func.params[j].ptype); 627 | } 628 | this.generateSignature(argTypes, func.returnType); 629 | 630 | this.generateStringRef(func.name.text); 631 | 632 | funcBegin[f] = this.writer.allocU32(); 633 | funcEnd[f] = this.writer.allocU32(); 634 | 635 | this.writer.u16(i32Locals.length); 636 | this.writer.u16(i64Locals.length); 637 | this.writer.u16(f32Locals.length); 638 | this.writer.u16(f64Locals.length); 639 | 640 | this.writer.u8(func.exportFunc | 0); 641 | this.writer.u8(0); // Not an extern. 642 | }; 643 | 644 | for (var m in module.memory) { 645 | var mem = module.memory[m]; 646 | this.writer.u32(mem.ptr); 647 | dataBegin[mem.ptr] = this.writer.allocU32(); 648 | this.writer.u32(mem.buffer.byteLength); 649 | this.writer.u8(1); 650 | } 651 | 652 | for (var f in module.funcs) { 653 | this.writer.patchU32(funcBegin[f], this.writer.pos); 654 | this.generateFunc(module.funcs[f]); 655 | this.writer.patchU32(funcEnd[f], this.writer.pos); 656 | }; 657 | 658 | for (var m in module.memory) { 659 | var mem = module.memory[m]; 660 | this.writer.patchU32(dataBegin[mem.ptr], this.writer.pos); 661 | this.writer.bytes(mem.buffer); 662 | } 663 | 664 | this.generateStringTable(); 665 | }; 666 | 667 | var generate = function(module) { 668 | var gen = new BinaryGenerator(); 669 | gen.generateModule(module); 670 | return gen.writer.getOutput(); 671 | }; 672 | 673 | var generateFuncs = function(module) { 674 | var table = {}; 675 | for (var f in module.funcs) { 676 | var func = module.funcs[f]; 677 | var gen = new BinaryGenerator(); 678 | gen.generateFunc(func); 679 | table[func.name.text] = gen.writer.getOutput(); 680 | } 681 | return table; 682 | }; 683 | 684 | return { 685 | generate: generate, 686 | generateFuncs: generateFuncs, 687 | }; 688 | }); 689 | -------------------------------------------------------------------------------- /wasm.pegjs: -------------------------------------------------------------------------------- 1 | { 2 | // HACK 3 | var wast = options.wast; 4 | 5 | function buildList(first, rest) { 6 | return [first].concat(rest); 7 | } 8 | 9 | function buildBinaryExpr(first, rest) { 10 | var e = first; 11 | for (var i in rest) { 12 | e = wast.InfixOp({ 13 | left: e, 14 | op: rest[i][1], 15 | right: rest[i][3], 16 | }); 17 | } 18 | return e; 19 | } 20 | 21 | function buildCallExpr(first, rest) { 22 | var e = first; 23 | for (var i in rest) { 24 | e = wast.Call({ 25 | expr: e, 26 | args: rest[i], 27 | }); 28 | } 29 | return e; 30 | } 31 | 32 | // Line 1 indexed, column 0 indexed. 33 | function getPos() { 34 | return {line: line(), column: column() - 1}; 35 | } 36 | } 37 | 38 | start = module 39 | 40 | whitespace "whitespace" = [ \t\r\n] 41 | 42 | comment "comment" = "//" [^\n]* 43 | 44 | S = (whitespace / comment)* 45 | 46 | EOT = ![a-zA-Z0-9_] 47 | 48 | keyword = ("if" / "func" / "memory" / "return" / "export" / "import" / "var" / "align" / "config" / "switch" / "case" / "default" / "while" / "loop" / "break") EOT 49 | 50 | identText = $([a-zA-Z_][a-zA-Z0-9_]*) 51 | 52 | ident "identifier" = !keyword text: identText { 53 | return wast.Identifier({ 54 | text: text, 55 | pos: getPos(), 56 | }); 57 | } 58 | 59 | mtypeU = "I32" {return "i32";} / "I64" {return "i64";} / "F32" {return "f32";} / "F64" {return "f64";} / "I8" {return "i8";} / "I16" {return "i16";} 60 | 61 | mtypeL = "i32" {return "i32";} / "i64" {return "i64";} / "f32" {return "f32";} / "f64" {return "f64";} / "i8" {return "i8";} / "i16" {return "i16";} 62 | 63 | 64 | loadExpr 65 | = "load" t:mtypeU S "(" S addr:expr S ")" { 66 | return wast.Load({ 67 | mtype: t, 68 | address: addr, 69 | pos: getPos(), 70 | }); 71 | } 72 | 73 | storeExpr 74 | = "store" t:mtypeU S "(" S addr:expr S "," S value:expr S ")" { 75 | return wast.Store({ 76 | mtype: t, 77 | address: addr, 78 | value: value, 79 | pos: getPos(), 80 | }); 81 | } 82 | 83 | coerceExpr 84 | = t:mtypeL S "(" S expr:expr S ")" { 85 | return wast.Coerce({ 86 | mtype: t, 87 | expr: expr, 88 | pos: getPos(), 89 | }); 90 | } 91 | 92 | 93 | number "number" = digits:$([0-9]+) { 94 | return +digits; 95 | } 96 | 97 | hexDigit "hex digit" = [0-9a-fA-F] 98 | 99 | constant 100 | = "0x" digits:$(hexDigit+) { 101 | return wast.ConstI32({ 102 | value: parseInt(digits, 16), 103 | pos: getPos(), 104 | }); 105 | } 106 | / digits:($([0-9]+ "." [0-9]*)) "f" { 107 | return wast.ConstF32({ 108 | value: Math.fround(+digits), 109 | pos: getPos(), 110 | }) 111 | } 112 | / digits:($([0-9]+ "." [0-9]*)) { 113 | return wast.ConstF64({ 114 | value: +digits, 115 | pos: getPos(), 116 | }) 117 | } 118 | / n:number { 119 | return wast.ConstI32({ 120 | value: n, 121 | pos: getPos(), 122 | }) 123 | } 124 | 125 | indirectCall = ftype:funcType S "(" S expr:expr S ")" S "(" S args:exprList S ")" { 126 | return wast.CallIndirect({ 127 | ftype: ftype, 128 | expr: expr, 129 | args: args, 130 | pos: getPos(), 131 | }); 132 | } 133 | 134 | atom 135 | = constant 136 | / indirectCall 137 | / "(" S e:expr S ")" {return e;} 138 | / loadExpr 139 | / storeExpr 140 | / coerceExpr 141 | / name:ident {return wast.GetName({name: name})} 142 | 143 | callExpr = first:atom rest:(S "(" S args:exprList S ")" {return args;})* {return buildCallExpr(first, rest);} 144 | 145 | prefixExpr = op:("!"/"~"/"+"/"-") S expr:prefixExpr {return wast.PrefixOp({op: op, expr: expr, pos: getPos()});} / callExpr 146 | 147 | mulOp = text:$("*"/"/"/"%") { 148 | return wast.Identifier({ 149 | text: text, 150 | pos: getPos(), 151 | }); 152 | } 153 | 154 | mulExpr = first:prefixExpr rest:(S mulOp S prefixExpr)* {return buildBinaryExpr(first, rest);} 155 | 156 | addOp = text:$("+"/"-") { 157 | return wast.Identifier({ 158 | text: text, 159 | pos: getPos(), 160 | }); 161 | } 162 | 163 | addExpr = first:mulExpr rest:(S addOp S mulExpr)* {return buildBinaryExpr(first, rest);} 164 | 165 | shiftOp = text:$("<<"/">>"/">>>") { 166 | return wast.Identifier({ 167 | text: text, 168 | pos: getPos(), 169 | }); 170 | } 171 | 172 | shiftExpr = first:addExpr rest:(S shiftOp S addExpr)* {return buildBinaryExpr(first, rest);} 173 | 174 | compareOp = text:$("<="/"<"/">="/">") { 175 | return wast.Identifier({ 176 | text: text, 177 | pos: getPos(), 178 | }); 179 | } 180 | 181 | compareExpr = first:shiftExpr rest:(S compareOp S shiftExpr)* {return buildBinaryExpr(first, rest);} 182 | 183 | equalOp = text:$("=="/"!=") { 184 | return wast.Identifier({ 185 | text: text, 186 | pos: getPos(), 187 | }); 188 | } 189 | 190 | equalExpr = first:compareExpr rest:(S equalOp S compareExpr)* {return buildBinaryExpr(first, rest);} 191 | 192 | bitwiseAndOp = text:$("&") { 193 | return wast.Identifier({ 194 | text: text, 195 | pos: getPos(), 196 | }); 197 | } 198 | 199 | bitwiseAndExpr = first:equalExpr rest:(S bitwiseAndOp S equalExpr)* {return buildBinaryExpr(first, rest);} 200 | 201 | bitwiseXorOp = text:$("^") { 202 | return wast.Identifier({ 203 | text: text, 204 | pos: getPos(), 205 | }); 206 | } 207 | 208 | bitwiseXorExpr = first:bitwiseAndExpr rest:(S bitwiseXorOp S bitwiseAndExpr)* {return buildBinaryExpr(first, rest);} 209 | 210 | bitwiseOrOp = text:$("|") { 211 | return wast.Identifier({ 212 | text: text, 213 | pos: getPos(), 214 | }); 215 | } 216 | 217 | bitwiseOrExpr = first:bitwiseXorExpr rest:(S bitwiseOrOp S bitwiseXorExpr)* {return buildBinaryExpr(first, rest);} 218 | 219 | expr = bitwiseOrExpr 220 | 221 | exprList = (first:expr rest:(S "," S e:expr {return e;})* {return buildList(first, rest);} / {return [];} ) 222 | 223 | stmt 224 | = s:( 225 | ("if" EOT S "(" S cond:expr S ")" 226 | S "{" S t:body S "}" 227 | f:(S "else" S "{" S f:body S "}" {return f;} / {return null;}) { 228 | return wast.If({ 229 | cond:cond, 230 | t: t, 231 | f: f, 232 | pos: getPos(), 233 | }); 234 | }) 235 | /("while" EOT S "(" S cond:expr S ")" 236 | S "{" S b:body S "}" { 237 | return wast.While({ 238 | cond:cond, 239 | body: b, 240 | pos: getPos(), 241 | }); 242 | }) 243 | /("loop" EOT 244 | S "{" S b:body S "}" { 245 | return wast.Loop({ 246 | body: b, 247 | }); 248 | }) 249 | /("return" EOT S e:(expr/{return null}) S ";" { 250 | return wast.Return({ 251 | expr: e, 252 | pos: getPos(), 253 | }); 254 | }) 255 | / ("var" EOT S name:ident S type:typeRef 256 | value:(S "=" S e:expr {return e;} / {return null;}) S ";" { 257 | return wast.VarDecl({ 258 | name: name, 259 | vtype: type, 260 | value: value, 261 | pos: getPos(), 262 | }); 263 | }) 264 | / ("break" EOT S name:ident S ";" { 265 | return wast.BreakToLabel({ 266 | name: name, 267 | }); 268 | }) 269 | / (name:ident S ":" S stmt:stmt { 270 | return wast.Label({ 271 | name: name, 272 | stmt: stmt 273 | }); 274 | }) 275 | / (name:ident S "=" S value:expr S ";" { 276 | return wast.SetName({ 277 | name: name, 278 | value: value, 279 | }); 280 | }) 281 | / e:expr S ";" {return e;} 282 | ) {return s} 283 | 284 | body = (S stmt:stmt {return stmt})* 285 | 286 | typeRef = name:ident { 287 | return wast.TypeName({name: name}); 288 | } 289 | 290 | returnType = typeRef 291 | 292 | optionalExport = "export" EOT {return true} / {return false} 293 | 294 | param = name:ident S type:typeRef { 295 | return wast.Param({ 296 | name: name, 297 | ptype: type 298 | }); 299 | } 300 | 301 | paramList = (first:param rest:(S "," S p:param {return p;})* {return buildList(first, rest);} / {return [];} ) 302 | 303 | funcdecl = e:optionalExport S "func" EOT S name:ident S "(" S params:paramList S ")" S returnType:returnType S "{" S body:body S "}" { 304 | return wast.Function({ 305 | exportFunc: e, 306 | name: name, 307 | params: params, 308 | returnType: returnType, 309 | locals: [], 310 | body: body, 311 | pos: getPos(), 312 | }) 313 | } 314 | 315 | constExpr = constant 316 | 317 | configItem = path:(first:identText rest:(S "." S i:identText {return i;})* {return buildList(first, rest);} / {return [];} ) S ":" S value: constExpr { 318 | return wast.ConfigItem({ 319 | path: path, 320 | value: value 321 | }) 322 | } 323 | 324 | configItemList = (first:configItem rest:(S "," S i:configItem {return i;})* {return buildList(first, rest);} / {return [];} ) 325 | 326 | config = "config" EOT S "{" S items:configItemList S "}" { 327 | return wast.ConfigDecl({ 328 | items:items 329 | }) 330 | } 331 | 332 | typeList = (first:typeRef rest:(S "," S t:typeRef {return t;})* {return buildList(first, rest);} / {return [];} ) 333 | 334 | funcType = "(" S params:typeList S ")" S r:returnType { 335 | return wast.FuncType({ 336 | paramTypes: params, 337 | returnType: r, 338 | }); 339 | } 340 | 341 | import = "import" EOT S "func" EOT S name:ident S ftype:funcType S ";" { 342 | return wast.Extern({ 343 | name: name, 344 | ftype: ftype, 345 | pos: getPos(), 346 | }); 347 | } 348 | 349 | tls = "tls" EOT S name:ident S t:typeRef S ";" { 350 | return wast.TlsDecl({ 351 | name: name, 352 | mtype: t, 353 | }); 354 | } 355 | 356 | memoryAlign = "align" EOT S size:number S ";" { 357 | return wast.MemoryAlign({size: size}) 358 | } 359 | 360 | memoryLabel = name:ident S ":" { 361 | return wast.MemoryLabel({name: name}) 362 | } 363 | 364 | memoryZero = "zero" EOT S size:number S ";" { 365 | return wast.MemoryZero({size: size}) 366 | } 367 | 368 | hexByte = text:$(hexDigit hexDigit) {return parseInt(text, 16);} 369 | 370 | hexData = (first:hexByte rest:(S d:hexByte {return d;})* {return buildList(first, rest);}) / {return [];} 371 | 372 | memoryHex = "hex" EOT S data:hexData S ";" {return wast.MemoryHex({data: data});} 373 | 374 | stringByte = text:("\\" '"' {return '"';} / $[^\\"] ) {return text.charCodeAt(0);} 375 | 376 | stringData = (first:stringByte rest:(d:stringByte {return d;})* {return buildList(first, rest).concat([0]);}) / {return [0];} 377 | 378 | // HACK desugar to MemoryHex 379 | memoryString = "string" EOT S '"' data:stringData '"' S ";" {return wast.MemoryHex({data: data});} 380 | 381 | memoryDirective = memoryAlign / memoryZero / memoryHex / memoryString / memoryLabel 382 | 383 | memoryDirectiveList = (first:memoryDirective rest:(S d:memoryDirective {return d;})* {return buildList(first, rest);}) / {return [];} 384 | 385 | memoryDecl = "memory" EOT S "{" S d:memoryDirectiveList S "}" { 386 | return wast.MemoryDecl({ 387 | directives: d, 388 | }); 389 | } 390 | 391 | decl = funcdecl / import / memoryDecl / tls / config 392 | 393 | declList = (first:decl rest:(S d:decl {return d;})* {return buildList(first, rest);}) / {return [];} 394 | 395 | module = S decls:declList S { 396 | return wast.ParsedModule({ 397 | decls: decls, 398 | }) 399 | } -------------------------------------------------------------------------------- /wasm/ast.js: -------------------------------------------------------------------------------- 1 | define(['astutil'], function(astutil) { 2 | var schema = [ 3 | { 4 | name: "TypeName", 5 | fields: [ 6 | {name: "name"}, 7 | ], 8 | }, 9 | { 10 | name: "ConstI32", 11 | fields: [ 12 | {name: "value"}, 13 | {name: "pos"}, 14 | ], 15 | }, 16 | { 17 | name: "ConstI64", 18 | fields: [ 19 | {name: "value"}, // TODO this is imprecise. 20 | {name: "pos"}, 21 | ], 22 | }, 23 | { 24 | name: "ConstF32", 25 | fields: [ 26 | {name: "value"}, 27 | {name: "pos"}, 28 | ], 29 | }, 30 | { 31 | name: "ConstF64", 32 | fields: [ 33 | {name: "value"}, 34 | {name: "pos"}, 35 | ], 36 | }, 37 | { 38 | name: "Identifier", 39 | fields: [ 40 | {name: "text"}, 41 | {name: "pos"}, 42 | ], 43 | }, 44 | { 45 | name: "GetName", 46 | fields: [ 47 | {name: "name"}, 48 | ], 49 | }, 50 | { 51 | name: "SetName", 52 | fields: [ 53 | {name: "name"}, 54 | {name: "value"}, 55 | ], 56 | }, 57 | { 58 | name: "GetFunction", 59 | fields: [ 60 | {name: "func"}, 61 | {name: "pos"}, 62 | ], 63 | }, 64 | { 65 | name: "GetExtern", 66 | fields: [ 67 | {name: "func"}, 68 | {name: "pos"}, 69 | ], 70 | }, 71 | { 72 | name: "GetIntrinsic", 73 | fields: [ 74 | {name: "func"}, 75 | {name: "pos"}, 76 | ], 77 | }, 78 | { 79 | name: "GetLocal", 80 | fields: [ 81 | {name: "local"}, 82 | {name: "pos"}, 83 | ], 84 | }, 85 | { 86 | name: "SetLocal", 87 | fields: [ 88 | {name: "local"}, 89 | {name: "value"}, 90 | {name: "pos"}, 91 | ], 92 | }, 93 | { 94 | name: "GetTls", 95 | fields: [ 96 | {name: "tls"}, 97 | {name: "pos"}, 98 | ], 99 | }, 100 | { 101 | name: "SetTls", 102 | fields: [ 103 | {name: "tls"}, 104 | {name: "value"}, 105 | {name: "pos"}, 106 | ], 107 | }, 108 | { 109 | name: "Load", 110 | fields: [ 111 | {name: "mtype"}, 112 | {name: "address"}, 113 | {name: "pos"}, 114 | ], 115 | }, 116 | { 117 | name: "Store", 118 | fields: [ 119 | {name: "mtype"}, 120 | {name: "address"}, 121 | {name: "value"}, 122 | {name: "pos"}, 123 | ], 124 | }, 125 | { 126 | name: "Coerce", 127 | fields: [ 128 | {name: "mtype"}, 129 | {name: "expr"}, 130 | {name: "pos"}, 131 | ], 132 | }, 133 | { 134 | name: "PrefixOp", 135 | fields: [ 136 | {name: "op"}, 137 | {name: "expr"}, 138 | {name: "pos"}, 139 | ], 140 | }, 141 | { 142 | name: "InfixOp", 143 | fields: [ 144 | {name: "left"}, 145 | {name: "op"}, 146 | {name: "right"}, 147 | ], 148 | }, 149 | { 150 | name: "UnaryOp", 151 | fields: [ 152 | {name: "optype"}, 153 | {name: "op"}, 154 | {name: "expr"}, 155 | ], 156 | }, 157 | { 158 | name: "BinaryOp", 159 | fields: [ 160 | {name: "optype"}, 161 | {name: "op"}, 162 | {name: "left"}, 163 | {name: "right"}, 164 | ], 165 | }, 166 | { 167 | name: "Call", 168 | fields: [ 169 | {name: "expr"}, 170 | {name: "args"}, 171 | ], 172 | }, 173 | { 174 | name: "CallDirect", 175 | fields: [ 176 | {name: "func"}, 177 | {name: "args"}, 178 | {name: "pos"}, 179 | ], 180 | }, 181 | { 182 | name: "CallExternal", 183 | fields: [ 184 | {name: "func"}, 185 | {name: "args"}, 186 | {name: "pos"}, 187 | ], 188 | }, 189 | { 190 | name: "CallIndirect", 191 | fields: [ 192 | {name: "ftype"}, 193 | {name: "expr"}, 194 | {name: "args"}, 195 | {name: "pos"}, 196 | ], 197 | }, 198 | { 199 | name: "VarDecl", 200 | fields: [ 201 | {name: "name"}, 202 | {name: "vtype"}, 203 | {name: "value"}, 204 | {name: "pos"}, 205 | ], 206 | }, 207 | { 208 | name: "Return", 209 | fields: [ 210 | {name: "expr"}, 211 | {name: "pos"}, 212 | ], 213 | }, 214 | { 215 | name: "Label", 216 | fields: [ 217 | {name: "name"}, 218 | {name: "stmt"}, 219 | ], 220 | }, 221 | { 222 | name: "BreakToLabel", 223 | fields: [ 224 | {name: "name"}, 225 | ], 226 | }, 227 | { 228 | name: "Break", 229 | fields: [ 230 | {name: "depth"}, 231 | ], 232 | }, 233 | { 234 | name: "If", 235 | fields: [ 236 | {name: "cond"}, 237 | {name: "t"}, 238 | {name: "f"}, 239 | {name: "pos"}, 240 | ], 241 | }, 242 | { 243 | name: "While", 244 | fields: [ 245 | {name: "cond"}, 246 | {name: "body"}, 247 | {name: "pos"}, 248 | ], 249 | }, 250 | { 251 | name: "Loop", 252 | fields: [ 253 | {name: "body"}, 254 | ], 255 | }, 256 | { 257 | name: "Param", 258 | fields: [ 259 | {name: "name"}, 260 | {name: "ptype"}, 261 | ], 262 | }, 263 | { 264 | name: "Local", 265 | fields: [ 266 | {name: "name"}, 267 | {name: "ltype"}, 268 | {name: "index"}, 269 | ], 270 | }, 271 | { 272 | name: "Function", 273 | fields: [ 274 | {name: "exportFunc"}, 275 | {name: "name"}, 276 | {name: "params"}, 277 | {name: "returnType"}, 278 | {name: "locals"}, 279 | {name: "body"}, 280 | {name: "pos"}, 281 | ], 282 | }, 283 | { 284 | name: "FuncType", 285 | fields: [ 286 | {name: "paramTypes"}, 287 | {name: "returnType"}, 288 | ], 289 | }, 290 | { 291 | name: "Extern", 292 | fields: [ 293 | {name: "name"}, 294 | {name: "ftype"}, 295 | {name: "pos"}, 296 | ], 297 | }, 298 | { 299 | name: "TlsDecl", 300 | fields: [ 301 | {name: "name"}, 302 | {name: "mtype"}, 303 | ], 304 | }, 305 | { 306 | name: "MemoryAlign", 307 | fields: [ 308 | {name: "size"}, 309 | ], 310 | }, 311 | { 312 | name: "MemoryLabel", 313 | fields: [ 314 | {name: "name"}, 315 | ], 316 | }, 317 | { 318 | name: "MemoryZero", 319 | fields: [ 320 | {name: "size"}, 321 | ], 322 | }, 323 | { 324 | name: "MemoryHex", 325 | fields: [ 326 | {name: "data"}, 327 | ], 328 | }, 329 | { 330 | name: "MemoryDecl", 331 | fields: [ 332 | {name: "directives"}, 333 | ], 334 | }, 335 | { 336 | name: "ConfigItem", 337 | fields: [ 338 | {name: "path"}, 339 | {name: "value"}, 340 | ], 341 | }, 342 | { 343 | name: "ConfigDecl", 344 | fields: [ 345 | {name: "items"}, 346 | ], 347 | }, 348 | { 349 | name: "Module", 350 | fields: [ 351 | {name: "config"}, 352 | {name: "externs"}, 353 | {name: "funcs"}, 354 | {name: "tls"}, 355 | {name: "memory"}, 356 | {name: "top"}, 357 | ], 358 | }, 359 | { 360 | name: "ParsedModule", 361 | fields: [ 362 | {name: "decls"}, 363 | ], 364 | }, 365 | ]; 366 | 367 | return astutil.makeASTBuilder(schema); 368 | }); 369 | -------------------------------------------------------------------------------- /wasm/dce.js: -------------------------------------------------------------------------------- 1 | define(["wasm/traverse"], function(traverse) { 2 | var FindLive = function() { 3 | }; 4 | 5 | FindLive.prototype.markLiveFunc = function(func) { 6 | if (!this.liveFunc[func.index]) { 7 | this.liveFunc[func.index] = true; 8 | this.pending.push(func); 9 | } 10 | }; 11 | 12 | FindLive.prototype.markLiveExtern = function(func) { 13 | if (!this.liveExtern[func.index]) { 14 | this.liveExtern[func.index] = true; 15 | } 16 | }; 17 | 18 | FindLive.prototype.processExprPost = function(node) { 19 | switch(node.type) { 20 | case "GetFunction": 21 | this.markLiveFunc(node.func); 22 | break; 23 | case "GetExtern": 24 | this.markLiveExtern(node.func); 25 | break; 26 | case "CallDirect": 27 | this.markLiveFunc(node.func); 28 | break; 29 | case "CallExternal": 30 | this.markLiveExtern(node.func); 31 | break; 32 | } 33 | return node; 34 | }; 35 | 36 | FindLive.prototype.processStmtPost = function(node, out) { 37 | out.push(this.processExprPost(node)); 38 | }; 39 | 40 | FindLive.prototype.run = function(module) { 41 | this.liveFunc = []; 42 | for (var i = 0; i < module.funcs.length; i++) { 43 | this.liveFunc.push(false); 44 | } 45 | this.liveExtern = []; 46 | for (var i = 0; i < module.externs.length; i++) { 47 | this.liveExtern.push(false); 48 | } 49 | 50 | this.pending = []; 51 | 52 | // Exports are always live. 53 | for (var i = 0; i < module.funcs.length; i++) { 54 | var func = module.funcs[i]; 55 | if (func.exportFunc) { 56 | this.markLiveFunc(func); 57 | } 58 | } 59 | 60 | // Iterate until we've found everything that is live. 61 | while (this.pending.length) { 62 | this.traverse.processFunc(this.pending.shift()); 63 | } 64 | 65 | // Keep only the live funcs. 66 | var culledFuncs = []; 67 | for (var i = 0; i < module.funcs.length; i++) { 68 | if (this.liveFunc[i]) { 69 | var func = module.funcs[i]; 70 | func.index = culledFuncs.length; 71 | culledFuncs.push(func); 72 | } 73 | } 74 | module.funcs = culledFuncs; 75 | 76 | // Keep only the live externs. 77 | var culledExterns = []; 78 | for (var i = 0; i < module.externs.length; i++) { 79 | if (this.liveExtern[i]) { 80 | var func = module.externs[i]; 81 | func.index = culledExterns.length; 82 | culledExterns.push(func); 83 | } 84 | } 85 | module.externs = culledExterns; 86 | } 87 | 88 | var process = function(module) { 89 | var finder = new FindLive(); 90 | finder.traverse = new traverse.TopDownBottomUp(finder); 91 | finder.run(module); 92 | return module; 93 | }; 94 | 95 | return {process: process}; 96 | }); -------------------------------------------------------------------------------- /wasm/desugar.js: -------------------------------------------------------------------------------- 1 | define(["wasm/ast", "wasm/traverse", "wasm/opinfo"], function(wast, traverse, opinfo) { 2 | 3 | var simplified_type = { 4 | "i8": "i32", 5 | "i16": "i32", 6 | }; 7 | 8 | var naturallyBool = function(node) { 9 | if (node.etype != "i32") return false; 10 | switch (node.type) { 11 | case "ConstI32": 12 | return node.value === 0 || node.value === 1; 13 | case "UnaryOp": 14 | return node.op == "boolnot"; 15 | case "BinaryOp": 16 | return opinfo.isCompareOp(node.op); 17 | default: 18 | return false; 19 | } 20 | }; 21 | 22 | var peelBoolNot = function(node) { 23 | if (node.expr.type == "UnaryOp" && node.expr.op == "boolnot" && naturallyBool(node.expr.expr)) { 24 | return node.expr.expr; 25 | } 26 | return node; 27 | }; 28 | 29 | var Desugar = function() { 30 | }; 31 | 32 | Desugar.prototype.not = function(expr) { 33 | switch(expr.etype) { 34 | case "i32": 35 | expr = wast.UnaryOp({ 36 | optype: "i32", 37 | op: "boolnot", 38 | expr: expr, 39 | }); 40 | expr.etype = "i32"; 41 | return peelBoolNot(expr); 42 | case "i64": 43 | node = wast.BinaryOp({ 44 | optype: "i64", 45 | op: opinfo.binaryOps.eq, 46 | left: expr, 47 | right: this.constI64(0), 48 | }); 49 | node.etype = "i64"; 50 | return node; 51 | default: 52 | throw Error(expr.etype); 53 | } 54 | }; 55 | 56 | Desugar.prototype.constI32 = function(value) { 57 | var node = wast.ConstI32({ 58 | value: value, 59 | pos: null, 60 | }); 61 | node.etype = "i32"; 62 | return node; 63 | }; 64 | 65 | Desugar.prototype.constI64 = function(value) { 66 | var node = wast.ConstI64({ 67 | value: value, 68 | pos: null, 69 | }); 70 | node.etype = "i64"; 71 | return node; 72 | }; 73 | 74 | Desugar.prototype.constF32 = function(value) { 75 | var node = wast.ConstF32({ 76 | value: value, 77 | pos: null, 78 | }); 79 | node.etype = "f32"; 80 | return node; 81 | }; 82 | 83 | Desugar.prototype.constF64 = function(value) { 84 | var node = wast.ConstF64({ 85 | value: value, 86 | pos: null, 87 | }); 88 | node.etype = "f64"; 89 | return node; 90 | }; 91 | 92 | Desugar.prototype.processExprPost = function(node) { 93 | switch (node.type) { 94 | case "Coerce": 95 | var simplified = this.simplifyType(node.mtype); 96 | if (simplified == node.expr.etype) { 97 | node.expr.etype = node.mtype; 98 | node = node.expr; 99 | } else { 100 | node.mtype = simplified; 101 | } 102 | break; 103 | case "UnaryOp": 104 | switch(node.op) { 105 | case "boolnot": 106 | // Missing most "not" operations, lower into a compare. 107 | // Turn !v into v==0. 108 | switch (node.optype) { 109 | case "i64": 110 | node = wast.BinaryOp({ 111 | optype: node.optype, 112 | op: opinfo.binaryOps.eq, 113 | left: node.expr, 114 | right: this.constI64(0), 115 | }); 116 | node.etype = "i64"; 117 | break; 118 | 119 | case "f32": 120 | node = wast.BinaryOp({ 121 | optype: node.optype, 122 | op: opinfo.binaryOps.eq, 123 | left: node.expr, 124 | right: this.constF32(0.0), 125 | }); 126 | node.etype = "i32"; 127 | break; 128 | case "f64": 129 | node = wast.BinaryOp({ 130 | optype: node.optype, 131 | op: opinfo.binaryOps.eq, 132 | left: node.expr, 133 | right: this.constF64(0.0), 134 | }); 135 | node.etype = "i32"; 136 | break; 137 | default: 138 | node = peelBoolNot(node); 139 | } 140 | break; 141 | case "neg": 142 | // TODO support integer negation in the v8 backend. 143 | // Turn -i into 0-i. 144 | switch (node.optype) { 145 | case "i32": 146 | node = wast.BinaryOp({ 147 | optype: node.optype, 148 | op: opinfo.binaryOps.sub, 149 | left: this.constI32(0), 150 | right: node.expr, 151 | }); 152 | node.etype = node.optype; 153 | break; 154 | case "i64": 155 | node = wast.BinaryOp({ 156 | optype: node.optype, 157 | op: opinfo.binaryOps.sub, 158 | left: this.constI64(0), 159 | right: node.expr, 160 | }); 161 | node.etype = node.optype; 162 | break; 163 | } 164 | break; 165 | } 166 | break; 167 | } 168 | switch(node.etype) { 169 | case "i8": 170 | node.optype = "i32"; 171 | node.etype = "i32"; 172 | node = wast.BinaryOp({ 173 | optype: "i32", 174 | op: opinfo.binaryOps.sar, 175 | left: wast.BinaryOp({ 176 | optype: "i32", 177 | op: opinfo.binaryOps.shl, 178 | left: node, 179 | right: this.constI32(24), 180 | }), 181 | right: this.constI32(24), 182 | }); 183 | node.etype = "i32"; 184 | node.left.etype = "i32"; 185 | break; 186 | case "i16": 187 | node.optype = "i32"; 188 | node.etype = "i32"; 189 | node = wast.BinaryOp({ 190 | optype: "i32", 191 | op: opinfo.binaryOps.sar, 192 | left: wast.BinaryOp({ 193 | optype: "i32", 194 | op: opinfo.binaryOps.shl, 195 | left: node, 196 | right: this.constI32(16), 197 | }), 198 | right: this.constI32(16), 199 | }); 200 | node.etype = "i32"; 201 | node.left.etype = "i32"; 202 | break; 203 | } 204 | return node; 205 | }; 206 | 207 | Desugar.prototype.processStmtPost = function(node, out) { 208 | switch (node.type) { 209 | case "While": 210 | var body = [wast.If({ 211 | cond: this.not(node.cond), 212 | t: [wast.Break({depth: 1})], 213 | f: null, 214 | pos: null, 215 | })]; 216 | body = body.concat(node.body); 217 | node = wast.Loop({ 218 | body: body, 219 | }); 220 | break; 221 | } 222 | out.push(node); 223 | }; 224 | 225 | Desugar.prototype.simplifyType = function(t) { 226 | if (typeof t !== "string") { 227 | throw Error(t); 228 | } 229 | if (t in simplified_type) { 230 | return simplified_type[t]; 231 | } 232 | return t; 233 | }; 234 | 235 | Desugar.prototype.processFuncPost = function(node) { 236 | for (var i = 0; i < node.params.length; i++) { 237 | node.params[i].ptype = this.simplifyType(node.params[i].ptype); 238 | } 239 | for (var i = 0; i < node.locals.length; i++) { 240 | node.locals[i].ltype = this.simplifyType(node.locals[i].ltype); 241 | } 242 | node.returnType = this.simplifyType(node.returnType); 243 | }; 244 | 245 | Desugar.prototype.simplifyFuncType = function(node) { 246 | for (var i = 0; i < node.paramTypes.length; i++) { 247 | node.paramTypes[i] = this.simplifyType(node.paramTypes[i]); 248 | } 249 | node.returnType = this.simplifyType(node.returnType); 250 | return node; 251 | }; 252 | 253 | Desugar.prototype.processExtern = function(node) { 254 | node.ftype = this.simplifyFuncType(node.ftype); 255 | }; 256 | 257 | var process = function(module, config) { 258 | var desugar = new traverse.TopDownBottomUp(new Desugar(config)); 259 | desugar.processModule(module); 260 | return module; 261 | }; 262 | 263 | return { 264 | process: process, 265 | }; 266 | }); 267 | -------------------------------------------------------------------------------- /wasm/opinfo.js: -------------------------------------------------------------------------------- 1 | define(["astutil"], function(astutil) { 2 | var binaryOpList = [ 3 | "add", 4 | "sub", 5 | "mul", 6 | "div", 7 | "sdiv", 8 | "udiv", 9 | "srem", 10 | "urem", 11 | "and", 12 | "ior", 13 | "xor", 14 | "shl", 15 | "shr", 16 | "sar", 17 | "eq", 18 | "ne", // TODO eliminate this operation in the semantic pass? 19 | "lt", 20 | "le", 21 | "slt", 22 | "sle", 23 | "ult", 24 | "ule", 25 | "gt", 26 | "ge", 27 | "sgt", 28 | "sge", 29 | "ugt", 30 | "uge", 31 | "min", 32 | "max", 33 | ]; 34 | 35 | // Eventually op names may map to enums, but for now keep them as strings. 36 | var binaryOps = {}; 37 | for (var i = 0; i < binaryOpList.length; i++) { 38 | var op = binaryOpList[i]; 39 | binaryOps[op] = op; 40 | } 41 | 42 | var binaryOpTable = []; 43 | var classifyBinaryOp = {}; 44 | 45 | var binary = astutil.makeASTBuilder([ 46 | { 47 | name: "binary", 48 | fields: [ 49 | {name: "optype"}, 50 | {name: "op"}, 51 | {name: "right"}, 52 | {name: "result"}, 53 | {name: "text", defaultValue: null}, 54 | {name: "intrinsicName", defaultValue: null}, 55 | ], 56 | }, 57 | ]).binary; 58 | 59 | 60 | var classify = function(decl, left, right, result) { 61 | binaryOpTable.push(binary({optype: left, op: decl.op, right: right, result: result, text: decl.text})); 62 | }; 63 | 64 | var classifySimple = function(table, types) { 65 | for (var i = 0; i < table.length; i++) { 66 | var decl = table[i]; 67 | for (var j = 0; j < types.length; j++) { 68 | var t = types[j]; 69 | classify(decl, t, t, t); 70 | } 71 | } 72 | }; 73 | 74 | var classifyFixedResult = function(table, types, result) { 75 | for (var i = 0; i < table.length; i++) { 76 | var decl = table[i]; 77 | for (var j = 0; j < types.length; j++) { 78 | var t = types[j]; 79 | classify(decl, t, t, result); 80 | } 81 | } 82 | }; 83 | 84 | // Declare facts about binary operators. 85 | var arithmeticOps = [ 86 | {op: binaryOps.add, text: "+"}, 87 | {op: binaryOps.sub, text: "-"}, 88 | {op: binaryOps.mul, text: "*"}, 89 | ]; 90 | 91 | var floatArithmeticOps = [ 92 | {op: binaryOps.div, text: "/"}, 93 | ]; 94 | 95 | // TODO signed vs unsigned. 96 | var intArithmeticOps = [ 97 | {op: binaryOps.sdiv, text: "/"}, 98 | {op: binaryOps.srem, text: "%"}, 99 | ]; 100 | 101 | var bitOps = [ 102 | {op: binaryOps.and, text: "&"}, 103 | {op: binaryOps.ior, text: "|"}, 104 | {op: binaryOps.xor, text: "^"}, 105 | ]; 106 | 107 | var shiftOps = [ 108 | {op: binaryOps.shl, text: "<<"}, 109 | {op: binaryOps.sar, text: ">>"}, 110 | {op: binaryOps.shr, text: ">>>"}, 111 | ]; 112 | 113 | var compareOps = [ 114 | {op: binaryOps.eq, text: "=="}, 115 | {op: binaryOps.ne, text: "!="}, 116 | ]; 117 | 118 | var floatCompareOps = [ 119 | {op: binaryOps.lt, text: "<"}, 120 | {op: binaryOps.le, text: "<="}, 121 | {op: binaryOps.gt, text: ">"}, 122 | {op: binaryOps.ge, text: ">="}, 123 | ]; 124 | 125 | // TODO signed vs. unsigned. 126 | var intCompareOps = [ 127 | {op: binaryOps.slt, text: "<"}, 128 | {op: binaryOps.sle, text: "<="}, 129 | {op: binaryOps.sgt, text: ">"}, 130 | {op: binaryOps.sge, text: ">="}, 131 | ]; 132 | 133 | // Derive lookup tables from the declarations. 134 | var arithmeticTypes = ["i8", "i16", "i32", "i64", "f32", "f64"]; 135 | var intTypes = ["i8", "i16", "i32", "i64"]; 136 | var smallIntTypes = ["i8", "i16", "i32"]; 137 | var floatTypes = ["f32", "f64"]; 138 | 139 | classifySimple(arithmeticOps, arithmeticTypes); 140 | classifySimple(floatArithmeticOps, floatTypes); 141 | classifySimple(intArithmeticOps, intTypes); 142 | classifySimple(bitOps, intTypes); 143 | classifySimple(shiftOps, intTypes); 144 | 145 | classifyFixedResult(compareOps, floatTypes, "i32"); 146 | classifyFixedResult(floatCompareOps, floatTypes, "i32"); 147 | 148 | classifyFixedResult(compareOps, smallIntTypes, "i32"); 149 | classifyFixedResult(intCompareOps, smallIntTypes, "i32"); 150 | 151 | classifyFixedResult(compareOps, ["i64"], "i64"); 152 | classifyFixedResult(intCompareOps, ["i64"], "i64"); 153 | 154 | binaryOpTable.push(binary({optype: "f32", op: binaryOps.min, right: "f32", result: "f32", intrinsicName: "minF32"})); 155 | binaryOpTable.push(binary({optype: "f32", op: binaryOps.max, right: "f32", result: "f32", intrinsicName: "maxF32"})); 156 | binaryOpTable.push(binary({optype: "f64", op: binaryOps.min, right: "f64", result: "f64", intrinsicName: "minF64"})); 157 | binaryOpTable.push(binary({optype: "f64", op: binaryOps.max, right: "f64", result: "f64", intrinsicName: "maxF64"})); 158 | 159 | var classifyBinaryOp = astutil.index(["text", "optype"], binaryOpTable); 160 | var classifyBinaryIntrinsic = astutil.index(["intrinsicName"], binaryOpTable); 161 | 162 | var compareOpLut = { 163 | eq: true, 164 | ne: true, 165 | lt: true, 166 | le: true, 167 | slt: true, 168 | sle: true, 169 | ult: true, 170 | ule: true, 171 | gt: true, 172 | ge: true, 173 | sgt: true, 174 | sge: true, 175 | ugt: true, 176 | uge: true, 177 | }; 178 | 179 | var isCompareOp = function(op) { 180 | return op in compareOpLut; 181 | }; 182 | 183 | var unary = astutil.makeASTBuilder([ 184 | { 185 | name: "unary", 186 | fields: [ 187 | {name: "optype"}, 188 | {name: "op"}, 189 | {name: "result"}, 190 | {name: "prefix", defaultValue: null}, 191 | {name: "intrinsicName", defaultValue: null}, 192 | ], 193 | }, 194 | ]).unary; 195 | 196 | var unaryOpTable = [ 197 | unary({optype: "i32", op: "boolnot", result: "i32", prefix: "!"}), 198 | unary({optype: "i64", op: "boolnot", result: "i64", prefix: "!"}), 199 | unary({optype: "f32", op: "boolnot", result: "i32", prefix: "!"}), 200 | unary({optype: "f64", op: "boolnot", result: "i32", prefix: "!"}), 201 | unary({optype: "i32", op: "neg", result: "i32", prefix: "-"}), 202 | unary({optype: "i64", op: "neg", result: "i64", prefix: "-"}), 203 | unary({optype: "f32", op: "neg", result: "f32", prefix: "-"}), 204 | unary({optype: "f64", op: "neg", result: "f64", prefix: "-"}), 205 | unary({optype: "f32", op: "sqrt", result: "f32", intrinsicName: "sqrtF32"}), 206 | unary({optype: "f64", op: "sqrt", result: "f64", intrinsicName: "sqrtF64"}), 207 | ]; 208 | 209 | var classifyPrefixOp = astutil.index(["prefix", "optype"], unaryOpTable); 210 | var classifyUnaryIntrinsic = astutil.index(["intrinsicName"], unaryOpTable); 211 | 212 | return { 213 | classifyPrefixOp: classifyPrefixOp, 214 | classifyUnaryIntrinsic: classifyUnaryIntrinsic, 215 | binaryOps: binaryOps, 216 | classifyBinaryOp: classifyBinaryOp, 217 | classifyBinaryIntrinsic: classifyBinaryIntrinsic, 218 | isCompareOp: isCompareOp, 219 | }; 220 | }); -------------------------------------------------------------------------------- /wasm/semantic.js: -------------------------------------------------------------------------------- 1 | define(["compilerutil", "wasm/ast", "wasm/typeinfo", "wasm/opinfo"], function(compilerutil, wast, typeinfo, opinfo) { 2 | 3 | var configDefaults = { 4 | memory: { 5 | fixed: 65536, 6 | }, 7 | }; 8 | 9 | var setConfigDefaults = function(config, defaults) { 10 | for (var name in defaults) { 11 | if (typeof defaults[name] === "object") { 12 | if (!(name in config)) { 13 | config[name] = {}; 14 | } 15 | setConfigDefaults(config[name], defaults[name]); 16 | } else { 17 | if (!(name in config)) { 18 | config[name] = defaults[name]; 19 | } 20 | } 21 | } 22 | }; 23 | 24 | var getPos = function(node) { 25 | switch (node.type) { 26 | case "Call": 27 | return getPos(node.expr); 28 | case "BinaryOp": 29 | return getPos(node.left); 30 | case "GetName": 31 | return node.name.pos; 32 | case "SetName": 33 | return node.name.pos; 34 | default: 35 | if (node.pos) { 36 | return node.pos; 37 | } 38 | throw Error(node.type); 39 | } 40 | }; 41 | 42 | var isLoop = function(node) { 43 | return node.type == "While" || node.type == "Loop"; 44 | }; 45 | 46 | var SemanticPass = function(status) { 47 | this.status = status; 48 | }; 49 | 50 | SemanticPass.prototype.error = function(message, pos) { 51 | this.status.error(message, pos); 52 | this.dead = true; 53 | }; 54 | 55 | SemanticPass.prototype.setExprType = function(expr, t) { 56 | if(typeof t != "string") { 57 | console.log(Error(t)); 58 | throw t; 59 | } 60 | expr.etype = t; 61 | }; 62 | 63 | SemanticPass.prototype.checkCall = function(expr, ft) { 64 | if (expr.args.length != ft.paramTypes.length) { 65 | this.error("argument count mismatch - got " + expr.args.length + ", but expected " + ft.paramTypes.length, getPos(expr)); 66 | } else { 67 | for (var i = 0; i < expr.args.length; i++) { 68 | var arg = expr.args[i]; 69 | var expected = ft.paramTypes[i]; 70 | if (arg.etype != expected) { 71 | this.error("arg " + i + " - got " + arg.etype + ", but expected " + expected, getPos(arg)); 72 | } 73 | } 74 | } 75 | }; 76 | 77 | SemanticPass.prototype.processExpr = function(expr) { 78 | var old_dead = this.dead; 79 | this.dead = false; 80 | 81 | switch(expr.type) { 82 | case "ConstI32": 83 | this.setExprType(expr, "i32"); 84 | expr.etype = "i32"; 85 | break; 86 | case "ConstF32": 87 | this.setExprType(expr, "f32"); 88 | break; 89 | case "ConstF64": 90 | this.setExprType(expr, "f64"); 91 | break; 92 | case "GetName": 93 | var name = expr.name.text; 94 | var ref = this.localScope[name]; 95 | if (ref !== undefined) { 96 | var expr = wast.GetLocal({local: ref, pos: getPos(expr)}); 97 | this.setExprType(expr, ref.ltype); 98 | break; 99 | } 100 | 101 | var ref = this.moduleScope[name]; 102 | if (ref !== undefined) { 103 | switch(ref.type) { 104 | case "Function": 105 | expr = wast.GetFunction({func: ref, pos: getPos(expr)}); 106 | this.setExprType(expr, "i32"); 107 | break; 108 | case "Extern": 109 | expr = wast.GetExtern({func: ref, pos: getPos(expr)}); 110 | this.setExprType(expr, "i32"); 111 | break; 112 | case "Intrinsic": 113 | expr = wast.GetIntrinsic({func: ref, pos: getPos(expr)}); 114 | this.setExprType(expr, "i32"); 115 | break; 116 | case "TlsDecl": 117 | expr = wast.GetTls({tls: ref, pos: getPos(expr)}); 118 | this.setExprType(expr, ref.mtype); 119 | break; 120 | case "MemoryLabel": 121 | expr = wast.ConstI32({ 122 | value: ref.ptr, 123 | pos: getPos(expr), 124 | }); 125 | this.setExprType(expr, "i32"); 126 | break; 127 | default: 128 | throw Error(ref.type); 129 | } 130 | break; 131 | } 132 | this.error("cannot resolve name - " + name, expr.name.pos); 133 | break; 134 | case "SetName": 135 | var name = expr.name.text; 136 | var ref = this.localScope[name]; 137 | if (ref !== undefined) { 138 | expr = wast.SetLocal({ 139 | local: ref, 140 | value: this.processExpr(expr.value), 141 | pos: getPos(expr), 142 | }); 143 | this.setExprType(expr, "void"); 144 | break; 145 | } 146 | var ref = this.moduleScope[name]; 147 | if (ref !== undefined) { 148 | switch(ref.type) { 149 | case "TlsDecl": 150 | expr = wast.SetTls({ 151 | tls: ref, 152 | value: this.processExpr(expr.value), 153 | pos: getPos(expr), 154 | }); 155 | this.setExprType(expr, "void"); 156 | break; 157 | default: 158 | this.error("cannot assign to name - " + name, expr.name.pos); 159 | } 160 | break; 161 | } 162 | this.error("assigning to unknown name - " + name, expr.name.pos); 163 | break; 164 | case "Load": 165 | expr.address = this.processExpr(expr.address); 166 | // TODO type check 167 | this.setExprType(expr, expr.mtype); 168 | break; 169 | case "Store": 170 | expr.address = this.processExpr(expr.address); 171 | expr.value = this.processExpr(expr.value); 172 | // TODO type check 173 | this.setExprType(expr, expr.mtype); 174 | break; 175 | case "Coerce": 176 | expr.expr = this.processExpr(expr.expr); 177 | // TODO type check 178 | this.setExprType(expr, expr.mtype); 179 | break; 180 | case "PrefixOp": 181 | expr.expr = this.processExpr(expr.expr); 182 | var opText = expr.op; 183 | var types = opinfo.classifyPrefixOp[opText]; 184 | if (types === undefined) { 185 | this.error("unknown prefix operator " + opText, expr.pos); 186 | } 187 | if (this.dead) { 188 | break; 189 | } 190 | var t = expr.expr.etype; 191 | var decl = types[t]; 192 | if (decl === undefined) { 193 | this.error("prefix operator " + opText + " does not support type " + t, expr.pos); 194 | } 195 | if (this.dead) { 196 | break; 197 | } 198 | expr = wast.UnaryOp({ 199 | optype: t, 200 | op: decl.op, 201 | expr: expr.expr, 202 | }) 203 | this.setExprType(expr, decl.result); 204 | break; 205 | case "InfixOp": 206 | expr.left = this.processExpr(expr.left); 207 | expr.right = this.processExpr(expr.right); 208 | var opText = expr.op.text; 209 | var types = opinfo.classifyBinaryOp[opText]; 210 | if (types === undefined) { 211 | this.error("unknown binary operator " + opText, expr.op.pos); 212 | } 213 | if (this.dead) { 214 | break; 215 | } 216 | var t = expr.left.etype; 217 | var decl = types[t]; 218 | if (decl === undefined) { 219 | this.error("binary operator " + opText + " does not support type " + t, expr.op.pos); 220 | } 221 | if (this.dead) { 222 | break; 223 | } 224 | if (t != expr.right.etype) { 225 | this.error("binary op type error " + t + opText + expr.right.etype + " = ???", expr.op.pos); 226 | } 227 | if (this.dead) { 228 | break; 229 | } 230 | expr = wast.BinaryOp({ 231 | optype: t, 232 | op: decl.op, 233 | left: expr.left, 234 | right: expr.right, 235 | }) 236 | this.setExprType(expr, decl.result); 237 | break; 238 | case "Call": 239 | // Process children 240 | expr.expr = this.processExpr(expr.expr); 241 | for (var i = 0; i < expr.args.length; i++) { 242 | expr.args[i] = this.processExpr(expr.args[i]); 243 | } 244 | 245 | var args = expr.args; 246 | var ft = null; 247 | 248 | if (!this.dead) { 249 | switch (expr.expr.type) { 250 | case "GetFunction": 251 | var func = expr.expr.func; 252 | ft = func.funcType; 253 | this.checkCall(expr, ft); 254 | if (!this.dead) { 255 | expr = wast.CallDirect({ 256 | func: func, 257 | args: expr.args, 258 | pos: getPos(expr), 259 | }); 260 | this.setExprType(expr, ft.returnType); 261 | } 262 | break; 263 | case "GetExtern": 264 | var func = expr.expr.func; 265 | ft = func.ftype; 266 | this.checkCall(expr, ft); 267 | if (!this.dead) { 268 | expr = wast.CallExternal({ 269 | func: func, 270 | args: expr.args, 271 | pos: getPos(expr), 272 | }); 273 | this.setExprType(expr, ft.returnType); 274 | } 275 | break; 276 | case "GetIntrinsic": 277 | var func = expr.expr.func; 278 | ft = func.funcType; 279 | this.checkCall(expr, ft); 280 | if (!this.dead) { 281 | var op = func.op; 282 | switch (op.type) { 283 | case "unary": 284 | expr = wast.UnaryOp({ 285 | optype: op.optype, 286 | op: op.op, 287 | expr: expr.args[0], 288 | }); 289 | break; 290 | case "binary": 291 | expr = wast.BinaryOp({ 292 | optype: op.optype, 293 | op: op.op, 294 | left: expr.args[0], 295 | right: expr.args[1], 296 | }); 297 | break; 298 | default: 299 | throw Error(op.type); 300 | } 301 | this.setExprType(expr, ft.returnType); 302 | } 303 | break; 304 | default: 305 | throw Error(expr.expr.type); 306 | } 307 | } 308 | break; 309 | 310 | case "CallIndirect": 311 | expr.ftype = this.processFunctionType(expr.ftype); 312 | expr.expr = this.processExpr(expr.expr); 313 | if (!this.dead && expr.expr.etype != "i32") { 314 | this.error("type mismatch - " + expr.expr.etype + ", expected i32", getPos(expr.expr)); 315 | } 316 | for (var i = 0; i < expr.args.length; i++) { 317 | expr.args[i] = this.processExpr(expr.args[i]); 318 | } 319 | this.setExprType(expr, expr.ftype.returnType); 320 | this.checkCall(expr, expr.ftype); 321 | break; 322 | 323 | case "Return": 324 | var actual = "void"; 325 | if (expr.expr) { 326 | expr.expr = this.processExpr(expr.expr); 327 | actual = expr.expr.etype; 328 | } 329 | if (!this.dead && actual != this.func.returnType) { 330 | this.error("return type mismatch - " + actual + " vs. " + this.func.returnType, getPos(expr)); 331 | } 332 | this.setExprType(expr, "void"); 333 | break; 334 | case "If": 335 | expr.cond = this.processExpr(expr.cond); 336 | if (!this.dead && expr.cond.etype != "i32") { 337 | this.error("condition type mismatch - " + expr.cond.etype + ", expected i32", getPos(expr)); 338 | } 339 | 340 | expr.t = this.processBlock(expr.t); 341 | if (expr.f != null) { 342 | expr.f = this.processBlock(expr.f); 343 | } 344 | this.setExprType(expr, "void"); 345 | break; 346 | case "While": 347 | expr.cond = this.processExpr(expr.cond); 348 | if (!this.dead && expr.cond.etype != "i32") { 349 | this.error("condition type mismatch - " + expr.cond.etype + ", expected i32", getPos(expr)); 350 | } 351 | expr.body = this.processBlock(expr.body); 352 | this.setExprType(expr, "void"); 353 | break; 354 | case "Loop": 355 | expr.body = this.processBlock(expr.body); 356 | this.setExprType(expr, "void"); 357 | break; 358 | default: 359 | throw Error(expr.type); 360 | } 361 | 362 | this.dead = this.dead || old_dead; 363 | return expr; 364 | }; 365 | 366 | SemanticPass.prototype.processType = function(node) { 367 | switch (node.type) { 368 | case "TypeName": 369 | var t = node.name.text; 370 | switch (t) { 371 | case "i8": 372 | case "i16": 373 | case "i32": 374 | case "i64": 375 | case "f32": 376 | case "f64": 377 | case "void": 378 | break; 379 | default: 380 | this.error("unknown type name - " + type.text, type.pos); 381 | } 382 | return t; 383 | default: 384 | throw Error(node.type); 385 | } 386 | }; 387 | 388 | SemanticPass.prototype.createLocal = function(name, type) { 389 | if (typeof name !== "string") { 390 | throw Error(name); 391 | } 392 | if (typeof type !== "string") { 393 | throw Error(type); 394 | } 395 | 396 | if (name in this.localScope) { 397 | this.error("attemped to redeclare " + name); 398 | return -1; 399 | } 400 | 401 | var lcl = wast.Local({ 402 | name: name, 403 | ltype: type, 404 | index: this.func.locals.length 405 | }); 406 | this.func.locals.push(lcl); 407 | this.localScope[name] = lcl; 408 | return lcl; 409 | }; 410 | 411 | SemanticPass.prototype.processStmt = function(node, block) { 412 | var old_dead = this.dead; 413 | this.dead = false; 414 | 415 | switch (node.type) { 416 | case "VarDecl": 417 | var t = this.processType(node.vtype); 418 | var lcl = this.createLocal(node.name.text, t); 419 | if (node.value) { 420 | var pos = getPos(node); 421 | var value = this.processExpr(node.value); 422 | if (!this.dead) { 423 | if (t != value.etype) { 424 | this.error("expected " + t + ", got " + value.etype, pos); 425 | } 426 | node = wast.SetLocal({ 427 | local: lcl, 428 | value: value, 429 | pos: pos, 430 | }); 431 | } 432 | this.setExprType(node, "void"); 433 | block.push(node); 434 | } 435 | break; 436 | case "BreakToLabel": 437 | var name = node.name.text 438 | var labelInfo = this.labelScope[name]; 439 | if (labelInfo.poisoned) { 440 | // We aren't 100% sure what the user indended, so don't print additional 441 | // errors. 442 | this.dead = true; 443 | } else { 444 | if (labelInfo) { 445 | if (labelInfo.live) { 446 | var diff = this.depth - labelInfo.depth; 447 | // Break 0 breaks out of the current block. This difference in depth 448 | // between a break statement and the enclosing block will be 1, so 449 | // subtract 1 from the depth difference to get the correct encoding. 450 | node = wast.Break({depth: diff - 1}); 451 | } else { 452 | // "live" is true only while processing the subtree contained by the 453 | // label. If live is false, this means the break statement is 454 | // trying to transfer control to a label that is not a ancestor of 455 | // the break statement, and this is illegal. 456 | this.error("label " + name + " does not contain break statement", node.name.pos); 457 | } 458 | } else { 459 | this.error("unknown label " + name, node.name.pos); 460 | } 461 | } 462 | block.push(node); 463 | break; 464 | case "Label": 465 | var name = node.name.text; 466 | var labelInfo = this.labelScope[name]; 467 | if (labelInfo) { 468 | this.error("attempted to redeclare " + name, node.name.pos); 469 | labelInfo.poisoned = true; 470 | } else { 471 | labelInfo = {name: name, depth: this.depth, live: true, poisoned: false, loop: isLoop(node.stmt)}; 472 | this.labelScope[name] = labelInfo; 473 | } 474 | this.processStmt(node.stmt, block); 475 | if (labelInfo) { 476 | labelInfo.live = false; 477 | } 478 | break; 479 | default: 480 | block.push(this.processExpr(node)); 481 | } 482 | this.dead |= old_dead; 483 | }; 484 | 485 | SemanticPass.prototype.processBlock = function(block) { 486 | if (block === null) { 487 | return block; 488 | } 489 | var old_dead = this.dead; 490 | var out = []; 491 | this.depth += 1; 492 | for (var i in block) { 493 | this.processStmt(block[i], out); 494 | } 495 | this.depth -= 1; 496 | this.dead = this.dead || old_dead; 497 | return out; 498 | }; 499 | 500 | SemanticPass.prototype.processFunction = function(func) { 501 | this.func = func; 502 | this.localScope = {}; 503 | this.labelScope = {}; 504 | this.depth = 0; 505 | this.dead = false; 506 | 507 | for (var i in func.params) { 508 | var p = func.params[i]; 509 | p.local = this.createLocal(p.name.text, p.ptype); 510 | } 511 | func.body = this.processBlock(func.body); 512 | }; 513 | 514 | SemanticPass.prototype.processFunctionType = function(node) { 515 | for (var i = 0; i < node.paramTypes.length; i++) { 516 | node.paramTypes[i] = this.processType(node.paramTypes[i]); 517 | } 518 | node.returnType = this.processType(node.returnType); 519 | return node; 520 | }; 521 | 522 | SemanticPass.prototype.processExternSig = function(extern) { 523 | extern.ftype = this.processFunctionType(extern.ftype); 524 | }; 525 | 526 | SemanticPass.prototype.processFuncSig = function(func) { 527 | var paramTypes = []; 528 | for (var i in func.params) { 529 | var p = func.params[i]; 530 | p.ptype = this.processType(p.ptype); 531 | paramTypes.push(p.ptype); 532 | } 533 | func.returnType = this.processType(func.returnType); 534 | func.funcType = wast.FuncType({paramTypes: paramTypes, returnType: func.returnType}); 535 | }; 536 | 537 | SemanticPass.prototype.processTlsDecl = function(node) { 538 | node.mtype = this.processType(node.mtype); 539 | }; 540 | 541 | SemanticPass.prototype.registerInModule = function(name, decl) { 542 | if (name.text in this.moduleScope) { 543 | this.error("attempted to redefine name - " + name.text, name.pos); 544 | } else { 545 | this.moduleScope[name.text] = decl; 546 | } 547 | }; 548 | 549 | SemanticPass.prototype.indexMemoryDecl = function(node) { 550 | var base = this.ptr; 551 | node.ptr = base; 552 | 553 | var writer = new compilerutil.BinaryWriter(); 554 | for (var d = 0; d < node.directives.length; d++) { 555 | var m = node.directives[d]; 556 | m.ptr = base + writer.pos; 557 | switch(m.type) { 558 | case "MemoryAlign": 559 | var offset = this.ptr % m.size; 560 | if (offset != 0) { 561 | var padding = m.size - offset; 562 | writer.zeros(padding); 563 | } 564 | break; 565 | case "MemoryLabel": 566 | this.registerInModule(m.name, m); 567 | break; 568 | case "MemoryZero": 569 | writer.zeros(m.size); 570 | break; 571 | case "MemoryHex": 572 | writer.expect(m.data.length); 573 | for (var i = 0; i < m.data.length; i++) { 574 | writer.u8(m.data[i]); 575 | } 576 | break; 577 | default: 578 | throw Error(m.type); 579 | } 580 | } 581 | this.ptr = base + writer.pos; 582 | node.buffer = writer.getOutput(); 583 | }; 584 | 585 | SemanticPass.prototype.indexIntrinsics = function() { 586 | for (var name in opinfo.classifyUnaryIntrinsic) { 587 | var op = opinfo.classifyUnaryIntrinsic[name]; 588 | var decl = { 589 | type: "Intrinsic", 590 | op: op, 591 | funcType: wast.FuncType({ 592 | paramTypes: [op.optype], 593 | returnType: op.result, 594 | }), 595 | }; 596 | this.registerInModule({text: op.intrinsicName, pos: null}, decl); 597 | } 598 | for (var name in opinfo.classifyBinaryIntrinsic) { 599 | var op = opinfo.classifyBinaryIntrinsic[name]; 600 | var decl = { 601 | type: "Intrinsic", 602 | op: op, 603 | funcType: wast.FuncType({ 604 | paramTypes: [op.optype, op.right], 605 | returnType: op.result, 606 | }), 607 | }; 608 | this.registerInModule({text: op.intrinsicName, pos: null}, decl); 609 | } 610 | }; 611 | 612 | SemanticPass.prototype.indexModule = function(module) { 613 | for (var i = 0; i < module.externs.length; i++) { 614 | var e = module.externs[i]; 615 | e.index = i; 616 | this.registerInModule(e.name, e); 617 | this.processExternSig(e); 618 | } 619 | for (var i = 0; i < module.funcs.length; i++) { 620 | var func = module.funcs[i]; 621 | func.index = i; 622 | this.processFuncSig(func); 623 | this.registerInModule(func.name, func); 624 | } 625 | 626 | // Zero is null, so don't use that. 627 | this.ptr = 8; 628 | for (var i in module.memory) { 629 | this.indexMemoryDecl(module.memory[i]); 630 | } 631 | 632 | // Create label for the end of pre-reserved memory. 633 | var m = wast.MemoryLabel({ 634 | name: wast.Identifier({ 635 | text: "_end", 636 | pos: null, 637 | }) 638 | }); 639 | m.ptr = this.ptr; 640 | this.registerInModule(m.name, m); 641 | 642 | for (var i = 0; i < module.tls.length; i++) { 643 | var v = module.tls[i]; 644 | v.index = i; 645 | this.registerInModule(v.name, v); 646 | this.processTlsDecl(v); 647 | } 648 | }; 649 | 650 | SemanticPass.prototype.evalConstExpr = function(node) { 651 | switch (node.type) { 652 | case "ConstI32": 653 | return node.value; 654 | default: 655 | throw Error(node.type); 656 | } 657 | }; 658 | 659 | SemanticPass.prototype.processModule = function(module) { 660 | if (module.type != "ParsedModule") { 661 | console.log(module); 662 | throw module.type; 663 | } 664 | 665 | // Bucket the declarations. 666 | var externs = []; 667 | var funcs = []; 668 | var memory = []; 669 | var tls = []; 670 | 671 | var config = {}; 672 | 673 | for (var i in module.decls) { 674 | var decl = module.decls[i]; 675 | switch (decl.type) { 676 | case "Function": 677 | funcs.push(decl); 678 | break; 679 | case "Extern": 680 | externs.push(decl); 681 | break; 682 | case "MemoryDecl": 683 | memory.push(decl); 684 | break; 685 | case "ConfigDecl": 686 | for (var i = 0; i < decl.items.length; i++) { 687 | var item = decl.items[i]; 688 | var current = config; 689 | for (var p = 0; p < item.path.length - 1; p++) { 690 | var name = item.path[p]; 691 | if (!(name in current)) { 692 | current[name] = {}; 693 | } 694 | current = current[name]; 695 | } 696 | current[item.path[item.path.length - 1]] = this.evalConstExpr(item.value); 697 | } 698 | break; 699 | case "TlsDecl": 700 | tls.push(decl); 701 | break; 702 | default: 703 | console.log(decl); 704 | throw decl.type; 705 | } 706 | } 707 | 708 | setConfigDefaults(config, configDefaults); 709 | 710 | module = wast.Module({ 711 | config: config, 712 | externs: externs, 713 | funcs: funcs, 714 | tls: tls, 715 | memory: memory, 716 | top: 0, 717 | }); 718 | 719 | this.module = module; 720 | this.moduleScope = {}; 721 | this.indexIntrinsics(); 722 | this.indexModule(module); 723 | if (!this.dead) { 724 | for (var i in module.funcs) { 725 | this.processFunction(module.funcs[i]); 726 | } 727 | } 728 | module.top = this.ptr; 729 | return module; 730 | }; 731 | 732 | var processModule = function(module, status) { 733 | var semantic = new SemanticPass(status); 734 | return semantic.processModule(module); 735 | }; 736 | 737 | return { 738 | processModule: processModule, 739 | }; 740 | 741 | }); 742 | -------------------------------------------------------------------------------- /wasm/tojs.js: -------------------------------------------------------------------------------- 1 | define(["js/ast", "wasm/traverse", "wasm/typeinfo", "wasm/opinfo", "astutil"], function(jast, traverse, typeinfo, opinfo, astutil) { 2 | 3 | var TranslatorAnalysis = function() { 4 | this.breakStack = []; 5 | this.labelUID = 0; 6 | } 7 | 8 | TranslatorAnalysis.prototype.processStmtPre = function(stmt, out) { 9 | switch (stmt.type) { 10 | case "If": 11 | case "Loop": 12 | this.breakStack.push(stmt); 13 | break; 14 | case "Break": 15 | // Figure out what statement this break targets, and generate a label if it doesn't already exist. 16 | var target = this.breakStack[this.breakStack.length - stmt.depth - 1]; 17 | if (!target.generatedLabel) { 18 | target.generatedLabel = "label" + this.labelUID; 19 | this.labelUID += 1; 20 | } 21 | stmt.generatedLabel = target.generatedLabel; 22 | } 23 | out.push(stmt); 24 | } 25 | 26 | TranslatorAnalysis.prototype.processStmtPost = function(stmt, out) { 27 | switch (stmt.type) { 28 | case "If": 29 | case "Loop": 30 | this.breakStack.pop(); 31 | break; 32 | } 33 | out.push(stmt); 34 | } 35 | 36 | 37 | var views = [ 38 | { 39 | type: "u8", 40 | array_type: "Uint8Array", 41 | array_name: "U8", 42 | }, 43 | { 44 | type: "i8", 45 | array_type: "Int8Array", 46 | array_name: "I8", 47 | }, 48 | { 49 | type: "u16", 50 | array_type: "Uint16Array", 51 | array_name: "U16", 52 | }, 53 | { 54 | type: "i16", 55 | array_type: "Int16Array", 56 | array_name: "I16", 57 | }, 58 | { 59 | type: "u32", 60 | array_type: "Uint32Array", 61 | array_name: "U32", 62 | }, 63 | { 64 | type: "i32", 65 | array_type: "Int32Array", 66 | array_name: "I32", 67 | }, 68 | { 69 | type: "f32", 70 | array_type: "Float32Array", 71 | array_name: "F32" 72 | }, 73 | { 74 | type: "f64", 75 | array_type: "Float64Array", 76 | array_name: "F64", 77 | }, 78 | ]; 79 | 80 | var typeToArrayName = astutil.index(["type"], views, undefined, function(row) { return row.array_name }); 81 | 82 | var JSTranslator = function(use_shared_memory) { 83 | this.use_shared_memory = use_shared_memory; 84 | }; 85 | 86 | JSTranslator.prototype.localName = function(local) { 87 | return "$" + local.name; 88 | }; 89 | 90 | JSTranslator.prototype.funcName = function(func) { 91 | return func.name.text; 92 | }; 93 | 94 | JSTranslator.prototype.externName = function(func) { 95 | return func.name.text; 96 | }; 97 | 98 | JSTranslator.prototype.tlsName = function(tls) { 99 | return tls.name.text; 100 | }; 101 | 102 | JSTranslator.prototype.arrayViewName = function(name) { 103 | if (this.use_shared_memory) { 104 | name = "Shared" + name; 105 | } 106 | return name; 107 | } 108 | 109 | var binOpResult = { 110 | "+": "f64", 111 | "-": "f64", 112 | "*": "f64", 113 | "/": "f64", 114 | "%": "f64", 115 | "==": "bool", 116 | "!=": "bool", 117 | "<": "bool", 118 | "<=": "bool", 119 | ">": "bool", 120 | ">=": "bool", 121 | "&": "i32", 122 | "^": "i32", 123 | "|": "i32", 124 | "<<": "i32", 125 | ">>": "i32", 126 | ">>>": "u32", 127 | }; 128 | 129 | var wasmToJSInfixOp = astutil.index(["wasmop"], [ 130 | {wasmop: opinfo.binaryOps.add, jsop: "+"}, 131 | {wasmop: opinfo.binaryOps.sub, jsop: "-"}, 132 | {wasmop: opinfo.binaryOps.mul, jsop: "*"}, 133 | {wasmop: opinfo.binaryOps.div, jsop: "/"}, 134 | {wasmop: opinfo.binaryOps.sdiv, jsop: "/"}, 135 | {wasmop: opinfo.binaryOps.srem, jsop: "%"}, 136 | {wasmop: opinfo.binaryOps.and, jsop: "&"}, 137 | {wasmop: opinfo.binaryOps.ior, jsop: "|"}, 138 | {wasmop: opinfo.binaryOps.xor, jsop: "^"}, 139 | {wasmop: opinfo.binaryOps.shl, jsop: "<<"}, 140 | {wasmop: opinfo.binaryOps.sar, jsop: ">>"}, 141 | {wasmop: opinfo.binaryOps.shr, jsop: ">>>"}, 142 | {wasmop: opinfo.binaryOps.eq, jsop: "=="}, 143 | {wasmop: opinfo.binaryOps.ne, jsop: "!="}, 144 | {wasmop: opinfo.binaryOps.lt, jsop: "<"}, 145 | {wasmop: opinfo.binaryOps.le, jsop: "<="}, 146 | {wasmop: opinfo.binaryOps.slt, jsop: "<"}, 147 | {wasmop: opinfo.binaryOps.sle, jsop: "<="}, 148 | {wasmop: opinfo.binaryOps.gt, jsop: ">"}, 149 | {wasmop: opinfo.binaryOps.ge, jsop: ">="}, 150 | {wasmop: opinfo.binaryOps.sgt, jsop: ">"}, 151 | {wasmop: opinfo.binaryOps.sge, jsop: ">="}, 152 | ]); 153 | 154 | var prefixOpResult = { 155 | "!": "bool", 156 | }; 157 | 158 | var wasmToJSPrefixOp = astutil.index(["wasmop"], [ 159 | {wasmop: "boolnot", jsop: "!"}, 160 | {wasmop: "neg", jsop: "-"}, 161 | ]); 162 | 163 | JSTranslator.prototype.defaultTranslateBinaryOp = function(optype, op, left, right, resultType) { 164 | var jsOp = wasmToJSInfixOp[op].jsop; 165 | var actualType = binOpResult[jsOp]; 166 | var out = jast.BinaryOp({ 167 | left: left, 168 | op: jsOp, 169 | right: right, 170 | }); 171 | return this.coerce(out, actualType, resultType); 172 | }; 173 | 174 | JSTranslator.prototype.implicitType = function(expr) { 175 | switch (expr.type) { 176 | case "Call": 177 | case "GetName": 178 | return "?"; 179 | case "ConstNum": 180 | return "f64"; 181 | case "BinaryOp": 182 | var result = binOpResult[expr.op]; 183 | if (result === undefined) { 184 | throw Error(expr.op); 185 | } 186 | return result; 187 | case "PrefixOp": 188 | switch (expr.op) { 189 | case "!": 190 | return "bool"; 191 | default: 192 | throw Error(expr.op); 193 | } 194 | default: 195 | throw Error(expr.type); 196 | } 197 | } 198 | 199 | JSTranslator.prototype.implicitCoerce = function(expr, desiredType){ 200 | var actualType = this.implicitType(expr); 201 | return this.coerce(expr, actualType, desiredType); 202 | } 203 | 204 | JSTranslator.prototype.coerce = function(expr, actualType, desiredType){ 205 | if (actualType == desiredType) { 206 | return expr; 207 | } 208 | switch (desiredType) { 209 | case "i32": 210 | return jast.BinaryOp({ 211 | left: expr, 212 | op: "|", 213 | right: jast.ConstNum({ 214 | value: 0 215 | }), 216 | }); 217 | case "i64": 218 | // HACK i64 operations are just double operations that are truncated. 219 | return jast.Call({ 220 | expr: jast.GetName({ 221 | name: "trunc", 222 | }), 223 | args: [ 224 | expr, 225 | ], 226 | }); 227 | case "f32": 228 | return jast.Call({ 229 | expr: jast.GetName({ 230 | name: "fround", 231 | }), 232 | args: [ 233 | expr, 234 | ], 235 | }); 236 | case "f64": 237 | return jast.PrefixOp({ 238 | op: "+", 239 | expr: expr, 240 | }); 241 | default: 242 | throw Error(type); 243 | } 244 | } 245 | 246 | JSTranslator.prototype.processExpr = function(expr) { 247 | switch(expr.type) { 248 | case "ConstI32": 249 | return jast.ConstNum({ 250 | value: expr.value, 251 | }); 252 | case "ConstI64": 253 | // HACK this is imprecise. 254 | return jast.ConstNum({ 255 | value: expr.value, 256 | }); 257 | case "ConstF32": 258 | return jast.ConstNum({ 259 | value: expr.value, 260 | }); 261 | case "ConstF64": 262 | return jast.ConstNum({ 263 | value: expr.value, 264 | }); 265 | case "GetLocal": 266 | return jast.GetName({ 267 | name: this.localName(expr.local), 268 | }); 269 | case "GetTls": 270 | return jast.GetName({ 271 | name: this.tlsName(expr.tls), 272 | }); 273 | case "GetFunction": 274 | return jast.ConstNum({ 275 | value: expr.func.funcPtr, 276 | }); 277 | case "Load": 278 | if (!(expr.mtype in typeToArrayName)) throw Error(expr.mtype); 279 | 280 | var shift = Math.log2(typeinfo.sizeOf(expr.mtype)); 281 | 282 | return jast.GetIndex({ 283 | expr: jast.GetName({ 284 | name: typeToArrayName[expr.mtype], 285 | }), 286 | index: jast.BinaryOp({ 287 | left: this.processExpr(expr.address), 288 | op: ">>", 289 | right: jast.ConstNum({value: shift}), 290 | }), 291 | }); 292 | case "Store": 293 | if (!(expr.mtype in typeToArrayName)) throw Error(expr.mtype); 294 | 295 | var shift = Math.log2(typeinfo.sizeOf(expr.mtype)); 296 | 297 | return jast.Assign({ 298 | target: jast.GetIndex({ 299 | expr: jast.GetName({ 300 | name: typeToArrayName[expr.mtype], 301 | }), 302 | index: jast.BinaryOp({ 303 | left: this.processExpr(expr.address), 304 | op: ">>", 305 | right: jast.ConstNum({value: shift}), 306 | }), 307 | }), 308 | value: this.processExpr(expr.value), 309 | }); 310 | case "UnaryOp": 311 | var child = this.processExpr(expr.expr); 312 | switch (expr.op) { 313 | case "sqrt": 314 | var actualType = "f64"; 315 | var resultType = expr.etype; 316 | var out = jast.Call({ 317 | expr: jast.GetName({ 318 | name: "sqrt", 319 | }), 320 | args: [ 321 | child, 322 | ], 323 | }); 324 | break; 325 | default: 326 | var jsOp = wasmToJSPrefixOp[expr.op].jsop; 327 | var actualType = binOpResult[jsOp]; 328 | var resultType = expr.etype; 329 | var out = jast.PrefixOp({ 330 | op: jsOp, 331 | expr: child, 332 | }); 333 | } 334 | return this.coerce(out, actualType, resultType); 335 | case "Coerce": 336 | return this.implicitCoerce(this.processExpr(expr.expr), expr.mtype); 337 | case "BinaryOp": 338 | var left = this.processExpr(expr.left); 339 | var right = this.processExpr(expr.right); 340 | 341 | // Special cases 342 | switch (expr.op) { 343 | case "mul": 344 | switch (expr.optype) { 345 | case "i32": 346 | return jast.Call({ 347 | expr: jast.GetName({ 348 | name: "imul", 349 | }), 350 | args: [ 351 | left, 352 | right, 353 | ], 354 | }); 355 | } 356 | break; 357 | case "min": 358 | return this.coerce(jast.Call({ 359 | expr: jast.GetName({ 360 | name: "min", 361 | }), 362 | args: [ 363 | left, 364 | right, 365 | ], 366 | }), "f64", expr.etype); 367 | case "max": 368 | return this.coerce(jast.Call({ 369 | expr: jast.GetName({ 370 | name: "max", 371 | }), 372 | args: [ 373 | left, 374 | right, 375 | ], 376 | }), "f64", expr.etype); 377 | case ">>": 378 | case ">>>": 379 | case "<<": 380 | case "|": 381 | case "&": 382 | case "^": 383 | switch (expr.optype) { 384 | case "i64": 385 | throw Error(expr.op + " not yet supported for i64"); 386 | 387 | } 388 | break; 389 | } 390 | 391 | // The default 392 | return this.defaultTranslateBinaryOp(expr.optype, expr.op, left, right, expr.etype); 393 | case "CallDirect": 394 | var args = []; 395 | for (var i = 0; i < expr.args.length; i++) { 396 | args.push(this.processExpr(expr.args[i])); 397 | } 398 | return jast.Call({ 399 | expr: jast.GetName({name: this.funcName(expr.func)}), 400 | args: args, 401 | }); 402 | case "CallExternal": 403 | var args = []; 404 | for (var i = 0; i < expr.args.length; i++) { 405 | args.push(this.processExpr(expr.args[i])); 406 | } 407 | return jast.Call({ 408 | expr: jast.GetName({name: this.externName(expr.func)}), 409 | args: args, 410 | }); 411 | case "CallIndirect": 412 | var index = this.processExpr(expr.expr); 413 | var args = []; 414 | for (var i = 0; i < expr.args.length; i++) { 415 | args.push(this.processExpr(expr.args[i])); 416 | } 417 | return jast.Call({ 418 | expr: jast.GetIndex({ 419 | expr: jast.GetName({name: "ftable"}), 420 | index: index, 421 | }), 422 | args: args, 423 | }); 424 | default: 425 | console.log(expr); 426 | throw Error(expr.type); 427 | } 428 | }; 429 | 430 | JSTranslator.prototype.addLabel = function(stmt, result) { 431 | if (stmt.generatedLabel) { 432 | result = jast.Label({name: stmt.generatedLabel, stmt: result}); 433 | } 434 | return result; 435 | }; 436 | 437 | JSTranslator.prototype.processStmt = function(stmt, result) { 438 | switch(stmt.type) { 439 | case "If": 440 | result.push(this.addLabel(stmt, jast.If({ 441 | cond: this.processExpr(stmt.cond), 442 | t: this.processBlock(stmt.t, []), 443 | f: stmt.f ? this.processBlock(stmt.f, []) : null, 444 | }))); 445 | break; 446 | case "Loop": 447 | result.push(this.addLabel(stmt, jast.While({ 448 | cond: jast.GetName({name: "true"}), 449 | body: this.processBlock(stmt.body, []), 450 | }))); 451 | break; 452 | case "SetLocal": 453 | result.push(jast.Assign({ 454 | target: jast.GetName({ 455 | name: this.localName(stmt.local), 456 | }), 457 | value: this.processExpr(stmt.value), 458 | })); 459 | break; 460 | case "SetTls": 461 | result.push(jast.Assign({ 462 | target: jast.GetName({ 463 | name: this.tlsName(stmt.tls), 464 | }), 465 | value: this.processExpr(stmt.value), 466 | })); 467 | break; 468 | case "Return": 469 | result.push(jast.Return({ 470 | expr: stmt.expr ? this.processExpr(stmt.expr) : null, 471 | })); 472 | break; 473 | case "Break": 474 | result.push(jast.Break({ 475 | name: stmt.generatedLabel, 476 | })); 477 | break; 478 | default: 479 | result.push(this.processExpr(stmt)); 480 | } 481 | return result; 482 | }; 483 | 484 | JSTranslator.prototype.processBlock = function(block, result) { 485 | for (var i = 0; i < block.length; i++) { 486 | this.processStmt(block[i], result); 487 | } 488 | return result; 489 | }; 490 | 491 | JSTranslator.prototype.zeroValue = function(t) { 492 | switch (t) { 493 | case "i32": 494 | case "f32": 495 | case "f64": 496 | return this.coerce(jast.ConstNum({value: 0}), "f64", t); 497 | default: 498 | throw Error(t); 499 | } 500 | }; 501 | 502 | JSTranslator.prototype.processFunc = function(func) { 503 | this.func = func; 504 | 505 | var analysis = new traverse.TopDownBottomUp(new TranslatorAnalysis()); 506 | analysis.processFunc(func); 507 | 508 | 509 | var params = []; 510 | var body = []; 511 | for (var i = 0; i < func.params.length; i++){ 512 | var p = func.params[i]; 513 | var name = this.localName(p.local); 514 | params.push(name); 515 | body.push(jast.VarDecl({ 516 | name: name, 517 | expr: this.coerce(jast.GetName({name: name}), "?", p.ptype), 518 | })); 519 | } 520 | 521 | // HACK? 522 | for (var i = func.params.length; i < func.locals.length; i++) { 523 | var lcl = func.locals[i]; 524 | body.push(jast.VarDecl({ 525 | name: this.localName(lcl), 526 | expr: this.zeroValue(lcl.ltype), 527 | })); 528 | } 529 | 530 | this.processBlock(func.body, body); 531 | 532 | return jast.VarDecl({ 533 | name: this.funcName(func), 534 | expr: jast.FunctionExpr({ 535 | params: params, 536 | body: body, 537 | }), 538 | }); 539 | } 540 | 541 | JSTranslator.prototype.systemWrapper = function(module, system, use_shared_memory, generated) { 542 | var body = []; 543 | 544 | // The asm(ish) code. 545 | body.push(jast.VarDecl({ 546 | name: "module", 547 | expr: generated, 548 | })); 549 | 550 | // Derive stdlib names. 551 | var stdlibNames = [ 552 | "Math", 553 | ]; 554 | for (var i = 0; i < views.length; i++) { 555 | stdlibNames.push(this.arrayViewName(views[i].array_type)); 556 | } 557 | 558 | // Create stdlib structure. 559 | var stdlib = []; 560 | for (var i = 0; i < stdlibNames.length; i++) { 561 | var name = stdlibNames[i]; 562 | stdlib.push(jast.KeyValue({ 563 | key: name, 564 | value: jast.GetName({name: name}), 565 | })); 566 | } 567 | body.push(jast.VarDecl({ 568 | name: "stdlib", 569 | expr: jast.CreateObject({ 570 | args: stdlib 571 | }), 572 | })); 573 | 574 | // Initialize memory. 575 | body = body.concat(this.initMemory(module)); 576 | 577 | // Determine the names of system functions. 578 | var system_externs = {}; 579 | for (var i = 0; i < module.system_externs.length; i++) { 580 | system_externs[module.system_externs[i]] = true; 581 | } 582 | 583 | // Foreign dictionary rewriting. 584 | var wrapped_foreign = []; 585 | for (var i = 0; i < module.externs.length; i++) { 586 | var extern = module.externs[i]; 587 | var wrapper; 588 | 589 | if (extern.name.text in system_externs) { 590 | wrapper = jast.GetAttr({ 591 | expr: jast.GetName({ 592 | name: "system", 593 | }), 594 | attr: extern.name.text, 595 | }); 596 | } else { 597 | wrapper = jast.FunctionExpr({ 598 | params: [], 599 | body: [ 600 | jast.Return({ 601 | expr: jast.Call({ 602 | expr: jast.GetAttr({ 603 | expr: jast.GetAttr({ 604 | expr: jast.GetName({ 605 | name: "foreign", 606 | }), 607 | attr: extern.name.text, 608 | }), 609 | attr: "apply", 610 | }), 611 | args: [ 612 | jast.GetName({name: "instance"}), 613 | jast.GetName({name: "arguments"}), 614 | ], 615 | }), 616 | }), 617 | ], 618 | }); 619 | } 620 | 621 | wrapped_foreign.push(jast.KeyValue({ 622 | key: extern.name.text, 623 | value: wrapper, 624 | })); 625 | } 626 | body.push(jast.VarDecl({ 627 | name: "wrap_foreign", 628 | expr: jast.FunctionExpr({ 629 | params: ["system", "foreign"], 630 | body: [ 631 | jast.Return({ 632 | expr: jast.CreateObject({ 633 | args: wrapped_foreign, 634 | }), 635 | }), 636 | ], 637 | }), 638 | })); 639 | 640 | // JS System functions. 641 | body.push(jast.VarDecl({ 642 | name: "threading_supported", 643 | expr: jast.ConstNum({value: use_shared_memory | 0}), 644 | })); 645 | body.push(jast.InjectSource({ 646 | source: system, 647 | })); 648 | 649 | return jast.FunctionExpr({ 650 | params: [], 651 | body: body, 652 | }); 653 | }; 654 | 655 | JSTranslator.prototype.initMemory = function(module) { 656 | var body = []; 657 | 658 | // Create memory. 659 | body.push(jast.VarDecl({ 660 | name: "buffer", 661 | expr: jast.New({ 662 | expr: jast.GetName({ 663 | name: this.arrayViewName("ArrayBuffer"), 664 | }), 665 | args: [ 666 | jast.ConstNum({value: module.config.memory.fixed}), 667 | ], 668 | }), 669 | })); 670 | 671 | // View for initializing memory. 672 | body.push(jast.VarDecl({ 673 | name: "U8", 674 | expr: jast.New({ 675 | expr: jast.GetName({ 676 | name: this.arrayViewName("Uint8Array"), 677 | }), 678 | args: [ 679 | jast.GetName({name: "buffer"}), 680 | ], 681 | }), 682 | })); 683 | 684 | // Initialize memory. 685 | for (var i = 0; i < module.memory.length; i++) { 686 | var memory = module.memory[i]; 687 | var u8 = new Uint8Array(memory.buffer); 688 | for(var o = 0; o < u8.byteLength; o++) { 689 | if (u8[o] == 0) continue; 690 | 691 | body.push(jast.Assign({ 692 | target: jast.GetIndex({ 693 | expr: jast.GetName({ 694 | name: "U8", 695 | }), 696 | index: jast.ConstNum({value: memory.ptr + o}), 697 | }), 698 | value: jast.ConstNum({value: u8[o]}), 699 | })); 700 | } 701 | } 702 | 703 | body.push(jast.Return({ 704 | expr: jast.GetName({ 705 | name: "buffer", 706 | }), 707 | })); 708 | 709 | 710 | return [ 711 | jast.VarDecl({ 712 | name: "createMemory", 713 | expr: jast.FunctionExpr({ 714 | params: [], 715 | body: body, 716 | }), 717 | }), 718 | ]; 719 | }; 720 | 721 | JSTranslator.prototype.calcFuncInfo = function(module) { 722 | var funcPtr = 0; 723 | var funcTable = []; 724 | 725 | for (var i = 0; i < module.externs.length; i++) { 726 | var extern = module.externs[i]; 727 | extern.funcPtr = funcPtr; 728 | funcPtr += 1; 729 | 730 | funcTable.push(jast.GetName({ 731 | name: this.externName(extern), 732 | })); 733 | } 734 | 735 | for (var i = 0; i < module.funcs.length; i++) { 736 | var func = module.funcs[i]; 737 | func.funcPtr = funcPtr; 738 | funcPtr += 1; 739 | 740 | funcTable.push(jast.GetName({ 741 | name: this.funcName(func), 742 | })); 743 | } 744 | 745 | this.funcTable = funcTable; 746 | }; 747 | 748 | JSTranslator.prototype.processModule = function(module) { 749 | this.module = module; 750 | 751 | var body = []; 752 | 753 | for (var i = 0; i < views.length; i++) { 754 | var view = views[i]; 755 | body.push(jast.VarDecl({ 756 | name: view.array_name, 757 | expr: jast.New({ 758 | expr: jast.GetAttr({ 759 | expr: jast.GetName({ 760 | name: "stdlib", 761 | }), 762 | attr: this.arrayViewName(view.array_type), 763 | }), 764 | args: [ 765 | jast.GetName({name: "buffer"}), 766 | ], 767 | }), 768 | })); 769 | } 770 | 771 | var mathImports = ["fround", "imul", "trunc", "min", "max", "sqrt"]; 772 | 773 | for (var i = 0; i < mathImports.length; i++) { 774 | var name = mathImports[i]; 775 | body.push(jast.VarDecl({ 776 | name: name, 777 | expr: jast.GetAttr({ 778 | expr: jast.GetAttr({ 779 | expr: jast.GetName({ 780 | name: "stdlib", 781 | }), 782 | attr: "Math", 783 | }), 784 | attr: name, 785 | }), 786 | })); 787 | } 788 | 789 | // Foreign functions. 790 | for (var i = 0; i < module.externs.length; i++) { 791 | var extern = module.externs[i]; 792 | body.push(jast.VarDecl({ 793 | name: this.externName(extern), 794 | expr: jast.GetAttr({ 795 | expr: jast.GetName({ 796 | name: "foreign", 797 | }), 798 | attr: extern.name.text, 799 | }), 800 | })); 801 | } 802 | 803 | // Thread local storage (globals). 804 | for (var i = 0; i < module.tls.length; i++) { 805 | var v = module.tls[i]; 806 | body.push(jast.VarDecl({ 807 | name: this.tlsName(v), 808 | expr: jast.ConstNum({value: 0}), // TODO type coercion. 809 | })); 810 | } 811 | 812 | var exports = []; 813 | 814 | for (var i = 0; i < module.funcs.length; i++) { 815 | var func = module.funcs[i]; 816 | body.push(this.processFunc(func)); 817 | if (func.exportFunc) { 818 | exports.push(jast.KeyValue({ 819 | key: func.name.text, 820 | value: jast.GetName({ 821 | name: this.funcName(func), 822 | }), 823 | })); 824 | } 825 | } 826 | 827 | // Indirect function call table. 828 | body.push(jast.VarDecl({ 829 | name: "ftable", 830 | expr: jast.CreateArray({ 831 | args: this.funcTable, 832 | }), 833 | })); 834 | 835 | body.push(jast.Return({ 836 | expr: jast.CreateObject({ 837 | args: exports, 838 | }), 839 | })); 840 | 841 | return jast.FunctionExpr({ 842 | params: ["stdlib", "foreign", "buffer"], 843 | body: body, 844 | }); 845 | }; 846 | 847 | var translate = function(module, system, use_shared_memory) { 848 | var translator = new JSTranslator(use_shared_memory); 849 | translator.calcFuncInfo(module); 850 | return translator.systemWrapper(module, system, use_shared_memory, translator.processModule(module)); 851 | }; 852 | 853 | return { 854 | translate: translate, 855 | }; 856 | }); 857 | -------------------------------------------------------------------------------- /wasm/traverse.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | var TopDownBottomUp = function(visitor) { 3 | this.visitor = visitor; 4 | }; 5 | 6 | TopDownBottomUp.prototype.processExpr = function(node) { 7 | if (this.visitor.processExprPre) { 8 | node = this.visitor.processExprPre(node); 9 | } 10 | switch (node.type) { 11 | case "GetLocal": 12 | case "GetFunction": 13 | case "GetExtern": 14 | case "GetTls": 15 | case "ConstI32": 16 | case "ConstI64": 17 | case "ConstF32": 18 | case "ConstF64": 19 | break; 20 | case "PrefixOp": 21 | node.expr = this.processExpr(node.expr); 22 | break; 23 | case "InfixOp": 24 | node.left = this.processExpr(node.left); 25 | node.right = this.processExpr(node.right); 26 | break; 27 | case "UnaryOp": 28 | node.expr = this.processExpr(node.expr); 29 | break; 30 | case "BinaryOp": 31 | node.left = this.processExpr(node.left); 32 | node.right = this.processExpr(node.right); 33 | break; 34 | case "Store": 35 | node.address = this.processExpr(node.address); 36 | node.value = this.processExpr(node.value); 37 | break; 38 | case "Load": 39 | node.address = this.processExpr(node.address); 40 | break; 41 | case "Coerce": 42 | node.expr = this.processExpr(node.expr); 43 | break; 44 | case "CallDirect": 45 | case "CallExternal": 46 | for (var i = 0; i < node.args.length; i++) { 47 | node.args[i] = this.processExpr(node.args[i]); 48 | } 49 | break; 50 | case "CallIndirect": 51 | node.expr = this.processExpr(node.expr); 52 | for (var i = 0; i < node.args.length; i++) { 53 | node.args[i] = this.processExpr(node.args[i]); 54 | } 55 | break; 56 | default: 57 | console.log(node); 58 | throw Error(node.type); 59 | } 60 | if (this.visitor.processExprPost) { 61 | node = this.visitor.processExprPost(node); 62 | } 63 | return node; 64 | } 65 | 66 | TopDownBottomUp.prototype.processStmt = function(node, out) { 67 | var temp = []; 68 | if (this.visitor.processStmtPre) { 69 | this.visitor.processStmtPre(node, temp); 70 | } else { 71 | temp.push(node); 72 | } 73 | 74 | for (var i = 0; i < temp.length; i++) { 75 | node = temp[i]; 76 | switch (node.type) { 77 | case "SetLocal": 78 | case "SetTls": 79 | node.value = this.processExpr(node.value); 80 | break; 81 | case "Return": 82 | if (node.expr) { 83 | node.expr = this.processExpr(node.expr); 84 | } 85 | break; 86 | case "Break": 87 | break; 88 | case "If": 89 | node.cond = this.processExpr(node.cond); 90 | node.t = this.processBlock(node.t); 91 | if (node.f) { 92 | node.f = this.processBlock(node.f); 93 | } 94 | break; 95 | case "While": 96 | node.cond = this.processExpr(node.cond); 97 | node.body = this.processBlock(node.body); 98 | break; 99 | case "Loop": 100 | node.body = this.processBlock(node.body); 101 | break; 102 | default: 103 | node = this.processExpr(node); 104 | } 105 | if (this.visitor.processStmtPost) { 106 | this.visitor.processStmtPost(node, out); 107 | } else { 108 | out.push(node); 109 | } 110 | } 111 | }; 112 | 113 | TopDownBottomUp.prototype.processBlock = function(block) { 114 | var out = []; 115 | for (var i = 0; i < block.length; i++) { 116 | this.processStmt(block[i], out); 117 | } 118 | return out; 119 | }; 120 | 121 | TopDownBottomUp.prototype.processFunc = function(func) { 122 | if (this.visitor.processFuncPre) { 123 | this.visitor.processFuncPre(func); 124 | } 125 | func.body = this.processBlock(func.body); 126 | if (this.visitor.processFuncPost) { 127 | this.visitor.processFuncPost(func); 128 | } 129 | }; 130 | 131 | TopDownBottomUp.prototype.processModule = function(module) { 132 | for (var i = 0; i < module.externs.length; i++) { 133 | var extern = module.externs[i]; 134 | if (this.visitor.processExtern) { 135 | this.visitor.processExtern(extern); 136 | } 137 | } 138 | for (var i = 0; i < module.funcs.length; i++) { 139 | this.processFunc(module.funcs[i]); 140 | } 141 | }; 142 | 143 | return {TopDownBottomUp: TopDownBottomUp}; 144 | }); -------------------------------------------------------------------------------- /wasm/typeinfo.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | var sizeOf = function(t) { 3 | switch (t) { 4 | case "i8": 5 | return 1; 6 | case "i16": 7 | return 2; 8 | case "i32": 9 | return 4; 10 | case "f32": 11 | return 4; 12 | case "f64": 13 | return 8; 14 | default: 15 | throw Error(t); 16 | } 17 | }; 18 | 19 | return { 20 | sizeOf: sizeOf, 21 | }; 22 | }); -------------------------------------------------------------------------------- /wassembler.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | } 4 | 5 | textarea { 6 | font-family: monospace; 7 | } 8 | 9 | input:disabled + label { 10 | color: #aaa; 11 | } --------------------------------------------------------------------------------