├── .gitignore ├── .jscsrc ├── .jshintrc ├── Makefile ├── README.md ├── binding.gyp ├── lib ├── wasm.js └── wasm │ ├── base │ ├── codegen │ │ ├── builder.js │ │ └── index.js │ ├── gvn │ │ ├── commutative-math-test.js │ │ ├── commutative-math.js │ │ ├── index.js │ │ ├── same-node-test.js │ │ └── same-node.js │ ├── index.js │ └── reductions │ │ ├── combo-analysis │ │ ├── float-range.js │ │ ├── index.js │ │ ├── info.js │ │ └── int-range.js │ │ ├── combo-test.js │ │ ├── index.js │ │ ├── inline-params-test.js │ │ ├── inline-params.js │ │ ├── redundant-phi-test.js │ │ ├── redundant-phi.js │ │ ├── select-test.js │ │ └── select.js │ ├── compiler.js │ ├── pipeline.js │ ├── ref-table.js │ ├── std.js │ └── x64 │ ├── codegen │ ├── bool.js │ ├── branching.js │ ├── builder.js │ ├── cast.js │ ├── generic.js │ ├── index.js │ ├── math.js │ └── memory.js │ ├── gvn │ └── index.js │ ├── index.js │ └── reductions │ ├── index.js │ ├── select-test.js │ └── select.js ├── package.json ├── src ├── std.cc └── std.h └── test ├── api-test.js ├── fixtures.js └── x64 ├── base-test.js ├── bool-test.js ├── cast-test.js ├── math-test.js └── memory-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | build/ 4 | out/ 5 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowKeywordsOnNewLine": [ "else" ], 3 | "disallowMixedSpacesAndTabs": true, 4 | "disallowMultipleLineStrings": true, 5 | "disallowMultipleVarDecl": true, 6 | "disallowNewlineBeforeBlockStatements": true, 7 | "disallowQuotedKeysInObjects": false, 8 | "disallowSpaceAfterObjectKeys": true, 9 | "disallowSpaceAfterPrefixUnaryOperators": true, 10 | "disallowSpaceBeforePostfixUnaryOperators": true, 11 | "disallowSpacesInCallExpression": true, 12 | "disallowTrailingComma": true, 13 | "disallowTrailingWhitespace": true, 14 | "disallowYodaConditions": true, 15 | 16 | "requireCommaBeforeLineBreak": true, 17 | "requireOperatorBeforeLineBreak": true, 18 | "requireSpaceAfterBinaryOperators": true, 19 | "requireSpaceAfterKeywords": [ "if", "for", "while", "else", "try", "catch" ], 20 | "requireSpaceAfterLineComment": true, 21 | "requireSpaceBeforeBinaryOperators": true, 22 | "requireSpaceBeforeBlockStatements": true, 23 | "requireSpaceBeforeKeywords": [ "else", "catch" ], 24 | "requireSpaceBeforeObjectValues": true, 25 | "requireSpaceBetweenArguments": true, 26 | "requireSpacesInAnonymousFunctionExpression": { 27 | "beforeOpeningCurlyBrace": true 28 | }, 29 | "requireSpacesInFunctionDeclaration": { 30 | "beforeOpeningCurlyBrace": true 31 | }, 32 | "requireSpacesInFunctionExpression": { 33 | "beforeOpeningCurlyBrace": true 34 | }, 35 | "requireSpacesInConditionalExpression": true, 36 | "requireSpacesInForStatement": true, 37 | "requireSpacesInsideArrayBrackets": "all", 38 | "requireSpacesInsideObjectBrackets": "all", 39 | "requireDotNotation": false, 40 | 41 | "maximumLineLength": 80, 42 | "validateIndentation": 2, 43 | "validateLineBreaks": "LF", 44 | "validateParameterSeparator": ", ", 45 | "validateQuoteMarks": "'" 46 | } 47 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // JSHint Default Configuration File (as on JSHint website) 3 | // See http://jshint.com/docs/ for more details 4 | 5 | "maxerr" : 50, // {int} Maximum error before stopping 6 | 7 | // Enforcing 8 | "bitwise" : false, // true: Prohibit bitwise operators (&, |, ^, etc.) 9 | "camelcase" : false, // true: Identifiers must be in camelCase 10 | "curly" : false, // true: Require {} for every new block or scope 11 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 12 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 13 | "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. 14 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 15 | "indent" : 2, // {int} Number of spaces to use for indentation 16 | "latedef" : true, // true: Require variables/functions to be defined before being used 17 | "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()` 18 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 19 | "noempty" : false, // true: Prohibit use of empty blocks 20 | "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters. 21 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 22 | "plusplus" : false, // true: Prohibit use of `++` & `--` 23 | "quotmark" : "single", // Quotation mark consistency: 24 | // false : do nothing (default) 25 | // true : ensure whatever is used is consistent 26 | // "single" : require single quotes 27 | // "double" : require double quotes 28 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 29 | "unused" : true, // true: Require all defined variables be used 30 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 31 | "maxparams" : false, // {int} Max number of formal params allowed per function 32 | "maxdepth" : 3, // {int} Max depth of nested blocks (within functions) 33 | "maxstatements" : false, // {int} Max number statements per function 34 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 35 | "maxlen" : false, // {int} Max number of characters per line 36 | 37 | // Relaxing 38 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 39 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 40 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 41 | "eqnull" : false, // true: Tolerate use of `== null` 42 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 43 | "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) 44 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 45 | // (ex: `for each`, multiple try/catch, function expression…) 46 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 47 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 48 | "funcscope" : false, // true: Tolerate defining variables inside control statements 49 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 50 | "iterator" : false, // true: Tolerate using the `__iterator__` property 51 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 52 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 53 | "laxcomma" : false, // true: Tolerate comma-first style coding 54 | "loopfunc" : false, // true: Tolerate functions being defined in loops 55 | "multistr" : false, // true: Tolerate multi-line strings 56 | "noyield" : false, // true: Tolerate generator functions with no yield statement in them. 57 | "notypeof" : false, // true: Tolerate invalid typeof operator values 58 | "proto" : false, // true: Tolerate using the `__proto__` property 59 | "scripturl" : false, // true: Tolerate script-targeted URLs 60 | "shadow" : true, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 61 | "sub" : true, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 62 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 63 | "validthis" : false, // true: Tolerate using this in a non-constructor function 64 | 65 | // Environments 66 | "browser" : true, // Web Browser (window, document, etc) 67 | "browserify" : true, // Browserify (node.js code in the browser) 68 | "couch" : false, // CouchDB 69 | "devel" : true, // Development/debugging (alert, confirm, etc) 70 | "dojo" : false, // Dojo Toolkit 71 | "jasmine" : false, // Jasmine 72 | "jquery" : false, // jQuery 73 | "mocha" : true, // Mocha 74 | "mootools" : false, // MooTools 75 | "node" : true, // Node.js 76 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 77 | "prototypejs" : false, // Prototype and Scriptaculous 78 | "qunit" : false, // QUnit 79 | "rhino" : false, // Rhino 80 | "shelljs" : false, // ShellJS 81 | "worker" : false, // Web Workers 82 | "wsh" : false, // Windows Scripting Host 83 | "yui" : false, // Yahoo User Interface 84 | 85 | // Custom Globals 86 | "globals" : { 87 | "module": true, 88 | "Map": true 89 | } // additional predefined global variables 90 | } 91 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | JSCS ?= node_modules/.bin/jscs 2 | JSHINT ?= node_modules/.bin/jshint 3 | MOCHA ?= node_modules/.bin/mocha 4 | 5 | SRC ?= 6 | SRC += $(shell find lib/ -name '*.js') 7 | TEST ?= 8 | TEST += $(shell find test/ lib/ -name '*-test.js') 9 | GREP ?= 10 | 11 | test: 12 | $(MOCHA) --reporter=spec $(TEST) 13 | make lint 14 | 15 | test-grep: 16 | $(MOCHA) --reporter=spec $(TEST) --grep "$(GREP)" 17 | make lint 18 | 19 | lint: 20 | @echo Checking style 21 | @$(JSCS) $(SRC) 22 | @echo Checking stupid mistakes 23 | @$(JSHINT) $(SRC) 24 | 25 | .PHONY: lint test 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebAssembly JIT Compiler 2 | 3 | ## Usage 4 | 5 | ```javascript 6 | var wasm = require('wasm-jit'); 7 | 8 | var compiler = wasm.Compiler.create(); 9 | 10 | var module = 'your-module-name'; 11 | var src = 'void main() {}\n' + 12 | 'export main'; 13 | var info = compiler.compile(module, src); 14 | 15 | if (!info.main) 16 | throw new Error('No `main` function was exported'); 17 | 18 | var ctx = wasm.std.createContext(); 19 | info.main(ctx); 20 | ``` 21 | 22 | ## Project structure 23 | 24 | The project is at its very early phase, and please use it with care. The main 25 | npm dependencies are: 26 | 27 | - [wasm-ast][0] - parsing source string into AST form, which is similar to 28 | [Mozilla's AST][2] in many ways 29 | - [wasm-cfg][1] - taking the source and transforming it into Control Flow Graph 30 | - [json-pipeline][3] - basement for CFG graph, and for generic sea-of-nodes 31 | graph 32 | - [ssa.js][4], [dominance-frontier][5] - building SSA form out of non-SSA CFG 33 | - [json-pipeline-reducer][6] - performing reductions on sea-of-nodes graph 34 | - [gvn][9] - Global Value Numbering algorithm, for removing common 35 | sub-expressions 36 | - [json-pipeline-scheduler][7] - scheduling sea-of-nodes back to CFG 37 | - [jit.js][8] - generating machine code out of CFG 38 | 39 | ### Directories 40 | 41 | - `lib/*.js` - has very generic, API related files and entities like: 42 | - `compiler.js` - a Compiler instance and APIs 43 | - `pipeline.js` - responsible for transforming CFG to the machine code, 44 | invokes all reductions and optimizations 45 | - `ref-table.js` - RefTable instance, holds references to modules' 46 | functions (both exported and internal), also holds `namespace` hashmap, used 47 | for looking up imported functions 48 | - `std` - standard library module, and context creation API 49 | - `lib//**/*.js` - platform-specific implementation (or base 50 | for all platform implementations): 51 | - `codegen/` - instance, responsible for generating machine code out of the 52 | CFG, invoked by `lib/pipeline.js` 53 | - `codegen/builder.js` - convenience methods for defining opcodes on the 54 | Codegen (see `builder.opcode()` throughout the code base) 55 | - `reductions/` - holds optimizing and instruction selecting reductions, 56 | invoked by `lib/pipeline.js`. May have different reduction phases, which 57 | will be invoked in specific order by `lib/pipeline.js` 58 | - `gvn/` - Global Value Numbering relations, i.e. the criteria for determining 59 | that two expressions are equivalent, and could be replaced by a single one 60 | 61 | Each reduction and GVN relation have their own tests. Platform tests live in 62 | `test/`. General tests in `test/*-test.js`. 63 | 64 | ## LICENSE 65 | 66 | This software is licensed under the MIT License. 67 | 68 | Copyright Fedor Indutny, 2015. 69 | 70 | Permission is hereby granted, free of charge, to any person obtaining a 71 | copy of this software and associated documentation files (the 72 | "Software"), to deal in the Software without restriction, including 73 | without limitation the rights to use, copy, modify, merge, publish, 74 | distribute, sublicense, and/or sell copies of the Software, and to permit 75 | persons to whom the Software is furnished to do so, subject to the 76 | following conditions: 77 | 78 | The above copyright notice and this permission notice shall be included 79 | in all copies or substantial portions of the Software. 80 | 81 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 82 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 83 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 84 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 85 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 86 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 87 | USE OR OTHER DEALINGS IN THE SOFTWARE. 88 | 89 | [0]: https://github.com/indutny/wasm-ast 90 | [1]: https://github.com/indutny/wasm-cfg 91 | [2]: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API 92 | [3]: https://github.com/indutny/json-pipeline 93 | [4]: https://github.com/js-js/ssa.js 94 | [5]: https://github.com/js-js/dominance-frontier 95 | [6]: https://github.com/indutny/json-pipeline-reducer 96 | [7]: https://github.com/indutny/json-pipeline-scheduler 97 | [8]: https://github.com/js-js/jit.js 98 | [9]: https://github.com/indutny/gvn 99 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "std", 5 | 6 | "include_dirs": [ 7 | ".", 8 | "= 0; 24 | }; 25 | 26 | IntRange.prototype.add = function add(other) { 27 | var from = this.from.add(other.from); 28 | var to = this.to.add(other.to); 29 | 30 | // Overflow 31 | if (from.bitLength() > this.size || to.bitLength() > this.size) 32 | return null; 33 | return new IntRange(this.size, from, to); 34 | }; 35 | 36 | IntRange.prototype.union = function union(other) { 37 | return new IntRange(this.size, 38 | BN.min(this.from, other.from), 39 | BN.max(this.to, other.to)); 40 | }; 41 | 42 | IntRange.prototype.isConstant = function isConstant() { 43 | return this.from.cmp(this.to) === 0; 44 | }; 45 | -------------------------------------------------------------------------------- /lib/wasm/base/reductions/combo-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ComboAnalysis = require('./combo-analysis'); 4 | var fixtures = require('../../../../test/fixtures'); 5 | 6 | describe('base/reductions/ComboAnalysis', function() { 7 | var combo; 8 | beforeEach(function() { 9 | combo = new ComboAnalysis(); 10 | }); 11 | 12 | it('should remove trivially unreachable code', function() { 13 | fixtures.testReduction(combo, function() {/* 14 | pipeline { 15 | b0 { 16 | i0 = i64.const "1" 17 | i1 = i64.ret ^b0, i0 18 | i2 = ret ^i1 19 | i3 = i64.const "2" 20 | i4 = i64.ret ^i2, i3 21 | i5 = ret ^i4 22 | } 23 | } 24 | */}, function() {/* 25 | pipeline { 26 | b0 { 27 | i0 = i64.const "1" 28 | i1 = i64.ret ^b0, i0 29 | i2 = ret ^i1 30 | } 31 | } 32 | */}); 33 | }); 34 | 35 | it('should replace math operations with constants', function() { 36 | fixtures.testReduction(combo, function() {/* 37 | pipeline { 38 | b0 { 39 | i0 = i64.const "1" 40 | i1 = i64.const "2" 41 | i2 = i64.add i0, i1 42 | i3 = i64.ret ^b0, i2 43 | } 44 | } 45 | */}, function() {/* 46 | pipeline { 47 | b0 { 48 | i0 = i64.const "3" 49 | i1 = i64.ret ^b0, i0 50 | } 51 | } 52 | */}); 53 | }); 54 | 55 | it('should not replace overflowing math operations', function() { 56 | fixtures.testReduction(combo, function() {/* 57 | pipeline { 58 | b0 { 59 | i0 = i32.const "deadbeef" 60 | i1 = i32.const "abbadead" 61 | i2 = i32.add i0, i1 62 | i3 = i32.ret ^b0, i2 63 | } 64 | } 65 | */}, function() {/* 66 | pipeline { 67 | b0 { 68 | i0 = i32.const "deadbeef" 69 | i1 = i32.const "abbadead" 70 | i2 = i32.add i0, i1 71 | i3 = i32.ret ^b0, i2 72 | } 73 | } 74 | */}); 75 | }); 76 | 77 | it('should remove unreachable code under the constant branches', function() { 78 | fixtures.testReduction(combo, function() {/* 79 | pipeline { 80 | b0 { 81 | i0 = i64.const "1" 82 | i1 = i64.const "2" 83 | i2 = i64.add i0, i1 84 | i3 = if ^b0, i2 85 | } 86 | b0 -> b1, b2 87 | 88 | b1 { 89 | i4 = i64.const "123" 90 | i5 = jump ^b1 91 | } 92 | b1 -> b3 93 | 94 | b2 { 95 | i6 = i64.const "456" 96 | i7 = jump ^b2 97 | } 98 | b2 -> b3 99 | 100 | b3 { 101 | i8 = ssa:phi ^b3, i4, i6 102 | i9 = i64.ret ^i8, i8 103 | } 104 | } 105 | */}, function() {/* 106 | pipeline { 107 | b0 { 108 | i0 = jump ^b0 109 | } 110 | b0 -> b1 111 | b1 { 112 | i1 = jump ^b1 113 | } 114 | b1 -> b2 115 | b2 { 116 | i2 = i64.const "123" 117 | i3 = i64.ret ^b2, i2 118 | } 119 | } 120 | */}); 121 | }); 122 | 123 | it('should propagate constants through ssa:phi', function() { 124 | fixtures.testReduction(combo, function() {/* 125 | pipeline { 126 | b0 { 127 | i0 = i64.param 0 128 | i3 = if ^b0, i0 129 | } 130 | b0 -> b1, b2 131 | 132 | b1 { 133 | i4 = i64.const "456" 134 | i5 = jump ^b1 135 | } 136 | b1 -> b3 137 | 138 | b2 { 139 | i6 = i64.const "456" 140 | i7 = jump ^b2 141 | } 142 | b2 -> b3 143 | 144 | b3 { 145 | i8 = ssa:phi ^b3, i4, i6 146 | i9 = i64.ret ^i8, i8 147 | } 148 | } 149 | */}, function() {/* 150 | pipeline { 151 | b0 { 152 | i0 = i64.param 0 153 | i1 = if ^b0, i0 154 | } 155 | b0 -> b1, b2 156 | b1 { 157 | i2 = jump ^b1 158 | } 159 | b1 -> b3 160 | b2 { 161 | i3 = jump ^b2 162 | } 163 | b2 -> b3 164 | b3 { 165 | i4 = i64.const "456" 166 | i5 = i64.ret ^b3, i4 167 | } 168 | } 169 | */}); 170 | }); 171 | 172 | it('should propagate ranges through ssa:phi', function() { 173 | fixtures.testReduction(combo, function() {/* 174 | pipeline { 175 | b0 { 176 | i0 = i64.param 0 177 | i3 = if ^b0, i0 178 | } 179 | b0 -> b1, b2 180 | 181 | b1 { 182 | i4 = i64.const "456" 183 | i5 = jump ^b1 184 | } 185 | b1 -> b3 186 | 187 | b2 { 188 | i6 = i64.const "123" 189 | i7 = jump ^b2 190 | } 191 | b2 -> b3 192 | 193 | b3 { 194 | i8 = ssa:phi ^b3, i4, i6 195 | i9 = if ^i8, i8 196 | } 197 | b3 -> b4, b5 198 | 199 | b4 { 200 | i10 = i64.const "42" 201 | i11 = return ^b4, i10 202 | } 203 | 204 | b5 { 205 | i12 = i64.const "23" 206 | i13 = return ^b5, i12 207 | } 208 | } 209 | */}, function() {/* 210 | pipeline { 211 | b0 { 212 | i0 = i64.param 0 213 | i1 = if ^b0, i0 214 | } 215 | b0 -> b1, b2 216 | b1 { 217 | i2 = i64.const "456" 218 | i3 = jump ^b1 219 | } 220 | b1 -> b3 221 | b2 { 222 | i4 = i64.const "123" 223 | i5 = jump ^b2 224 | } 225 | b2 -> b3 226 | b3 { 227 | i6 = ssa:phi ^b3, i2, i4 228 | i7 = jump ^i6 229 | } 230 | b3 -> b4 231 | b4 { 232 | i8 = i64.const "42" 233 | i9 = return ^b4, i8 234 | } 235 | } 236 | */}); 237 | }); 238 | 239 | it('should not kill branch with too wide range', function() { 240 | fixtures.testReduction(combo, function() {/* 241 | pipeline { 242 | b0 { 243 | i0 = i64.param 0 244 | i3 = if ^b0, i0 245 | } 246 | b0 -> b1, b2 247 | 248 | b1 { 249 | i4 = i64.const "0" 250 | i5 = jump ^b1 251 | } 252 | b1 -> b3 253 | 254 | b2 { 255 | i6 = i64.const "123" 256 | i7 = jump ^b2 257 | } 258 | b2 -> b3 259 | 260 | b3 { 261 | i8 = ssa:phi ^b3, i4, i6 262 | i9 = if ^i8, i8 263 | } 264 | b3 -> b4, b5 265 | 266 | b4 { 267 | i10 = i64.const "42" 268 | i11 = return ^b4, i10 269 | } 270 | 271 | b5 { 272 | i12 = i64.const "23" 273 | i13 = return ^b5, i12 274 | } 275 | } 276 | */}, function() {/* 277 | pipeline { 278 | b0 { 279 | i0 = i64.param 0 280 | i1 = if ^b0, i0 281 | } 282 | b0 -> b1, b2 283 | b1 { 284 | i2 = i64.const "0" 285 | i3 = jump ^b1 286 | } 287 | b1 -> b3 288 | b2 { 289 | i4 = i64.const "123" 290 | i5 = jump ^b2 291 | } 292 | b2 -> b3 293 | b3 { 294 | i6 = ssa:phi ^b3, i2, i4 295 | i7 = if ^i6, i6 296 | } 297 | b3 -> b4, b5 298 | b4 { 299 | i8 = i64.const "42" 300 | i9 = return ^b4, i8 301 | } 302 | b5 { 303 | i10 = i64.const "23" 304 | i11 = return ^b5, i10 305 | } 306 | } 307 | */}); 308 | }); 309 | }); 310 | -------------------------------------------------------------------------------- /lib/wasm/base/reductions/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.InlineParams = require('./inline-params'); 4 | exports.Select = require('./select'); 5 | exports.RedundantPhi = require('./redundant-phi'); 6 | exports.ComboAnalysis = require('./combo-analysis'); 7 | 8 | exports.stages = { 9 | generic: [ 10 | exports.ComboAnalysis, 11 | exports.Select, 12 | exports.RedundantPhi 13 | ], 14 | platform: [ 15 | exports.InlineParams 16 | ] 17 | }; 18 | -------------------------------------------------------------------------------- /lib/wasm/base/reductions/inline-params-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var InlineParams = require('./inline-params'); 4 | var fixtures = require('../../../../test/fixtures'); 5 | var wasm = require('../../../wasm'); 6 | 7 | var PARAM_COUNT = 10; 8 | 9 | describe('base/reductions/InlineParams', function() { 10 | var inline; 11 | beforeEach(function() { 12 | var params = []; 13 | for (var i = 0; i < PARAM_COUNT; i++) { 14 | params.push({ 15 | type: 'ParamDeclaration', 16 | result: { type: 'Type', name: 'i64' }, 17 | name: { type: 'Param', index: i * 2 } 18 | }); 19 | params.push({ 20 | type: 'ParamDeclaration', 21 | result: { type: 'Type', name: 'f64' }, 22 | name: { type: 'Param', index: i * 2 + 1 } 23 | }); 24 | } 25 | inline = new InlineParams(wasm.platform.x64, { 26 | params: params 27 | }); 28 | }); 29 | 30 | it('should replace params with inlined versions', function() { 31 | var src = 'pipeline {\n'; 32 | 33 | src += ' b0 {\n'; 34 | var control = 'b0'; 35 | for (var i = 0; i < 2 * PARAM_COUNT; i++) { 36 | var type = i % 2 === 0 ? 'i64' : 'f64'; 37 | var id = 'i' + i; 38 | src += ' ' + 39 | id + ' = ' + type + '.param ^' + control + ', ' + i + '\n'; 40 | control = id; 41 | } 42 | src += ' }\n'; 43 | src += ' b0 -> b1\n'; 44 | 45 | src += ' b1 {\n'; 46 | control = 'b1'; 47 | for (var i = 0; i < 2 * PARAM_COUNT; i++) { 48 | var id = 'i' + (i + 2 * PARAM_COUNT); 49 | src += id + ' = use ^' + control + ', i' + i + '\n'; 50 | control = id; 51 | } 52 | src += ' }\n'; 53 | 54 | src += '}\n'; 55 | fixtures.testReduction(inline, src, function() {/* 56 | pipeline { 57 | b0 { 58 | i0 = x64:rsi ^b0 59 | i1 = x64:xmm0 ^i0 60 | i2 = x64:rdx ^i1 61 | i3 = x64:xmm1 ^i2 62 | i4 = x64:rcx ^i3 63 | i5 = x64:xmm2 ^i4 64 | i6 = x64:r8 ^i5 65 | i7 = x64:xmm3 ^i6 66 | i8 = x64:r9 ^i7 67 | i9 = x64:xmm4 ^i8 68 | i10 = x64:xmm5 ^i9 69 | i11 = x64:xmm6 ^i10 70 | i12 = x64:xmm7 ^i11 71 | } 72 | b0 -> b1 73 | b1 { 74 | i13 = use ^b1, i0 75 | i14 = use ^i13, i1 76 | i15 = use ^i14, i2 77 | i16 = use ^i15, i3 78 | i17 = use ^i16, i4 79 | i18 = use ^i17, i5 80 | i19 = use ^i18, i6 81 | i20 = use ^i19, i7 82 | i21 = use ^i20, i8 83 | i22 = use ^i21, i9 84 | i23 = x64:int.param 0 85 | i24 = use ^i22, i23 86 | i25 = use ^i24, i10 87 | i26 = x64:int.param 1 88 | i27 = use ^i25, i26 89 | i28 = use ^i27, i11 90 | i29 = x64:int.param 2 91 | i30 = use ^i28, i29 92 | i31 = use ^i30, i12 93 | i32 = x64:int.param 3 94 | i33 = use ^i31, i32 95 | i34 = x64:f64.param 4 96 | i35 = use ^i33, i34 97 | i36 = x64:int.param 5 98 | i37 = use ^i35, i36 99 | i38 = x64:f64.param 6 100 | i39 = use ^i37, i38 101 | } 102 | } 103 | */}); 104 | }); 105 | 106 | it('should add alignment and replace pushArg of call', function() { 107 | fixtures.testReduction(inline, function() {/* 108 | pipeline { 109 | b0 { 110 | i0 = i64.const "123" 111 | i1 = i64.call ^b0, 0, "i64", "i64", i0, i0 112 | } 113 | } 114 | */}, function() {/* 115 | pipeline { 116 | b0 { 117 | i0 = i64.const "123" 118 | i1 = x64:call ^b0, 0, "i64", "i64", i0, i0 119 | } 120 | } 121 | */}); 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /lib/wasm/base/reductions/inline-params.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var Reducer = require('json-pipeline-reducer'); 5 | 6 | function InlineParams(platform, ast) { 7 | Reducer.Reduction.call(this); 8 | 9 | this.platform = platform; 10 | this.ast = ast; 11 | 12 | this.params = new Array(this.ast.params.length); 13 | this.computeParams(); 14 | } 15 | util.inherits(InlineParams, Reducer.Reduction); 16 | module.exports = InlineParams; 17 | 18 | InlineParams.prototype.computeParams = function computeParams() { 19 | var intIndex = 0; 20 | var floatIndex = 0; 21 | var stackIndex = 0; 22 | for (var i = 0; i < this.params.length; i++) { 23 | var param = this.ast.params[i]; 24 | var isInt = /^i/.test(param.result.name); 25 | 26 | var index; 27 | var params; 28 | if (isInt) { 29 | params = this.platform.params.int; 30 | index = intIndex++; 31 | } else { 32 | params = this.platform.params.float; 33 | index = floatIndex++; 34 | } 35 | this.params[i] = index < params.length ? params[index] : stackIndex++; 36 | } 37 | }; 38 | 39 | InlineParams.prototype.reduce = function reduce(node, reducer) { 40 | if (node.opcode === 'i32.param' || node.opcode === 'i64.param') 41 | return this.reduceParam(node, reducer); 42 | if (node.opcode === 'f32.param' || node.opcode === 'f64.param') 43 | return this.reduceParam(node, reducer); 44 | if (/\.call$/.test(node.opcode)) 45 | return this.reduceCall(node, reducer); 46 | }; 47 | 48 | InlineParams.prototype.reduceParam = function reduceParam(node, reducer) { 49 | var prefix = this.platform.prefix + ':'; 50 | 51 | var index = node.literals[0]; 52 | var param = this.params[index]; 53 | if (typeof param === 'string') { 54 | // Replace with register 55 | var reg = reducer.graph.create(prefix + param); 56 | reducer.replace(node, reg); 57 | return; 58 | } 59 | 60 | // Replace with `:type.param` 61 | var opcode = prefix; 62 | if (node.opcode === 'i32.param' || node.opcode === 'i64.param') 63 | opcode += 'int.param'; 64 | else if (node.opcode === 'f32.param') 65 | opcode += 'f32.param'; 66 | else if (node.opcode === 'f64.param') 67 | opcode += 'f64.param'; 68 | 69 | for (var i = node.uses.length - 2; i >= 0; i -= 2) { 70 | var use = node.uses[i]; 71 | var index = node.uses[i + 1]; 72 | 73 | var load = reducer.graph.create(opcode); 74 | load.addLiteral(param); 75 | reducer.add(load); 76 | 77 | use.replaceInput(index, load); 78 | reducer.change(use); 79 | } 80 | 81 | reducer.remove(node); 82 | }; 83 | 84 | InlineParams.prototype.reduceCall = function reduceCall(node, reducer) { 85 | if (node.opcode === 'x64:call') 86 | return; 87 | 88 | // TODO(indutny): count params, align stack if some of them needs to be stored 89 | // there. 90 | node.opcode = 'x64:call'; 91 | 92 | reducer.change(node); 93 | }; 94 | -------------------------------------------------------------------------------- /lib/wasm/base/reductions/redundant-phi-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var RedundantPhi = require('./redundant-phi'); 4 | var fixtures = require('../../../../test/fixtures'); 5 | 6 | describe('base/reductions/RedundantPhi', function() { 7 | var select; 8 | beforeEach(function() { 9 | select = new RedundantPhi(); 10 | }); 11 | 12 | it('should remove redundant phi', function() { 13 | fixtures.testReduction(select, function() {/* 14 | pipeline { 15 | b0 { 16 | i0 = if ^b0 17 | } 18 | b0 -> b1, b2 19 | b1 { 20 | i1 = i64.const "1" 21 | i2 = jump ^b1 22 | } 23 | b1 -> b3 24 | b2 { 25 | i3 = i64.const "1" 26 | i4 = jump ^b2 27 | } 28 | b2 -> b3 29 | b3 { 30 | i5 = ssa:phi ^b3, i1, i3 31 | i6 = ret ^i5 32 | } 33 | } 34 | */}, function() {/* 35 | pipeline { 36 | b0 { 37 | i0 = if ^b0 38 | } 39 | b0 -> b1, b2 40 | b1 { 41 | i1 = jump ^b1 42 | } 43 | b1 -> b3 44 | b2 { 45 | i2 = jump ^b2 46 | } 47 | b2 -> b3 48 | b3 { 49 | i3 = ret ^b3 50 | } 51 | } 52 | */}); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /lib/wasm/base/reductions/redundant-phi.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var Reducer = require('json-pipeline-reducer'); 5 | 6 | function RedundantPhi() { 7 | Reducer.Reduction.call(this); 8 | } 9 | util.inherits(RedundantPhi, Reducer.Reduction); 10 | module.exports = RedundantPhi; 11 | 12 | RedundantPhi.prototype.reduce = function reduce(node, reducer) { 13 | // Remove phis without uses 14 | if (node.opcode !== 'ssa:phi' || node.uses.length !== 0) 15 | return; 16 | 17 | for (var i = 0; i < node.inputs.length; i++) 18 | reducer.change(node.inputs[i]); 19 | reducer.remove(node); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/wasm/base/reductions/select-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Select = require('./select'); 4 | var fixtures = require('../../../../test/fixtures'); 5 | 6 | describe('base/reductions/SelectOpcode', function() { 7 | var select; 8 | beforeEach(function() { 9 | select = new Select(); 10 | }); 11 | 12 | it('should replace addr.page_size with constant', function() { 13 | fixtures.testReduction(select, function() {/* 14 | pipeline { 15 | b0 { 16 | i0 = addr.page_size 17 | i1 = i64.from_addr i0 18 | i2 = i64.ret ^b0, i1 19 | } 20 | } 21 | */}, function() {/* 22 | pipeline { 23 | b0 { 24 | i0 = i32.const 4096 25 | i1 = addr.from_i32 i0 26 | i2 = i64.from_addr i1 27 | i3 = i64.ret ^b0, i2 28 | } 29 | } 30 | */}); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /lib/wasm/base/reductions/select.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var mmap = require('mmap.js'); 5 | var Reducer = require('json-pipeline-reducer'); 6 | 7 | function SelectBaseOpcode() { 8 | Reducer.Reduction.call(this); 9 | } 10 | util.inherits(SelectBaseOpcode, Reducer.Reduction); 11 | module.exports = SelectBaseOpcode; 12 | 13 | SelectBaseOpcode.prototype.reduce = function reduce(node, reducer) { 14 | // Replace with constant 15 | if (node.opcode === 'addr.page_size') 16 | return this.reducePageSize(node, reducer); 17 | }; 18 | 19 | SelectBaseOpcode.prototype.reducePageSize = function reducePageSize(node, 20 | reducer) { 21 | var c = reducer.graph.create('i32.const').addLiteral(mmap.PAGE_SIZE); 22 | reducer.add(c); 23 | 24 | var addr = reducer.graph.create('addr.from_i32', c); 25 | reducer.add(addr); 26 | 27 | // Just replace with the input, we are ready to handle integers! 28 | reducer.replace(node, addr); 29 | }; 30 | -------------------------------------------------------------------------------- /lib/wasm/compiler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var wasmAST = require('wasm-ast'); 4 | var wasmCFG = require('wasm-cfg'); 5 | var jit = require('jit.js'); 6 | var mmap = require('mmap.js'); 7 | var debug = require('debug')('wasm:compiler'); 8 | 9 | var wasm = require('../wasm'); 10 | var Pipeline = wasm.Pipeline; 11 | var RefTable = wasm.RefTable; 12 | 13 | function Compiler() { 14 | this.namespace = { 15 | std: wasm.std.table 16 | }; 17 | } 18 | module.exports = Compiler; 19 | 20 | Compiler.create = function create() { 21 | return new Compiler(); 22 | }; 23 | 24 | Compiler.prototype.generateCode = function generateCode(module, source) { 25 | var table = new RefTable(this.namespace); 26 | 27 | debug('parse AST start'); 28 | var ast = wasmAST.parse(source, { index: true }); 29 | debug('parse AST end'); 30 | debug('build CFG start'); 31 | var infos = wasmCFG.build(ast, table); 32 | debug('build CFG end'); 33 | 34 | // TODO(indutny): make this configurable 35 | var platform = wasm.platform.x64; 36 | 37 | var masm = jit.create(platform.arch); 38 | for (var i = 0; i < infos.length; i++) { 39 | var info = infos[i]; 40 | table.register(info, masm.label()); 41 | } 42 | 43 | if (module !== null) 44 | this.namespace[module] = table; 45 | 46 | for (var i = 0; i < infos.length; i++) { 47 | var info = infos[i]; 48 | debug('compiling %j start', info.name); 49 | var pipeline = Pipeline.create(platform, table, info); 50 | 51 | pipeline.compile(masm); 52 | debug('compiling %j end', info.name); 53 | } 54 | 55 | return { 56 | reloc: masm.compile(), 57 | table: table 58 | }; 59 | }; 60 | 61 | Compiler.prototype.compile = function compile(module, source) { 62 | var info = this.generateCode(module, source); 63 | var reloc = info.reloc; 64 | 65 | var size = reloc.buffer.length; 66 | if (size % mmap.PAGE_SIZE !== 0) 67 | size += mmap.PAGE_SIZE - (size % mmap.PAGE_SIZE); 68 | 69 | var prot = mmap.PROT_READ | mmap.PROT_WRITE | mmap.PROT_EXEC; 70 | var flags = mmap.MAP_PRIVATE | mmap.MAP_ANON; 71 | var code = mmap.alloc(size, prot, flags, -1, 0); 72 | 73 | reloc.buffer.copy(code); 74 | reloc.resolve(code); 75 | info.table.finalize(code); 76 | 77 | var api = {}; 78 | for (var i = 0; i < info.table.fn.length; i++) { 79 | var fn = info.table.fn[i]; 80 | if (fn.signature.public) 81 | api[fn.name] = jit.toFunction(code.slice(fn.offset)); 82 | } 83 | return api; 84 | }; 85 | -------------------------------------------------------------------------------- /lib/wasm/pipeline.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var linearscan = require('linearscan'); 5 | var Dominance = require('dominance-frontier'); 6 | var SSA = require('ssa.js'); 7 | var Scheduler = require('json-pipeline-scheduler'); 8 | var GVN = require('gvn'); 9 | var Reducer = require('json-pipeline-reducer'); 10 | var debug = require('debug')('wasm:pipeline'); 11 | 12 | var wasm = require('../wasm'); 13 | 14 | function Pipeline(platform, table, info) { 15 | this.table = table; 16 | this.info = info; 17 | this.ast = this.info.ast; 18 | this.cfg = this.info.cfg; 19 | this.platform = platform; 20 | } 21 | module.exports = Pipeline; 22 | 23 | Pipeline.create = function create(platform, table, info) { 24 | return new Pipeline(platform, table, info); 25 | }; 26 | 27 | Pipeline.prototype.compile = function compile(masm) { 28 | debug('compile'); 29 | debug('dominance start'); 30 | Dominance.create(this.cfg).compute(); 31 | debug('dominance end'); 32 | debug('ssa start'); 33 | SSA.create(this.cfg).compute(); 34 | debug('ssa end'); 35 | 36 | this.reduce(this.cfg, 'generic'); 37 | this.gvn(this.cfg, 'generic'); 38 | this.reduce(this.cfg, 'platform'); 39 | this.gvn(this.cfg, 'platform'); 40 | 41 | debug('schedule start'); 42 | var scheduled = Scheduler.create(this.cfg).run(); 43 | debug('schedule end'); 44 | 45 | this.reduce(scheduled, 'lir'); 46 | 47 | // Ensure proper ordering of blocks 48 | debug('reindex start'); 49 | scheduled.reindex(); 50 | debug('reindex start'); 51 | return this.compileLow(scheduled, masm); 52 | }; 53 | 54 | Pipeline.prototype.reduce = function reduce(cfg, stage) { 55 | debug('reduce start: %j', stage); 56 | var reducer = new Reducer(); 57 | var list = wasm.platform.base.reductions.stages[stage] || []; 58 | list = list.concat(this.platform.reductions.stages[stage] || []); 59 | 60 | for (var i = 0; i < list.length; i++) { 61 | var Reduction = list[i]; 62 | reducer.addReduction(new Reduction(this.platform, this.ast)); 63 | } 64 | 65 | reducer.reduce(cfg); 66 | debug('reduce end: %j', stage); 67 | }; 68 | 69 | Pipeline.prototype.gvn = function gvn(cfg, stage) { 70 | debug('gvn start: %j', stage); 71 | var reducer = new Reducer(); 72 | var gvn = new GVN(); 73 | var list = wasm.platform.base.gvn.stages[stage] || []; 74 | list = list.concat(this.platform.gvn.stages[stage] || []); 75 | 76 | for (var i = 0; i < list.length; i++) { 77 | var Relation = list[i]; 78 | gvn.addRelation(new Relation(this.platform, this.ast)); 79 | } 80 | 81 | reducer.addReduction(gvn); 82 | reducer.reduce(cfg); 83 | debug('gvn end: %j', stage); 84 | }; 85 | 86 | Pipeline.prototype.compileLow = function compileLow(cfg, masm) { 87 | var codegen = new this.platform.Codegen(masm, this.table); 88 | 89 | debug('linearscan start'); 90 | var lir = linearscan.allocate(cfg, codegen.linearscan); 91 | debug('linearscan end'); 92 | 93 | debug('codegen start'); 94 | var self = this; 95 | masm.labelScope(function() { 96 | codegen.prologue(lir, self.info); 97 | for (var i = 0; i < lir.instructions.length; i++) { 98 | var instr = lir.instructions[i]; 99 | if (instr.linkUses.length !== 0) 100 | masm.bind(instr.index); 101 | assert(codegen[instr.opcode], 'Opcode not found: ' + instr.opcode); 102 | codegen[instr.opcode](instr); 103 | } 104 | 105 | // No need in epilogue, there is always a return block that everyone ends at 106 | }); 107 | debug('codegen end'); 108 | }; 109 | -------------------------------------------------------------------------------- /lib/wasm/ref-table.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var wasmCFG = require('wasm-cfg'); 5 | 6 | function Entry(info, label) { 7 | this.index = info.index; 8 | this.name = info.name; 9 | this.signature = info.signature; 10 | this.label = label; 11 | this.offset = null; 12 | this.code = null; 13 | } 14 | 15 | function RefTable(namespace) { 16 | this.fn = []; 17 | this.fnByName = {}; 18 | this.namespace = namespace; 19 | } 20 | module.exports = RefTable; 21 | 22 | RefTable.prototype.register = function register(info, label) { 23 | var entry = new Entry(info, label); 24 | this.fn[entry.index] = entry; 25 | this.fnByName[entry.name] = entry; 26 | return entry; 27 | }; 28 | 29 | RefTable.prototype._binding = function binding(result, name, params, code) { 30 | var info = { 31 | index: this.fn.length, 32 | name: name, 33 | signature: new wasmCFG.Signature(result, params) 34 | }; 35 | var entry = this.register(info, null); 36 | entry.code = code; 37 | return entry; 38 | }; 39 | 40 | RefTable.prototype.get = function get(module, index) { 41 | if (module === null) 42 | return this.fn[index]; 43 | else 44 | return this.namespace[module].get(null, index); 45 | }; 46 | 47 | RefTable.prototype.getByName = function getByName(module, name) { 48 | if (module === null) 49 | return this.fnByName[name]; 50 | else 51 | return this.namespace[module].getByName(null, name); 52 | }; 53 | 54 | RefTable.prototype.setOffset = function setOffset(index, offset) { 55 | this.fn[index].offset = offset; 56 | }; 57 | 58 | RefTable.prototype.getExternal = function getExternal(module, name) { 59 | // TODO(indutny): dynamically load? 60 | assert(this.namespace.hasOwnProperty(module), 61 | 'Module: ' + module + ' is unknown'); 62 | var entry = this.namespace[module].getByName(null, name); 63 | assert(entry.signature.public, 64 | module + '::' + name + ' is not for public use'); 65 | return entry; 66 | }; 67 | 68 | RefTable.prototype.finalize = function finalize(buffer) { 69 | for (var i = 0; i < this.fn.length; i++) { 70 | var entry = this.fn[i]; 71 | entry.code = buffer.slice(entry.offset); 72 | entry.label = null; 73 | } 74 | }; 75 | -------------------------------------------------------------------------------- /lib/wasm/std.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var std = exports; 4 | 5 | var jit = require('jit.js'); 6 | 7 | var wasm = require('../wasm'); 8 | var binding = require('bindings')('std'); 9 | 10 | std.createContext = function createContext() { 11 | var ctx = binding.createContext(); 12 | var ptr = jit.ptr(ctx); 13 | ptr._ctx = ctx; 14 | return ptr; 15 | }; 16 | 17 | std.table = new wasm.RefTable(null); 18 | std.table._binding('void', 'resize_memory', [ 'addr' ], binding.resize_memory); 19 | std.table._binding('void', 'grow_memory', [ 'addr' ], binding.resize_memory); 20 | std.table._binding('void', 'print', [ 'addr', 'addr' ], binding.print); 21 | -------------------------------------------------------------------------------- /lib/wasm/x64/codegen/bool.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var builder = require('./').builder; 4 | 5 | function unaryBoolean(name, operand, generator) { 6 | builder.opcode(name, function() { 7 | return { 8 | output: this.int('any'), 9 | inputs: [ operand('register'), operand('register') ] 10 | }; 11 | }, function(node) { 12 | var out = this.output(node); 13 | var left = this.input(node, 0); 14 | var right = this.input(node, 1); 15 | 16 | var cond = generator.call(this, left, right); 17 | this.masm.set(cond, out); 18 | }); 19 | } 20 | 21 | var floatConditions = [ 22 | { name: 'eq', asm: 'e' }, 23 | { name: 'ne', asm: 'ne' }, 24 | { name: 'lt', asm: 'l' }, 25 | { name: 'le', asm: 'le' }, 26 | { name: 'gt', asm: 'g' }, 27 | { name: 'ge', asm: 'ge' } 28 | ]; 29 | 30 | [ 'f32', 'f64' ].forEach(function(type) { 31 | floatConditions.forEach(function(cond) { 32 | unaryBoolean(type + '.' + cond.name, builder.float, function(left, right) { 33 | if (type === 'f32') 34 | this.masm.ucomiss(left, right); 35 | else 36 | this.masm.ucomisd(left, right); 37 | 38 | return cond.asm; 39 | }); 40 | }); 41 | }); 42 | 43 | var intConditions = [ 44 | { name: 'eq', asm: 'e', unsigned: true }, 45 | { name: 'ne', asm: 'ne', unsigned: true }, 46 | { name: 'lt_u', asm: 'b' }, 47 | { name: 'le_u', asm: 'be' }, 48 | { name: 'gt_u', asm: 'a' }, 49 | { name: 'ge_u', asm: 'ae' }, 50 | { name: 'lt_s', asm: 'l' }, 51 | { name: 'le_s', asm: 'le' }, 52 | { name: 'gt_s', asm: 'g' }, 53 | { name: 'ge_s', asm: 'ge' } 54 | ]; 55 | 56 | intConditions.forEach(function(cond) { 57 | // NOTE: i32->i64 truncation should be done by Select reducer 58 | unaryBoolean('i64.' + cond.name, builder.int, function(left, right) { 59 | this.masm.cmp(left, right); 60 | 61 | return cond.asm; 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /lib/wasm/x64/codegen/branching.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var jit = require('jit.js'); 4 | 5 | var x64 = require('../'); 6 | var builder = require('./').builder; 7 | 8 | builder.opcode('if', function() { 9 | return { branch: true, inputs: [ this.int('any') ] }; 10 | }, function(node) { 11 | this.masm.cmp(this.input(node, 0), 0x0); 12 | var left = node.links[0].index; 13 | var right = node.links[1].index; 14 | 15 | var next = node.index + 1; 16 | if (left !== next) 17 | this.masm.jl('ne', left); 18 | if (right !== next) 19 | this.masm.jl('e', right); 20 | }); 21 | 22 | builder.opcode('x64:if.i64.eq', function() { 23 | return { branch: true, inputs: [ this.int('register'), this.int('any') ] }; 24 | }, function(node) { 25 | this.masm.cmp(this.input(node, 0), this.input(node, 1)); 26 | var left = node.links[0].index; 27 | var right = node.links[1].index; 28 | 29 | var next = node.index + 1; 30 | if (left !== next) 31 | this.masm.jl('e', left); 32 | if (right !== next) 33 | this.masm.jl('ne', right); 34 | }); 35 | 36 | builder.opcode('jump', function() { 37 | return { branch: true }; 38 | }, function(node) { 39 | var dst = node.links[0].index; 40 | if (dst !== node.index + 1) 41 | this.masm.jl(dst); 42 | }); 43 | 44 | builder.opcode('x64:call', function(node) { 45 | var counters = { 46 | int: x64.params.int.length, 47 | float: x64.params.float.length 48 | }; 49 | 50 | return { 51 | output: this.int('register', 'rax'), 52 | inputs: node.inputs.map(function(input, i) { 53 | // State 54 | if (i === 0) 55 | return this.int('none'); 56 | 57 | var type = node.literals[i]; 58 | var params; 59 | var def; 60 | if (type === 'f32' || type === 'f64') { 61 | params = x64.params.float; 62 | type = 'float'; 63 | def = this.float; 64 | } else { 65 | params = x64.params.int; 66 | type = 'int'; 67 | def = this.int; 68 | } 69 | 70 | var param; 71 | if (counters[type] <= 0) 72 | param = def('any'); 73 | else 74 | param = def('register', params[params.length - counters[type]--]); 75 | 76 | return param; 77 | }, this), 78 | spills: this.spills 79 | }; 80 | }, function intCall(node) { 81 | var masm = this.masm; 82 | var fn = node.literals[0]; 83 | var output = this.output(node); 84 | 85 | // TODO(indutny): push some args to stack 86 | 87 | var entry = this.table.get(fn.module, fn.index); 88 | if (entry.label) { 89 | masm.call(output, entry.label); 90 | } else { 91 | masm.mov(output, jit.ptr(entry.code)); 92 | masm.call(output); 93 | } 94 | }); 95 | -------------------------------------------------------------------------------- /lib/wasm/x64/codegen/builder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var linearscan = require('linearscan'); 5 | 6 | var wasm = require('../../../wasm'); 7 | var x64 = require('../'); 8 | var BaseBuilder = wasm.platform.base.Codegen.Builder; 9 | 10 | function X64CodegenBuilder(Codegen) { 11 | BaseBuilder.call(this, Codegen); 12 | 13 | this.linearscan = linearscan.config.create({ 14 | registers: x64.registers 15 | }); 16 | } 17 | util.inherits(X64CodegenBuilder, BaseBuilder); 18 | module.exports = X64CodegenBuilder; 19 | 20 | function int(kind, value) { 21 | return { group: 'general', kind: kind, value: value }; 22 | } 23 | X64CodegenBuilder.prototype.int = int; 24 | 25 | function float(kind, value) { 26 | return { group: 'float', kind: kind, value: value }; 27 | } 28 | X64CodegenBuilder.prototype.float = float; 29 | 30 | X64CodegenBuilder.prototype.iterateTypes = function iterateTypes(body) { 31 | body('i32', int); 32 | body('i64', int); 33 | body('f32', float); 34 | body('f64', float); 35 | }; 36 | 37 | var spills = x64.temporaryRegs.general.map(function(name) { 38 | return int('register', name); 39 | }).concat(x64.temporaryRegs.float.map(function(name) { 40 | return float('register', name); 41 | })); 42 | X64CodegenBuilder.prototype.spills = spills; 43 | -------------------------------------------------------------------------------- /lib/wasm/x64/codegen/cast.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var builder = require('./').builder; 4 | 5 | builder.opcode('i32.wrap', function() { 6 | return { 7 | output: this.int('register'), inputs: [ this.int('any') ] 8 | }; 9 | }, function(node) { 10 | var out = this.output(node); 11 | var input = this.input(node, 0); 12 | 13 | this.masm.movl(out, input); 14 | }); 15 | 16 | builder.opcode('x64:change-u32-to-i64', function() { 17 | return { 18 | output: this.int('register'), inputs: [ this.int('any') ] 19 | }; 20 | }, function(node) { 21 | var out = this.output(node); 22 | var input = this.input(node, 0); 23 | 24 | this.masm.movl(out, input); 25 | }); 26 | 27 | builder.opcode('x64:change-s32-to-i64', function() { 28 | return { 29 | output: this.int('register'), inputs: [ this.int('any') ] 30 | }; 31 | }, function(node) { 32 | var out = this.output(node); 33 | var input = this.input(node, 0); 34 | 35 | this.masm.movsxl(out, input); 36 | }); 37 | 38 | var trunc = [ 39 | { result: 32, signed: false, input: 32 }, 40 | { result: 32, signed: true, input: 32 }, 41 | { result: 32, signed: false, input: 64 }, 42 | { result: 32, signed: true, input: 64 }, 43 | { result: 64, signed: false, input: 32 }, 44 | { result: 64, signed: true, input: 32 }, 45 | { result: 64, signed: false, input: 64 }, 46 | { result: 64, signed: true, input: 64 } 47 | ]; 48 | 49 | trunc.forEach(function(info) { 50 | var result = info.result; 51 | var signed = info.signed; 52 | var inputSize = info.input; 53 | 54 | // Float to int 55 | var key = 'i' + result + '.trunc_' + (signed ? 's_' : 'u_') + inputSize; 56 | 57 | builder.opcode(key, function() { 58 | return { 59 | output: this.int('any'), inputs: [ this.float('register') ] 60 | }; 61 | }, function(node) { 62 | var out = this.output(node); 63 | var input = this.input(node, 0); 64 | 65 | if (result === 64) { 66 | if (inputSize === 64) 67 | this.masm.cvttsd2si(out, input); 68 | else 69 | this.masm.cvttss2si(out, input); 70 | } else { 71 | if (inputSize === 64) 72 | this.masm.cvttsd2sil(out, input); 73 | else 74 | this.masm.cvttss2sil(out, input); 75 | 76 | if (signed) 77 | this.masm.movsxl(out, out); 78 | else 79 | this.masm.movl(out, out); 80 | } 81 | }); 82 | 83 | // Int to float 84 | key = 'f' + inputSize + '.convert_' + (signed ? 's_' : 'u_') + result; 85 | 86 | builder.opcode(key, function() { 87 | return { 88 | output: this.float('register'), inputs: [ this.int('any') ] 89 | }; 90 | }, function(node) { 91 | var out = this.output(node); 92 | var input = this.input(node, 0); 93 | 94 | if (result === 64) { 95 | if (inputSize === 64) 96 | this.masm.cvtsi2sd(out, input); 97 | else 98 | this.masm.cvtsi2ss(out, input); 99 | } else { 100 | if (inputSize === 64) 101 | this.masm.cvtsi2sdl(out, input); 102 | else 103 | this.masm.cvtsi2ssl(out, input); 104 | } 105 | }); 106 | }); 107 | 108 | [ 32, 64 ].forEach(function(size) { 109 | builder.opcode('i' + size + '.reinterpret', function() { 110 | return { 111 | output: this.int('register'), inputs: [ this.float('any') ] 112 | }; 113 | }, function(node) { 114 | var out = this.output(node); 115 | var input = this.input(node, 0); 116 | 117 | this.masm.movq(out, input); 118 | }); 119 | 120 | builder.opcode('f' + size + '.reinterpret', function() { 121 | return { 122 | output: this.float('register'), inputs: [ this.int('any') ] 123 | }; 124 | }, function(node) { 125 | var out = this.output(node); 126 | var input = this.input(node, 0); 127 | 128 | this.masm.movq(out, input); 129 | }); 130 | }); 131 | 132 | builder.opcode('f32.demote', function() { 133 | return { 134 | output: this.float('register'), inputs: [ this.float('any') ] 135 | }; 136 | }, function(node) { 137 | var out = this.output(node); 138 | var input = this.input(node, 0); 139 | 140 | this.masm.cvtsd2ss(out, input); 141 | }); 142 | 143 | builder.opcode('f64.promote', function() { 144 | return { 145 | output: this.float('register'), inputs: [ this.float('any') ] 146 | }; 147 | }, function(node) { 148 | var out = this.output(node); 149 | var input = this.input(node, 0); 150 | 151 | this.masm.cvtss2sd(out, input); 152 | }); 153 | -------------------------------------------------------------------------------- /lib/wasm/x64/codegen/generic.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var BN = require('bn.js'); 4 | var Buffer = require('buffer').Buffer; 5 | 6 | var x64 = require('../'); 7 | 8 | var builder = require('./').builder; 9 | 10 | builder.opcode('i32.const', function() { 11 | return { output: this.int('any') }; 12 | }, function(node) { 13 | var val = node.literals[0] | 0; 14 | this.masm.mov(this.output(node), val & 0xffffffff); 15 | }); 16 | 17 | builder.opcode('i64.const', function() { 18 | return { output: this.int('any') }; 19 | }, function(node) { 20 | var out = this.output(node); 21 | var masked = node.literals[0]; 22 | 23 | // Short number 24 | if (masked.bitLength() <= 31) 25 | return this.masm.mov(out, masked.toString(10) | 0); 26 | 27 | if (masked.sign) 28 | masked = new BN('10000000000000000', 16).iadd(masked); 29 | masked = masked.maskn(64); 30 | 31 | var arr = masked.toArray('le'); 32 | while (arr.length < 8) 33 | arr.push(0); 34 | 35 | this.masm.mov(out, new Buffer(arr)); 36 | }); 37 | 38 | builder.opcode('f32.const', function() { 39 | return { output: this.float('any') }; 40 | }, function(node) { 41 | var out = this.output(node); 42 | this.masm.loadFloat(x64.scratch, node.literals[0]); 43 | this.masm.movd(out, x64.scratch); 44 | }); 45 | 46 | builder.opcode('f64.const', function() { 47 | return { output: this.float('any') }; 48 | }, function(node) { 49 | var out = this.output(node); 50 | this.masm.loadDouble(x64.scratch, node.literals[0]); 51 | this.masm.movq(out, x64.scratch); 52 | }); 53 | 54 | builder.opcode('x64:int.param', function() { 55 | return { output: this.int('any') }; 56 | }, function(node) { 57 | this.masm.mov(this.output(node), this.param(node.literals[0])); 58 | }); 59 | 60 | builder.opcode('x64:f32.param', function() { 61 | return { output: this.float('any') }; 62 | }, function(node) { 63 | this.masm.movd(this.output(node), this.param(node.literals[0])); 64 | }); 65 | 66 | builder.opcode('x64:f64.param', function() { 67 | return { output: this.float('any') }; 68 | }, function(node) { 69 | this.masm.movq(this.output(node), this.param(node.literals[0])); 70 | }); 71 | 72 | builder.opcode('exit', function() { 73 | return {}; 74 | }, function(node) { 75 | return this.epilogue(node); 76 | }); 77 | 78 | builder.opcode('x64:int.ret', function() { 79 | return { inputs: [ this.int('register', 'rax') ] }; 80 | }, function() { 81 | // No-op 82 | }); 83 | 84 | builder.opcode('x64:float.ret', function() { 85 | return { inputs: [ this.float('register', 'xmm0') ] }; 86 | }, function() { 87 | // No-op 88 | }); 89 | 90 | builder.opcode('state', function() { 91 | return { output: this.int('none') }; 92 | }, function() { 93 | // No-op 94 | }); 95 | 96 | builder.opcode('updateState', function() { 97 | return { output: this.int('none'), inputs: [ this.int('none') ] }; 98 | }, function() { 99 | // No-op 100 | }); 101 | -------------------------------------------------------------------------------- /lib/wasm/x64/codegen/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | 5 | var wasm = require('../../../wasm'); 6 | var x64 = require('../'); 7 | 8 | function Codegen(masm, table) { 9 | wasm.platform.base.Codegen.call(this, masm, table); 10 | 11 | this.lastEpilogue = -1; 12 | this.calleeSaved = null; 13 | } 14 | util.inherits(Codegen, wasm.platform.base.Codegen); 15 | module.exports = Codegen; 16 | 17 | Codegen.Builder = require('./builder'); 18 | Codegen.builder = new Codegen.Builder(Codegen); 19 | 20 | Codegen.prototype.linearscan = Codegen.builder.linearscan; 21 | 22 | Codegen.prototype.spill = function spill(index) { 23 | return [ 'rbp', -x64.ptrSize * (1 + index) ]; 24 | }; 25 | 26 | Codegen.prototype.param = function param(index) { 27 | return [ 'rbp', x64.ptrSize * (2 + index) ]; 28 | }; 29 | 30 | Codegen.prototype._align = function _align(masm) { 31 | // Align code 32 | var pad = masm.getOffset() % (x64.ptrSize * 2); 33 | if (pad !== 0) 34 | pad = x64.ptrSize * 2 - pad; 35 | for (; pad > 0; pad--) 36 | masm.int3(); 37 | }; 38 | 39 | Codegen.prototype._saveCalleeRegs = function _saveCalleeRegs(pipeline, 40 | masm, 41 | offset) { 42 | var usedRegs = pipeline.getUsedRegisters(); 43 | this.calleeSaved = usedRegs.filter(function(reg) { 44 | return x64.calleeSaveRegs.general.indexOf(reg) !== -1 || 45 | x64.calleeSaveRegs.float.indexOf(reg) !== -1; 46 | }).map(function(reg, i) { 47 | return { 48 | reg: reg, 49 | spill: this.spill(offset + i) 50 | }; 51 | }, this); 52 | 53 | // Align spills 54 | var stackSize = offset + this.calleeSaved.length; 55 | if (stackSize % 2 !== 0) 56 | stackSize++; 57 | stackSize *= x64.ptrSize; 58 | 59 | if (stackSize !== 0) 60 | masm.sub('rsp', stackSize); 61 | 62 | // Save regs 63 | for (var i = 0; i < this.calleeSaved.length; i++) { 64 | var item = this.calleeSaved[i]; 65 | masm.mov(item.spill, item.reg); 66 | } 67 | }; 68 | 69 | Codegen.prototype.prologue = function prologue(pipeline, info) { 70 | var masm = this.masm; 71 | 72 | this._align(masm); 73 | 74 | masm.bind(this.table.get(null, info.index).label); 75 | this.table.setOffset(info.index, masm.getOffset()); 76 | masm.push('rbp'); 77 | masm.mov('rbp', 'rsp'); 78 | 79 | // Spills have equal size for x64 80 | var spillSize = 0; 81 | for (var i = 0; i < pipeline.spillType.length; i++) 82 | spillSize = Math.max(pipeline.spillType[i].to, spillSize); 83 | 84 | this._saveCalleeRegs(pipeline, masm, spillSize); 85 | }; 86 | 87 | Codegen.prototype.epilogue = function epilogue(node) { 88 | var masm = this.masm; 89 | if (!node && masm.getOffset() === this.lastEpilogue) 90 | return; 91 | 92 | // Restore callee regs 93 | for (var i = 0; i < this.calleeSaved.length; i++) { 94 | var item = this.calleeSaved[i]; 95 | masm.mov(item.reg, item.spill); 96 | } 97 | 98 | masm.mov('rsp', 'rbp'); 99 | masm.pop('rbp'); 100 | masm.ret(); 101 | 102 | this.lastEpilogue = masm.getOffset(); 103 | }; 104 | 105 | // 106 | // Linearscan stuff 107 | // 108 | 109 | Codegen.builder.opcode('ls:move.general', function() { 110 | return {}; 111 | }, function(node) { 112 | var dst = this.output(node); 113 | var src = this.input(node, 0); 114 | 115 | if (Array.isArray(dst) && Array.isArray(src)) { 116 | this.masm.mov(x64.scratch, src); 117 | this.masm.mov(dst, x64.scratch); 118 | } else { 119 | this.masm.mov(dst, src); 120 | } 121 | }); 122 | 123 | // TODO(indutny): write tests for this 124 | Codegen.builder.opcode('ls:swap.general', function() { 125 | return {}; 126 | }, function(node) { 127 | var dst = this.output(node); 128 | var src = this.input(node, 0); 129 | 130 | var masm = this.masm; 131 | if (Array.isArray(dst) && Array.isArray(src)) { 132 | masm.mov(x64.scratch, dst); 133 | masm.xchg(x64.scratch, src); 134 | masm.mov(dst, x64.scratch); 135 | } else { 136 | masm.xchg(dst, src); 137 | } 138 | }); 139 | 140 | Codegen.builder.opcode('ls:move.float', function() { 141 | return {}; 142 | }, function move(node) { 143 | var dst = this.output(node); 144 | var src = this.input(node, 0); 145 | 146 | this.masm.movq(dst, src); 147 | }); 148 | 149 | // TODO(indutny): write tests for this 150 | Codegen.builder.opcode('ls:swap.float', function() { 151 | return {}; 152 | }, function(node) { 153 | var dst = this.output(node); 154 | var src = this.input(node, 0); 155 | 156 | var masm = this.masm; 157 | if (Array.isArray(dst) && Array.isArray(src)) { 158 | // Swap by words 159 | masm.mov(x64.scratch, dst); 160 | masm.xchg(x64.scratch, src); 161 | masm.mov(dst, x64.scratch); 162 | 163 | var ndst = [ dst[0], dst[1] + 8 ]; 164 | var nsrc = [ src[0], src[1] + 8 ]; 165 | 166 | masm.mov(x64.scratch, ndst); 167 | masm.xchg(x64.scratch, nsrc); 168 | masm.mov(ndst, x64.scratch); 169 | } else { 170 | masm.movsd(x64.floatScratch, dst); 171 | masm.movsd(dst, src); 172 | masm.movsd(src, x64.floatScratch); 173 | } 174 | }); 175 | 176 | // 177 | // X64 specific instructions 178 | // 179 | function declareReg(type, reg) { 180 | Codegen.builder.opcode('x64:' + reg, function() { 181 | return { output: this[type]('register', reg) }; 182 | }, function() { 183 | // No-op, the return type is all that matters 184 | }); 185 | } 186 | x64.registers.general.forEach(function(reg) { 187 | return declareReg('int', reg); 188 | }); 189 | x64.registers.float.forEach(function(reg) { 190 | return declareReg('float', reg); 191 | }); 192 | 193 | // 194 | // Load Codegen extensions 195 | // 196 | require('./generic'); 197 | require('./math'); 198 | require('./bool'); 199 | require('./memory'); 200 | require('./cast'); 201 | require('./branching'); 202 | -------------------------------------------------------------------------------- /lib/wasm/x64/codegen/math.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var x64 = require('../'); 4 | var builder = require('./').builder; 5 | 6 | function intMath(type, name, generate) { 7 | builder.opcode(name, function() { 8 | return { 9 | output: this.int('register'), 10 | inputs: [ this.int('any'), this.int('any') ] 11 | }; 12 | }, function(node) { 13 | var out = this.output(node); 14 | var left = this.input(node, 0); 15 | var right = this.input(node, 1); 16 | 17 | var other; 18 | if (out === left) { 19 | other = right; 20 | } else if (out === right) { 21 | if (type === 'comm') { 22 | other = left; 23 | } else { 24 | this.masm.mov(x64.scratch, left); 25 | generate.call(this, x64.scratch, right); 26 | this.masm.mov(out, x64.scratch); 27 | return; 28 | } 29 | } else { 30 | this.masm.mov(out, left); 31 | other = right; 32 | } 33 | generate.call(this, out, other); 34 | }); 35 | } 36 | 37 | intMath('comm', 'x64:int.add', function(out, other) { 38 | this.masm.add(out, other); 39 | }); 40 | 41 | intMath('non-comm', 'x64:int.sub', function(out, other) { 42 | this.masm.sub(out, other); 43 | }); 44 | 45 | intMath('comm', 'x64:int.mul', function(out, other) { 46 | this.masm.imul(out, other); 47 | }); 48 | 49 | intMath('comm', 'x64:int.and', function(out, other) { 50 | this.masm.and(out, other); 51 | }); 52 | 53 | intMath('comm', 'x64:int.xor', function(out, other) { 54 | this.masm.xor(out, other); 55 | }); 56 | 57 | intMath('comm', 'x64:int.or', function(out, other) { 58 | this.masm.or(out, other); 59 | }); 60 | 61 | function intDiv(type, name, generate) { 62 | builder.opcode(name, function() { 63 | return { 64 | output: this.int('register', type === 'rem' ? 'rdx' : 'rax'), 65 | scratches: [ this.int('register', 'rdx') ], 66 | inputs: [ this.int('register', 'rax'), this.int('any') ] 67 | }; 68 | }, function(node) { 69 | generate.call(this, this.input(node, 1)); 70 | }); 71 | } 72 | 73 | var divs = [ 74 | { type: 'div', result: 64, signed: false }, 75 | { type: 'div', result: 64, signed: true }, 76 | { type: 'div', result: 32, signed: true }, 77 | { type: 'div', result: 32, signed: true }, 78 | { type: 'rem', result: 64, signed: false }, 79 | { type: 'rem', result: 64, signed: true }, 80 | { type: 'rem', result: 32, signed: true }, 81 | { type: 'rem', result: 32, signed: true } 82 | ]; 83 | 84 | divs.forEach(function(div) { 85 | var type = div.type; 86 | var result = div.result; 87 | var signed = div.signed; 88 | 89 | var key = 'i' + result + '.' + type + (signed ? '_s' : '_u'); 90 | intDiv(type, key, function(input) { 91 | if (signed) { 92 | if (result === 64) { 93 | this.masm.cqo(); 94 | this.masm.idiv(input); 95 | } else { 96 | this.masm.cdq(); 97 | this.masm.idivl(input); 98 | } 99 | } else { 100 | this.masm.xor('rdx', 'rdx'); 101 | if (result === 64) 102 | this.masm.div(input); 103 | else 104 | this.masm.divl(input); 105 | } 106 | }); 107 | }); 108 | 109 | function intUnary(name, generate) { 110 | builder.opcode(name, function() { 111 | return { 112 | output: this.int('register'), inputs: [ this.int('any') ] 113 | }; 114 | }, function(node) { 115 | var out = this.output(node); 116 | var input = this.input(node, 0); 117 | 118 | generate.call(this, out, input); 119 | }); 120 | } 121 | 122 | intUnary('i32.clz', function(out, input) { 123 | this.masm.lzcntl(out, input); 124 | }); 125 | 126 | intUnary('i64.clz', function(out, input) { 127 | this.masm.lzcnt(out, input); 128 | }); 129 | 130 | intUnary('i32.ctz', function(out, input) { 131 | this.masm.tzcntl(out, input); 132 | }); 133 | 134 | intUnary('i64.ctz', function(out, input) { 135 | this.masm.tzcnt(out, input); 136 | }); 137 | 138 | intUnary('i32.popcnt', function(out, input) { 139 | this.masm.popcntl(out, input); 140 | }); 141 | 142 | intUnary('i64.popcnt', function(out, input) { 143 | this.masm.popcnt(out, input); 144 | }); 145 | 146 | function shiftIntMath(name, generate) { 147 | builder.opcode(name, function() { 148 | return { 149 | output: this.int('register'), 150 | inputs: [ this.int('any'), this.int('register', 'rcx') ] 151 | }; 152 | }, function(node) { 153 | var out = this.output(node); 154 | var left = this.input(node, 0); 155 | var right = this.input(node, 1); 156 | 157 | var other; 158 | if (out === left) { 159 | other = right; 160 | } else if (out === right) { 161 | this.masm.mov(x64.scratch, left); 162 | generate.call(this, x64.scratch, right); 163 | this.masm.mov(out, x64.scratch); 164 | return; 165 | } else { 166 | this.masm.mov(out, left); 167 | other = right; 168 | } 169 | 170 | generate.call(this, out, other); 171 | }); 172 | } 173 | 174 | shiftIntMath('x64:int.shl', function(out, other) { 175 | this.masm.shl(out, other); 176 | }); 177 | 178 | shiftIntMath('i64.shr_s', function(out, other) { 179 | this.masm.sar(out, other); 180 | }); 181 | 182 | shiftIntMath('i64.shr_u', function(out, other) { 183 | this.masm.shr(out, other); 184 | }); 185 | 186 | shiftIntMath('i32.shr_s', function(out, other) { 187 | this.masm.sarl(out, other); 188 | }); 189 | 190 | shiftIntMath('i32.shr_u', function(out, other) { 191 | this.masm.shrl(out, other); 192 | }); 193 | 194 | // 195 | // Floating point opcodes 196 | // 197 | 198 | function floatMath(type, name, size, generate) { 199 | builder.opcode(name, function() { 200 | return { 201 | output: this.float('register'), 202 | inputs: [ this.float('any'), this.float('any') ] 203 | }; 204 | }, function(node) { 205 | var out = this.output(node); 206 | var left = this.input(node, 0); 207 | var right = this.input(node, 1); 208 | 209 | var other; 210 | if (out === left) { 211 | other = right; 212 | } else if (out === right) { 213 | if (type === 'comm') { 214 | other = left; 215 | } else if (size === 32) { 216 | this.masm.movss(this.doubleScratch, left); 217 | generate.call(this, this.doubleScratch, right); 218 | this.masm.movss(out, this.doubleScratch); 219 | } else { 220 | this.masm.movsd(this.doubleScratch, left); 221 | generate.call(this, this.doubleScratch, right); 222 | this.masm.movsd(out, this.doubleScratch); 223 | } 224 | return; 225 | } else { 226 | if (size === 32) 227 | this.masm.movss(out, left); 228 | else 229 | this.masm.movsd(out, left); 230 | other = right; 231 | } 232 | generate.call(this, out, other); 233 | }); 234 | } 235 | 236 | [ 32, 64 ].forEach(function(size) { 237 | floatMath('comm', 'f' + size + '.add', size, function(out, other) { 238 | if (size === 32) 239 | this.masm.addss(out, other); 240 | else 241 | this.masm.addsd(out, other); 242 | }); 243 | 244 | floatMath('comm', 'f' + size + '.mul', size, function(out, other) { 245 | if (size === 32) 246 | this.masm.mulss(out, other); 247 | else 248 | this.masm.mulsd(out, other); 249 | }); 250 | 251 | floatMath('non-comm', 'f' + size + '.sub', size, function(out, other) { 252 | if (size === 32) 253 | this.masm.subss(out, other); 254 | else 255 | this.masm.subsd(out, other); 256 | }); 257 | 258 | floatMath('non-comm', 'f' + size + '.div', size, function(out, other) { 259 | if (size === 32) 260 | this.masm.divss(out, other); 261 | else 262 | this.masm.divsd(out, other); 263 | }); 264 | 265 | floatMath('comm', 'f' + size + '.max', size, function(out, other) { 266 | if (size === 32) 267 | this.masm.maxss(out, other); 268 | else 269 | this.masm.maxsd(out, other); 270 | }); 271 | 272 | floatMath('comm', 'f' + size + '.min', size, function(out, other) { 273 | if (size === 32) 274 | this.masm.minss(out, other); 275 | else 276 | this.masm.minsd(out, other); 277 | }); 278 | 279 | // NOTE: select reduction should take `abs` value of input 280 | floatMath('non-comm', 'f' + size + '.copysign', size, function(out, other) { 281 | var masm = this.masm; 282 | 283 | // Put all 1s to `floatScratch` 284 | masm.pcmpeqd(x64.floatScratch, x64.floatScratch); 285 | masm.psllq(x64.floatScratch, size - 1); 286 | masm.andpd(x64.floatScratch, other); 287 | masm.xorpd(out, x64.floatScratch); 288 | }); 289 | }); 290 | 291 | function floatUnary(name, generator) { 292 | builder.opcode(name, function() { 293 | return { 294 | output: this.float('register'), 295 | inputs: [ this.float('any') ] 296 | }; 297 | }, function(node) { 298 | var output = this.output(node); 299 | var input = this.input(node, 0); 300 | generator.call(this, output, input); 301 | }); 302 | } 303 | 304 | [ 32, 64 ].forEach(function(size) { 305 | floatUnary('f' + size + '.sqrt', function(out, input) { 306 | if (size === 64) 307 | this.masm.sqrtsd(out, input); 308 | else 309 | this.masm.sqrtss(out, input); 310 | }); 311 | 312 | var rounds = [ 313 | { name: 'ceil', mode: 'up' }, 314 | { name: 'floor', mode: 'down' }, 315 | { name: 'nearest', mode: 'nearest' }, 316 | { name: 'trunc', mode: 'zero' } 317 | ]; 318 | 319 | rounds.forEach(function(round) { 320 | var name = round.name; 321 | var mode = round.mode; 322 | 323 | floatUnary('f' + size + '.' + name, function(out, input) { 324 | if (size === 64) 325 | this.masm.roundsd(mode, out, input); 326 | else 327 | this.masm.roundss(mode, out, input); 328 | }); 329 | }); 330 | 331 | floatUnary('f' + size + '.abs', function(out, input) { 332 | var masm = this.masm; 333 | 334 | // Put all 1s to `floatScratch` 335 | masm.pcmpeqd(x64.floatScratch, x64.floatScratch); 336 | masm.psrlq(x64.floatScratch, (64 - size) + 1); 337 | if (out !== input) { 338 | if (size === 32) 339 | masm.movss(out, input); 340 | else 341 | masm.movsd(out, input); 342 | } 343 | masm.andpd(out, x64.floatScratch); 344 | }); 345 | 346 | floatUnary('f' + size + '.neg', function(out, input) { 347 | var masm = this.masm; 348 | 349 | // Put all 1s to `floatScratch` 350 | masm.pcmpeqd(x64.floatScratch, x64.floatScratch); 351 | masm.psllq(x64.floatScratch, size - 1); 352 | if (out !== input) { 353 | if (size === 32) 354 | masm.movss(out, input); 355 | else 356 | masm.movsd(out, input); 357 | } 358 | masm.xorpd(out, x64.floatScratch); 359 | }); 360 | }); 361 | -------------------------------------------------------------------------------- /lib/wasm/x64/codegen/memory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | var x64 = require('../'); 6 | 7 | var builder = require('./').builder; 8 | 9 | builder.opcode('x64:memory.space', function() { 10 | return { output: this.int('register'), inputs: [ this.int('none') ] }; 11 | }, function(node) { 12 | this.masm.mov(this.output(node), x64.memory.space); 13 | }); 14 | 15 | builder.opcode('x64:memory.size', function() { 16 | return { output: this.int('register'), inputs: [ this.int('none') ] }; 17 | }, function(node) { 18 | this.masm.mov(this.output(node), x64.memory.size); 19 | }); 20 | 21 | builder.opcode('x64:memory.bounds-check', function() { 22 | return { 23 | output: this.int('register'), 24 | inputs: [ this.int('none'), this.int('register'), this.int('any') ] 25 | }; 26 | }, function(node) { 27 | var masm = this.masm; 28 | var output = this.output(node); 29 | var input = this.input(node, 1); 30 | var size = this.input(node, 2); 31 | 32 | var setValue = masm.label(); 33 | var merge = masm.label(); 34 | 35 | masm.lea(x64.scratch, [ input, node.literals[0] ]); 36 | masm.cmp(x64.scratch, size); 37 | 38 | masm.j('le', setValue); 39 | masm.xor(output, output); 40 | if (output === input) { 41 | masm.bind(setValue); 42 | } else { 43 | masm.j(merge); 44 | masm.bind(setValue); 45 | masm.mov(output, input); 46 | } 47 | masm.bind(merge); 48 | }); 49 | 50 | // 51 | // XXX(indutny): ALIGNMENT! 52 | // 53 | 54 | var access = [ 55 | { size: 8, extend: '_s' }, 56 | { size: 8, extend: '_u' }, 57 | { size: 16, extend: '_s' }, 58 | { size: 16, extend: '_u' }, 59 | { size: 32, extend: '_s' }, 60 | { size: 32, extend: '_u' }, 61 | { size: 32, extend: '' }, 62 | { size: 64, extend: '' } 63 | ]; 64 | 65 | access.forEach(function(item) { 66 | var size = item.size; 67 | var extend = item.extend; 68 | 69 | builder.opcode('x64:load' + size + extend, function() { 70 | return { 71 | output: this.int('register'), 72 | inputs: [ this.int('none'), this.int('register'), this.int('register') ] 73 | }; 74 | }, function(node) { 75 | var masm = this.masm; 76 | 77 | var output = this.output(node); 78 | var space = this.input(node, 1); 79 | var off = this.input(node, 2); 80 | 81 | var op = [ space, off, 0 ]; 82 | if (size === 64) 83 | masm.mov(output, op); 84 | else if (size === 32 && (extend === '' || extend === '_u')) 85 | masm.movl(output, op); 86 | else if (size === 32 && extend === '_s') 87 | masm.movsxl(output, op); 88 | else if (size === 16 && extend === '_u') 89 | masm.movzxw(output, op); 90 | else if (size === 16 && extend === '_s') 91 | masm.movsxw(output, op); 92 | else if (size === 8 && extend === '_u') 93 | masm.movzxb(output, op); 94 | else if (size === 8 && extend === '_s') 95 | masm.movsxb(output, op); 96 | else 97 | masm.int3(); 98 | }); 99 | }); 100 | 101 | [ 8, 16, 32, 64 ].forEach(function(size) { 102 | builder.opcode('x64:store' + size, function() { 103 | return { 104 | inputs: [ 105 | // State 106 | this.int('none'), 107 | 108 | // Real params 109 | this.int('register'), this.int('register'), this.int('register') 110 | ] 111 | }; 112 | }, function(node) { 113 | var masm = this.masm; 114 | 115 | var space = this.input(node, 1); 116 | var off = this.input(node, 2); 117 | var value = this.input(node, 3); 118 | var op = [ space, off, 0 ]; 119 | 120 | if (size === 64) 121 | masm.mov(op, value); 122 | else if (size === 32) 123 | masm.movl(op, value); 124 | else if (size === 16) 125 | masm.movw(op, value); 126 | else if (size === 8) 127 | masm.movb(op, value); 128 | }); 129 | }); 130 | 131 | [ 32, 64 ].forEach(function(size) { 132 | builder.opcode('x64:f' + size + '.load', function() { 133 | return { 134 | output: this.float('register'), 135 | inputs: [ 136 | // State 137 | this.int('none'), 138 | 139 | // Real params 140 | this.int('register'), this.int('register') 141 | ] 142 | }; 143 | }, function(node) { 144 | var masm = this.masm; 145 | 146 | var out = this.output(node); 147 | var space = this.input(node, 1); 148 | var off = this.input(node, 2); 149 | var op = [ space, off, 0 ]; 150 | 151 | if (size === 64) 152 | masm.movq(out, op); 153 | else if (size === 32) 154 | masm.movd(out, op); 155 | }); 156 | 157 | builder.opcode('x64:f' + size + '.store', function() { 158 | return { 159 | output: this.float('register'), 160 | inputs: [ 161 | // State 162 | this.int('none'), 163 | 164 | // Real params 165 | this.int('register'), this.int('register'), this.int('register') 166 | ] 167 | }; 168 | }, function(node) { 169 | var masm = this.masm; 170 | 171 | var space = this.input(node, 1); 172 | var off = this.input(node, 2); 173 | var value = this.input(node, 3); 174 | var op = [ space, off, 0 ]; 175 | 176 | if (size === 64) 177 | masm.movq(op, value); 178 | else if (size === 32) 179 | masm.movd(op, value); 180 | }); 181 | }); 182 | 183 | // TODO(indutny): eventually sync it up with x64:call 184 | builder.opcode('addr.resize_memory', function() { 185 | return { 186 | inputs: [ 187 | // State 188 | this.int('none'), 189 | 190 | // Size 191 | this.int('register', 'rsi') 192 | ], 193 | spills: this.spills 194 | }; 195 | }, function(node) { 196 | var size = this.input(node, 1); 197 | 198 | var masm = this.masm; 199 | 200 | // context is already in `rdi` 201 | // size is already in `rsi` 202 | assert.equal(x64.context, 'rdi'); 203 | assert.equal(size, 'rsi'); 204 | 205 | // TODO(indutny): should it be exposed? 206 | masm.mov('rax', this.table.get('std', 'resize_memory')); 207 | masm.call('rax'); 208 | }); 209 | -------------------------------------------------------------------------------- /lib/wasm/x64/gvn/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.stages = { 4 | generic: [], 5 | platform: [] 6 | }; 7 | -------------------------------------------------------------------------------- /lib/wasm/x64/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var x64 = exports; 4 | 5 | x64.prefix = 'x64'; 6 | x64.arch = 'x64'; 7 | x64.ptrSize = 8; 8 | x64.context = 'rdi'; 9 | x64.memory = { 10 | space: [ x64.context, x64.ptrSize * 0 ], 11 | size: [ x64.context, x64.ptrSize * 1 ] 12 | }; 13 | x64.scratch = 'r15'; 14 | x64.floatScratch = 'xmm15'; 15 | x64.params = { 16 | int: [ 'rsi', 'rdx', 'rcx', 'r8', 'r9' ], 17 | float: [ 'xmm0', 'xmm1', 'xmm2', 'xmm3', 'xmm4', 'xmm5', 'xmm6', 'xmm7' ] 18 | }; 19 | 20 | x64.registers = { 21 | general: [ 22 | 'rax', 'rcx', 'rdx', 'rsi', 'r8', 'r9', 'r10', 'r11', 23 | 24 | // Allocate callee-save registers only as a last resort 25 | 'rbx', 'r12', 'r13', 'r14' 26 | 27 | // rdi - context (aka `this` in C++) 28 | // TODO(indutny): get rid of scratch 29 | // r15 - scratch 30 | ], 31 | float: [ 32 | 'xmm0', 'xmm1', 'xmm2', 'xmm3', 33 | 'xmm4', 'xmm5', 'xmm6', 'xmm7', 34 | 'xmm8', 'xmm9', 'xmm10', 'xmm11', 35 | 'xmm12', 'xmm13', 'xmm14' 36 | ] 37 | }; 38 | 39 | // Non callee-save registers 40 | x64.temporaryRegs = { 41 | general: [ 42 | 'rax', 'rcx', 'rdx', 'rsi', 43 | 'r8', 'r9', 'r10', 'r11' 44 | ], 45 | float: x64.registers.float 46 | }; 47 | 48 | x64.calleeSaveRegs = { 49 | general: [ 'rbx', 'r12', 'r13', 'r14', 'r15' ], 50 | float: [] 51 | }; 52 | 53 | x64.Codegen = require('./codegen'); 54 | 55 | x64.reductions = require('./reductions'); 56 | x64.gvn = require('./gvn'); 57 | -------------------------------------------------------------------------------- /lib/wasm/x64/reductions/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.Select = require('./select'); 4 | 5 | exports.stages = { 6 | platform: [ 7 | exports.Select 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /lib/wasm/x64/reductions/select-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Select = require('./select'); 4 | var fixtures = require('../../../../test/fixtures'); 5 | 6 | describe('x64/reductions/SelectOpcode', function() { 7 | var select; 8 | beforeEach(function() { 9 | select = new Select(); 10 | }); 11 | 12 | describe('unary ops', function() { 13 | it('should reduce i64.extend_s', function() { 14 | fixtures.testReduction(select, function() {/* 15 | pipeline { 16 | b0 { 17 | i0 = i32.const 123 18 | i1 = i64.extend_s i0 19 | i2 = ret ^b0, i1 20 | } 21 | } 22 | */}, function() {/* 23 | pipeline { 24 | b0 { 25 | i0 = i32.const 123 26 | i1 = x64:change-s32-to-i64 i0 27 | i2 = ret ^b0, i1 28 | } 29 | } 30 | */}); 31 | }); 32 | 33 | it('should reduce i64.extend_u', function() { 34 | fixtures.testReduction(select, function() {/* 35 | pipeline { 36 | b0 { 37 | i0 = i32.const 123 38 | i1 = i64.extend_u i0 39 | i2 = ret ^b0, i1 40 | } 41 | } 42 | */}, function() {/* 43 | pipeline { 44 | b0 { 45 | i0 = i32.const 123 46 | i1 = x64:change-u32-to-i64 i0 47 | i2 = ret ^b0, i1 48 | } 49 | } 50 | */}); 51 | }); 52 | 53 | it('should reduce addr.from_32', function() { 54 | fixtures.testReduction(select, function() {/* 55 | pipeline { 56 | b0 { 57 | i0 = i32.const 123 58 | i1 = addr.from_32 i0 59 | i2 = ret ^b0, i1 60 | } 61 | } 62 | */}, function() {/* 63 | pipeline { 64 | b0 { 65 | i0 = i32.const 123 66 | i1 = x64:change-u32-to-i64 i0 67 | i2 = ret ^b0, i1 68 | } 69 | } 70 | */}); 71 | }); 72 | }); 73 | 74 | describe('binary ops', function() { 75 | it('should reduce iXX.add', function() { 76 | fixtures.testReduction(select, function() {/* 77 | pipeline { 78 | b0 { 79 | i0 = i64.const 123 80 | i1 = i64.add i0, i0 81 | i2 = ret ^b0, i1 82 | } 83 | } 84 | */}, function() {/* 85 | pipeline { 86 | b0 { 87 | i0 = i64.const 123 88 | i1 = x64:int.add i0, i0 89 | i2 = ret ^b0, i1 90 | } 91 | } 92 | */}); 93 | }); 94 | 95 | it('should not reduce fXX.add', function() { 96 | fixtures.testReduction(select, function() {/* 97 | pipeline { 98 | b0 { 99 | i0 = f64.const 123 100 | i1 = f64.add i0, i0 101 | i2 = ret ^b0, i1 102 | } 103 | } 104 | */}, function() {/* 105 | pipeline { 106 | b0 { 107 | i0 = f64.const 123 108 | i1 = f64.add i0, i0 109 | i2 = ret ^b0, i1 110 | } 111 | } 112 | */}); 113 | }); 114 | 115 | it('should add abs to f64.copysign', function() { 116 | fixtures.testReduction(select, function() {/* 117 | pipeline { 118 | b0 { 119 | i0 = f64.const 123 120 | i1 = f64.const 456 121 | i2 = f64.copysign i0, i1 122 | i3 = ret ^b0, i2 123 | } 124 | } 125 | */}, function() {/* 126 | pipeline { 127 | b0 { 128 | i0 = f64.const 123 129 | i1 = f64.abs i0 130 | i2 = f64.const 456 131 | i3 = f64.copysign i1, i2 132 | i4 = ret ^b0, i3 133 | } 134 | } 135 | */}); 136 | }); 137 | }); 138 | 139 | describe('branching', function() { 140 | it('should reduce iXX.bool', function() { 141 | fixtures.testReduction(select, function() {/* 142 | pipeline { 143 | b0 { 144 | i0 = i64.const 123 145 | i1 = i64.bool i0 146 | i2 = ret ^b0, i1 147 | } 148 | } 149 | */}, function() {/* 150 | pipeline { 151 | b0 { 152 | i0 = i64.const 123 153 | i1 = ret ^b0, i0 154 | } 155 | } 156 | */}); 157 | }); 158 | 159 | it('should not reduce fXX.bool', function() { 160 | fixtures.testReduction(select, function() {/* 161 | pipeline { 162 | b0 { 163 | i0 = f64.const 123 164 | i1 = f64.bool i0 165 | i2 = ret ^b0, i1 166 | } 167 | } 168 | */}, function() {/* 169 | pipeline { 170 | b0 { 171 | i0 = f64.const 123 172 | i1 = f64.bool i0 173 | i2 = ret ^b0, i1 174 | } 175 | } 176 | */}); 177 | }); 178 | 179 | it('should reduce if + i64.eq', function() { 180 | fixtures.testReduction(select, function() {/* 181 | pipeline { 182 | b0 { 183 | i0 = i64.const 123 184 | i1 = i64.const 456 185 | i2 = i64.eq i0, i1 186 | i3 = if ^b0, i2 187 | } 188 | b0 -> b1, b2 189 | b1 { 190 | i4 = ret ^b1 191 | } 192 | b2 { 193 | i5 = ret ^b2 194 | } 195 | } 196 | */}, function() {/* 197 | pipeline { 198 | b0 { 199 | i0 = i64.const 123 200 | i1 = i64.const 456 201 | i2 = x64:if.i64.eq ^b0, i0, i1 202 | } 203 | b0 -> b1, b2 204 | b1 { 205 | i3 = ret ^b1 206 | } 207 | b2 { 208 | i4 = ret ^b2 209 | } 210 | } 211 | */}); 212 | }); 213 | 214 | it('should add changes for i32 booleans', function() { 215 | fixtures.testReduction(select, function() {/* 216 | pipeline { 217 | b0 { 218 | i0 = i32.const 123 219 | i1 = i32.const 456 220 | i2 = i32.gt_s i0, i1 221 | i3 = ret ^b0, i2 222 | } 223 | } 224 | */}, function() {/* 225 | pipeline { 226 | b0 { 227 | i0 = i32.const 123 228 | i1 = x64:change-s32-to-i64 i0 229 | i2 = i32.const 456 230 | i3 = x64:change-s32-to-i64 i2 231 | i4 = i64.gt_s i1, i3 232 | i5 = ret ^b0, i4 233 | } 234 | } 235 | */}); 236 | }); 237 | }); 238 | 239 | describe('.ret', function() { 240 | it('should reduce int return', function() { 241 | fixtures.testReduction(select, function() {/* 242 | pipeline { 243 | b0 { 244 | i0 = i64.const 123 245 | i1 = i64.ret ^b0, i0 246 | } 247 | } 248 | */}, function() {/* 249 | pipeline { 250 | b0 { 251 | i0 = i64.const 123 252 | i1 = x64:int.ret ^b0, i0 253 | } 254 | } 255 | */}); 256 | }); 257 | 258 | it('should reduce float return', function() { 259 | fixtures.testReduction(select, function() {/* 260 | pipeline { 261 | b0 { 262 | i0 = f64.const 123 263 | i1 = f64.ret ^b0, i0 264 | } 265 | } 266 | */}, function() {/* 267 | pipeline { 268 | b0 { 269 | i0 = f64.const 123 270 | i1 = x64:float.ret ^b0, i0 271 | } 272 | } 273 | */}); 274 | }); 275 | }); 276 | 277 | describe('memory access', function() { 278 | it('should add space input to store/load', function() { 279 | fixtures.testReduction(select, function() {/* 280 | pipeline { 281 | b0 { 282 | i0 = state 283 | i1 = i64.const 1 284 | i2 = addr.from_64 i1 285 | i3 = i64.store ^b0, i0, i2, i1 286 | } 287 | } 288 | */}, function() {/* 289 | pipeline { 290 | b0 { 291 | i0 = state 292 | i1 = x64:memory.space i0 293 | i2 = i64.const 1 294 | i3 = x64:memory.size i0 295 | i4 = x64:memory.bounds-check 8, i0, i2, i3 296 | i5 = x64:store64 ^b0, i0, i1, i4, i2 297 | } 298 | } 299 | */}); 300 | }); 301 | 302 | it('should reuse space input between store/load', function() { 303 | fixtures.testReduction(select, function() {/* 304 | pipeline { 305 | b0 { 306 | i0 = state 307 | i1 = i64.const 1 308 | i2 = addr.from_64 i1 309 | i3 = i64.store ^b0, i0, i2, i1 310 | i4 = updateState ^i3, 4, i0 311 | i5 = i32.load i4, i2 312 | i6 = ret ^b0, i5 313 | } 314 | } 315 | */}, function() {/* 316 | pipeline { 317 | b0 { 318 | i0 = state 319 | i1 = x64:memory.space i0 320 | i2 = i64.const 1 321 | i3 = x64:memory.size i0 322 | i4 = x64:memory.bounds-check 8, i0, i2, i3 323 | i5 = x64:store64 ^b0, i0, i1, i4, i2 324 | i6 = updateState ^i5, 4, i0 325 | i7 = x64:memory.space i6 326 | i8 = x64:memory.size i6 327 | i9 = x64:memory.bounds-check 4, i6, i2, i8 328 | i10 = x64:load32 i6, i7, i9 329 | i11 = ret ^b0, i10 330 | } 331 | } 332 | */}); 333 | }); 334 | 335 | it('should cast load outputs', function() { 336 | fixtures.testReduction(select, function() {/* 337 | pipeline { 338 | b0 { 339 | i0 = state 340 | i1 = i64.const 1 341 | i2 = addr.from_64 i1 342 | i3 = i64.load32_u i0, i2 343 | i4 = i64.load32_s i0, i2 344 | i5 = i64.load8_s i0, i2 345 | i6 = i64.add i3, i4 346 | i7 = i64.add i5, i6 347 | i8 = ret ^b0, i7 348 | } 349 | } 350 | */}, function() {/* 351 | pipeline { 352 | b0 { 353 | i0 = state 354 | i1 = x64:memory.space i0 355 | i2 = i64.const 1 356 | i3 = x64:memory.size i0 357 | i4 = x64:memory.bounds-check 1, i0, i2, i3 358 | i5 = x64:load8_s i0, i1, i4 359 | i6 = x64:memory.space i0 360 | i7 = x64:memory.size i0 361 | i8 = x64:memory.bounds-check 4, i0, i2, i7 362 | i9 = x64:load32_u i0, i6, i8 363 | i10 = x64:memory.space i0 364 | i11 = x64:memory.size i0 365 | i12 = x64:memory.bounds-check 4, i0, i2, i11 366 | i13 = x64:load32_s i0, i10, i12 367 | i14 = x64:int.add i9, i13 368 | i15 = x64:int.add i5, i14 369 | i16 = ret ^b0, i15 370 | } 371 | } 372 | */}); 373 | }); 374 | 375 | it('should cast float loads/stores', function() { 376 | fixtures.testReduction(select, function() {/* 377 | pipeline { 378 | b0 { 379 | i0 = state 380 | i1 = i64.const 1 381 | i2 = f64.const 1 382 | i3 = addr.from_64 i1 383 | i4 = f64.store ^b0, i0, i3, i2 384 | i5 = updateState ^i4, 4, i0 385 | i6 = f64.load i5, i3 386 | i7 = ret ^b0, i6 387 | } 388 | } 389 | */}, function() {/* 390 | pipeline { 391 | b0 { 392 | i0 = state 393 | i1 = x64:memory.space i0 394 | i2 = i64.const 1 395 | i3 = x64:memory.size i0 396 | i4 = x64:memory.bounds-check 8, i0, i2, i3 397 | i5 = f64.const 1 398 | i6 = x64:f64.store ^b0, i0, i1, i4, i5 399 | i7 = updateState ^i6, 4, i0 400 | i8 = x64:memory.space i7 401 | i9 = x64:memory.size i7 402 | i10 = x64:memory.bounds-check 8, i7, i2, i9 403 | i11 = x64:f64.load i7, i8, i10 404 | i12 = ret ^b0, i11 405 | } 406 | } 407 | */}); 408 | }); 409 | }); 410 | }); 411 | -------------------------------------------------------------------------------- /lib/wasm/x64/reductions/select.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var util = require('util'); 5 | var wasmCFG = require('wasm-cfg'); 6 | var Reducer = require('json-pipeline-reducer'); 7 | 8 | function SelectX64Opcode() { 9 | Reducer.Reduction.call(this); 10 | } 11 | util.inherits(SelectX64Opcode, Reducer.Reduction); 12 | module.exports = SelectX64Opcode; 13 | 14 | SelectX64Opcode.prototype.reduce = function reduce(node, reducer) { 15 | // Already replaced 16 | if (/^x64:/.test(node.opcode)) 17 | return; 18 | 19 | if (/\.bool$/.test(node.opcode)) 20 | return this.reduceBool(node, reducer); 21 | 22 | if (/\.ret$/.test(node.opcode)) 23 | return this.reduceRet(node, reducer); 24 | 25 | if (/i(32|64)\.(add|sub|mul|xor|and|or|shl)$/.test(node.opcode)) 26 | return this.reduceIntBinop(node, reducer); 27 | 28 | if (/\.load/.test(node.opcode) || /\.store/.test(node.opcode)) 29 | return this.reduceMemoryAccess(node, reducer); 30 | 31 | if (/^addr\.from_/.test(node.opcode) || 32 | /^i(32|64)\.from_addr/.test(node.opcode)) { 33 | return this.reduceMemoryCoercion(node, reducer); 34 | } 35 | 36 | if (/^i32\.(eq|ne|gt_[su]|lt_[su]|ge_[su]|le_[su])$/.test(node.opcode)) 37 | return this.reduceI32Bool(node, reducer); 38 | 39 | if (node.opcode === 'i64.extend_s' || node.opcode === 'i64.extend_u') 40 | return this.reduceI64Extend(node, reducer); 41 | 42 | if (node.opcode === 'if') 43 | return this.reduceIf(node, reducer); 44 | 45 | if (node.opcode === 'f32.copysign' || node.opcode === 'f64.copysign') 46 | return this.reduceCopySign(node, reducer); 47 | }; 48 | 49 | SelectX64Opcode.prototype.reduceBool = function reduceBool(node, reducer) { 50 | if (/^f/.test(node.opcode)) 51 | return; 52 | 53 | // Just replace with the input, we are ready to handle 64bit integers! 54 | if (node.opcode === 'i64.bool') 55 | reducer.replace(node, node.inputs[0]); 56 | }; 57 | 58 | SelectX64Opcode.prototype.reduceRet = function reduceRet(node, reducer) { 59 | if (node.opcode === 'f32.ret' || node.opcode === 'f64.ret') 60 | node.opcode = 'x64:float.ret'; 61 | else 62 | node.opcode = 'x64:int.ret'; 63 | reducer.change(node); 64 | }; 65 | 66 | SelectX64Opcode.prototype._getAccessSize = function _getAccessSize(opcode) { 67 | var size; 68 | if (/(store|load)8/.test(opcode)) 69 | size = 8; 70 | else if (/(store|load)16/.test(opcode)) 71 | size = 16; 72 | else if (/(store|load)32|[if]32\.(store|load)/.test(opcode)) 73 | size = 32; 74 | else 75 | size = 64; 76 | return size; 77 | }; 78 | 79 | SelectX64Opcode.prototype.reduceMemoryAccess = function reduceMemoryAccess( 80 | node, reducer) { 81 | var opcode = node.opcode; 82 | 83 | var state = node.inputs[0]; 84 | var cell = node.inputs[1]; 85 | 86 | // Only for stores 87 | var value = node.inputs[2]; 88 | 89 | // Generalize opcode 90 | var size = this._getAccessSize(opcode); 91 | var type = /load/.test(opcode) ? 'load' : 'store'; 92 | var accessOpcode = 'x64:' + type; 93 | accessOpcode += size; 94 | if (/_u$/.test(opcode)) 95 | accessOpcode += '_u'; 96 | else if (/_s$/.test(opcode)) 97 | accessOpcode += '_s'; 98 | 99 | // Floating point loads/stores are left as they are 100 | if (/f(32|64)/.test(opcode)) 101 | accessOpcode = 'x64:' + node.opcode; 102 | 103 | // Skip non resizing states 104 | var checkState = state; 105 | while ( 106 | checkState.opcode === 'updateState' && 107 | (checkState.literals[0] & wasmCFG.effects.EFFECT_MEMORY_RESIZE) === 0) { 108 | checkState = checkState.inputs[0]; 109 | } 110 | 111 | var memorySpace = reducer.graph.create('x64:memory.space', checkState); 112 | reducer.add(memorySpace); 113 | 114 | var memorySize = reducer.graph.create('x64:memory.size', checkState); 115 | reducer.add(memorySize); 116 | 117 | // Create bounds check to wrap memory index 118 | var access = reducer.graph.create(accessOpcode); 119 | cell = reducer.graph.create('x64:memory.bounds-check', [ 120 | checkState, cell, memorySize ]); 121 | cell.addLiteral(size / 8); 122 | reducer.add(cell); 123 | 124 | access.addInput(state); 125 | access.addInput(memorySpace); 126 | access.addInput(cell); 127 | 128 | if (type === 'store') 129 | access.addInput(value); 130 | 131 | reducer.replace(node, access); 132 | }; 133 | 134 | SelectX64Opcode.prototype.reduceMemoryCoercion = function reduceMemoryCoercion( 135 | node, reducer) { 136 | // Int64 is an address 137 | if (node.opcode === 'addr.from_64' || node.opcode === 'i64.from_addr') { 138 | reducer.replace(node, node.inputs[0]); 139 | return; 140 | } 141 | 142 | // Change i32 to i64 143 | if (node.opcode === 'addr.from_32') { 144 | node.opcode = 'i64.extend_u'; 145 | reducer.change(node); 146 | return; 147 | } 148 | 149 | assert.equal(node.opcode, 'i32.from_addr'); 150 | reducer.replace(node, this.changeI64ToI32(node.inputs[0], reducer)); 151 | }; 152 | 153 | SelectX64Opcode.prototype.reduceIntBinop = function reduceIntBinop(node, 154 | reducer) { 155 | node.opcode = 'x64:int.' + node.opcode.replace(/^i(32|64)\./, ''); 156 | return reducer.change(node); 157 | }; 158 | 159 | SelectX64Opcode.prototype.reduceIf = function reduceIf(node, reducer) { 160 | var input = node.inputs[0]; 161 | 162 | var opcode; 163 | if (input.opcode === 'i64.eq') 164 | opcode = 'i64.eq'; 165 | 166 | if (!opcode) 167 | return; 168 | 169 | var select = reducer.graph.create('x64:if.' + opcode, [ 170 | input.inputs[0], 171 | input.inputs[1] 172 | ]); 173 | reducer.replace(node, select); 174 | }; 175 | 176 | SelectX64Opcode.prototype.changeI64ToI32 = function changeI64toI32(node, 177 | reducer) { 178 | // TODO(indutny): change(constant64) = constant32 179 | var change = reducer.graph.create('i32.wrap'); 180 | change.addInput(node); 181 | reducer.add(change); 182 | 183 | return change; 184 | }; 185 | 186 | SelectX64Opcode.prototype.changeI32ToI64 = function changeI32ToI64(node, 187 | signed, 188 | reducer) { 189 | // TODO(indutny): change(constant32) = constant64 190 | var change = reducer.graph.create(signed ? 'x64:change-s32-to-i64' : 191 | 'x64:change-u32-to-i64'); 192 | change.addInput(node); 193 | reducer.add(change); 194 | 195 | return change; 196 | }; 197 | 198 | SelectX64Opcode.prototype.reduceI32Bool = function reduceI32Bool(node, 199 | reducer) { 200 | var signed = /_s$/.test(node.opcode); 201 | 202 | node.opcode = node.opcode.replace(/^i32/, 'i64'); 203 | node.replaceInput(0, this.changeI32ToI64(node.inputs[0], signed, reducer)); 204 | node.replaceInput(1, this.changeI32ToI64(node.inputs[1], signed, reducer)); 205 | reducer.change(node); 206 | }; 207 | 208 | SelectX64Opcode.prototype.reduceI64Extend = function reduce64Extend(node, 209 | reducer) { 210 | var signed = node.opcode === 'i64.extend_s'; 211 | 212 | reducer.replace(node, this.changeI32ToI64(node.inputs[0], signed, reducer)); 213 | }; 214 | 215 | SelectX64Opcode.prototype.reduceCopySign = function reducCopySign(node, 216 | reducer) { 217 | var left = node.inputs[0]; 218 | if (left.opcode === 'f32.abs' || left.opcode === 'f64.abs') 219 | return; 220 | 221 | // Clear the sign bit, before copying over new one 222 | var absOpcode = node.opcode === 'f32.copysign' ? 'f32.abs' : 'f64.abs'; 223 | var abs = reducer.graph.create(absOpcode, left); 224 | reducer.add(abs); 225 | 226 | node.replaceInput(0, abs); 227 | reducer.change(node); 228 | }; 229 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wasm-jit", 3 | "version": "1.2.2", 4 | "description": "WebAssembly JIT Compiler", 5 | "main": "lib/wasm.js", 6 | "scripts": { 7 | "test": "make test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/indutny/wasm-jit.git" 12 | }, 13 | "keywords": [ 14 | "wasm", 15 | "web", 16 | "assembly", 17 | "jit", 18 | "compiler", 19 | "webasm", 20 | "asm", 21 | "web" 22 | ], 23 | "author": "Fedor Indutny ", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/indutny/wasm-jit/issues" 27 | }, 28 | "homepage": "https://github.com/indutny/wasm-jit#readme", 29 | "dependencies": { 30 | "bindings": "^1.2.1", 31 | "bn.js": "^3.2.0", 32 | "debug": "^2.2.0", 33 | "dominance-frontier": "^2.0.0", 34 | "gvn": "^1.0.0", 35 | "jit.js": "^1.16.0", 36 | "json-pipeline-reducer": "^1.4.0", 37 | "json-pipeline-scheduler": "^2.0.0", 38 | "linearscan": "^5.1.0", 39 | "mmap.js": "^1.0.1", 40 | "nan": "^2.1.0", 41 | "ssa.js": "^2.0.0", 42 | "wasm-ast": "^3.0.1", 43 | "wasm-cfg": "^3.0.0" 44 | }, 45 | "devDependencies": { 46 | "assert-text": "^1.1.2", 47 | "disasm": "^1.5.0", 48 | "jscs": "^2.1.1", 49 | "jshint": "^2.8.0", 50 | "json-pipeline": "^3.8.0", 51 | "mocha": "^2.3.3" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/std.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "nan.h" 6 | 7 | #include "src/std.h" 8 | 9 | #define container_of(ptr, type, member) \ 10 | ((type *) ((char *) (ptr) - offsetof(type, member))) 11 | 12 | namespace wasm { 13 | namespace std { 14 | 15 | using namespace v8; 16 | 17 | WasmContext::WasmContext() { 18 | space.size = kInitialSpaceSize; 19 | space.ptr = new uint8_t[space.size]; 20 | } 21 | 22 | 23 | WasmContext::~WasmContext() { 24 | delete[] space.ptr; 25 | space.ptr = NULL; 26 | space.size = 0; 27 | } 28 | 29 | 30 | void WasmContext::Resize(uintptr_t size) { 31 | if (size <= space.size) 32 | return; 33 | 34 | uint8_t* new_space = new uint8_t[size]; 35 | memcpy(new_space, space.ptr, space.size); 36 | space.ptr = new_space; 37 | space.size = size; 38 | } 39 | 40 | 41 | uint8_t* WasmContext::Load(uintptr_t off, uintptr_t size) { 42 | if (off + size > space.size) 43 | return NULL; 44 | 45 | return space.ptr + off; 46 | } 47 | 48 | 49 | void WasmContext::Free(char* data, void* hint) { 50 | WasmContext* ctx = reinterpret_cast(hint); 51 | delete ctx; 52 | } 53 | 54 | 55 | NAN_METHOD(CreateContext) { 56 | WasmContext* ctx = new WasmContext(); 57 | 58 | info.GetReturnValue().Set(Nan::NewBuffer( 59 | ctx->data(), 60 | ctx->data_length(), 61 | WasmContext::Free, 62 | reinterpret_cast(ctx)).ToLocalChecked()); 63 | } 64 | 65 | 66 | void WasmContext::ResizeMemory(void* ictx, uintptr_t size) { 67 | WasmContext* ctx = container_of(ictx, WasmContext, space); 68 | ctx->Resize(size); 69 | } 70 | 71 | 72 | static void FreeFn(char* data, void* hint) { 73 | // No-op 74 | } 75 | 76 | 77 | void WasmContext::Print(void* ictx, uintptr_t str, uintptr_t size) { 78 | WasmContext* ctx = container_of(ictx, WasmContext, space); 79 | uint8_t* data = ctx->Load(str, size); 80 | if (data == NULL) 81 | return; 82 | 83 | fprintf(stdout, "%.*s", static_cast(size), 84 | reinterpret_cast(data)); 85 | } 86 | 87 | 88 | static Local GetFnPtr(uintptr_t fn) { 89 | return Nan::NewBuffer(reinterpret_cast(fn), 90 | 64, 91 | FreeFn, 92 | NULL).ToLocalChecked(); 93 | } 94 | 95 | 96 | static void Init(Handle target) { 97 | target->Set(Nan::New("resize_memory").ToLocalChecked(), 98 | GetFnPtr(reinterpret_cast(WasmContext::ResizeMemory))); 99 | target->Set(Nan::New("print").ToLocalChecked(), 100 | GetFnPtr(reinterpret_cast(WasmContext::Print))); 101 | 102 | Nan::SetMethod(target, "createContext", CreateContext); 103 | } 104 | 105 | } // namespace std 106 | } // namespace wasm 107 | 108 | NODE_MODULE(std, wasm::std::Init); 109 | -------------------------------------------------------------------------------- /src/std.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "nan.h" 4 | 5 | namespace wasm { 6 | namespace std { 7 | 8 | class WasmContext { 9 | public: 10 | WasmContext(); 11 | ~WasmContext(); 12 | 13 | void Resize(uintptr_t size); 14 | uint8_t* Load(uintptr_t off, uintptr_t size); 15 | 16 | static void Free(char* data, void* hint); 17 | static void ResizeMemory(void* ictx, uintptr_t size); 18 | static void Print(void* ictx, uintptr_t str, uintptr_t size); 19 | 20 | inline char* data() { return reinterpret_cast(&space); } 21 | inline size_t data_length() { return sizeof(space); } 22 | 23 | protected: 24 | static const uintptr_t kInitialSpaceSize = 1024 * 1024; 25 | 26 | struct { 27 | uint8_t* ptr; 28 | uintptr_t size; 29 | } space; 30 | }; 31 | 32 | } // namespace std 33 | } // namespace wasm 34 | -------------------------------------------------------------------------------- /test/api-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var wasm = require('../'); 4 | var fixtures = require('./fixtures'); 5 | var compile = fixtures.compile; 6 | 7 | describe('wasm Compiler/API', function() { 8 | var ctx; 9 | beforeEach(function() { 10 | ctx = wasm.std.createContext(); 11 | }); 12 | 13 | it('should run empty function', function() { 14 | var main = compile(function() {/* 15 | void main() { 16 | } 17 | 18 | export main 19 | */}).main; 20 | main(ctx); 21 | }); 22 | 23 | it('should run add function', function() { 24 | var add = compile(function() {/* 25 | i64 add(i64 a, i64 b) { 26 | return i64.add(a, b); 27 | } 28 | 29 | export add 30 | */}).add; 31 | assert.equal(add(ctx, 1, 2), 3); 32 | }); 33 | 34 | it('should compute fibonacci number iteratively', function() { 35 | var fib = compile(function() {/* 36 | i64 fib(i64 count) { 37 | i64 a = i64.const(1); 38 | i64 b = i64.const(1); 39 | i64 i = count; 40 | do { 41 | i64 c = i64.add(a, b); 42 | a = b; 43 | b = c; 44 | i = i64.sub(i, i64.const(1)); 45 | } while (i); 46 | return a; 47 | } 48 | 49 | export fib 50 | */}).fib; 51 | 52 | function referenceFib(count) { 53 | var a = 1; 54 | var b = 1; 55 | var i = count; 56 | do { 57 | var c = a + b; 58 | a = b; 59 | b = c; 60 | i = i - 1; 61 | } while (i); 62 | return a; 63 | } 64 | 65 | for (var i = 1; i < 60; i++) 66 | assert.equal(fib(ctx, i), referenceFib(i)); 67 | }); 68 | 69 | it('should compute fibonacci number recursively', function() { 70 | var fib = compile(function() {/* 71 | i64 fib(i64 count) { 72 | if (i64.eq(count, i64.const(0))) 73 | return i64.const(1); 74 | if (i64.eq(count, i64.const(1))) 75 | return i64.const(1); 76 | 77 | return i64.add(fib(i64.sub(count, i64.const(2))), 78 | fib(i64.sub(count, i64.const(1)))); 79 | } 80 | 81 | export fib 82 | */}).fib; 83 | 84 | function referenceFib(count) { 85 | if (count === 0 || count === 1) 86 | return 1; 87 | return referenceFib(count - 2) + referenceFib(count - 1); 88 | } 89 | 90 | for (var i = 1; i < 10; i++) 91 | assert.equal(fib(ctx, i), referenceFib(i)); 92 | }); 93 | 94 | it('should grow memory', function() { 95 | var resize = compile(function() {/* 96 | void resize() { 97 | std::grow_memory(addr.from_64(i64.const(0x1000))); 98 | } 99 | 100 | export resize 101 | */}).resize; 102 | 103 | resize(ctx); 104 | }); 105 | 106 | it('should print string', function() { 107 | var print = compile(function() {/* 108 | void print() { 109 | i32.store(addr.from_64(i64.const(0)), i32.const(0x6c6c6568)); 110 | i32.store(addr.from_64(i64.const(4)), i32.const(0x6177206f)); 111 | i32.store(addr.from_64(i64.const(8)), i32.const(0x000a6d73)); 112 | 113 | std::print(addr.from_64(i64.const(0)), 114 | addr.from_64(i64.const(11))); 115 | } 116 | 117 | export print 118 | */}).print; 119 | 120 | print(ctx); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /test/fixtures.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assertText = require('assert-text'); 4 | assertText.options.trim = true; 5 | 6 | var pipeline = require('json-pipeline'); 7 | var Reducer = require('json-pipeline-reducer'); 8 | var Scheduler = require('json-pipeline-scheduler'); 9 | var GVN = require('gvn'); 10 | var disasm = require('disasm'); 11 | var fixtures = require('./fixtures'); 12 | 13 | var wasm = require('../'); 14 | 15 | exports.fn2str = function fn2str(fn) { 16 | return fn.toString().replace(/^function[^{]+{\/\*|\*\/}$/g, ''); 17 | }; 18 | 19 | exports.testAsm = function testAsm(input, expected) { 20 | var c = wasm.Compiler.create(); 21 | 22 | var info = c.generateCode('test', exports.fn2str(input)).reloc; 23 | var asm = disasm.create('x64').disasm(info.buffer); 24 | var pad = /int3\s*\n(\s*int3\s*\n)*/g; 25 | asm = disasm.stringify(asm).replace(pad, '(padding)\n'); 26 | 27 | assertText.equal(asm, exports.fn2str(expected)); 28 | }; 29 | 30 | exports.testReduction = function testReduction(reduction, input, expected) { 31 | var p = pipeline.create('cfg'); 32 | p.parse(exports.fn2str(input), { cfg: true }, 'printable'); 33 | 34 | var reducer = Reducer.create(); 35 | reducer.addReduction(reduction); 36 | reducer.reduce(p); 37 | 38 | var scheduled = Scheduler.create(p).run(); 39 | scheduled.reindex(); 40 | assertText.equal(scheduled.render({ cfg: true }, 'printable'), 41 | exports.fn2str(expected)); 42 | }; 43 | 44 | exports.testGVN = function testGVN(relation, input, expected) { 45 | var p = pipeline.create('cfg'); 46 | p.parse(exports.fn2str(input), { cfg: true }, 'printable'); 47 | 48 | var gvn = GVN.create(); 49 | gvn.addRelation(relation); 50 | 51 | var reducer = Reducer.create(); 52 | reducer.addReduction(gvn); 53 | reducer.reduce(p); 54 | 55 | var scheduled = Scheduler.create(p).run(); 56 | scheduled.reindex(); 57 | assertText.equal(scheduled.render({ cfg: true }, 'printable'), 58 | exports.fn2str(expected)); 59 | }; 60 | 61 | exports.compile = function compile(input) { 62 | var c = wasm.Compiler.create(); 63 | 64 | return c.compile('test', exports.fn2str(input)); 65 | }; 66 | -------------------------------------------------------------------------------- /test/x64/base-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var fixtures = require('../fixtures'); 3 | var testAsm = fixtures.testAsm; 4 | 5 | describe('wasm Compiler/x64', function() { 6 | it('should compile empty function', function() { 7 | testAsm(function() {/* 8 | void main() { 9 | } 10 | */}, function() {/* 11 | push rbp 12 | mov rbp, rsp 13 | mov rsp, rbp 14 | pop rbp 15 | ret 16 | */}); 17 | }); 18 | 19 | it('should compile i64 params', function() { 20 | testAsm(function() {/* 21 | i64 main(i64 a, i64 b) { 22 | return i64.add(a, b); 23 | } 24 | */}, function() {/* 25 | push rbp 26 | mov rbp, rsp 27 | mov rax, rsi 28 | add rax, rdx 29 | mov rsp, rbp 30 | pop rbp 31 | ret 32 | */}); 33 | }); 34 | 35 | it('should compile chain of expression', function() { 36 | testAsm(function() {/* 37 | i64 main(i64 a, i64 b) { 38 | return i64.add(a, i64.add(b, i64.const(1358))); 39 | } 40 | */}, function() {/* 41 | push rbp 42 | mov rbp, rsp 43 | mov rax, 0x54e 44 | add rax, rdx 45 | add rax, rsi 46 | mov rsp, rbp 47 | pop rbp 48 | ret 49 | */}); 50 | }); 51 | 52 | it('should work with local variables', function() { 53 | testAsm(function() {/* 54 | f64 main(f64 a) { 55 | f64 b = f64.const(123.456); 56 | return f64.add(a, b); 57 | } 58 | */}, function() {/* 59 | push rbp 60 | mov rbp, rsp 61 | mov r15, 0x405edd2f1a9fbe77 62 | vmovq xmm1, r15 63 | vaddsd xmm0, xmm1 64 | mov rsp, rbp 65 | pop rbp 66 | ret 67 | */}); 68 | }); 69 | 70 | it('should work with branches', function() { 71 | testAsm(function() {/* 72 | i64 main(i64 a) { 73 | if (a) { 74 | return a; 75 | } 76 | return i64.const(1); 77 | } 78 | */}, function() {/* 79 | push rbp 80 | mov rbp, rsp 81 | cmp rsi, 0x0 82 | far-jcc z, 0x8 83 | 84 | mov rax, rsi 85 | jmp 0x7 86 | 87 | mov rax, 0x1 88 | mov rsp, rbp 89 | pop rbp 90 | ret 91 | */}); 92 | }); 93 | 94 | it('should compile forever loop', function() { 95 | testAsm(function() {/* 96 | i64 main() { 97 | i64 t = i64.const(0); 98 | forever { 99 | t = i64.add(t, i64.const(1)); 100 | } 101 | return t; 102 | } 103 | */}, function() {/* 104 | push rbp 105 | mov rbp, rsp 106 | mov rax, 0x0 107 | mov rcx, 0x1 108 | add rax, rcx 109 | jmp -0x8 110 | */}); 111 | }); 112 | 113 | it('should compile forever loop with break/continue', function() { 114 | testAsm(function() {/* 115 | i64 main() { 116 | i64 t = i64.const(0); 117 | forever { 118 | t = i64.add(t, i64.const(1)); 119 | if (t) 120 | continue; 121 | else 122 | break; 123 | } 124 | return t; 125 | } 126 | */}, function() {/* 127 | push rbp 128 | mov rbp, rsp 129 | mov rax, 0x0 130 | mov rcx, 0x1 131 | add rax, rcx 132 | cmp rax, 0x0 133 | far-jcc z, 0x5 134 | jmp -0x14 135 | mov rsp, rbp 136 | pop rbp 137 | ret 138 | */}); 139 | }); 140 | 141 | it('should compile do {} while loop', function() { 142 | testAsm(function() {/* 143 | i64 main() { 144 | i64 t = i64.const(10); 145 | do { 146 | t = i64.add(t, i64.const(-1)); 147 | } while (t); 148 | return t; 149 | } 150 | */}, function() {/* 151 | push rbp 152 | mov rbp, rsp 153 | mov rax, 0xa 154 | mov rcx, -0x1 155 | add rax, rcx 156 | cmp rax, 0x0 157 | far-jcc z, 0x5 158 | jmp -0x14 159 | mov rsp, rbp 160 | pop rbp 161 | ret 162 | */}); 163 | }); 164 | 165 | it('should compile calls', function() { 166 | testAsm(function() {/* 167 | i64 main() { 168 | i64 t = i64.const(123); 169 | i64 a = add(t, i64.const(456), t); 170 | return i64.add(t, a); 171 | } 172 | 173 | i64 add(i64 a, i64 b, i64 c) { 174 | return i64.add(a, b); 175 | } 176 | */}, function() {/* 177 | push rbp 178 | mov rbp, rsp 179 | sub rsp, 0x10 180 | mov [rbp, -0x8], rbx 181 | mov rsi, 0x7b 182 | mov rdx, 0x1c8 183 | mov rbx, rsi 184 | mov rcx, rsi 185 | lea rax, [rip, 0x18] 186 | call eax 187 | add rax, rbx 188 | mov rbx, [rbp, -0x8] 189 | mov rsp, rbp 190 | pop rbp 191 | ret 192 | (padding) 193 | push rbp 194 | mov rbp, rsp 195 | mov rax, rsi 196 | add rax, rdx 197 | mov rsp, rbp 198 | pop rbp 199 | ret 200 | */}); 201 | }); 202 | }); 203 | -------------------------------------------------------------------------------- /test/x64/bool-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var fixtures = require('../fixtures'); 3 | var testAsm = fixtures.testAsm; 4 | 5 | describe('wasm Compiler/x64/bool', function() { 6 | describe('int', function() { 7 | it('should support i64.eq', function() { 8 | testAsm(function() {/* 9 | i32 main(i64 a) { 10 | return i64.eq(a, i64.const(0x1)); 11 | } 12 | */}, function() {/* 13 | push rbp 14 | mov rbp, rsp 15 | mov rax, 0x1 16 | cmp rsi, rax 17 | setcc z, eax 18 | mov rsp, rbp 19 | pop rbp 20 | ret 21 | */}); 22 | }); 23 | 24 | it('should support i64.gt_u', function() { 25 | testAsm(function() {/* 26 | i32 main(i64 a) { 27 | return i64.gt_u(a, i64.const(0x1)); 28 | } 29 | */}, function() {/* 30 | push rbp 31 | mov rbp, rsp 32 | mov rax, 0x1 33 | cmp rsi, rax 34 | setcc nbe, eax 35 | mov rsp, rbp 36 | pop rbp 37 | ret 38 | */}); 39 | }); 40 | 41 | it('should support i64.eq in branch', function() { 42 | testAsm(function() {/* 43 | i64 main(i64 a, i64 b) { 44 | if (i64.eq(a, b)) 45 | return a; 46 | else 47 | return b; 48 | } 49 | */}, function() {/* 50 | push rbp 51 | mov rbp, rsp 52 | cmp rsi, rdx 53 | far-jcc nz, 0x8 54 | mov rax, rsi 55 | jmp 0x3 56 | mov rax, rdx 57 | mov rsp, rbp 58 | pop rbp 59 | ret 60 | */}); 61 | }); 62 | }); 63 | 64 | describe('truncating bools', function() { 65 | it('should support i32.eq', function() { 66 | testAsm(function() {/* 67 | i32 main(i32 a) { 68 | return i32.eq(a, i32.const(0x1)); 69 | } 70 | */}, function() {/* 71 | push rbp 72 | mov rbp, rsp 73 | mov eax, esi 74 | mov rcx, 0x1 75 | mov ecx, ecx 76 | cmp rax, rcx 77 | setcc z, eax 78 | mov rsp, rbp 79 | pop rbp 80 | ret 81 | */}); 82 | }); 83 | 84 | it('should support i32.gt_s', function() { 85 | testAsm(function() {/* 86 | i32 main(i32 a) { 87 | return i32.gt_s(a, i32.const(0x1)); 88 | } 89 | */}, function() {/* 90 | push rbp 91 | mov rbp, rsp 92 | movsxd rax, rsi 93 | mov rcx, 0x1 94 | movsxd rcx, rcx 95 | cmp rax, rcx 96 | setcc nle, eax 97 | mov rsp, rbp 98 | pop rbp 99 | ret 100 | */}); 101 | }); 102 | }); 103 | 104 | describe('floating point', function() { 105 | it('should support f64.eq', function() { 106 | testAsm(function() {/* 107 | i32 main(f64 a, f64 b) { 108 | return f64.eq(a, b); 109 | } 110 | */}, function() {/* 111 | push rbp 112 | mov rbp, rsp 113 | vucomisd xmm0, xmm1 114 | setcc z, eax 115 | mov rsp, rbp 116 | pop rbp 117 | ret 118 | */}); 119 | }); 120 | 121 | it('should support f32.eq', function() { 122 | testAsm(function() {/* 123 | i32 main(f32 a, f32 b) { 124 | return f32.eq(a, b); 125 | } 126 | */}, function() {/* 127 | push rbp 128 | mov rbp, rsp 129 | vucomiss xmm0, xmm1 130 | setcc z, eax 131 | mov rsp, rbp 132 | pop rbp 133 | ret 134 | */}); 135 | }); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /test/x64/cast-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var fixtures = require('../fixtures'); 3 | var testAsm = fixtures.testAsm; 4 | 5 | describe('wasm Compiler/x64/cast', function() { 6 | it('should compile i64.trunc_s_64', function() { 7 | testAsm(function() {/* 8 | i64 main(f64 a) { 9 | return i64.trunc_s_64(a); 10 | } 11 | */}, function() {/* 12 | push rbp 13 | mov rbp, rsp 14 | vcvttsd2si rax, xmm0 15 | mov rsp, rbp 16 | pop rbp 17 | ret 18 | */}); 19 | }); 20 | 21 | it('should compile i64.trunc_s_32', function() { 22 | testAsm(function() {/* 23 | i64 main(f32 a) { 24 | return i64.trunc_s_32(a); 25 | } 26 | */}, function() {/* 27 | push rbp 28 | mov rbp, rsp 29 | vcvttss2si rax, xmm0 30 | mov rsp, rbp 31 | pop rbp 32 | ret 33 | */}); 34 | }); 35 | 36 | it('should compile i32.trunc_u_64', function() { 37 | testAsm(function() {/* 38 | i32 main(f64 a) { 39 | return i32.trunc_u_64(a); 40 | } 41 | */}, function() {/* 42 | push rbp 43 | mov rbp, rsp 44 | vcvttsd2si eax, xmm0 45 | mov eax, eax 46 | mov rsp, rbp 47 | pop rbp 48 | ret 49 | */}); 50 | }); 51 | 52 | it('should compile i32.trunc_u_32', function() { 53 | testAsm(function() {/* 54 | i32 main(f32 a) { 55 | return i32.trunc_u_32(a); 56 | } 57 | */}, function() {/* 58 | push rbp 59 | mov rbp, rsp 60 | vcvttss2si eax, xmm0 61 | mov eax, eax 62 | mov rsp, rbp 63 | pop rbp 64 | ret 65 | */}); 66 | }); 67 | 68 | it('should compile i32.trunc_s_64', function() { 69 | testAsm(function() {/* 70 | i32 main(f64 a) { 71 | return i32.trunc_s_64(a); 72 | } 73 | */}, function() {/* 74 | push rbp 75 | mov rbp, rsp 76 | vcvttsd2si eax, xmm0 77 | movsxd rax, rax 78 | mov rsp, rbp 79 | pop rbp 80 | ret 81 | */}); 82 | }); 83 | 84 | it('should compile i32.trunc_s_32', function() { 85 | testAsm(function() {/* 86 | i32 main(f32 a) { 87 | return i32.trunc_s_32(a); 88 | } 89 | */}, function() {/* 90 | push rbp 91 | mov rbp, rsp 92 | vcvttss2si eax, xmm0 93 | movsxd rax, rax 94 | mov rsp, rbp 95 | pop rbp 96 | ret 97 | */}); 98 | }); 99 | 100 | it('should compile f64.convert_s_64', function() { 101 | testAsm(function() {/* 102 | f64 main(i64 a) { 103 | return f64.convert_s_64(a); 104 | } 105 | */}, function() {/* 106 | push rbp 107 | mov rbp, rsp 108 | vcvtsi2sd xmm0, rsi 109 | mov rsp, rbp 110 | pop rbp 111 | ret 112 | */}); 113 | }); 114 | 115 | it('should compile f32.convert_s_64', function() { 116 | testAsm(function() {/* 117 | f32 main(i64 a) { 118 | return f32.convert_s_64(a); 119 | } 120 | */}, function() {/* 121 | push rbp 122 | mov rbp, rsp 123 | vcvtsi2ss xmm0, rsi 124 | mov rsp, rbp 125 | pop rbp 126 | ret 127 | */}); 128 | }); 129 | 130 | it('should compile i32.from_addr', function() { 131 | testAsm(function() {/* 132 | i32 main() { 133 | return i32.from_addr(addr.from_64(i64.const(0xdead))); 134 | } 135 | */}, function() {/* 136 | push rbp 137 | mov rbp, rsp 138 | mov rax, 0xdead 139 | mov eax, eax 140 | mov rsp, rbp 141 | pop rbp 142 | ret 143 | */}); 144 | }); 145 | 146 | it('should support i64.reinterpret', function() { 147 | testAsm(function() {/* 148 | i64 main(f64 a) { 149 | return i64.reinterpret(a); 150 | } 151 | */}, function() {/* 152 | push rbp 153 | mov rbp, rsp 154 | vmovq rax, xmm0 155 | mov rsp, rbp 156 | pop rbp 157 | ret 158 | */}); 159 | }); 160 | 161 | it('should support f64.reinterpret', function() { 162 | testAsm(function() {/* 163 | f64 main(i64 a) { 164 | return f64.reinterpret(a); 165 | } 166 | */}, function() {/* 167 | push rbp 168 | mov rbp, rsp 169 | vmovq xmm0, rsi 170 | mov rsp, rbp 171 | pop rbp 172 | ret 173 | */}); 174 | }); 175 | 176 | it('should support f64.promote', function() { 177 | testAsm(function() {/* 178 | f64 main(f32 a) { 179 | return f64.promote(a); 180 | } 181 | */}, function() {/* 182 | push rbp 183 | mov rbp, rsp 184 | vcvtss2sd xmm0, xmm0 185 | mov rsp, rbp 186 | pop rbp 187 | ret 188 | */}); 189 | }); 190 | 191 | it('should support f32.demote', function() { 192 | testAsm(function() {/* 193 | f32 main(f64 a) { 194 | return f32.demote(a); 195 | } 196 | */}, function() {/* 197 | push rbp 198 | mov rbp, rsp 199 | vcvtsd2ss xmm0, xmm0 200 | mov rsp, rbp 201 | pop rbp 202 | ret 203 | */}); 204 | }); 205 | }); 206 | -------------------------------------------------------------------------------- /test/x64/math-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var fixtures = require('../fixtures'); 3 | var testAsm = fixtures.testAsm; 4 | 5 | describe('wasm Compiler/x64/math', function() { 6 | describe('int', function() { 7 | it('should support i64.add', function() { 8 | testAsm(function() {/* 9 | i64 main(i64 a) { 10 | return i64.add(a, i64.const(0x1)); 11 | } 12 | */}, function() {/* 13 | push rbp 14 | mov rbp, rsp 15 | mov rax, 0x1 16 | add rax, rsi 17 | mov rsp, rbp 18 | pop rbp 19 | ret 20 | */}); 21 | }); 22 | 23 | it('should support i64.sub', function() { 24 | testAsm(function() {/* 25 | i64 main(i64 a) { 26 | return i64.sub(a, i64.const(0x1)); 27 | } 28 | */}, function() {/* 29 | push rbp 30 | mov rbp, rsp 31 | mov rax, 0x1 32 | mov r15, rsi 33 | sub r15, rax 34 | mov rax, r15 35 | mov rsp, rbp 36 | pop rbp 37 | ret 38 | */}); 39 | }); 40 | 41 | it('should support i64.mul', function() { 42 | testAsm(function() {/* 43 | i64 main(i64 a) { 44 | return i64.mul(a, i64.const(0x1)); 45 | } 46 | */}, function() {/* 47 | push rbp 48 | mov rbp, rsp 49 | mov rax, 0x1 50 | imul rax, rsi 51 | mov rsp, rbp 52 | pop rbp 53 | ret 54 | */}); 55 | }); 56 | 57 | it('should support i64.div_s', function() { 58 | testAsm(function() {/* 59 | i64 main(i64 a, i64 b) { 60 | return i64.div_s(a, b); 61 | } 62 | */}, function() {/* 63 | push rbp 64 | mov rbp, rsp 65 | mov rcx, rdx 66 | mov rax, rsi 67 | cwd 68 | idiv rax, rcx 69 | mov rsp, rbp 70 | pop rbp 71 | ret 72 | */}); 73 | }); 74 | 75 | it('should support i64.div_u', function() { 76 | testAsm(function() {/* 77 | i64 main(i64 a, i64 b) { 78 | return i64.div_u(a, b); 79 | } 80 | */}, function() {/* 81 | push rbp 82 | mov rbp, rsp 83 | mov rcx, rdx 84 | mov rax, rsi 85 | xor rdx, rdx 86 | div rax, rcx 87 | mov rsp, rbp 88 | pop rbp 89 | ret 90 | */}); 91 | }); 92 | 93 | it('should support i32.div_s', function() { 94 | testAsm(function() {/* 95 | i32 main(i32 a, i32 b) { 96 | return i32.div_s(a, b); 97 | } 98 | */}, function() {/* 99 | push rbp 100 | mov rbp, rsp 101 | mov rcx, rdx 102 | mov rax, rsi 103 | cwd 104 | idiv rax, ecx 105 | mov rsp, rbp 106 | pop rbp 107 | ret 108 | */}); 109 | }); 110 | 111 | it('should support i64.rem_s', function() { 112 | testAsm(function() {/* 113 | i64 main(i64 a, i64 b) { 114 | return i64.rem_s(a, b); 115 | } 116 | */}, function() {/* 117 | push rbp 118 | mov rbp, rsp 119 | mov rcx, rdx 120 | mov rax, rsi 121 | cwd 122 | idiv rax, rcx 123 | mov rax, rdx 124 | mov rsp, rbp 125 | pop rbp 126 | ret 127 | */}); 128 | }); 129 | 130 | it('should support i64.and', function() { 131 | testAsm(function() {/* 132 | i64 main(i64 a) { 133 | return i64.and(a, i64.const(0x1)); 134 | } 135 | */}, function() {/* 136 | push rbp 137 | mov rbp, rsp 138 | mov rax, 0x1 139 | and rax, rsi 140 | mov rsp, rbp 141 | pop rbp 142 | ret 143 | */}); 144 | }); 145 | 146 | it('should support i64.shl', function() { 147 | testAsm(function() {/* 148 | i64 main(i64 a) { 149 | return i64.shl(a, i64.const(0x4)); 150 | } 151 | */}, function() {/* 152 | push rbp 153 | mov rbp, rsp 154 | mov rcx, 0x4 155 | mov rax, rsi 156 | shl rax, cl 157 | mov rsp, rbp 158 | pop rbp 159 | ret 160 | */}); 161 | }); 162 | 163 | it('should support i64.shr_s', function() { 164 | testAsm(function() {/* 165 | i64 main(i64 a) { 166 | return i64.shr_s(a, i64.const(0x4)); 167 | } 168 | */}, function() {/* 169 | push rbp 170 | mov rbp, rsp 171 | mov rcx, 0x4 172 | mov rax, rsi 173 | sar rax, cl 174 | mov rsp, rbp 175 | pop rbp 176 | ret 177 | */}); 178 | }); 179 | 180 | it('should support i64.shr_u', function() { 181 | testAsm(function() {/* 182 | i64 main(i64 a) { 183 | return i64.shr_u(a, i64.const(0x4)); 184 | } 185 | */}, function() {/* 186 | push rbp 187 | mov rbp, rsp 188 | mov rcx, 0x4 189 | mov rax, rsi 190 | shr rax, cl 191 | mov rsp, rbp 192 | pop rbp 193 | ret 194 | */}); 195 | }); 196 | 197 | it('should support i32.shr_u', function() { 198 | testAsm(function() {/* 199 | i32 main(i32 a) { 200 | return i32.shr_u(a, i32.const(0x4)); 201 | } 202 | */}, function() {/* 203 | push rbp 204 | mov rbp, rsp 205 | mov rcx, 0x4 206 | mov rax, rsi 207 | shr eax, cl 208 | mov rsp, rbp 209 | pop rbp 210 | ret 211 | */}); 212 | }); 213 | 214 | it('should support i64.clz', function() { 215 | testAsm(function() {/* 216 | i64 main(i64 a) { 217 | return i64.clz(a); 218 | } 219 | */}, function() {/* 220 | push rbp 221 | mov rbp, rsp 222 | lzcnt rax, rsi 223 | mov rsp, rbp 224 | pop rbp 225 | ret 226 | */}); 227 | }); 228 | 229 | it('should support i32.clz', function() { 230 | testAsm(function() {/* 231 | i32 main(i32 a) { 232 | return i32.clz(a); 233 | } 234 | */}, function() {/* 235 | push rbp 236 | mov rbp, rsp 237 | lzcnt eax, esi 238 | mov rsp, rbp 239 | pop rbp 240 | ret 241 | */}); 242 | }); 243 | 244 | it('should support i64.ctz', function() { 245 | testAsm(function() {/* 246 | i64 main(i64 a) { 247 | return i64.ctz(a); 248 | } 249 | */}, function() {/* 250 | push rbp 251 | mov rbp, rsp 252 | tzcnt rax, rsi 253 | mov rsp, rbp 254 | pop rbp 255 | ret 256 | */}); 257 | }); 258 | 259 | it('should support i32.ctz', function() { 260 | testAsm(function() {/* 261 | i32 main(i32 a) { 262 | return i32.ctz(a); 263 | } 264 | */}, function() {/* 265 | push rbp 266 | mov rbp, rsp 267 | tzcnt eax, esi 268 | mov rsp, rbp 269 | pop rbp 270 | ret 271 | */}); 272 | }); 273 | 274 | it('should support i64.popcnt', function() { 275 | testAsm(function() {/* 276 | i64 main(i64 a) { 277 | return i64.popcnt(a); 278 | } 279 | */}, function() {/* 280 | push rbp 281 | mov rbp, rsp 282 | popcnt rax, rsi 283 | mov rsp, rbp 284 | pop rbp 285 | ret 286 | */}); 287 | }); 288 | 289 | it('should support i32.popcnt', function() { 290 | testAsm(function() {/* 291 | i32 main(i32 a) { 292 | return i32.popcnt(a); 293 | } 294 | */}, function() {/* 295 | push rbp 296 | mov rbp, rsp 297 | popcnt eax, esi 298 | mov rsp, rbp 299 | pop rbp 300 | ret 301 | */}); 302 | }); 303 | }); 304 | 305 | describe('floating point', function() { 306 | it('should support f32.add', function() { 307 | testAsm(function() {/* 308 | f32 main(f32 a) { 309 | return f32.add(a, f32.const(123.456)); 310 | } 311 | */}, function() {/* 312 | push rbp 313 | mov rbp, rsp 314 | mov r15, 0x42f6e979 315 | vmovd xmm1, r15 316 | vaddss xmm0, xmm1 317 | mov rsp, rbp 318 | pop rbp 319 | ret 320 | */}); 321 | }); 322 | 323 | it('should support f64.add', function() { 324 | testAsm(function() {/* 325 | f64 main(f64 a) { 326 | return f64.add(a, f64.const(123.456)); 327 | } 328 | */}, function() {/* 329 | push rbp 330 | mov rbp, rsp 331 | mov r15, 0x405edd2f1a9fbe77 332 | vmovq xmm1, r15 333 | vaddsd xmm0, xmm1 334 | mov rsp, rbp 335 | pop rbp 336 | ret 337 | */}); 338 | }); 339 | 340 | it('should support f32.mul', function() { 341 | testAsm(function() {/* 342 | f32 main(f32 a) { 343 | return f32.mul(a, f32.const(123.456)); 344 | } 345 | */}, function() {/* 346 | push rbp 347 | mov rbp, rsp 348 | mov r15, 0x42f6e979 349 | vmovd xmm1, r15 350 | vmulss xmm0, xmm1 351 | mov rsp, rbp 352 | pop rbp 353 | ret 354 | */}); 355 | }); 356 | 357 | it('should support f64.mul', function() { 358 | testAsm(function() {/* 359 | f64 main(f64 a) { 360 | return f64.mul(a, f64.const(123.456)); 361 | } 362 | */}, function() {/* 363 | push rbp 364 | mov rbp, rsp 365 | mov r15, 0x405edd2f1a9fbe77 366 | vmovq xmm1, r15 367 | vmulsd xmm0, xmm1 368 | mov rsp, rbp 369 | pop rbp 370 | ret 371 | */}); 372 | }); 373 | 374 | it('should support f64.sqrt', function() { 375 | testAsm(function() {/* 376 | f64 main(f64 a) { 377 | return f64.sqrt(a); 378 | } 379 | */}, function() {/* 380 | push rbp 381 | mov rbp, rsp 382 | vsqrtsd xmm0, xmm0 383 | mov rsp, rbp 384 | pop rbp 385 | ret 386 | */}); 387 | }); 388 | 389 | it('should support f32.sqrt', function() { 390 | testAsm(function() {/* 391 | f32 main(f32 a) { 392 | return f32.sqrt(a); 393 | } 394 | */}, function() {/* 395 | push rbp 396 | mov rbp, rsp 397 | vsqrtss xmm0, xmm0 398 | mov rsp, rbp 399 | pop rbp 400 | ret 401 | */}); 402 | }); 403 | 404 | it('should support f64.ceil', function() { 405 | testAsm(function() {/* 406 | f64 main(f64 a) { 407 | return f64.ceil(a); 408 | } 409 | */}, function() {/* 410 | push rbp 411 | mov rbp, rsp 412 | vroundsd xmm0, xmm0, 0x2 413 | mov rsp, rbp 414 | pop rbp 415 | ret 416 | */}); 417 | }); 418 | 419 | it('should support f64.floor', function() { 420 | testAsm(function() {/* 421 | f64 main(f64 a) { 422 | return f64.floor(a); 423 | } 424 | */}, function() {/* 425 | push rbp 426 | mov rbp, rsp 427 | vroundsd xmm0, xmm0, 0x1 428 | mov rsp, rbp 429 | pop rbp 430 | ret 431 | */}); 432 | }); 433 | 434 | it('should support f64.nearest', function() { 435 | testAsm(function() {/* 436 | f64 main(f64 a) { 437 | return f64.nearest(a); 438 | } 439 | */}, function() {/* 440 | push rbp 441 | mov rbp, rsp 442 | vroundsd xmm0, xmm0, 0x0 443 | mov rsp, rbp 444 | pop rbp 445 | ret 446 | */}); 447 | }); 448 | 449 | it('should support f64.trunc', function() { 450 | testAsm(function() {/* 451 | f64 main(f64 a) { 452 | return f64.trunc(a); 453 | } 454 | */}, function() {/* 455 | push rbp 456 | mov rbp, rsp 457 | vroundsd xmm0, xmm0, 0x3 458 | mov rsp, rbp 459 | pop rbp 460 | ret 461 | */}); 462 | }); 463 | 464 | it('should support f64.abs', function() { 465 | testAsm(function() {/* 466 | f64 main(f64 a) { 467 | return f64.abs(a); 468 | } 469 | */}, function() {/* 470 | push rbp 471 | mov rbp, rsp 472 | vpcmpeqd xmm15, xmm15 473 | vpsrlq xmm15, 0x1 474 | vandpd xmm0, xmm15 475 | mov rsp, rbp 476 | pop rbp 477 | ret 478 | */}); 479 | }); 480 | 481 | it('should support f32.abs', function() { 482 | testAsm(function() {/* 483 | f32 main(f32 a) { 484 | return f32.abs(a); 485 | } 486 | */}, function() {/* 487 | push rbp 488 | mov rbp, rsp 489 | vpcmpeqd xmm15, xmm15 490 | vpsrlq xmm15, 0x21 491 | vandpd xmm0, xmm15 492 | mov rsp, rbp 493 | pop rbp 494 | ret 495 | */}); 496 | }); 497 | 498 | it('should support f64.neg', function() { 499 | testAsm(function() {/* 500 | f64 main(f64 a) { 501 | return f64.neg(a); 502 | } 503 | */}, function() {/* 504 | push rbp 505 | mov rbp, rsp 506 | vpcmpeqd xmm15, xmm15 507 | vpsllq xmm15, 0x3f 508 | vxorpd xmm0, xmm15 509 | mov rsp, rbp 510 | pop rbp 511 | ret 512 | */}); 513 | }); 514 | 515 | it('should support f32.neg', function() { 516 | testAsm(function() {/* 517 | f32 main(f32 a) { 518 | return f32.neg(a); 519 | } 520 | */}, function() {/* 521 | push rbp 522 | mov rbp, rsp 523 | vpcmpeqd xmm15, xmm15 524 | vpsllq xmm15, 0x1f 525 | vxorpd xmm0, xmm15 526 | mov rsp, rbp 527 | pop rbp 528 | ret 529 | */}); 530 | }); 531 | 532 | it('should support f64.max', function() { 533 | testAsm(function() {/* 534 | f64 main(f64 a, f64 b) { 535 | return f64.max(a, b); 536 | } 537 | */}, function() {/* 538 | push rbp 539 | mov rbp, rsp 540 | vmaxsd xmm0, xmm1 541 | mov rsp, rbp 542 | pop rbp 543 | ret 544 | */}); 545 | }); 546 | 547 | it('should support f64.sub', function() { 548 | testAsm(function() {/* 549 | f64 main(f64 a, f64 b) { 550 | return f64.sub(a, b); 551 | } 552 | */}, function() {/* 553 | push rbp 554 | mov rbp, rsp 555 | vsubsd xmm0, xmm1 556 | mov rsp, rbp 557 | pop rbp 558 | ret 559 | */}); 560 | }); 561 | 562 | it('should support f64.div', function() { 563 | testAsm(function() {/* 564 | f64 main(f64 a, f64 b) { 565 | return f64.div(a, b); 566 | } 567 | */}, function() {/* 568 | push rbp 569 | mov rbp, rsp 570 | vdivsd xmm0, xmm1 571 | mov rsp, rbp 572 | pop rbp 573 | ret 574 | */}); 575 | }); 576 | 577 | it('should support f64.copysign', function() { 578 | testAsm(function() {/* 579 | f64 main(f64 a, f64 b) { 580 | return f64.copysign(a, b); 581 | } 582 | */}, function() {/* 583 | push rbp 584 | mov rbp, rsp 585 | vpcmpeqd xmm15, xmm15 586 | vpsrlq xmm15, 0x1 587 | vandpd xmm0, xmm15 588 | vpcmpeqd xmm15, xmm15 589 | vpsllq xmm15, 0x3f 590 | vandpd xmm15, xmm1 591 | vxorpd xmm0, xmm15 592 | mov rsp, rbp 593 | pop rbp 594 | ret 595 | */}); 596 | }); 597 | }); 598 | }); 599 | -------------------------------------------------------------------------------- /test/x64/memory-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var fixtures = require('../fixtures'); 3 | var testAsm = fixtures.testAsm; 4 | 5 | describe('wasm Compiler/x64/Memory', function() { 6 | it('should compile i64 load/store', function() { 7 | testAsm(function() {/* 8 | i64 main() { 9 | i64 t = i64.const(0); 10 | i64.store(addr.from_64(t), i64.const(0xdead)); 11 | return i64.load(addr.from_64(t)); 12 | } 13 | */}, function() {/* 14 | push rbp 15 | mov rbp, rsp 16 | 17 | mov rax, [rdi, 0x0] 18 | mov rcx, 0x0 19 | mov rdx, [rdi, 0x8] 20 | lea r15, [rcx, 0x8] 21 | cmp r15, rdx 22 | jcc le, 0x3 23 | 24 | xor rcx, rcx 25 | 26 | mov rdx, 0xdead 27 | mov [rax, rcx, 0x0], rdx 28 | mov rax, [rax, rcx, 0x0] 29 | mov rsp, rbp 30 | pop rbp 31 | ret 32 | */}); 33 | }); 34 | 35 | it('should compile i32 load/store', function() { 36 | testAsm(function() {/* 37 | i32 main() { 38 | i64 t = i64.const(0); 39 | i32.store(addr.from_64(t), i32.const(0xdead)); 40 | 41 | i32 r = i32.load(addr.from_64(t)); 42 | r = i32.add(r, i32.wrap(i64.load32_s(addr.from_64(t)))); 43 | r = i32.add(r, i32.wrap(i64.load32_u(addr.from_64(t)))); 44 | return r; 45 | } 46 | */}, function() {/* 47 | push rbp 48 | mov rbp, rsp 49 | 50 | mov rax, [rdi, 0x0] 51 | mov rcx, 0x0 52 | mov rdx, [rdi, 0x8] 53 | lea r15, [rcx, 0x4] 54 | cmp r15, rdx 55 | jcc le, 0x3 56 | xor rcx, rcx 57 | mov rdx, 0xdead 58 | mov [eax, ecx, 0x0], edx 59 | 60 | mov edx, [eax, ecx, 0x0] 61 | movsxd rsi, [rax, rcx, 0x0] 62 | mov esi, esi 63 | add rdx, rsi 64 | 65 | mov eax, [eax, ecx, 0x0] 66 | mov eax, eax 67 | add rax, rdx 68 | 69 | mov rsp, rbp 70 | pop rbp 71 | ret 72 | */}); 73 | }); 74 | 75 | it('should compile i16 load/store', function() { 76 | testAsm(function() {/* 77 | i32 main() { 78 | i64 t = i64.const(0); 79 | i32.store16(addr.from_64(t), i32.const(0xdead)); 80 | 81 | i32 r = i32.load16_s(addr.from_64(t)); 82 | r = i32.add(r, i32.load16_u(addr.from_64(t))); 83 | return r; 84 | } 85 | */}, function() {/* 86 | push rbp 87 | mov rbp, rsp 88 | 89 | mov rax, [rdi, 0x0] 90 | mov rcx, 0x0 91 | mov rdx, [rdi, 0x8] 92 | lea r15, [rcx, 0x2] 93 | cmp r15, rdx 94 | jcc le, 0x3 95 | xor rcx, rcx 96 | mov rdx, 0xdead 97 | mov [eax, ecx, 0x0], edx 98 | 99 | movsxw rdx, [rax, rcx, 0x0] 100 | movzxw rax, [rax, rcx, 0x0] 101 | 102 | add rax, rdx 103 | mov rsp, rbp 104 | pop rbp 105 | ret 106 | */}); 107 | }); 108 | 109 | it('should compile i8 load/store', function() { 110 | testAsm(function() {/* 111 | i32 main() { 112 | i64 t = i64.const(0); 113 | i32.store8(addr.from_64(t), i32.const(0xdead)); 114 | 115 | i32 r = i32.load8_s(addr.from_64(t)); 116 | r = i32.add(r, i32.load8_u(addr.from_64(t))); 117 | return r; 118 | } 119 | */}, function() {/* 120 | push rbp 121 | mov rbp, rsp 122 | 123 | mov rax, [rdi, 0x0] 124 | mov rcx, 0x0 125 | mov rdx, [rdi, 0x8] 126 | lea r15, [rcx, 0x1] 127 | cmp r15, rdx 128 | jcc le, 0x3 129 | xor rcx, rcx 130 | mov rdx, 0xdead 131 | movb [eax, ecx, 0x0], edx 132 | 133 | movsxb rdx, [rax, rcx, 0x0] 134 | movzxb rax, [rax, rcx, 0x0] 135 | 136 | add rax, rdx 137 | mov rsp, rbp 138 | pop rbp 139 | ret 140 | */}); 141 | }); 142 | 143 | it('should compile f32 load/store', function() { 144 | testAsm(function() {/* 145 | f32 main(f32 a) { 146 | i64 t = i64.const(0); 147 | f32.store(addr.from_64(t), a); 148 | return f32.load(addr.from_64(t)); 149 | } 150 | */}, function() {/* 151 | push rbp 152 | mov rbp, rsp 153 | 154 | mov rax, [rdi, 0x0] 155 | mov rcx, 0x0 156 | mov rdx, [rdi, 0x8] 157 | lea r15, [rcx, 0x4] 158 | cmp r15, rdx 159 | jcc le, 0x3 160 | xor rcx, rcx 161 | vmovd [eax, ecx, 0x0], xmm0 162 | vmovd xmm0, [eax, ecx, 0x0] 163 | mov rsp, rbp 164 | pop rbp 165 | ret 166 | */}); 167 | }); 168 | 169 | it('should compile f64 load/store', function() { 170 | testAsm(function() {/* 171 | f64 main(f64 a) { 172 | i64 t = i64.const(0); 173 | f64.store(addr.from_64(t), a); 174 | return f64.load(addr.from_64(t)); 175 | } 176 | */}, function() {/* 177 | push rbp 178 | mov rbp, rsp 179 | 180 | mov rax, [rdi, 0x0] 181 | mov rcx, 0x0 182 | mov rdx, [rdi, 0x8] 183 | lea r15, [rcx, 0x8] 184 | cmp r15, rdx 185 | jcc le, 0x3 186 | xor rcx, rcx 187 | vmovq [rax, rcx, 0x0], xmm0 188 | vmovq xmm0, [rax, rcx, 0x0] 189 | mov rsp, rbp 190 | pop rbp 191 | ret 192 | */}); 193 | }); 194 | }); 195 | --------------------------------------------------------------------------------