├── .babelrc ├── .editorconfig ├── .gitignore ├── .npmignore ├── README.md ├── package.json ├── src ├── func.js ├── index.js ├── program.js ├── tables.js ├── types.js └── util.js └── test ├── .babelrc └── fixtures ├── actual.js └── expected.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0, 3 | "loose": ["es6.forOf", "es6.classes"], 4 | "optional": ["runtime"] 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | trim_trailing_whitespace = true 6 | indent_style = tab 7 | indent_size = 4 8 | insert_final_newline = true 9 | 10 | [*.{json,babelrc,eslintrc}] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | lib 4 | /dist 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | src 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # babel-plugin-asm-js 2 | 3 | Compiles statically typed ES6 (Flow-flavoured) module into asm.js while preserving ES6 module interface. Still a subset of JavaScript, but much more suitable for hand-writing (so that you don't need to use C/C++ -> Emscripten -> asm.js for maths) 4 | 5 | Work in progress. 6 | 7 | ## Currently supported: 8 | 9 | * ES6 `import` and named `export` 10 | * automatic `var`, `let`, `const` extraction and conversion 11 | * automatic function layout 12 | * function parameter annotations 13 | * function return type annotation 14 | * assignment with automatic type conversion 15 | * Flow type cast into asm.js type cast 16 | * automatic asm.js imports for stdlib and foreign references 17 | * automatic program layout 18 | * automatic wrapping into asm.js module with `initialize` for passing own `heap` 19 | 20 | ## TODO: 21 | 22 | * bug fixing 23 | * global variable support 24 | * string support (literals are already converted to IDs, need to support operations) 25 | * better asm.js<->normal code communication 26 | * limited object and array support 27 | 28 | ## Installation 29 | 30 | ```sh 31 | $ npm install babel-plugin-asm-js 32 | ``` 33 | 34 | ## Usage 35 | 36 | ### Via `.babelrc` (Recommended) 37 | 38 | **.babelrc** 39 | 40 | ```json 41 | { 42 | "plugins": ["asm-js"] 43 | } 44 | ``` 45 | 46 | ### Via CLI 47 | 48 | ```sh 49 | $ babel --plugins asm-js script.js 50 | ``` 51 | 52 | ### Via Node API 53 | 54 | ```javascript 55 | require("babel-core").transform("code", { 56 | plugins: ["asm-js"] 57 | }); 58 | ``` 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-asm-js", 3 | "version": "1.0.0", 4 | "description": "Compile Flow annotated functions into asm.js", 5 | "repository": "babel-plugins/babel-plugin-asm-js", 6 | "license": "MIT", 7 | "main": "lib/index.js", 8 | "devDependencies": { 9 | "babel": "^5.6.14", 10 | "better-log": "^1.2.0" 11 | }, 12 | "scripts": { 13 | "build": "babel src --out-dir dist", 14 | "test": "mocha", 15 | "watch": "babel src --out-dir dist --watch" 16 | }, 17 | "keywords": [ 18 | "babel-plugin" 19 | ], 20 | "dependencies": { 21 | "asm.js": "0.0.2", 22 | "babel-runtime": "^5.6.15" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/func.js: -------------------------------------------------------------------------------- 1 | import { assert, typeError, validateType, getWrap, replaceWrap, flowToAsm, unish } from './util'; 2 | import { Fixnum, Unsigned, Double, Arrow, Overloaded, Int, Intish, Str } from './types'; 3 | import { UNOPS, BINOPS } from './tables'; 4 | 5 | const LIMIT_FIXNUM = (1 << 31) >>> 0; 6 | const LIMIT_UNSIGNED = Math.pow(2, 32); 7 | 8 | var funcVisitor = { 9 | SequenceExpression: { 10 | exit: function SequenceExpression(node, parent, scope, state) { 11 | var expressions = this.get('expressions'); 12 | this.setData('asmType', validateType( 13 | expressions[expressions.length - 1], 14 | state.type 15 | )); 16 | } 17 | }, 18 | 19 | UnaryExpression: { 20 | exit: function UnaryExpression(node, parent, scope, state) { 21 | var opTypes = UNOPS.get(node.operator).alts; 22 | var argType = this.get('argument').getData('asmType'); 23 | for (let type of opTypes) { 24 | if (argType.subtype(type.params[0])) { 25 | this.setData('asmType', type.result); 26 | this::unish(); 27 | return; 28 | } 29 | } 30 | this::typeError(`Unsupported operation: ${node.operator} ${argType}`); 31 | } 32 | }, 33 | 34 | BinaryExpression: { 35 | exit: function BinaryExpression(node, parent, scope, state) { 36 | var opTypes = BINOPS.get(node.operator).alts; 37 | var leftType = this.get('left').getData('asmType'); 38 | var rightType = this.get('right').getData('asmType'); 39 | for (let type of opTypes) { 40 | if (leftType.subtype(type.params[0]) && rightType.subtype(type.params[1])) { 41 | this.setData('asmType', type.result); 42 | this::unish(); 43 | return; 44 | } 45 | } 46 | this::typeError(`Unsupported operation: ${leftType} ${node.operator} ${rightType}`); 47 | } 48 | }, 49 | 50 | Literal(node, parent, scope, state) { 51 | var { value } = node; 52 | if (typeof value === 'string') { 53 | let id = state.program.strings.indexOf(value); 54 | if (id < 0) { 55 | id = state.program.strings.push(value) - 1; 56 | } 57 | this.setData('asmType', Str); 58 | node.value = id; 59 | return; 60 | } 61 | this::assert(typeof value === 'number', 'only numeric and string literals are supported'); 62 | if (node.raw && node.raw.indexOf('.') < 0) { 63 | if (value < LIMIT_FIXNUM) { 64 | this.setData('asmType', Fixnum); 65 | return; 66 | } 67 | if (value < LIMIT_UNSIGNED) { 68 | this.setData('asmType', Unsigned); 69 | return; 70 | } 71 | node.raw += '.0'; 72 | } 73 | this.setData('asmType', Double); 74 | }, 75 | 76 | ReferencedIdentifier(node, parent, scope, state) { 77 | var binding = scope.getBinding(node.name); 78 | if (binding) { 79 | this.setData('asmType', binding.path.getData('asmType')); 80 | } else { 81 | state.program.import(this); 82 | } 83 | }, 84 | 85 | MemberExpression(node, parent, scope, state) { 86 | if (state.program.import(this, parent.type === 'CallExpression')) { 87 | this.skip(); 88 | } 89 | }, 90 | 91 | ReturnStatement: { 92 | exit: function ReturnStatement(node, parent, scope, state) { 93 | if (state.returnType === 'void') { 94 | let arg = node.argument; 95 | if (arg === null) { 96 | return; 97 | } 98 | return [ 99 | state.t.expressionStatement(arg), 100 | node 101 | ]; 102 | } 103 | this.get('argument')::replaceWrap(state.returnType, true); 104 | } 105 | }, 106 | 107 | VariableDeclarator: { 108 | enter: function VariableDeclarator(node, parent, scope) { 109 | // Babel plugins can't depend on other transformations (yet?) 110 | // so I have to loosely reimplement block scoping like this 111 | this::assert(node.init, 'variable should have an initializer'); 112 | if (parent.kind !== 'var') { 113 | scope.rename(node.id.name); 114 | } 115 | }, 116 | exit: function VariableDeclarator(node, parent, scope, state) { 117 | var init = this.get('init'); 118 | var asmType = init.getData('asmType'); 119 | this.setData('asmType', asmType); 120 | state.vars.push(node); 121 | if (node.init.type === 'Literal' && node.init.value === 0) { 122 | return this.dangerouslyRemove(); 123 | } 124 | node.init = state.t.literal(0); 125 | if (!asmType.subtype(Intish)) { 126 | node.init.raw = '0.0'; 127 | } 128 | return state.t.assignmentExpression('=', node.id, init.node); 129 | } 130 | }, 131 | 132 | VariableDeclaration: { 133 | exit: function VariableDeclaration(node, parent, scope, state) { 134 | var expr = state.t.sequenceExpression(node.declarations); 135 | if (parent.type === 'ForStatement') { 136 | return expr; 137 | } 138 | return state.t.expressionStatement(expr); 139 | } 140 | }, 141 | 142 | AssignmentExpression: { 143 | exit: function AssignmentExpression(node, parent, scope, state) { 144 | var asmType = scope.getBinding(node.left.name).path.getData('asmType'); 145 | this.get('right')::replaceWrap(asmType); 146 | } 147 | }, 148 | 149 | CallExpression: { 150 | exit: function CallExpression(node, parent, scope, state) { 151 | var callee = this.get('callee'); 152 | callee::assert(callee.node.type === 'Identifier', 'only calls to direct identifiers are possible'); 153 | var resultType = callee.getData('asmType').result; 154 | this::replaceWrap(resultType, true)::unish(); 155 | } 156 | }, 157 | 158 | TypeCastExpression: { 159 | exit: function TypeCastExpression(node) { 160 | var asmType = this.get('typeAnnotation')::flowToAsm(); 161 | this.setData('asmType', asmType); 162 | return this.get('expression')::getWrap(asmType); 163 | } 164 | } 165 | }; 166 | 167 | class FuncState { 168 | constructor(programState, returnType) { 169 | this.t = programState.t; 170 | this.program = programState; 171 | this.returnType = returnType; 172 | this.vars = []; 173 | } 174 | } 175 | 176 | export default function visit(programState) { 177 | var paramTypes = []; 178 | var wrappedParams = this.get('params').map(param => { 179 | var asmType = param.get('typeAnnotation')::flowToAsm(); 180 | param.setData('asmType', asmType); 181 | paramTypes.push(asmType); 182 | var { node } = param; 183 | return programState.t.expressionStatement(programState.t.assignmentExpression( 184 | '=', 185 | node, 186 | param::getWrap(asmType) 187 | )); 188 | }); 189 | var returnType = this.get('returnType')::flowToAsm(); 190 | this.setData('asmType', new Arrow(paramTypes, returnType)); 191 | var funcState = new FuncState(programState, returnType); 192 | this.get('body').traverse(funcVisitor, funcState); 193 | if (funcState.vars.length) { 194 | this.get('body.body.0').insertBefore(programState.t.variableDeclaration('var', funcState.vars)); 195 | } 196 | this.get('body.body.0').insertBefore(wrappedParams); 197 | programState.funcs.push(this.node); 198 | } 199 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import 'better-log/install'; 2 | import visitProgram, { ProgramState } from './program'; 3 | 4 | module.exports = function ({ Plugin, types: t }) { 5 | function reattach(member, newRoot) { 6 | if (t.isIdentifier(member)) { 7 | return t.memberExpression(newRoot, member); 8 | } 9 | return t.memberExpression( 10 | reattach(member.object, newRoot), 11 | member.property 12 | ); 13 | } 14 | 15 | return new Plugin("asm-js", { 16 | visitor: { 17 | Program(node, parent, scope, file) { 18 | this.skip(); 19 | var directive = this.get('body.0'); 20 | if (!directive.isExpressionStatement()) return; 21 | directive = directive.get('expression'); 22 | if (!directive.isLiteral({ value: 'use asm' })) return; 23 | var state = new ProgramState(t); 24 | this::visitProgram(state); 25 | var funcBody = [t.expressionStatement(t.literal('use asm'))]; 26 | if (state.stdlibImports.size) { 27 | funcBody.push(t.variableDeclaration('var', Array.from( 28 | state.stdlibImports.values(), 29 | ({ uid, expr }) => t.variableDeclarator(uid, reattach(expr, t.identifier('stdlib'))) 30 | ))); 31 | } 32 | if (state.foreignImports.size) { 33 | funcBody.push(t.variableDeclaration('var', Array.from( 34 | state.foreignImports.values(), 35 | ({ uid }) => t.variableDeclarator(uid, t.memberExpression( 36 | t.identifier('foreign'), 37 | uid 38 | )) 39 | ))); 40 | } 41 | funcBody = funcBody.concat(state.varDecls, state.funcs, state.funcTables); 42 | funcBody.push(t.returnStatement(t.objectExpression(Array.from( 43 | state.exports.values(), 44 | ({ id }) => t.property('init', id, id) 45 | )))); 46 | var func = t.functionDeclaration( 47 | t.identifier('asm'), 48 | [t.identifier('stdlib'), t.identifier('foreign'), t.identifier('heap')], 49 | t.blockStatement(funcBody) 50 | ); 51 | this.replaceWith(t.program([ 52 | ...state.outside, 53 | t.variableDeclaration('var', [ 54 | t.variableDeclarator( 55 | t.identifier('selfGlobal'), 56 | t.callExpression(file.addHelper('self-global'), []) 57 | ), 58 | t.variableDeclarator( 59 | t.identifier('foreign'), 60 | t.objectExpression(Array.from( 61 | state.foreignImports.values(), 62 | ({ uid, outExpr }) => t.property('init', uid, outExpr) 63 | )) 64 | ), 65 | t.variableDeclarator( 66 | t.identifier('strings'), 67 | t.arrayExpression(state.strings.map(t.literal)) 68 | ) 69 | ]), 70 | t.exportNamedDeclaration(t.variableDeclaration('var', Array.from( 71 | state.exports.values(), 72 | ({ uid }) => t.variableDeclarator(uid) 73 | ))), 74 | func, 75 | t.exportDefaultDeclaration(t.functionDeclaration( 76 | t.identifier('setHeap'), 77 | [t.identifier('heap')], 78 | t.blockStatement([ 79 | t.variableDeclaration('var', [t.variableDeclarator( 80 | t.identifier('exported'), 81 | t.callExpression(t.identifier('asm'), ['selfGlobal', 'foreign', 'heap'].map(t.identifier)) 82 | )]), 83 | ...Array.from( 84 | state.exports.values(), 85 | ({ uid, id }) => t.expressionStatement(t.assignmentExpression( 86 | '=', 87 | uid, 88 | t.memberExpression(t.identifier('exported'), id) 89 | )) 90 | ), 91 | t.returnStatement(t.identifier('exported')) 92 | ]) 93 | )) 94 | ])); 95 | file.scope = this.scope; 96 | file.moduleFormatter.constructor.call(file.moduleFormatter, file); 97 | } 98 | } 99 | }); 100 | }; 101 | -------------------------------------------------------------------------------- /src/program.js: -------------------------------------------------------------------------------- 1 | import { assert, wrap, GLOBALS, typeError } from './util'; 2 | import visitFunc from './func'; 3 | import { STDLIB_MATH_TYPES } from './tables'; 4 | import { Func } from './types'; 5 | 6 | var programVisitor = { 7 | FunctionDeclaration(node, parent, scope, state) { 8 | this.skip(); 9 | this::visitFunc(state); 10 | }, 11 | 12 | ExportNamedDeclaration(node, parent, scope, state) { 13 | state.export(this); 14 | }, 15 | 16 | ExportDefaultDeclaration(node, parent, scope, state) { 17 | this::typeError('Default declaration is reserved for initializer'); 18 | }, 19 | 20 | ImportDeclaration(node, parent, scope, state) { 21 | this.skip(); 22 | state.outside.push(node); 23 | }, 24 | 25 | VariableDeclaration(node, parent, scope, state) { 26 | state.varDecls.push(node); 27 | } 28 | }; 29 | 30 | export class ProgramState { 31 | constructor(types) { 32 | this.t = types; 33 | this.outside = []; 34 | this.stdlibImports = new Map(); 35 | this.foreignImports = new Map(); 36 | this.varDecls = []; 37 | this.funcs = []; 38 | this.funcTables = []; 39 | this.exports = new Map(); 40 | this.strings = []; 41 | } 42 | 43 | import(ref, wrapFunc) { 44 | var path = [], onlyIds = true; 45 | var fullRef = ref; 46 | for (; ref.isMemberExpression(); ref = ref.get('object')) { 47 | let property = ref.get('property'); 48 | let propName; 49 | if (ref.is('computed')) { 50 | onlyIds = false; 51 | propName = property.node.value; 52 | } else { 53 | propName = property.node.name; 54 | } 55 | path.unshift(propName); 56 | } 57 | ref::assert(ref.isIdentifier(), 'root object should be an identifier'); 58 | if (ref.scope.hasBinding(ref.node.name, true)) { 59 | return; 60 | } 61 | fullRef::assert(onlyIds, 'computed properties are not allowed'); 62 | path.unshift(ref.node.name); 63 | var topPath = path[0]; 64 | var importName = path.join('$'); 65 | var expr = fullRef.node, outExpr = expr; 66 | var kind, map; 67 | if (topPath === 'Math' || GLOBALS.has(topPath)) { 68 | kind = 'stdlib'; 69 | map = this.stdlibImports; 70 | } else { 71 | kind = 'foreign'; 72 | map = this.foreignImports; 73 | if (wrapFunc) { 74 | importName += '_func'; 75 | let {t} = this; 76 | outExpr = t.functionExpression(null, [], t.blockStatement([ 77 | t.returnStatement(t.callExpression(outExpr, [t.spreadElement(t.identifier('arguments'))])) 78 | ])); 79 | } 80 | } 81 | var importRec = map.get(importName); 82 | if (!importRec) { 83 | let asmType; 84 | if (kind === 'stdlib') { 85 | if (topPath === 'Math') { 86 | ref::assert(path.length === 2, 'too long path'); 87 | asmType = STDLIB_MATH_TYPES.get(path[1]); 88 | ref::assert(asmType, 'unknown Math property'); 89 | } else { 90 | asmType = GLOBALS.get(topPath); 91 | } 92 | } else { 93 | asmType = Func; 94 | } 95 | importRec = { 96 | kind, 97 | uid: ref.scope.generateUidIdentifier(importName), 98 | expr, 99 | outExpr, 100 | type: asmType 101 | }; 102 | map.set(importName, importRec); 103 | } 104 | fullRef.replaceWith(importRec.uid); 105 | fullRef.setData('asmType', importRec.type); 106 | } 107 | 108 | export(ref) { 109 | var func = ref.get('declaration'); 110 | ref::assert(func.isFunctionDeclaration(), 'only immediate function declarations are supported'); 111 | var id = func.get('id').node; 112 | this.exports.set(id.name, { 113 | uid: ref.scope.generateUidIdentifier(id.name), 114 | id 115 | }); 116 | } 117 | } 118 | 119 | export default function visit(state) { 120 | this.traverse(programVisitor, state); 121 | } 122 | -------------------------------------------------------------------------------- /src/tables.js: -------------------------------------------------------------------------------- 1 | import { 2 | View, 3 | Intish, 4 | Double, 5 | MaybeDouble, 6 | Float, 7 | MaybeFloat, 8 | Floatish, 9 | Signed, 10 | Unsigned, 11 | Int, 12 | Overloaded, 13 | Arrow, 14 | Str 15 | } 16 | from './types'; 17 | 18 | function dict(obj) { 19 | var map = new Map(); 20 | for (let key in obj) { 21 | map.set(key, obj[key]); 22 | } 23 | return map; 24 | } 25 | 26 | export const HEAP_VIEW_TYPES = dict({ 27 | Int8Array: new View(1, Intish), 28 | Uint8Array: new View(1, Intish), 29 | Int16Array: new View(2, Intish), 30 | Uint16Array: new View(2, Intish), 31 | Int32Array: new View(4, Intish), 32 | Uint32Array: new View(4, Intish), 33 | Float32Array: new View(4, MaybeFloat, new Overloaded([Floatish, MaybeDouble])), 34 | Float64Array: new View(8, MaybeDouble, new Overloaded([MaybeFloat, MaybeDouble])) 35 | }); 36 | 37 | var MaybeDoubleToDouble = new Arrow([MaybeDouble], Double); 38 | var MaybeFloatToFloatish = new Arrow([MaybeFloat], Floatish); 39 | 40 | var LimitFloatFunc = new Overloaded([ 41 | MaybeDoubleToDouble, 42 | MaybeFloatToFloatish 43 | ]); 44 | 45 | var MaybeDoublesToDouble = new Arrow([MaybeDouble, MaybeDouble], Double); 46 | 47 | export const STDLIB_TYPES = dict({ 48 | Infinity: Double, 49 | NaN: Double 50 | }); 51 | 52 | var minmax = new Overloaded([ 53 | new Arrow([Signed, Signed], Signed), 54 | new Arrow([MaybeDouble, MaybeDouble], Double) 55 | ]); 56 | 57 | export const STDLIB_MATH_TYPES = dict({ 58 | acos: MaybeDoubleToDouble, 59 | asin: MaybeDoubleToDouble, 60 | atan: MaybeDoubleToDouble, 61 | cos: MaybeDoubleToDouble, 62 | sin: MaybeDoubleToDouble, 63 | tan: MaybeDoubleToDouble, 64 | ceil: LimitFloatFunc, 65 | floor: LimitFloatFunc, 66 | exp: MaybeDoubleToDouble, 67 | log: MaybeDoubleToDouble, 68 | sqrt: LimitFloatFunc, 69 | min: minmax, 70 | max: minmax, 71 | abs: new Overloaded([ 72 | new Arrow([Signed], Unsigned), 73 | MaybeDoubleToDouble, 74 | MaybeFloatToFloatish 75 | ]), 76 | atan2: MaybeDoublesToDouble, 77 | pow: MaybeDoublesToDouble, 78 | imul: new Arrow([Int, Int], Signed), 79 | E: Double, 80 | LN10: Double, 81 | LN2: Double, 82 | LOG2E: Double, 83 | LOG10E: Double, 84 | PI: Double, 85 | SQRT1_2: Double, 86 | SQRT2: Double 87 | }); 88 | 89 | var SignedBitwise = new Overloaded([ new Arrow([Intish, Intish], Signed) ]); 90 | 91 | var RelOp = new Overloaded([ 92 | new Arrow([Signed, Signed], Int), 93 | new Arrow([Unsigned, Unsigned], Int), 94 | new Arrow([Double, Double], Int), 95 | new Arrow([Float, Float], Int) 96 | ]); 97 | 98 | export const BINOPS = dict({ 99 | '+': new Overloaded([ 100 | new Arrow([Intish, Intish], Intish), // added to comply with 6.8.9 101 | new Arrow([MaybeDouble, MaybeDouble], Double), 102 | new Arrow([MaybeFloat, MaybeFloat], Floatish) 103 | ]), 104 | '-': new Overloaded([ 105 | new Arrow([Intish, Intish], Intish), // added to comply with 6.8.9 106 | new Arrow([MaybeDouble, MaybeDouble], Double), 107 | new Arrow([MaybeFloat, MaybeFloat], Floatish) 108 | ]), 109 | '*': new Overloaded([ 110 | new Arrow([MaybeDouble, MaybeDouble], Double), 111 | new Arrow([MaybeFloat, MaybeFloat], Floatish) 112 | ]), 113 | '/': new Overloaded([ 114 | new Arrow([Signed, Signed], Intish), 115 | new Arrow([Unsigned, Unsigned], Intish), 116 | new Arrow([MaybeDouble, MaybeDouble], Double), 117 | new Arrow([MaybeFloat, MaybeFloat], Floatish) 118 | ]), 119 | '%': new Overloaded([ 120 | new Arrow([Signed, Signed], Intish), 121 | new Arrow([Unsigned, Unsigned], Intish), 122 | new Arrow([MaybeDouble, MaybeDouble], Double) 123 | ]), 124 | '|': SignedBitwise, 125 | '&': SignedBitwise, 126 | '^': SignedBitwise, 127 | '<<': SignedBitwise, 128 | '>>': SignedBitwise, 129 | '>>>': new Overloaded([ new Arrow([Intish, Intish], Unsigned) ]), 130 | '<': RelOp, 131 | '<=': RelOp, 132 | '>': RelOp, 133 | '>=': RelOp, 134 | '==': RelOp, 135 | '!=': RelOp 136 | }); 137 | 138 | export const UNOPS = dict({ 139 | '+': new Overloaded([ 140 | new Arrow([Signed], Double), 141 | new Arrow([Unsigned], Double), 142 | new Arrow([MaybeDouble], Double), 143 | new Arrow([MaybeFloat], Double) 144 | ]), 145 | '-': new Overloaded([ 146 | new Arrow([Int], Intish), 147 | MaybeDoubleToDouble, 148 | MaybeFloatToFloatish 149 | ]), 150 | '~': new Overloaded([ new Arrow([Intish], Signed) ]), 151 | '!': new Overloaded([ new Arrow([Int], Int) ]) 152 | }); 153 | -------------------------------------------------------------------------------- /src/types.js: -------------------------------------------------------------------------------- 1 | class Type { 2 | equals(other) { 3 | return this === other; 4 | } 5 | 6 | subtype(other) { 7 | return this === other; 8 | } 9 | } 10 | 11 | // ============================================================================= 12 | // Value Types 13 | // ============================================================================= 14 | 15 | export class ValueType extends Type { 16 | constructor(name, supertypes = []) { 17 | super(); 18 | this.name = name; 19 | this.supertypes = new Set([this]); 20 | for (let directSuper of supertypes) { 21 | for (let type of directSuper.supertypes) { 22 | this.supertypes.add(type); 23 | } 24 | } 25 | } 26 | 27 | subtype(other) { 28 | return this.supertypes.has(other); 29 | } 30 | 31 | toString() { 32 | return this.name; 33 | } 34 | } 35 | 36 | export const Floatish = new ValueType('floatish'); 37 | 38 | export const MaybeFloat = new ValueType('float?', [Floatish]); 39 | 40 | export const Float = new ValueType('float', [MaybeFloat]); 41 | 42 | export const Intish = new ValueType('intish'); 43 | 44 | export const Int = new ValueType('int', [Intish]); 45 | 46 | export const Extern = new ValueType('extern'); 47 | 48 | export const Signed = new ValueType('signed', [Extern, Int]); 49 | 50 | export const Unsigned = new ValueType('unsigned', [Extern, Int]); 51 | 52 | export const MaybeDouble = new ValueType('double?'); 53 | 54 | export const Double = new ValueType('double', [Extern, MaybeDouble]); 55 | 56 | export const Fixnum = new ValueType('fixnum', [Signed, Unsigned]); 57 | 58 | export const Void = new ValueType('void'); 59 | 60 | export const Str = new ValueType('string', [Extern]); 61 | 62 | // ============================================================================= 63 | // Global Types 64 | // ============================================================================= 65 | 66 | // ([ValueType], ValueType) -> Arrow 67 | export class Arrow extends Type { 68 | constructor(params, result) { 69 | super(); 70 | this.params = params; 71 | this.result = result; 72 | } 73 | 74 | equals(other) { 75 | return other instanceof Arrow && 76 | this.params.length === other.params.length && 77 | this.params.every((p, i) => p.equals(other.params[i])) && 78 | this.result.equals(other.result); 79 | } 80 | 81 | toString() { 82 | return `(${this.params.join(', ')}) => ${this.result}`; 83 | } 84 | } 85 | 86 | // ([Arrow]) -> Overloaded 87 | export class Overloaded extends Type { 88 | constructor(alts) { 89 | super(); 90 | this.alts = alts; 91 | } 92 | 93 | toString() { 94 | return this.alts.join(' | '); 95 | } 96 | } 97 | 98 | // (1|2|4|8, ValueType) -> View 99 | export class View extends Type { 100 | constructor(bytes, loadType, storeType = loadType) { 101 | super(); 102 | this.bytes = bytes; 103 | this.loadType = loadType; 104 | this.storeType = storeType; 105 | } 106 | 107 | toString() { 108 | var {loadType, storeType} = this; 109 | var ext = loadType === storeType ? loadType : `load: ${loadType}, store: ${storeType}`; 110 | return `View<${this.bytes}, ext)>`; 111 | } 112 | } 113 | 114 | // (Arrow, integer) -> Table 115 | export class Table extends Type { 116 | constructor(type, length) { 117 | super(); 118 | this.type = type; 119 | this.length = length; 120 | } 121 | 122 | toString() { 123 | return `(${this.type})[${this.length}]`; 124 | } 125 | } 126 | 127 | class ExternFunc extends Type { 128 | toString() { 129 | return 'Function'; 130 | } 131 | } 132 | 133 | ExternFunc.prototype.result = Extern; 134 | 135 | export const Func = new ExternFunc(); 136 | 137 | export class Module extends Type {} 138 | 139 | export class ModuleParameter extends Type {} 140 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | import { STDLIB_TYPES, HEAP_VIEW_TYPES } from './tables'; 2 | import { Int, Double, Float, Extern, Str, Intish, Floatish } from './types'; 3 | 4 | export const GLOBALS = new Map([...STDLIB_TYPES.entries(), ...HEAP_VIEW_TYPES.entries()]); 5 | 6 | export function typeError(msg) { 7 | var e = this.errorWithNode(msg, TypeError); 8 | Error.captureStackTrace(e, typeError); 9 | throw e; 10 | } 11 | 12 | export function assert(cond, msg) { 13 | if (!cond) { 14 | this::typeError(`Assertion failed: ${msg}`); 15 | } 16 | } 17 | 18 | const flowToAsmMappings = { 19 | int: Int, 20 | int32: Int, 21 | double: Double, 22 | float64: Double, 23 | float: Float, 24 | float32: Float 25 | }; 26 | 27 | export function flowToAsm() { 28 | var annotation = this.get('typeAnnotation'); 29 | if (annotation.type === 'StringTypeAnnotation') { 30 | return Str; 31 | } 32 | this::assert(annotation.type === 'GenericTypeAnnotation', 'only generic type annotations are accepted'); 33 | var type = annotation.node.id.name; 34 | this::assert(type in flowToAsmMappings, `unknown type ${type}`); 35 | return flowToAsmMappings[type]; 36 | } 37 | 38 | export function getWrap(type) { 39 | var {node} = this; 40 | if (type.subtype(Int)) { 41 | return node._wrapFor === Int ? node : { 42 | type: 'BinaryExpression', 43 | left: node, 44 | operator: '|', 45 | right: { type: 'Literal', value: 0 }, 46 | _wrapFor: Int 47 | }; 48 | } 49 | if (type.subtype(Double)) { 50 | return node._wrapFor === Double ? node : { 51 | type: 'UnaryExpression', 52 | operator: '+', 53 | argument: node, 54 | _wrapFor: Double 55 | }; 56 | } 57 | if (type.subtype(Float)) { 58 | return node._wrapFor === Float ? node : { 59 | type: 'CallExpression', 60 | callee: { 61 | type: 'MemberExpression', 62 | object: { type: 'Identifier', name: 'Math' }, 63 | property: { type: 'Identifier', name: 'fround' } 64 | }, 65 | arguments: [node], 66 | _wrapFor: Float 67 | }; 68 | } 69 | if (type.subtype(Extern)) { 70 | return node; 71 | } 72 | this::typeError(`cannot wrap into type ${type}`); 73 | } 74 | 75 | export function replaceWrap(type, force) { 76 | if (!force) { 77 | let asmType = this.getData('asmType'); 78 | if (asmType && asmType.subtype(type)) { 79 | return this; 80 | } 81 | } 82 | this.replaceWith(this::getWrap(type)); 83 | this.setData('asmType', type); 84 | return this; 85 | } 86 | 87 | export function unish() { 88 | var asmType = this.getData('asmType'); 89 | if (asmType.equals(Intish)) { 90 | return this::replaceWrap(Int); 91 | } 92 | if (asmType.equals(Floatish)) { 93 | return this::replaceWrap(Float); 94 | } 95 | return this; 96 | } 97 | 98 | export function validateType(path, expectedType) { 99 | var type = path.getData('asmType'); 100 | if (expectedType !== undefined) { 101 | path::assert(type.subtype(expectedType), `expected ${expectedType} but got ${type}`); 102 | } 103 | return type; 104 | } 105 | -------------------------------------------------------------------------------- /test/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "loose": ["es6.forOf", "es6.classes"], 3 | "optional": ["runtime", "es7.functionBind"], 4 | "plugins": ["../dist/index"] 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/actual.js: -------------------------------------------------------------------------------- 1 | // directive 2 | "use asm"; 3 | 4 | type int = number; 5 | type double = number; 6 | type float = number; 7 | 8 | import stuff from './stuff'; 9 | 10 | var values = new Float64Array(heap); 11 | 12 | function logSum(start: int, end: int): double { 13 | var sum = 0.0; 14 | 15 | // asm.js forces byte addressing of the heap by requiring shifting by 3 16 | for (let p = start << 3, q = end << 3; p < q; p = p + 8) { 17 | sum = sum + Math.log(values[p>>3]); 18 | } 19 | 20 | return sum; 21 | } 22 | 23 | export function geometricMean(start: int, end: int): double { 24 | console.log(start, end); 25 | return Math.exp(logSum(start, end) / (end - start: double)); 26 | } 27 | -------------------------------------------------------------------------------- /test/fixtures/expected.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _selfGlobal = require("babel-runtime/helpers/self-global")["default"]; 4 | 5 | var _interopRequireDefault = require("babel-runtime/helpers/interop-require-default")["default"]; 6 | 7 | Object.defineProperty(exports, "__esModule", { 8 | value: true 9 | }); 10 | exports["default"] = setHeap; 11 | 12 | var _stuff = require("./stuff"); 13 | 14 | var _stuff2 = _interopRequireDefault(_stuff); 15 | 16 | var selfGlobal = _selfGlobal(), 17 | foreign = { 18 | _console$log_func: function _console$log_func() { 19 | return console.log.apply(console, arguments); 20 | } 21 | }, 22 | strings = []; 23 | 24 | var _geometricMean; 25 | 26 | exports._geometricMean = _geometricMean; 27 | 28 | function asm(stdlib, foreign, heap) { 29 | "use asm"; 30 | var _Math$log = stdlib.Math.log, 31 | _Math$exp = stdlib.Math.exp; 32 | var _console$log_func = foreign._console$log_func; 33 | 34 | var values = new Float64Array(heap); 35 | 36 | function logSum(start, end) { 37 | start = start | 0; 38 | end = end | 0; 39 | var sum = 0.0, 40 | _p = 0, 41 | _q = 0; 42 | 43 | // asm.js forces byte addressing of the heap by requiring shifting by 3 44 | for (_p = start << 3, _q = end << 3; _p < _q; _p = _p + 8 | 0) { 45 | sum = sum + +_Math$log(values[_p >> 3]); 46 | } 47 | 48 | return +sum; 49 | } 50 | 51 | function geometricMean(start, end) { 52 | start = start | 0; 53 | end = end | 0; 54 | 55 | _console$log_func(start, end); 56 | return +_Math$exp(+logSum(start, end) / +(end - start | 0)); 57 | } 58 | return { 59 | geometricMean: geometricMean 60 | }; 61 | } 62 | 63 | function setHeap(heap) { 64 | var exported = asm(selfGlobal, foreign, heap); 65 | exports._geometricMean = _geometricMean = exported.geometricMean; 66 | return exported; 67 | } 68 | 69 | // directive 70 | --------------------------------------------------------------------------------