Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Helper module for creating collections of objects that store their properties inside typed arrays. Useful for managing objects that will be used by asm.js modules. Code by Jasper Palfree (http://wellcaffeinated.net) License: MIT See comments for usage examples. See this blog post for explanation: http://wellcaffeinated.net/articles/2013/04/16/pl…
/**
* 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 <jasper@wellcaffeinated.net>
* 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;
}));

garata commented Sep 21, 2014

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.

ajuc commented Dec 31, 2014

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;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment