-
-
Save wellcaffeinated/5399067 to your computer and use it in GitHub Desktop.
| /** | |
| * asm-helpers.js | |
| * A simple helper module for managing memory allocation of | |
| * collections of objects, particularly for use in asm.js code | |
| * | |
| * Copyright (c) 2013 Jasper Palfree <[email protected]> | |
| * Licensed MIT | |
| */ | |
| (function (root, factory) { | |
| if (typeof exports === 'object') { | |
| // Node. | |
| module.exports = factory(); | |
| } else if (typeof define === 'function' && define.amd) { | |
| // AMD. Register as an anonymous module. | |
| define(factory); | |
| } else { | |
| // Browser globals (root is window) | |
| root.ASMHelpers = factory(); | |
| } | |
| }(this, function () { | |
| 'use strict'; | |
| var ASMHelpers = {}; | |
| ASMHelpers.Types = { | |
| 'bool' : { // uint8 | |
| size: Uint8Array.BYTES_PER_ELEMENT, | |
| pow: Math.ceil(Math.log(Uint8Array.BYTES_PER_ELEMENT)/Math.LN2), | |
| view: Uint8Array | |
| }, | |
| 'uint8' : { | |
| size: Uint8Array.BYTES_PER_ELEMENT, | |
| pow: Math.ceil(Math.log(Uint8Array.BYTES_PER_ELEMENT)/Math.LN2), | |
| view: Uint8Array | |
| }, | |
| 'int8' : { | |
| size: Int8Array.BYTES_PER_ELEMENT, | |
| pow: Math.ceil(Math.log(Int8Array.BYTES_PER_ELEMENT)/Math.LN2), | |
| view: Int8Array | |
| }, | |
| 'uint16' : { | |
| size: Int16Array.BYTES_PER_ELEMENT, | |
| pow: Math.ceil(Math.log(Int16Array.BYTES_PER_ELEMENT)/Math.LN2), | |
| view: Int16Array | |
| }, | |
| 'int16' : { | |
| size: Int16Array.BYTES_PER_ELEMENT, | |
| pow: Math.ceil(Math.log(Int16Array.BYTES_PER_ELEMENT)/Math.LN2), | |
| view: Int16Array | |
| }, | |
| 'uint32' : { | |
| size: Int32Array.BYTES_PER_ELEMENT, | |
| pow: Math.ceil(Math.log(Int32Array.BYTES_PER_ELEMENT)/Math.LN2), | |
| view: Int32Array | |
| }, | |
| 'uint' : { // uint32 | |
| size: Uint32Array.BYTES_PER_ELEMENT, | |
| pow: Math.ceil(Math.log(Uint32Array.BYTES_PER_ELEMENT)/Math.LN2), | |
| view: Uint32Array | |
| }, | |
| 'int32' : { | |
| size: Int32Array.BYTES_PER_ELEMENT, | |
| pow: Math.ceil(Math.log(Int32Array.BYTES_PER_ELEMENT)/Math.LN2), | |
| view: Int32Array | |
| }, | |
| 'int' : { // int32 | |
| size: Int32Array.BYTES_PER_ELEMENT, | |
| pow: Math.ceil(Math.log(Int32Array.BYTES_PER_ELEMENT)/Math.LN2), | |
| view: Int32Array | |
| }, | |
| 'float32': { | |
| size: Float32Array.BYTES_PER_ELEMENT, | |
| pow: Math.ceil(Math.log(Float32Array.BYTES_PER_ELEMENT)/Math.LN2), | |
| view: Float32Array | |
| }, | |
| 'float' : { // float32 | |
| size: Float32Array.BYTES_PER_ELEMENT, | |
| pow: Math.ceil(Math.log(Float32Array.BYTES_PER_ELEMENT)/Math.LN2), | |
| view: Float32Array | |
| }, | |
| 'float64': { | |
| size: Float64Array.BYTES_PER_ELEMENT, | |
| pow: Math.ceil(Math.log(Float64Array.BYTES_PER_ELEMENT)/Math.LN2), | |
| view: Float64Array | |
| }, | |
| 'double' : { // float64 | |
| size: Float64Array.BYTES_PER_ELEMENT, | |
| pow: Math.ceil(Math.log(Float64Array.BYTES_PER_ELEMENT)/Math.LN2), | |
| view: Float64Array | |
| }, | |
| }; | |
| var Struct = function Struct( data, ptr, schema, buffer ){ | |
| var key | |
| ,props | |
| ; | |
| this.setPtr( ptr ); | |
| this.buffer = buffer; | |
| // setup getters/setters | |
| for (key in schema){ | |
| props = schema[ key ]; | |
| this.addProp( key, props.ptr, props.type ); | |
| } | |
| // set data | |
| for (key in data){ | |
| this[ key ] = data[ key ]; | |
| } | |
| }; | |
| Struct.prototype = { | |
| setPtr: function( ptr ){ | |
| this.ptr = ptr; | |
| }, | |
| getPtr: function(){ | |
| return this.ptr; | |
| }, | |
| addProp: function( name, ptr, type ){ | |
| var self = this | |
| ,typeProps = ASMHelpers.Types[ type ] | |
| ,viewInst = new typeProps.view( self.buffer ) | |
| ,pow = typeProps.pow | |
| ; | |
| self.__defineGetter__(name, function(){ | |
| return viewInst[ (self.ptr + ptr) >> pow ]; | |
| }); | |
| self.__defineSetter__(name, function( val ){ | |
| return viewInst[ (self.ptr + ptr) >> pow ] = val; | |
| }); | |
| } | |
| }; | |
| var Collection = function Collection( schema, options ){ | |
| if (!(this instanceof Collection)){ | |
| return new Collection( schema, options ); | |
| } | |
| options = options || {}; | |
| var key | |
| ,type | |
| ,size | |
| ,objSize = 0 | |
| ,tmp | |
| ,idx | |
| ,bufferSize | |
| ,largest = 0 | |
| ,sc = {} | |
| ,table = [ {},{},{},{} ] // four hashes to guide the order of memory allocation | |
| ,maxObjects = options.maxObjects || 1000 | |
| ; | |
| for (key in schema){ | |
| type = schema[ key ]; | |
| if (typeof type === 'string'){ | |
| type = type.toLowerCase(); | |
| tmp = ASMHelpers.Types[ type ]; | |
| size = tmp.size; | |
| if (!size){ | |
| throw 'Type ' + type + ' not supported.'; | |
| } | |
| // assemble total object size | |
| objSize += size; | |
| largest = ( size > largest ) ? size : largest; | |
| idx = tmp.pow; | |
| table[ idx ][ key ] = type; | |
| } | |
| } | |
| // round up to the nearest multiple of the largest object size | |
| objSize = Math.ceil(objSize / largest) * largest; | |
| this.objSize = objSize; | |
| this.blockSize = largest; | |
| // need the largest power of 2 greater than required size | |
| bufferSize = 1 << Math.ceil(Math.log(objSize * maxObjects)/Math.LN2); | |
| this.buffer = new ArrayBuffer( bufferSize ); | |
| tmp = 0; | |
| for ( var i = table.length - 1; i >= 0; i-- ){ | |
| for (key in table[ i ]){ | |
| !function(key, type, ptr){ | |
| var params = ASMHelpers.Types[ type ]; | |
| sc[ key ] = { | |
| ptr: ptr, | |
| pow: params.pow, | |
| size: params.size, | |
| type: type | |
| }; | |
| tmp += params.size; | |
| }(key, table[ i ][ key ], tmp); | |
| } | |
| } | |
| this._schema = sc; | |
| this.objs = []; | |
| }; | |
| Collection.prototype = { | |
| /** | |
| * Add a member | |
| * @param {Object} obj Values to set for the object | |
| */ | |
| add: function( obj ){ | |
| var ptr = this.objSize * this.objs.length | |
| ,inst = new Struct( obj, ptr, this._schema, this.buffer ) | |
| ; | |
| this.objs.push( inst ); | |
| }, | |
| /** | |
| * Remove one or more members | |
| * @param {Number} idx Start index | |
| * @param {Number} count (optional) Number of objects to remove beginning at idx | |
| * @return {void} | |
| */ | |
| remove: function( idx, count ){ | |
| idx = idx|0; | |
| count = count|0 || 1; | |
| if (idx > this.objs.length){ | |
| return; | |
| } | |
| // start the view at the selected index | |
| var objs = this.objs | |
| ,obj | |
| ,size = this.objSize | |
| ,view = new Int8Array( this.buffer, idx * size ) | |
| ; | |
| // sub array at one object later | |
| // set the current view to the sub array minus that "first" object | |
| view.set( view.subarray( count * size ) ); | |
| // repoint objects | |
| for ( var i = idx + count, l = objs.length; i < l; ++i ){ | |
| obj = objs[ i ]; | |
| obj.setPtr( obj.getPtr() - count * size ); | |
| } | |
| // remove the struct | |
| objs.splice( idx, count ); | |
| }, | |
| /** | |
| * Get struct at index | |
| * @return {Struct} | |
| */ | |
| at: function( idx ){ | |
| return this.objs[ idx ]; | |
| }, | |
| /** | |
| * Get number of members | |
| * @return {Number} | |
| */ | |
| count: function(){ | |
| return this.objs.length; | |
| }, | |
| /** | |
| * Remove all members | |
| * @return {void} | |
| */ | |
| clear: function(){ | |
| delete this.objs; | |
| this.objs = []; | |
| }, | |
| /** | |
| * Execute a function for each member | |
| * @param {Function} fn | |
| * @return {void} | |
| */ | |
| each: function( fn ){ | |
| var objs = this.objs; | |
| for ( var i = 0, l = objs.length; i < l; ++i ){ | |
| fn( objs[ i ], i ); | |
| } | |
| }, | |
| /** | |
| * Include an ASM Module | |
| * @param {Function} fn ASM module factory | |
| * @return {void} | |
| */ | |
| include: function( fn ){ | |
| var self = this | |
| ,stdlib = { | |
| Uint8Array: Uint8Array, | |
| Int8Array: Int8Array, | |
| Uint16Array: Uint16Array, | |
| Int16Array: Int16Array, | |
| Uint32Array: Uint32Array, | |
| Int32Array: Int32Array, | |
| Float32Array: Float32Array, | |
| Float64Array: Float64Array, | |
| Math: Math | |
| } | |
| ,coln = { | |
| getLen: function(){ | |
| return self.objs.length; | |
| }, | |
| ptr: self.ptr, | |
| objSize: self.objSize | |
| } | |
| ,mixin | |
| ,key | |
| ; | |
| for ( key in self._schema ){ | |
| coln[ '$'+key ] = self._schema[ key ].ptr; | |
| } | |
| mixin = fn( stdlib, coln, self.buffer ); | |
| for ( key in mixin ){ | |
| self[ key ] = mixin[ key ]; | |
| } | |
| } | |
| }; | |
| ASMHelpers.Collection = Collection; | |
| return ASMHelpers; | |
| })); |
Hi Jasper,
I am really fond of the work you have accomplished experimented with ASM.js.
Just to put my two cents in on this topic, I would like to highlight an issued I had yesterday on Nigthly 35.0a1 (2014-09-20). Simply put the browser log pane show these two messages while experimenting with JSFIDDLE:
Successfully compiled asm.js code (total compilation time 0ms; not stored in cache) _display
TypeError: asm.js link error: ArrayBuffer byteLength 0x800 is not a valid heap length. The next valid length is 0x1000
The easier workaround I have been able to come up with, seems to be multiply by two the ArrayBuffer's byteLenght value. Here is the amended GIST line pasted below.
// need the largest power of 2 greater than required size
bufferSize = 1 << Math.ceil(Math.log(objSize * maxObjects)/Math.LN2) * 2;
Is the mentioned approach any correct. Has anybody experienced my same issue using 5399067 gist?
Eager to read any comments about what I have described here.
Thanks in advance.
Best regards, Giorgio Arata.
The problem with buffer size is because asm.js guys changed the implementation without changing the specs :)
https://bugzilla.mozilla.org/show_bug.cgi?id=1087178
http://discourse.specifiction.org/t/increase-minimum-heap-length-to-64kb/564
Now the minimum size for heap buffer is 64kb, the next valid sizes are powers of 2 between 64kb and 64mb, then it's multiples of 64MB.
I made a function to fix this - call it in line 199 in place of the current code:
function nextValidHeapSize(realSize) {
var SIZE_64_KB = 65536,
SIZE_64_MB = 67108864;
if (realSize <= SIZE_64_KB) {
return SIZE_64_KB;
} else if (realSize <= SIZE_64_MB) {
return 1 << (Math.ceil(Math.log(realSize)/Math.LN2)|0);
} else {
return (SIZE_64_MB*Math.ceil(realSize/SIZE_64_MB)|0)|0;
}
}
Usage example #1: http://codepen.io/wellcaffeinated/pen/aucEf