├── .gitignore ├── lib ├── ccall.js ├── simple-stack.js ├── simple-logger.js ├── env.js ├── simple-heap.js └── types.js ├── index.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | 4 | -------------------------------------------------------------------------------- /lib/ccall.js: -------------------------------------------------------------------------------- 1 | /* eslint-env es6 */ 2 | 3 | // Unroll C++ mappings into easily callable functions 4 | module.exports = function(instance) { 5 | if (!instance || !instance.exports) { 6 | return {}; 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /lib/simple-stack.js: -------------------------------------------------------------------------------- 1 | /* eslint-env es6 */ 2 | module.exports = function(limit = 1024) { 3 | const STACKTOP = 0; 4 | const STACK_MAX = limit; 5 | const abortStackOverflow = function() { throw `WASM Stack Overflow, MAX: ${STACK_MAX}`; }; 6 | return { 7 | STACKTOP, 8 | STACK_MAX, 9 | abortStackOverflow 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export const logger = require('./lib/simple-logger'); 2 | export const heap = require('./lib/simple-heap'); 3 | export const stack = require('./lib/simple-stack'); 4 | export const types = require('./lib/types'); 5 | export const env = require('./lib/env'); 6 | 7 | export default { 8 | logger, 9 | heap, 10 | stack, 11 | types, 12 | env 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wasm-utils", 3 | "version": "0.0.1", 4 | "description": "Collection of JS WASM utility modules", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npm test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/ballercat/wasm-utils.git" 12 | }, 13 | "keywords": [ 14 | "wasm", 15 | "WebAssembly", 16 | "JavaScript", 17 | "JS" 18 | ], 19 | "author": "Arthur Buldauskas", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/ballercat/wasm-utils/issues" 23 | }, 24 | "homepage": "https://github.com/ballercat/wasm-utils#readme" 25 | } 26 | -------------------------------------------------------------------------------- /lib/simple-logger.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(memory) { 3 | if (!WebAssembly || !WebAssembly.Memory) { 4 | throw 'Logger cannot be crated, window.WebAssembly is missing or not supported (Must contain Memory)'; 5 | } 6 | if (!(memory instanceof WebAssembly.Memory)) { 7 | throw 'Logger must be initialized with a Memory instance'; 8 | } 9 | 10 | return { 11 | logString: function(pointer) { 12 | var buffer = new Uint8Array(memory.buffer); 13 | var str = new TextDecoder('utf8').decode(buffer.slice(pointer, buffer.indexOf(0, pointer))); 14 | console.log(str); 15 | }, 16 | 17 | logNumeric: function(value) { 18 | console.log(value); 19 | } 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /lib/env.js: -------------------------------------------------------------------------------- 1 | /* eslint-env es6 */ 2 | const heap = require('./simple-heap'); 3 | const stack = require('./simple-stack'); 4 | 5 | module.exports = function(options = {}) { 6 | if (!('memory' in options)) { 7 | options.memory = new WebAssembly.Memory({initial: 1, maximum: 256}); 8 | } 9 | 10 | if (!('table' in options)) { 11 | options.table = new WebAssembly.Table({initial: 0, element: 'anyfunc', maximum: 0}); 12 | } 13 | 14 | const { memory, table } = options; 15 | const { malloc, free } = heap(memory); 16 | const simpleStack = stack(); 17 | 18 | return Object.assign({ 19 | STACKTOP: simpleStack.STACKTOP, 20 | STACK_MAX: simpleStack.STACK_MAX, 21 | abortStackOverflow: simpleStack.abortStackOverflow, 22 | memoryBase: 0, 23 | tableBase: 0, 24 | __Znwj: malloc, 25 | __ZdlPv: free, 26 | malloc, 27 | free 28 | }, options); 29 | }; 30 | -------------------------------------------------------------------------------- /lib/simple-heap.js: -------------------------------------------------------------------------------- 1 | /* eslint-env es6 */ 2 | module.exports = function(memory) { 3 | let heap = new Uint32Array(memory.buffer); 4 | 5 | const PAGE_SIZE = (64 * 1024) >> 2; 6 | 7 | const brk = () => heap.length; 8 | 9 | const blocks = []; 10 | 11 | const findBlock = (size) => { 12 | for(let i = 0; i < blocks.length; i++) { 13 | let blockSize = heap[blocks[i]]; 14 | if (blockSize >= size) { 15 | return blocks[i]; 16 | } 17 | } 18 | 19 | return 0; 20 | }; 21 | 22 | const expand = () => { 23 | const oldBreak = brk(); 24 | memory.grow(1); 25 | heap = new Uint32Array(memory.buffer); 26 | heap[oldBreak] = PAGE_SIZE; 27 | blocks.push(oldBreak); 28 | return oldBreak; 29 | } 30 | 31 | /** 32 | * 33 | * @param {Number} size Word aligned address 34 | */ 35 | const malloc = size => { 36 | const freeBlock = findBlock(size) || expand(); 37 | const blockSize = heap[freeBlock]; 38 | if (blockSize > size) { 39 | // pad with a 32bit word for block size 40 | const split = freeBlock + 1 + size; 41 | heap[split] = blockSize - size; 42 | blocks.push(split); 43 | } 44 | 45 | // Remove freeBlock address from freeBlocks 46 | blocks.splice(blocks.indexOf(freeBlock), 1); 47 | 48 | // offset with one 32bit word for block header 49 | return (freeBlock + 1) << 2; 50 | }; 51 | 52 | const free = address => { 53 | blocks.unshift((address >> 2) - 1); 54 | } 55 | 56 | return { 57 | malloc, 58 | free 59 | }; 60 | } 61 | 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WASM JavaScript Utils 2 | 3 | A tiny modular collection of JavaScript Utils for use with WASM Modules. 4 | 5 | ### Deps to use with a wasm module Instance: 6 | 7 | * `logger` - Logs strings from WASM code. 8 | * `stack` - Stack 9 | * `heap` - JavaScript implementation of malloc, free to pass into wasm. 10 | * `types` - Helper methods for seamless mapping of JS object to WASM memory 11 | 12 | 13 | #### Types 14 | 15 | The types module provides a method of mapping a JavaScript object to a section of WASM memory. Allowing you to set and get values from a memory buffer seamlessly. 16 | 17 | * `types.define(typedef) -> struct(DataView)` 18 | - Param: `typedef` - Object where keys are mapped to low level C-like typedefs 19 | - Returns: `struct(DataView)` - A new typedef which can be attached to a generic DataView object. 20 | + `struct.size` - Size of the defined struct in bytes. 21 | 22 | - Example: 23 | 24 | Assuming you already have a wasm module setup: 25 | 26 | ```javascript 27 | // let's say you have a wasm function which creates a new pointer to C struct 28 | // This struct contains { float x; float y; float z; } 29 | const ptr = wasmExports.createVec3(-1, -1, -1); 30 | 31 | // You can create a new Object in JavaScript to access the data pointed to by the pointer 32 | const vec3Struct = types.define({ 33 | x: types.f32, 34 | y: types.f32, 35 | z: types.f32 36 | }); 37 | 38 | // vec3Struct can now map to a 12 continues bytes in our memory buffer 39 | console.log(vec3Struct.size); // 12 40 | 41 | // Create a DataView to access the data within wasmMemory.buffer 42 | const vec3Dataview = new DataView(wasMemory.buffer, ptr, vec3Struct.size); 43 | 44 | const myVector = vec3Struct(vec3DataView); 45 | 46 | console.log(myVector.x); // -1 47 | console.log(myVector.y); // -1 48 | console.log(myVector.z); // -1 49 | 50 | // WASM changes will reflect in our JS struct 51 | wasmExports.setVec3X(ptr, 128); 52 | 53 | console.log(myVector.x); // 128 54 | 55 | // ... and vice-versa 56 | myVector.y = 100; 57 | 58 | wasmExports.logVec3Y(ptr); // 100 59 | ``` 60 | *structs can be nested*: 61 | 62 | ```javascript 63 | const objectStruct = types.define({ 64 | value: types.u32, // unsigned 32 bit integer 65 | position: vec3Struct 66 | }); 67 | 68 | console.log(objectStruct.size); // 16 69 | 70 | // Let's say this wasm function returns a pointer to object with position set to 100, 100, 0 71 | const objPtr = wasmExports.createObject(); 72 | 73 | // ... we create the dataView etc., 74 | 75 | const myObject = objectStruct(objectDataView); 76 | 77 | console.log(myObject.position.x); // 100 78 | console.log(myObject.position.y); // 100 79 | console.log(myObject.position.z); // 0 80 | ``` 81 | 82 | -------------------------------------------------------------------------------- /lib/types.js: -------------------------------------------------------------------------------- 1 | /* eslint-env es6 */ 2 | /** 3 | * WASM types 4 | * 5 | * @author Arthur Buldauskas 6 | * 7 | * @license MIT 8 | */ 9 | 10 | const i32 = 1; 11 | const i64 = 1 << 1; 12 | const f32 = 1 << 2; 13 | const f64 = 1 << 3; 14 | const anyfunc = 1 << 4; 15 | const func = 1 << 5; 16 | const block_type = 1 << 6; 17 | 18 | // C type mappings 19 | const i8 = 1 << 7; 20 | const u8 = 1 << 8; 21 | const i16 = 1 << 9; 22 | const u16 = 1 << 10; 23 | const u32 = 1 << 11; 24 | const u64 = 1 << 12; 25 | 26 | const word = 4; 27 | 28 | const sizeof = { 29 | [i32]: word, 30 | [i64]: word * 2, 31 | [f32]: word, 32 | [f64]: word * 2, 33 | [u32]: word, 34 | [u16]: word >> 1, 35 | [u8]: word >> 2, 36 | [i8]: word >> 2, 37 | [i16]: word >> 1, 38 | [anyfunc]: word, 39 | [func]: word, 40 | [block_type]: word 41 | }; 42 | 43 | // TODO: Make this configurable. 44 | const LITTLE_ENDIAN = true; 45 | 46 | const getter = (type, index, dataView) => function() { 47 | switch (type) { 48 | case i32: return dataView.getInt32(index, LITTLE_ENDIAN); 49 | case i64: return dataView.getInt64(index, LITTLE_ENDIAN); 50 | case f32: return dataView.getFloat32(index, LITTLE_ENDIAN); 51 | case f64: return dataView.getFloat64(index, LITTLE_ENDIAN); 52 | case anyfunc: return dataView.getUint32(index, LITTLE_ENDIAN); 53 | case func: return dataView.getUint32(index, LITTLE_ENDIAN); 54 | case i8: return dataView.getInt8(index, LITTLE_ENDIAN); 55 | case u8: return dataView.getUint8(index, LITTLE_ENDIAN); 56 | case i16: return dataView.getInt16(index, LITTLE_ENDIAN); 57 | case u16: return dataView.getUint16(index, LITTLE_ENDIAN); 58 | case u32: return dataView.getUint32(index, LITTLE_ENDIAN); 59 | case u64: return dataView.getUint64(index, LITTLE_ENDIAN); 60 | default: 61 | return dataView.getUint8(index, LITTLE_ENDIAN); 62 | }; 63 | }; 64 | 65 | const setter = (type, index, dataView) => (value) => { 66 | switch (type) { 67 | case i32: return dataView.setInt32(index, value, LITTLE_ENDIAN); 68 | case i64: return dataView.setInt64(index, value, LITTLE_ENDIAN); 69 | case f32: return dataView.setFloat32(index, value, LITTLE_ENDIAN); 70 | case f64: return dataView.setFloat64(index, value, LITTLE_ENDIAN); 71 | case anyfunc: return dataView.setUint32(index, value, LITTLE_ENDIAN); 72 | case func: return dataView.setUint32(index, value, LITTLE_ENDIAN); 73 | case i8: return dataView.setInt8(index, value, LITTLE_ENDIAN); 74 | case u8: return dataView.setUint8(index, value, LITTLE_ENDIAN); 75 | case i16: return dataView.setInt16(index, value, LITTLE_ENDIAN); 76 | case u16: return dataView.setUint16(index, value, LITTLE_ENDIAN); 77 | case u32: return dataView.setUint32(index, value, LITTLE_ENDIAN); 78 | case u64: return dataView.setUint64(index, value, LITTLE_ENDIAN); 79 | default: 80 | return dataView.setUint8(index, value, LITTLE_ENDIAN); 81 | }; 82 | } 83 | 84 | /** 85 | * 86 | */ 87 | const define = (types) => { 88 | 89 | const struct = dataView => { 90 | const object = {}; 91 | let offset = 0; 92 | Object.keys(types).map(type => { 93 | const pointer = typeof types[type] === 'function'; 94 | const instance = pointer ? types[type](new DataView(dataView.buffer, dataView.byteOffset + offset, types[type].size)) : null; 95 | Object.defineProperty(object, type, { 96 | get: (pointer) ? () => instance : getter(types[type], offset, dataView), 97 | set: setter(types[type], offset, dataView) 98 | }); 99 | 100 | offset = offset + (pointer ? types[type].size : sizeof[types[type]]); 101 | }); 102 | 103 | return object; 104 | }; 105 | 106 | let size = 0; 107 | Object.keys(types).map(type => { 108 | if (typeof types[type] === 'function') { 109 | size += types[type].size | 0; 110 | } else { 111 | size += sizeof[types[type]] | 0; 112 | } 113 | }); 114 | 115 | struct.size = size; 116 | return struct; 117 | }; 118 | 119 | module.exports = { 120 | define, 121 | i32, 122 | i64, 123 | f32, 124 | f64, 125 | anyfunc, 126 | func, 127 | block_type, 128 | i8, 129 | u8, 130 | i16, 131 | u16, 132 | u32, 133 | u64 134 | } 135 | 136 | --------------------------------------------------------------------------------