diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/embind/embind.js | 436 | ||||
-rw-r--r-- | src/embind/emval.js | 21 | ||||
-rw-r--r-- | src/emscripten-source-map.min.js | 3 | ||||
-rw-r--r-- | src/jsifier.js | 10 | ||||
-rw-r--r-- | src/library.js | 323 | ||||
-rw-r--r-- | src/library_browser.js | 146 | ||||
-rw-r--r-- | src/library_fs.js | 77 | ||||
-rw-r--r-- | src/library_gl.js | 47 | ||||
-rw-r--r-- | src/library_glfw.js | 2 | ||||
-rw-r--r-- | src/library_html5.js | 6 | ||||
-rw-r--r-- | src/library_sdl.js | 394 | ||||
-rw-r--r-- | src/modules.js | 1 | ||||
-rw-r--r-- | src/parseTools.js | 19 | ||||
-rw-r--r-- | src/preamble.js | 236 | ||||
-rw-r--r-- | src/relooper/Relooper.cpp | 122 | ||||
-rw-r--r-- | src/relooper/Relooper.h | 14 | ||||
-rw-r--r-- | src/relooper/fuzzer.py | 6 | ||||
-rw-r--r-- | src/relooper/test.cpp | 31 | ||||
-rw-r--r-- | src/relooper/test.txt | 153 | ||||
-rw-r--r-- | src/runtime.js | 13 | ||||
-rw-r--r-- | src/settings.js | 18 | ||||
-rw-r--r-- | src/shell.html | 18 | ||||
-rw-r--r-- | src/shell.js | 6 | ||||
-rw-r--r-- | src/shell_minimal.html | 18 | ||||
-rw-r--r-- | src/simd.js | 1643 | ||||
-rw-r--r-- | src/struct_info.json | 18 |
26 files changed, 2314 insertions, 1467 deletions
diff --git a/src/embind/embind.js b/src/embind/embind.js index 6ec07cd9..8c8d73ad 100644 --- a/src/embind/embind.js +++ b/src/embind/embind.js @@ -1,13 +1,15 @@ -/*global Module*/ +/*global Module, asm*/ /*global _malloc, _free, _memcpy*/ /*global FUNCTION_TABLE, HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAPF64*/ /*global readLatin1String*/ /*global __emval_register, _emval_handle_array, __emval_decref*/ /*global ___getTypeName*/ +/*global requireHandle*/ /*jslint sub:true*/ /* The symbols 'fromWireType' and 'toWireType' must be accessed via array notation to be closure-safe since craftInvokerFunction crafts functions as strings that can't be closured. */ var InternalError = Module['InternalError'] = extendError(Error, 'InternalError'); var BindingError = Module['BindingError'] = extendError(Error, 'BindingError'); var UnboundTypeError = Module['UnboundTypeError'] = extendError(BindingError, 'UnboundTypeError'); +var PureVirtualError = Module['PureVirtualError'] = extendError(BindingError, 'PureVirtualError'); function throwInternalError(message) { throw new InternalError(message); @@ -151,6 +153,59 @@ function _embind_repr(v) { } } +// raw pointer -> instance +var registeredInstances = {}; + +function getBasestPointer(class_, ptr) { + if (ptr === undefined) { + throwBindingError('ptr should not be undefined'); + } + while (class_.baseClass) { + ptr = class_.upcast(ptr); + class_ = class_.baseClass; + } + return ptr; +} + +function registerInheritedInstance(class_, ptr, instance) { + ptr = getBasestPointer(class_, ptr); + if (registeredInstances.hasOwnProperty(ptr)) { + throwBindingError('Tried to register registered instance: ' + ptr); + } else { + registeredInstances[ptr] = instance; + } +} + +function unregisterInheritedInstance(class_, ptr) { + ptr = getBasestPointer(class_, ptr); + if (registeredInstances.hasOwnProperty(ptr)) { + delete registeredInstances[ptr]; + } else { + throwBindingError('Tried to unregister unregistered instance: ' + ptr); + } +} + +function getInheritedInstance(class_, ptr) { + ptr = getBasestPointer(class_, ptr); + return registeredInstances[ptr]; +} + +function getInheritedInstanceCount() { + return Object.keys(registeredInstances).length; +} +Module['getInheritedInstanceCount'] = getInheritedInstanceCount; + +function getLiveInheritedInstances() { + var rv = []; + for (var k in registeredInstances) { + if (registeredInstances.hasOwnProperty(k)) { + rv.push(registeredInstances[k]); + } + } + return rv; +} +Module['getLiveInheritedInstances'] = getLiveInheritedInstances; + // typeID -> { toWireType: ..., fromWireType: ... } var registeredTypes = {}; @@ -535,6 +590,9 @@ function __embind_register_emval(rawType, name) { 'argPackAdvance': 8, 'readValueFromPointer': simpleReadValueFromPointer, destructorFunction: null, // This type does not need a destructor + + // TODO: do we need a deleteObject here? write a test where + // emval is passed into JS via an interface }); } @@ -622,10 +680,6 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp var isClassMethodFunc = (argTypes[1] !== null && classType !== null); - if (!isClassMethodFunc && !FUNCTION_TABLE[cppTargetFunc]) { - throwBindingError('Global function '+humanName+' is not defined!'); - } - // Free functions with signature "void function()" do not need an invoker that marshalls between wire types. // TODO: This omits argument count check - enable only at -O3 or similar. // if (ENABLE_UNSAFE_OPTS && argCount == 2 && argTypes[0].name == "void" && !isClassMethodFunc) { @@ -634,7 +688,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp var argsList = ""; var argsListWired = ""; - for(var i = 0; i < argCount-2; ++i) { + for(var i = 0; i < argCount - 2; ++i) { argsList += (i!==0?", ":"")+"arg"+i; argsListWired += (i!==0?", ":"")+"arg"+i+"Wired"; } @@ -662,14 +716,14 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp } var dtorStack = needsDestructorStack ? "destructors" : "null"; - var args1 = ["throwBindingError", "classType", "invoker", "fn", "runDestructors", "retType", "classParam"]; - var args2 = [throwBindingError, classType, cppInvokerFunc, cppTargetFunc, runDestructors, argTypes[0], argTypes[1]]; + var args1 = ["throwBindingError", "invoker", "fn", "runDestructors", "retType", "classParam"]; + var args2 = [throwBindingError, cppInvokerFunc, cppTargetFunc, runDestructors, argTypes[0], argTypes[1]]; if (isClassMethodFunc) { invokerFnBody += "var thisWired = classParam.toWireType("+dtorStack+", this);\n"; } - for(var i = 0; i < argCount-2; ++i) { + for(var i = 0; i < argCount - 2; ++i) { invokerFnBody += "var arg"+i+"Wired = argType"+i+".toWireType("+dtorStack+", arg"+i+"); // "+argTypes[i+2].name+"\n"; args1.push("argType"+i); args2.push(argTypes[i+2]); @@ -688,7 +742,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp invokerFnBody += "runDestructors(destructors);\n"; } else { for(var i = isClassMethodFunc?1:2; i < argTypes.length; ++i) { // Skip return value at index 0 - it's not deleted here. Also skip class type if not a method. - var paramName = (i === 1 ? "thisWired" : ("arg"+(i-2)+"Wired")); + var paramName = (i === 1 ? "thisWired" : ("arg"+(i - 2)+"Wired")); if (argTypes[i].destructorFunction !== null) { invokerFnBody += paramName+"_dtor("+paramName+"); // "+argTypes[i].name+"\n"; args1.push(paramName+"_dtor"); @@ -708,10 +762,50 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp return invokerFunction; } -function __embind_register_function(name, argCount, rawArgTypesAddr, rawInvoker, fn) { +function requireFunction(signature, rawFunction) { + signature = readLatin1String(signature); + var fp; + // asm.js does not define FUNCTION_TABLE + if (typeof FUNCTION_TABLE === "undefined") { + // asm.js does not give direct access to the function tables, + // and thus we must go through the dynCall interface which allows + // calling into a signature's function table by pointer value. + // + // https://github.com/dherman/asm.js/issues/83 + // + // This has three main penalties: + // - dynCall is another function call in the path from JavaScript to C++. + // - JITs may not predict through the function table indirection at runtime. + // - Function.prototype.bind generally benchmarks poorly relative to + // function objects, but using 'arguments' would confound JITs and + // possibly allocate. + var dc = asm['dynCall_' + signature]; + if (dc === undefined) { + // We will always enter this branch if the signature + // contains 'f' and PRECISE_F32 is not enabled. + // + // Try again, replacing 'f' with 'd'. + dc = asm['dynCall_' + signature.replace(/f/g, 'd')]; + if (dc === undefined) { + throwBindingError("No dynCall invoker for signature: " + signature); + } + } + fp = dc.bind(undefined, rawFunction); + } else { + fp = FUNCTION_TABLE[rawFunction]; + } + + if (typeof fp !== "function") { + throwBindingError("unknown function pointer with signature " + signature + ": " + rawFunction); + } + return fp; +} + +function __embind_register_function(name, argCount, rawArgTypesAddr, signature, rawInvoker, fn) { var argTypes = heap32VectorToArray(argCount, rawArgTypesAddr); name = readLatin1String(name); - rawInvoker = FUNCTION_TABLE[rawInvoker]; + + rawInvoker = requireFunction(signature, rawInvoker); exposePublicSymbol(name, function() { throwUnboundTypeError('Cannot call ' + name + ' due to unbound types', argTypes); @@ -726,11 +820,11 @@ function __embind_register_function(name, argCount, rawArgTypesAddr, rawInvoker, var tupleRegistrations = {}; -function __embind_register_value_array(rawType, name, rawConstructor, rawDestructor) { +function __embind_register_value_array(rawType, name, constructorSignature, rawConstructor, destructorSignature, rawDestructor) { tupleRegistrations[rawType] = { name: readLatin1String(name), - rawConstructor: FUNCTION_TABLE[rawConstructor], - rawDestructor: FUNCTION_TABLE[rawDestructor], + rawConstructor: requireFunction(constructorSignature, rawConstructor), + rawDestructor: requireFunction(destructorSignature, rawDestructor), elements: [], }; } @@ -738,18 +832,20 @@ function __embind_register_value_array(rawType, name, rawConstructor, rawDestruc function __embind_register_value_array_element( rawTupleType, getterReturnType, + getterSignature, getter, getterContext, setterArgumentType, + setterSignature, setter, setterContext ) { tupleRegistrations[rawTupleType].elements.push({ getterReturnType: getterReturnType, - getter: FUNCTION_TABLE[getter], + getter: requireFunction(getterSignature, getter), getterContext: getterContext, setterArgumentType: setterArgumentType, - setter: FUNCTION_TABLE[setter], + setter: requireFunction(setterSignature, setter), setterContext: setterContext, }); } @@ -818,13 +914,15 @@ var structRegistrations = {}; function __embind_register_value_object( rawType, name, + constructorSignature, rawConstructor, + destructorSignature, rawDestructor ) { structRegistrations[rawType] = { name: readLatin1String(name), - rawConstructor: FUNCTION_TABLE[rawConstructor], - rawDestructor: FUNCTION_TABLE[rawDestructor], + rawConstructor: requireFunction(constructorSignature, rawConstructor), + rawDestructor: requireFunction(destructorSignature, rawDestructor), fields: [], }; } @@ -833,19 +931,21 @@ function __embind_register_value_object_field( structType, fieldName, getterReturnType, + getterSignature, getter, getterContext, setterArgumentType, + setterSignature, setter, setterContext ) { structRegistrations[structType].fields.push({ fieldName: readLatin1String(fieldName), getterReturnType: getterReturnType, - getter: FUNCTION_TABLE[getter], + getter: requireFunction(getterSignature, getter), getterContext: getterContext, setterArgumentType: setterArgumentType, - setter: FUNCTION_TABLE[setter], + setter: requireFunction(setterSignature, setter), setterContext: setterContext, }); } @@ -1082,14 +1182,14 @@ function RegisteredPointer( } } -RegisteredPointer.prototype.getPointee = function(ptr) { +RegisteredPointer.prototype.getPointee = function getPointee(ptr) { if (this.rawGetPointee) { ptr = this.rawGetPointee(ptr); } return ptr; }; -RegisteredPointer.prototype.destructor = function(ptr) { +RegisteredPointer.prototype.destructor = function destructor(ptr) { if (this.rawDestructor) { this.rawDestructor(ptr); } @@ -1098,7 +1198,13 @@ RegisteredPointer.prototype.destructor = function(ptr) { RegisteredPointer.prototype['argPackAdvance'] = 8; RegisteredPointer.prototype['readValueFromPointer'] = simpleReadValueFromPointer; -RegisteredPointer.prototype['fromWireType'] = function(ptr) { +RegisteredPointer.prototype['deleteObject'] = function deleteObject(handle) { + if (handle !== null) { + handle['delete'](); + } +}; + +RegisteredPointer.prototype['fromWireType'] = function fromWireType(ptr) { // ptr is a raw pointer (or a raw smartpointer) // rawPointer is a maybe-null raw pointer @@ -1108,6 +1214,22 @@ RegisteredPointer.prototype['fromWireType'] = function(ptr) { return null; } + var registeredInstance = getInheritedInstance(this.registeredClass, rawPointer); + if (undefined !== registeredInstance) { + // JS object has been neutered, time to repopulate it + if (0 === registeredInstance.$$.count.value) { + registeredInstance.$$.ptr = rawPointer; + registeredInstance.$$.smartPtr = ptr; + return registeredInstance['clone'](); + } else { + // else, just increment reference count on existing object + // it already has a reference to the smart pointer + var rv = registeredInstance['clone'](); + this.destructor(ptr); + return rv; + } + } + function makeDefaultHandle() { if (this.isSmartPointer) { return makeClassHandle(this.registeredClass.instancePrototype, { @@ -1183,7 +1305,7 @@ function getInstanceTypeName(handle) { return handle.$$.ptrType.registeredClass.name; } -ClassHandle.prototype['isAliasOf'] = function(other) { +ClassHandle.prototype['isAliasOf'] = function isAliasOf(other) { if (!(this instanceof ClassHandle)) { return false; } @@ -1213,19 +1335,24 @@ function throwInstanceAlreadyDeleted(obj) { throwBindingError(getInstanceTypeName(obj) + ' instance already deleted'); } -ClassHandle.prototype['clone'] = function() { +ClassHandle.prototype['clone'] = function clone() { if (!this.$$.ptr) { throwInstanceAlreadyDeleted(this); } - var clone = Object.create(Object.getPrototypeOf(this), { - $$: { - value: shallowCopy(this.$$), - } - }); + if (this.$$.preservePointerOnDelete) { + this.$$.count.value += 1; + return this; + } else { + var clone = Object.create(Object.getPrototypeOf(this), { + $$: { + value: shallowCopy(this.$$), + } + }); - clone.$$.count.value += 1; - return clone; + clone.$$.count.value += 1; + return clone; + } }; function runDestructor(handle) { @@ -1241,16 +1368,20 @@ ClassHandle.prototype['delete'] = function ClassHandle_delete() { if (!this.$$.ptr) { throwInstanceAlreadyDeleted(this); } - if (this.$$.deleteScheduled) { + + if (this.$$.deleteScheduled && !this.$$.preservePointerOnDelete) { throwBindingError('Object already scheduled for deletion'); } this.$$.count.value -= 1; - if (0 === this.$$.count.value) { + var toDelete = 0 === this.$$.count.value; + if (toDelete) { runDestructor(this); } - this.$$.smartPtr = undefined; - this.$$.ptr = undefined; + if (!this.$$.preservePointerOnDelete) { + this.$$.smartPtr = undefined; + this.$$.ptr = undefined; + } }; var deletionQueue = []; @@ -1263,7 +1394,7 @@ ClassHandle.prototype['deleteLater'] = function deleteLater() { if (!this.$$.ptr) { throwInstanceAlreadyDeleted(this); } - if (this.$$.deleteScheduled) { + if (this.$$.deleteScheduled && !this.$$.preservePointerOnDelete) { throwBindingError('Object already scheduled for deletion'); } deletionQueue.push(this); @@ -1309,12 +1440,15 @@ function RegisteredClass( this.getActualType = getActualType; this.upcast = upcast; this.downcast = downcast; + this.pureVirtualFunctions = []; } function shallowCopy(o) { var rv = {}; for (var k in o) { - rv[k] = o[k]; + if (Object.prototype.hasOwnProperty.call(o, k)) { + rv[k] = o[k]; + } } return rv; } @@ -1324,17 +1458,25 @@ function __embind_register_class( rawPointerType, rawConstPointerType, baseClassRawType, + getActualTypeSignature, getActualType, + upcastSignature, upcast, + downcastSignature, downcast, name, + destructorSignature, rawDestructor ) { name = readLatin1String(name); - rawDestructor = FUNCTION_TABLE[rawDestructor]; - getActualType = FUNCTION_TABLE[getActualType]; - upcast = FUNCTION_TABLE[upcast]; - downcast = FUNCTION_TABLE[downcast]; + getActualType = requireFunction(getActualTypeSignature, getActualType); + if (upcast) { + upcast = requireFunction(upcastSignature, upcast); + } + if (downcast) { + downcast = requireFunction(downcastSignature, downcast); + } + rawDestructor = requireFunction(destructorSignature, rawDestructor); var legalFunctionName = makeLegalFunctionName(name); exposePublicSymbol(legalFunctionName, function() { @@ -1424,11 +1566,12 @@ function __embind_register_class_constructor( rawClassType, argCount, rawArgTypesAddr, + invokerSignature, invoker, rawConstructor ) { var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); - invoker = FUNCTION_TABLE[invoker]; + invoker = requireFunction(invokerSignature, invoker); whenDependentTypesAreResolved([], [rawClassType], function(classType) { classType = classType[0]; @@ -1440,12 +1583,12 @@ function __embind_register_class_constructor( if (undefined !== classType.registeredClass.constructor_body[argCount - 1]) { throw new BindingError("Cannot register multiple constructors with identical number of parameters (" + (argCount-1) + ") for class '" + classType.name + "'! Overload resolution is currently only performed using the parameter count, not actual type info!"); } - classType.registeredClass.constructor_body[argCount - 1] = function() { + classType.registeredClass.constructor_body[argCount - 1] = function unboundTypeHandler() { throwUnboundTypeError('Cannot construct ' + classType.name + ' due to unbound types', rawArgTypes); }; whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { - classType.registeredClass.constructor_body[argCount - 1] = function() { + classType.registeredClass.constructor_body[argCount - 1] = function constructor_body() { if (arguments.length !== argCount - 1) { throwBindingError(humanName + ' called with ' + arguments.length + ' arguments, expected ' + (argCount-1)); } @@ -1474,9 +1617,12 @@ function downcastPointer(ptr, ptrClass, desiredClass) { if (undefined === desiredClass.baseClass) { return null; // no conversion } - // O(depth) stack space used - return desiredClass.downcast( - downcastPointer(ptr, ptrClass, desiredClass.baseClass)); + + var rv = downcastPointer(ptr, ptrClass, desiredClass.baseClass); + if (rv === null) { + return null; + } + return desiredClass.downcast(rv); } function upcastPointer(ptr, ptrClass, desiredClass) { @@ -1513,32 +1659,38 @@ function __embind_register_class_function( methodName, argCount, rawArgTypesAddr, // [ReturnType, ThisType, Args...] + invokerSignature, rawInvoker, - context + context, + isPureVirtual ) { var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); methodName = readLatin1String(methodName); - rawInvoker = FUNCTION_TABLE[rawInvoker]; + rawInvoker = requireFunction(invokerSignature, rawInvoker); whenDependentTypesAreResolved([], [rawClassType], function(classType) { classType = classType[0]; var humanName = classType.name + '.' + methodName; - var unboundTypesHandler = function() { + if (isPureVirtual) { + classType.registeredClass.pureVirtualFunctions.push(methodName); + } + + function unboundTypesHandler() { throwUnboundTypeError('Cannot call ' + humanName + ' due to unbound types', rawArgTypes); - }; + } var proto = classType.registeredClass.instancePrototype; var method = proto[methodName]; - if (undefined === method || (undefined === method.overloadTable && method.className !== classType.name && method.argCount === argCount-2)) { + if (undefined === method || (undefined === method.overloadTable && method.className !== classType.name && method.argCount === argCount - 2)) { // This is the first overload to be registered, OR we are replacing a function in the base class with a function in the derived class. - unboundTypesHandler.argCount = argCount-2; + unboundTypesHandler.argCount = argCount - 2; unboundTypesHandler.className = classType.name; proto[methodName] = unboundTypesHandler; } else { // There was an existing function with the same name registered. Set up a function overload routing table. ensureOverloadTable(proto, methodName, humanName); - proto[methodName].overloadTable[argCount-2] = unboundTypesHandler; + proto[methodName].overloadTable[argCount - 2] = unboundTypesHandler; } whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { @@ -1550,7 +1702,7 @@ function __embind_register_class_function( if (undefined === proto[methodName].overloadTable) { proto[methodName] = memberFunction; } else { - proto[methodName].overloadTable[argCount-2] = memberFunction; + proto[methodName].overloadTable[argCount - 2] = memberFunction; } return []; @@ -1559,64 +1711,20 @@ function __embind_register_class_function( }); } -function __embind_register_class_class_function( - rawClassType, - methodName, - argCount, - rawArgTypesAddr, - rawInvoker, - fn -) { - var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); - methodName = readLatin1String(methodName); - rawInvoker = FUNCTION_TABLE[rawInvoker]; - whenDependentTypesAreResolved([], [rawClassType], function(classType) { - classType = classType[0]; - var humanName = classType.name + '.' + methodName; - - var unboundTypesHandler = function() { - throwUnboundTypeError('Cannot call ' + humanName + ' due to unbound types', rawArgTypes); - }; - - var proto = classType.registeredClass.constructor; - if (undefined === proto[methodName]) { - // This is the first function to be registered with this name. - unboundTypesHandler.argCount = argCount-1; - proto[methodName] = unboundTypesHandler; - } else { - // There was an existing function with the same name registered. Set up a function overload routing table. - ensureOverloadTable(proto, methodName, humanName); - proto[methodName].overloadTable[argCount-1] = unboundTypesHandler; - } - - whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { - // Replace the initial unbound-types-handler stub with the proper function. If multiple overloads are registered, - // the function handlers go into an overload table. - var invokerArgsArray = [argTypes[0] /* return value */, null /* no class 'this'*/].concat(argTypes.slice(1) /* actual params */); - var func = craftInvokerFunction(humanName, invokerArgsArray, null /* no class 'this'*/, rawInvoker, fn); - if (undefined === proto[methodName].overloadTable) { - proto[methodName] = func; - } else { - proto[methodName].overloadTable[argCount-1] = func; - } - return []; - }); - return []; - }); -} - function __embind_register_class_property( classType, fieldName, getterReturnType, + getterSignature, getter, getterContext, setterArgumentType, + setterSignature, setter, setterContext ) { fieldName = readLatin1String(fieldName); - getter = FUNCTION_TABLE[getter]; + getter = requireFunction(getterSignature, getter); whenDependentTypesAreResolved([], [classType], function(classType) { classType = classType[0]; @@ -1654,7 +1762,7 @@ function __embind_register_class_property( }; if (setter) { - setter = FUNCTION_TABLE[setter]; + setter = requireFunction(setterSignature, setter); var setterArgumentType = types[1]; desc.set = function(v) { var ptr = validateThis(this, classType, humanName + ' setter'); @@ -1672,6 +1780,112 @@ function __embind_register_class_property( }); } +function __embind_register_class_class_function( + rawClassType, + methodName, + argCount, + rawArgTypesAddr, + invokerSignature, + rawInvoker, + fn +) { + var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); + methodName = readLatin1String(methodName); + rawInvoker = requireFunction(invokerSignature, rawInvoker); + whenDependentTypesAreResolved([], [rawClassType], function(classType) { + classType = classType[0]; + var humanName = classType.name + '.' + methodName; + + function unboundTypesHandler() { + throwUnboundTypeError('Cannot call ' + humanName + ' due to unbound types', rawArgTypes); + } + + var proto = classType.registeredClass.constructor; + if (undefined === proto[methodName]) { + // This is the first function to be registered with this name. + unboundTypesHandler.argCount = argCount-1; + proto[methodName] = unboundTypesHandler; + } else { + // There was an existing function with the same name registered. Set up a function overload routing table. + ensureOverloadTable(proto, methodName, humanName); + proto[methodName].overloadTable[argCount-1] = unboundTypesHandler; + } + + whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { + // Replace the initial unbound-types-handler stub with the proper function. If multiple overloads are registered, + // the function handlers go into an overload table. + var invokerArgsArray = [argTypes[0] /* return value */, null /* no class 'this'*/].concat(argTypes.slice(1) /* actual params */); + var func = craftInvokerFunction(humanName, invokerArgsArray, null /* no class 'this'*/, rawInvoker, fn); + if (undefined === proto[methodName].overloadTable) { + proto[methodName] = func; + } else { + proto[methodName].overloadTable[argCount-1] = func; + } + return []; + }); + return []; + }); +} + +function __embind_create_inheriting_constructor(constructorName, wrapperType, properties) { + constructorName = readLatin1String(constructorName); + wrapperType = requireRegisteredType(wrapperType, 'wrapper'); + properties = requireHandle(properties); + + var arraySlice = [].slice; + + var registeredClass = wrapperType.registeredClass; + var wrapperPrototype = registeredClass.instancePrototype; + var baseClass = registeredClass.baseClass; + var baseClassPrototype = baseClass.instancePrototype; + var baseConstructor = registeredClass.baseClass.constructor; + var ctor = createNamedFunction(constructorName, function() { + registeredClass.baseClass.pureVirtualFunctions.forEach(function(name) { + if (this[name] === baseClassPrototype[name]) { + throw new PureVirtualError('Pure virtual function ' + name + ' must be implemented in JavaScript'); + } + }.bind(this)); + + Object.defineProperty(this, '__parent', { + value: wrapperPrototype + }); + this.__construct.apply(this, arraySlice.call(arguments)); + }); + + // It's a little nasty that we're modifying the wrapper prototype here. + + wrapperPrototype.__construct = function __construct() { + if (this === wrapperPrototype) { + throwBindingError("Pass correct 'this' to __construct"); + } + + var inner = baseConstructor.implement.apply( + undefined, + [this].concat(arraySlice.call(arguments))); + var $$ = inner.$$; + inner.notifyOnDestruction(); + $$.preservePointerOnDelete = true; + Object.defineProperty(this, '$$', { + value: $$ + }); + registerInheritedInstance(registeredClass, $$.ptr, this); + }; + + wrapperPrototype.__destruct = function __destruct() { + if (this === wrapperPrototype) { + throwBindingError("Pass correct 'this' to __destruct"); + } + + unregisterInheritedInstance(registeredClass, this.$$.ptr); + }; + + ctor.prototype = Object.create(wrapperPrototype); + for (var p in properties) { + ctor.prototype[p] = properties[p]; + } + return __emval_register(ctor); +} + var char_0 = '0'.charCodeAt(0); var char_9 = '9'.charCodeAt(0); function makeLegalFunctionName(name) { @@ -1689,16 +1903,20 @@ function __embind_register_smart_ptr( rawPointeeType, name, sharingPolicy, + getPointeeSignature, rawGetPointee, + constructorSignature, rawConstructor, + shareSignature, rawShare, + destructorSignature, rawDestructor ) { name = readLatin1String(name); - rawGetPointee = FUNCTION_TABLE[rawGetPointee]; - rawConstructor = FUNCTION_TABLE[rawConstructor]; - rawShare = FUNCTION_TABLE[rawShare]; - rawDestructor = FUNCTION_TABLE[rawDestructor]; + rawGetPointee = requireFunction(getPointeeSignature, rawGetPointee); + rawConstructor = requireFunction(constructorSignature, rawConstructor); + rawShare = requireFunction(shareSignature, rawShare); + rawDestructor = requireFunction(destructorSignature, rawDestructor); whenDependentTypesAreResolved([rawType], [rawPointeeType], function(pointeeType) { pointeeType = pointeeType[0]; diff --git a/src/embind/emval.js b/src/embind/emval.js index 4007701a..1661bc02 100644 --- a/src/embind/emval.js +++ b/src/embind/emval.js @@ -265,7 +265,14 @@ function __emval_get_method_caller(argCount, argTypes) { " args += argType" + i + ".argPackAdvance;\n"; } functionBody += - " var rv = handle[name](" + argsList + ");\n" + + " var rv = handle[name](" + argsList + ");\n"; + for (var i = 0; i < argCount - 1; ++i) { + if (types[i + 1]['deleteObject']) { + functionBody += + " argType" + i + ".deleteObject(arg" + i + ");\n"; + } + } + functionBody += " return retType.toWireType(destructors, rv);\n" + "};\n"; @@ -281,8 +288,16 @@ function __emval_call_method(caller, handle, methodName, destructorsRef, args) { return caller(handle, methodName, allocateDestructors(destructorsRef), args); } -function __emval_has_function(handle, name) { +function __emval_has_function(handle, name, classType) { handle = requireHandle(handle); name = getStringOrSymbol(name); - return handle[name] instanceof Function; + classType = requireRegisteredType(classType, 'class wrapper filter'); + + var filter = classType.registeredClass.instancePrototype[name]; + return (handle[name] instanceof Function) && (filter === undefined || handle[name] !== filter); +} + +function __emval_typeof(handle) { + handle = requireHandle(handle); + return __emval_register(typeof handle); } diff --git a/src/emscripten-source-map.min.js b/src/emscripten-source-map.min.js index 9151400f..44cb1d84 100644 --- a/src/emscripten-source-map.min.js +++ b/src/emscripten-source-map.min.js @@ -4,7 +4,7 @@ var emscripten_sourcemap_xmlHttp = undefined; function emscripten_sourceMapLoaded() { if (emscripten_sourcemap_xmlHttp.readyState === 4) { Module['removeRunDependency']('sourcemap'); - if (emscripten_sourcemap_xmlHttp.status === 200) { + if (emscripten_sourcemap_xmlHttp.status === 200 || emscripten_sourcemap_xmlHttp.status === 0) { emscripten_source_map = new window.sourceMap.SourceMapConsumer(emscripten_sourcemap_xmlHttp.responseText); console.log('Source map data loaded.'); } else { @@ -20,6 +20,7 @@ function emscripten_loadSourceMap() { emscripten_sourcemap_xmlHttp = new XMLHttpRequest(); emscripten_sourcemap_xmlHttp.onreadystatechange = emscripten_sourceMapLoaded; emscripten_sourcemap_xmlHttp.open("GET", url, true); + emscripten_sourcemap_xmlHttp.responseType = "text"; emscripten_sourcemap_xmlHttp.send(null); } diff --git a/src/jsifier.js b/src/jsifier.js index 065c66a8..791273f4 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -1321,10 +1321,10 @@ function JSify(data, functionsOnly) { // vector load var native = getVectorNativeType(item.valueType); var base = getSIMDName(native); - return base + '32x4(' + makeGetValue(value, 0, native, 0, item.unsigned, 0, item.align) + ',' + - makeGetValue(value, 4, native, 0, item.unsigned, 0, item.align) + ',' + - makeGetValue(value, 8, native, 0, item.unsigned, 0, item.align) + ',' + - makeGetValue(value, 12, native, 0, item.unsigned, 0, item.align) + ');'; + return 'SIMD.' + base + '32x4(' + makeGetValue(value, 0, native, 0, item.unsigned, 0, item.align) + ',' + + makeGetValue(value, 4, native, 0, item.unsigned, 0, item.align) + ',' + + makeGetValue(value, 8, native, 0, item.unsigned, 0, item.align) + ',' + + makeGetValue(value, 12, native, 0, item.unsigned, 0, item.align) + ');'; } var impl = item.ident ? getVarImpl(item.funcData, item.ident) : VAR_EMULATED; switch (impl) { @@ -1395,7 +1395,7 @@ function JSify(data, functionsOnly) { } for (var i = 0; i < 4; i++) assert(mask[0] == 0 || mask == 1); i = 0; - return base + '32x4(' + mask.map(function(m) { + return 'SIMD.' + base + '32x4(' + mask.map(function(m) { return (m == 1 ? second : first) + '.' + simdLane[i++]; }).join(',') + ')'; } diff --git a/src/library.js b/src/library.js index 1d5a9140..c17952b3 100644 --- a/src/library.js +++ b/src/library.js @@ -762,12 +762,18 @@ LibraryManager.library = { // http://pubs.opengroup.org/onlinepubs/000095399/functions/crypt.html // TODO: Implement (probably compile from C). ___setErrNo(ERRNO_CODES.ENOSYS); +#if ASSERTIONS + Runtime.warnOnce('crypt() returning an error as we do not support it'); +#endif return 0; }, encrypt: function(block, edflag) { // void encrypt(char block[64], int edflag); // http://pubs.opengroup.org/onlinepubs/000095399/functions/encrypt.html // TODO: Implement (probably compile from C). +#if ASSERTIONS + Runtime.warnOnce('encrypt() returning an error as we do not support it'); +#endif ___setErrNo(ERRNO_CODES.ENOSYS); }, fpathconf__deps: ['__setErrNo', '$ERRNO_CODES'], @@ -940,6 +946,9 @@ LibraryManager.library = { // It is possible to implement this using two device streams, but pipes make // little sense in a single-threaded environment, so we do not support them. ___setErrNo(ERRNO_CODES.ENOSYS); +#if ASSERTIONS + Runtime.warnOnce('pipe() returning an error as we do not support them'); +#endif return -1; }, pread__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], @@ -1584,7 +1593,6 @@ LibraryManager.library = { return /^[+-]?[0-9]*\.?[0-9]+([eE][+-]?[0-9]+)?/.exec(text); }, - // TODO: Document. _scanString__deps: ['_getFloat'], _scanString: function(format, get, unget, varargs) { if (!__scanString.whiteSpace) { @@ -1726,6 +1734,7 @@ LibraryManager.library = { } var long_ = false; var half = false; + var quarter = false; var longLong = false; if (format[formatIndex] == 'l') { long_ = true; @@ -1737,6 +1746,10 @@ LibraryManager.library = { } else if (format[formatIndex] == 'h') { half = true; formatIndex++; + if (format[formatIndex] == 'h') { + quarter = true; + formatIndex++; + } } var type = format[formatIndex]; formatIndex++; @@ -1795,20 +1808,21 @@ LibraryManager.library = { var text = buffer.join(''); var argPtr = {{{ makeGetValue('varargs', 'argIndex', 'void*') }}}; argIndex += Runtime.getAlignSize('void*', null, true); + var base = 10; switch (type) { + case 'X': case 'x': + base = 16; case 'd': case 'u': case 'i': - if (half) { - {{{ makeSetValue('argPtr', 0, 'parseInt(text, 10)', 'i16') }}}; + if (quarter) { + {{{ makeSetValue('argPtr', 0, 'parseInt(text, base)', 'i8') }}}; + } else if (half) { + {{{ makeSetValue('argPtr', 0, 'parseInt(text, base)', 'i16') }}}; } else if (longLong) { - {{{ makeSetValue('argPtr', 0, 'parseInt(text, 10)', 'i64') }}}; + {{{ makeSetValue('argPtr', 0, 'parseInt(text, base)', 'i64') }}}; } else { - {{{ makeSetValue('argPtr', 0, 'parseInt(text, 10)', 'i32') }}}; + {{{ makeSetValue('argPtr', 0, 'parseInt(text, base)', 'i32') }}}; } break; - case 'X': - case 'x': - {{{ makeSetValue('argPtr', 0, 'parseInt(text, 16)', 'i32') }}}; - break; case 'F': case 'f': case 'E': @@ -2788,34 +2802,6 @@ LibraryManager.library = { var stdin = {{{ makeGetValue(makeGlobalUse('_stdin'), '0', 'void*') }}}; return _fscanf(stdin, format, varargs); }, - sscanf__deps: ['_scanString'], - sscanf: function(s, format, varargs) { - // int sscanf(const char *restrict s, const char *restrict format, ... ); - // http://pubs.opengroup.org/onlinepubs/000095399/functions/scanf.html - var index = 0; - function get() { return {{{ makeGetValue('s', 'index++', 'i8') }}}; }; - function unget() { index--; }; - return __scanString(format, get, unget, varargs); - }, - snprintf__deps: ['_formatString', 'malloc'], - snprintf: function(s, n, format, varargs) { - // int snprintf(char *restrict s, size_t n, const char *restrict format, ...); - // http://pubs.opengroup.org/onlinepubs/000095399/functions/printf.html - var result = __formatString(format, varargs); - var limit = (n === undefined) ? result.length - : Math.min(result.length, Math.max(n - 1, 0)); - if (s < 0) { - s = -s; - var buf = _malloc(limit+1); - {{{ makeSetValue('s', '0', 'buf', 'i8*') }}}; - s = buf; - } - for (var i = 0; i < limit; i++) { - {{{ makeSetValue('s', 'i', 'result[i]', 'i8') }}}; - } - if (limit < n || (n === undefined)) {{{ makeSetValue('s', 'i', '0', 'i8') }}}; - return result.length; - }, fprintf__deps: ['fwrite', '_formatString'], fprintf: function(stream, format, varargs) { // int fprintf(FILE *restrict stream, const char *restrict format, ...); @@ -2833,16 +2819,6 @@ LibraryManager.library = { var stdout = {{{ makeGetValue(makeGlobalUse('_stdout'), '0', 'void*') }}}; return _fprintf(stdout, format, varargs); }, - sprintf__deps: ['snprintf'], - sprintf: function(s, format, varargs) { - // int sprintf(char *restrict s, const char *restrict format, ...); - // http://pubs.opengroup.org/onlinepubs/000095399/functions/printf.html - return _snprintf(s, undefined, format, varargs); - }, - asprintf__deps: ['sprintf'], - asprintf: function(s, format, varargs) { - return _sprintf(-s, format, varargs); - }, dprintf__deps: ['_formatString', 'write'], dprintf: function(fd, format, varargs) { var result = __formatString(format, varargs); @@ -2854,14 +2830,10 @@ LibraryManager.library = { #if TARGET_X86 // va_arg is just like our varargs vfprintf: 'fprintf', - vsnprintf: 'snprintf', vprintf: 'printf', - vsprintf: 'sprintf', - vasprintf: 'asprintf', vdprintf: 'dprintf', vscanf: 'scanf', vfscanf: 'fscanf', - vsscanf: 'sscanf', #endif #if TARGET_ASMJS_UNKNOWN_EMSCRIPTEN @@ -2870,22 +2842,10 @@ LibraryManager.library = { vfprintf: function(s, f, va_arg) { return _fprintf(s, f, {{{ makeGetValue('va_arg', 0, '*') }}}); }, - vsnprintf__deps: ['snprintf'], - vsnprintf: function(s, n, format, va_arg) { - return _snprintf(s, n, format, {{{ makeGetValue('va_arg', 0, '*') }}}); - }, vprintf__deps: ['printf'], vprintf: function(format, va_arg) { return _printf(format, {{{ makeGetValue('va_arg', 0, '*') }}}); }, - vsprintf__deps: ['sprintf'], - vsprintf: function(s, format, va_arg) { - return _sprintf(s, format, {{{ makeGetValue('va_arg', 0, '*') }}}); - }, - vasprintf__deps: ['asprintf'], - vasprintf: function(s, format, va_arg) { - return _asprintf(s, format, {{{ makeGetValue('va_arg', 0, '*') }}}); - }, vdprintf__deps: ['dprintf'], vdprintf: function (fd, format, va_arg) { return _dprintf(fd, format, {{{ makeGetValue('va_arg', 0, '*') }}}); @@ -2898,10 +2858,6 @@ LibraryManager.library = { vfscanf: function(s, format, va_arg) { return _fscanf(s, format, {{{ makeGetValue('va_arg', 0, '*') }}}); }, - vsscanf__deps: ['sscanf'], - vsscanf: function(s, format, va_arg) { - return _sscanf(s, format, {{{ makeGetValue('va_arg', 0, '*') }}}); - }, #endif // ========================================================================== @@ -3208,39 +3164,6 @@ LibraryManager.library = { {{{ makeStructuralReturn([makeGetTempDouble(0, 'i32'), makeGetTempDouble(1, 'i32')]) }}}; }, #endif - strtoll__deps: ['_parseInt64'], - strtoll: function(str, endptr, base) { - return __parseInt64(str, endptr, base, '-9223372036854775808', '9223372036854775807'); // LLONG_MIN, LLONG_MAX. - }, - strtoll_l__deps: ['strtoll'], - strtoll_l: function(str, endptr, base) { - return _strtoll(str, endptr, base); // no locale support yet - }, - strtol__deps: ['_parseInt'], - strtol: function(str, endptr, base) { - return __parseInt(str, endptr, base, -2147483648, 2147483647, 32); // LONG_MIN, LONG_MAX. - }, - strtol_l__deps: ['strtol'], - strtol_l: function(str, endptr, base) { - return _strtol(str, endptr, base); // no locale support yet - }, - strtoul__deps: ['_parseInt'], - strtoul: function(str, endptr, base) { - return __parseInt(str, endptr, base, 0, 4294967295, 32, true); // ULONG_MAX. - }, - strtoul_l__deps: ['strtoul'], - strtoul_l: function(str, endptr, base) { - return _strtoul(str, endptr, base); // no locale support yet - }, - strtoull__deps: ['_parseInt64'], - strtoull: function(str, endptr, base) { - return __parseInt64(str, endptr, base, 0, '18446744073709551615', true); // ULONG_MAX. - }, - strtoull_l__deps: ['strtoull'], - strtoull_l: function(str, endptr, base) { - return _strtoull(str, endptr, base); // no locale support yet - }, - environ: 'allocate(1, "i32*", ALLOC_STATIC)', __environ__deps: ['environ'], __environ: '_environ', @@ -3430,6 +3353,7 @@ LibraryManager.library = { return 0; } else { var size = Math.min(4095, absolute.path.length); // PATH_MAX - 1. + if (resolved_name === 0) resolved_name = _malloc(size+1); for (var i = 0; i < size; i++) { {{{ makeSetValue('resolved_name', 'i', 'absolute.path.charCodeAt(i)', 'i8') }}}; } @@ -3605,28 +3529,6 @@ LibraryManager.library = { return pdest|0; }, - strlwr__deps:['tolower'], - strlwr: function(pstr){ - var i = 0; - while(1) { - var x = {{{ makeGetValue('pstr', 'i', 'i8') }}}; - if (x == 0) break; - {{{ makeSetValue('pstr', 'i', '_tolower(x)', 'i8') }}}; - i++; - } - }, - - strupr__deps:['toupper'], - strupr: function(pstr){ - var i = 0; - while(1) { - var x = {{{ makeGetValue('pstr', 'i', 'i8') }}}; - if (x == 0) break; - {{{ makeSetValue('pstr', 'i', '_toupper(x)', 'i8') }}}; - i++; - } - }, - strcat__asm: true, strcat__sig: 'iii', strcat__deps: ['strlen'], @@ -3667,132 +3569,6 @@ LibraryManager.library = { // ctype.h // ========================================================================== - isascii: function(chr) { - return chr >= 0 && (chr & 0x80) == 0; - }, - toascii: function(chr) { - return chr & 0x7F; - }, - toupper: function(chr) { - if (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}) { - return chr - {{{ charCode('a') }}} + {{{ charCode('A') }}}; - } else { - return chr; - } - }, - _toupper: 'toupper', - toupper_l__deps: ['toupper'], - toupper_l: function(str, endptr, base) { - return _toupper(str, endptr, base); // no locale support yet - }, - - tolower__asm: true, - tolower__sig: 'ii', - tolower: function(chr) { - chr = chr|0; - if ((chr|0) < {{{ charCode('A') }}}) return chr|0; - if ((chr|0) > {{{ charCode('Z') }}}) return chr|0; - return (chr - {{{ charCode('A') }}} + {{{ charCode('a') }}})|0; - }, - _tolower: 'tolower', - tolower_l__deps: ['tolower'], - tolower_l: function(chr) { - return _tolower(chr); // no locale support yet - }, - - // The following functions are defined as macros in glibc. - islower: function(chr) { - return chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}; - }, - islower_l__deps: ['islower'], - islower_l: function(chr) { - return _islower(chr); // no locale support yet - }, - isupper: function(chr) { - return chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('Z') }}}; - }, - isupper_l__deps: ['isupper'], - isupper_l: function(chr) { - return _isupper(chr); // no locale support yet - }, - isalpha: function(chr) { - return (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}) || - (chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('Z') }}}); - }, - isalpha_l__deps: ['isalpha'], - isalpha_l: function(chr) { - return _isalpha(chr); // no locale support yet - }, - isdigit: function(chr) { - return chr >= {{{ charCode('0') }}} && chr <= {{{ charCode('9') }}}; - }, - isdigit_l__deps: ['isdigit'], - isdigit_l: function(chr) { - return _isdigit(chr); // no locale support yet - }, - isxdigit: function(chr) { - return (chr >= {{{ charCode('0') }}} && chr <= {{{ charCode('9') }}}) || - (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('f') }}}) || - (chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('F') }}}); - }, - isxdigit_l__deps: ['isxdigit'], - isxdigit_l: function(chr) { - return _isxdigit(chr); // no locale support yet - }, - isalnum: function(chr) { - return (chr >= {{{ charCode('0') }}} && chr <= {{{ charCode('9') }}}) || - (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}) || - (chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('Z') }}}); - }, - isalnum_l__deps: ['isalnum'], - isalnum_l: function(chr) { - return _isalnum(chr); // no locale support yet - }, - ispunct: function(chr) { - return (chr >= {{{ charCode('!') }}} && chr <= {{{ charCode('/') }}}) || - (chr >= {{{ charCode(':') }}} && chr <= {{{ charCode('@') }}}) || - (chr >= {{{ charCode('[') }}} && chr <= {{{ charCode('`') }}}) || - (chr >= {{{ charCode('{') }}} && chr <= {{{ charCode('~') }}}); - }, - ispunct_l__deps: ['ispunct'], - ispunct_l: function(chr) { - return _ispunct(chr); // no locale support yet - }, - isspace: function(chr) { - return (chr == 32) || (chr >= 9 && chr <= 13); - }, - isspace_l__deps: ['isspace'], - isspace_l: function(chr) { - return _isspace(chr); // no locale support yet - }, - isblank: function(chr) { - return chr == {{{ charCode(' ') }}} || chr == {{{ charCode('\t') }}}; - }, - isblank_l__deps: ['isblank'], - isblank_l: function(chr) { - return _isblank(chr); // no locale support yet - }, - iscntrl: function(chr) { - return (0 <= chr && chr <= 0x1F) || chr === 0x7F; - }, - iscntrl_l__deps: ['iscntrl'], - iscntrl_l: function(chr) { - return _iscntrl(chr); // no locale support yet - }, - isprint: function(chr) { - return 0x1F < chr && chr < 0x7F; - }, - isprint_l__deps: ['isprint'], - isprint_l: function(chr) { - return _isprint(chr); // no locale support yet - }, - isgraph: function(chr) { - return 0x20 < chr && chr < 0x7F; - }, - isgraph_l__deps: ['isgraph'], - isgraph_l: function(chr) { - return _isgraph(chr); // no locale support yet - }, // Lookup tables for glibc ctype implementation. __ctype_b_loc__deps: ['malloc'], __ctype_b_loc: function() { @@ -3908,12 +3684,18 @@ LibraryManager.library = { {{{ makeCopyValues('(ppdest+'+Runtime.QUANTUM_SIZE+')', '(ppsrc+'+Runtime.QUANTUM_SIZE+')', Runtime.QUANTUM_SIZE, 'null', null, 1) }}}; }, + llvm_bswap_i16__asm: true, + llvm_bswap_i16__sig: 'ii', llvm_bswap_i16: function(x) { - return ((x&0xff)<<8) | ((x>>8)&0xff); + x = x|0; + return (((x&0xff)<<8) | ((x>>8)&0xff))|0; }, + llvm_bswap_i32__asm: true, + llvm_bswap_i32__sig: 'ii', llvm_bswap_i32: function(x) { - return ((x&0xff)<<24) | (((x>>8)&0xff)<<16) | (((x>>16)&0xff)<<8) | (x>>>24); + x = x|0; + return (((x&0xff)<<24) | (((x>>8)&0xff)<<16) | (((x>>16)&0xff)<<8) | (x>>>24))|0; }, llvm_bswap_i64__deps: ['llvm_bswap_i32'], @@ -4670,23 +4452,6 @@ LibraryManager.library = { {{{ makeSetValue('intpart', 0, 'Math.floor(x)', 'float') }}}; return x - {{{ makeGetValue('intpart', 0, 'float') }}}; }, - frexp: function(x, exp_addr) { - var sig = 0, exp_ = 0; - if (x !== 0) { - var sign = 1; - if (x < 0) { - x = -x; - sign = -1; - } - var raw_exp = Math.log(x)/Math.log(2); - exp_ = Math.ceil(raw_exp); - if (exp_ === raw_exp) exp_ += 1; - sig = sign*x/Math.pow(2, exp_); - } - {{{ makeSetValue('exp_addr', 0, 'exp_', 'i32') }}}; - return sig; - }, - frexpf: 'frexp', finite: function(x) { return isFinite(x); }, @@ -5771,7 +5536,7 @@ LibraryManager.library = { pattern = pattern.replace(new RegExp('\\%'+pattern[i+1], 'g'), ''); } - var matches = new RegExp('^'+pattern).exec(Pointer_stringify(buf)) + var matches = new RegExp('^'+pattern, "i").exec(Pointer_stringify(buf)) // Module['print'](Pointer_stringify(buf)+ ' is matched by '+((new RegExp('^'+pattern)).source)+' into: '+JSON.stringify(matches)); function initDate() { @@ -6084,15 +5849,15 @@ LibraryManager.library = { var i = 0; setjmpId = (setjmpId+1)|0; {{{ makeSetValueAsm('env', '0', 'setjmpId', 'i32') }}}; - while ((i|0) < {{{ 2*MAX_SETJMPS }}}) { - if ({{{ makeGetValueAsm('table', '(i<<2)', 'i32') }}} == 0) { - {{{ makeSetValueAsm('table', '(i<<2)', 'setjmpId', 'i32') }}}; - {{{ makeSetValueAsm('table', '(i<<2)+4', 'label', 'i32') }}}; + while ((i|0) < {{{ MAX_SETJMPS }}}) { + if ({{{ makeGetValueAsm('table', '(i<<3)', 'i32') }}} == 0) { + {{{ makeSetValueAsm('table', '(i<<3)', 'setjmpId', 'i32') }}}; + {{{ makeSetValueAsm('table', '(i<<3)+4', 'label', 'i32') }}}; // prepare next slot - {{{ makeSetValueAsm('table', '(i<<2)+8', '0', 'i32') }}}; + {{{ makeSetValueAsm('table', '(i<<3)+8', '0', 'i32') }}}; return 0; } - i = (i+2)|0; + i = i+1|0; } {{{ makePrintChars('too many setjmps in a function call, build with a higher value for MAX_SETJMPS') }}}; abort(0); @@ -6106,12 +5871,12 @@ LibraryManager.library = { table = table|0; var i = 0, curr = 0; while ((i|0) < {{{ MAX_SETJMPS }}}) { - curr = {{{ makeGetValueAsm('table', '(i<<2)', 'i32') }}}; + curr = {{{ makeGetValueAsm('table', '(i<<3)', 'i32') }}}; if ((curr|0) == 0) break; if ((curr|0) == (id|0)) { - return {{{ makeGetValueAsm('table', '(i<<2)+4', 'i32') }}}; + return {{{ makeGetValueAsm('table', '(i<<3)+4', 'i32') }}}; } - i = (i+2)|0; + i = i+1|0; } return 0; }, @@ -6197,8 +5962,10 @@ LibraryManager.library = { raise__deps: ['$ERRNO_CODES', '__setErrNo'], raise: function(sig) { - // TODO: ___setErrNo(ERRNO_CODES.ENOSYS); +#if ASSERTIONS + Runtime.warnOnce('raise() returning an error as we do not support it'); +#endif return -1; }, diff --git a/src/library_browser.js b/src/library_browser.js index 4be7315e..57ca5a24 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -196,41 +196,42 @@ mergeInto(LibraryManager.library, { // Canvas event setup var canvas = Module['canvas']; - - // forced aspect ratio can be enabled by defining 'forcedAspectRatio' on Module - // Module['forcedAspectRatio'] = 4 / 3; - - canvas.requestPointerLock = canvas['requestPointerLock'] || - canvas['mozRequestPointerLock'] || - canvas['webkitRequestPointerLock'] || - canvas['msRequestPointerLock'] || - function(){}; - canvas.exitPointerLock = document['exitPointerLock'] || - document['mozExitPointerLock'] || - document['webkitExitPointerLock'] || - document['msExitPointerLock'] || - function(){}; // no-op if function does not exist - canvas.exitPointerLock = canvas.exitPointerLock.bind(document); - - function pointerLockChange() { - Browser.pointerLock = document['pointerLockElement'] === canvas || - document['mozPointerLockElement'] === canvas || - document['webkitPointerLockElement'] === canvas || - document['msPointerLockElement'] === canvas; - } + if (canvas) { + // forced aspect ratio can be enabled by defining 'forcedAspectRatio' on Module + // Module['forcedAspectRatio'] = 4 / 3; + + canvas.requestPointerLock = canvas['requestPointerLock'] || + canvas['mozRequestPointerLock'] || + canvas['webkitRequestPointerLock'] || + canvas['msRequestPointerLock'] || + function(){}; + canvas.exitPointerLock = document['exitPointerLock'] || + document['mozExitPointerLock'] || + document['webkitExitPointerLock'] || + document['msExitPointerLock'] || + function(){}; // no-op if function does not exist + canvas.exitPointerLock = canvas.exitPointerLock.bind(document); + + function pointerLockChange() { + Browser.pointerLock = document['pointerLockElement'] === canvas || + document['mozPointerLockElement'] === canvas || + document['webkitPointerLockElement'] === canvas || + document['msPointerLockElement'] === canvas; + } - document.addEventListener('pointerlockchange', pointerLockChange, false); - document.addEventListener('mozpointerlockchange', pointerLockChange, false); - document.addEventListener('webkitpointerlockchange', pointerLockChange, false); - document.addEventListener('mspointerlockchange', pointerLockChange, false); + document.addEventListener('pointerlockchange', pointerLockChange, false); + document.addEventListener('mozpointerlockchange', pointerLockChange, false); + document.addEventListener('webkitpointerlockchange', pointerLockChange, false); + document.addEventListener('mspointerlockchange', pointerLockChange, false); - if (Module['elementPointerLock']) { - canvas.addEventListener("click", function(ev) { - if (!Browser.pointerLock && canvas.requestPointerLock) { - canvas.requestPointerLock(); - ev.preventDefault(); - } - }, false); + if (Module['elementPointerLock']) { + canvas.addEventListener("click", function(ev) { + if (!Browser.pointerLock && canvas.requestPointerLock) { + canvas.requestPointerLock(); + ev.preventDefault(); + } + }, false); + } } }, @@ -321,11 +322,6 @@ mergeInto(LibraryManager.library, { #endif // Set the background of the WebGL canvas to black canvas.style.backgroundColor = "black"; - - // Warn on context loss - canvas.addEventListener('webglcontextlost', function(event) { - alert('WebGL context lost. You will need to reload the page.'); - }, false); } if (setInModule) { GLctx = Module.ctx = ctx; @@ -429,11 +425,13 @@ mergeInto(LibraryManager.library, { }); }, safeSetTimeout: function(func, timeout) { + Module['noExitRuntime'] = true; return setTimeout(function() { if (!ABORT) func(); }, timeout); }, safeSetInterval: function(func, timeout) { + Module['noExitRuntime'] = true; return setInterval(function() { if (!ABORT) func(); }, timeout); @@ -475,7 +473,21 @@ mergeInto(LibraryManager.library, { }, getMouseWheelDelta: function(event) { - return Math.max(-1, Math.min(1, event.type === 'DOMMouseScroll' ? event.detail : -event.wheelDelta)); + var delta = 0; + switch (event.type) { + case 'DOMMouseScroll': + delta = event.detail; + break; + case 'mousewheel': + delta = -event.wheelDelta; + break; + case 'wheel': + delta = event.deltaY; + break; + default: + throw 'unrecognized mouse wheel event: ' + event.type; + } + return Math.max(-1, Math.min(1, delta)); }, mouseX: 0, @@ -685,15 +697,22 @@ mergeInto(LibraryManager.library, { emscripten_async_wget: function(url, file, onload, onerror) { var _url = Pointer_stringify(url); var _file = Pointer_stringify(file); + function doCallback(callback) { + if (callback) { + var stack = Runtime.stackSave(); + Runtime.dynCall('vi', callback, [allocate(intArrayFromString(_file), 'i8', ALLOC_STACK)]); + Runtime.stackRestore(stack); + } + } FS.createPreloadedFile( PATH.dirname(_file), PATH.basename(_file), _url, true, true, function() { - if (onload) Runtime.dynCall('vi', onload, [file]); + doCallback(onload); }, function() { - if (onerror) Runtime.dynCall('vi', onerror, [file]); + doCallback(onerror); } ); }, @@ -724,7 +743,11 @@ mergeInto(LibraryManager.library, { http.onload = function http_onload(e) { if (http.status == 200) { FS.createDataFile( _file.substr(0, index), _file.substr(index + 1), new Uint8Array(http.response), true, true); - if (onload) Runtime.dynCall('vii', onload, [arg, file]); + if (onload) { + var stack = Runtime.stackSave(); + Runtime.dynCall('vii', onload, [arg, allocate(intArrayFromString(_file), 'i8', ALLOC_STACK)]); + Runtime.stackRestore(stack); + } } else { if (onerror) Runtime.dynCall('vii', onerror, [arg, http.status]); } @@ -737,8 +760,8 @@ mergeInto(LibraryManager.library, { // PROGRESS http.onprogress = function http_onprogress(e) { - if (e.lengthComputable || (e.lengthComputable === undefined && e.totalSize != 0)) { - var percentComplete = (e.position / e.totalSize)*100; + if (e.lengthComputable || (e.lengthComputable === undefined && e.total != 0)) { + var percentComplete = (e.loaded / e.total)*100; if (onprogress) Runtime.dynCall('vii', onprogress, [arg, percentComplete]); } }; @@ -886,6 +909,8 @@ mergeInto(LibraryManager.library, { emscripten_set_main_loop: function(func, fps, simulateInfiniteLoop, arg) { Module['noExitRuntime'] = true; + assert(!Browser.mainLoop.scheduler, 'there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one, if you want to'); + Browser.mainLoop.runner = function Browser_mainLoop_runner() { if (ABORT) return; if (Browser.mainLoop.queue.length > 0) { @@ -1150,6 +1175,39 @@ mergeInto(LibraryManager.library, { var info = Browser.workers[id]; if (!info) return -1; return info.awaited; + }, + + emscripten_get_preloaded_image_data: function(path, w, h) { + if (typeof path === "number") { + path = Pointer_stringify(path); + } + + path = PATH.resolve(path); + + var canvas = Module["preloadedImages"][path]; + if (canvas) { + var ctx = canvas.getContext("2d"); + var image = ctx.getImageData(0, 0, canvas.width, canvas.height); + var buf = _malloc(canvas.width * canvas.height * 4); + + HEAPU8.set(image.data, buf); + + {{{ makeSetValue('w', '0', 'canvas.width', 'i32') }}}; + {{{ makeSetValue('h', '0', 'canvas.height', 'i32') }}}; + return buf; + } + + return 0; + }, + + emscripten_get_preloaded_image_data_from_FILE__deps: ['emscripten_get_preloaded_image_data'], + emscripten_get_preloaded_image_data_from_FILE: function(file, w, h) { + var stream = FS.getStreamFromPtr(file); + if (stream) { + return _emscripten_get_preloaded_image_data(stream.path, w, h); + } + + return 0; } }); diff --git a/src/library_fs.js b/src/library_fs.js index 3d0036ee..5f7f1dea 100644 --- a/src/library_fs.js +++ b/src/library_fs.js @@ -26,7 +26,13 @@ mergeInto(LibraryManager.library, { // This is set to false when the runtime is initialized, allowing you // to modify the filesystem freely before run() is called. ignorePermissions: true, - + trackingDelegate: {}, + tracking: { + openFlags: { + READ: 1 << 0, + WRITE: 1 << 1 + } + }, ErrnoError: null, // set during init genericErrors: {}, @@ -394,16 +400,12 @@ mergeInto(LibraryManager.library, { } }); } - if (stream.__proto__) { - // reuse the object - stream.__proto__ = FS.FSStream.prototype; - } else { - var newStream = new FS.FSStream(); - for (var p in stream) { - newStream[p] = stream[p]; - } - stream = newStream; + // clone it, so we can return an instance of FSStream + var newStream = new FS.FSStream(); + for (var p in stream) { + newStream[p] = stream[p]; } + stream = newStream; var fd = FS.nextfd(fd_start, fd_end); stream.fd = fd; FS.streams[fd] = stream; @@ -717,6 +719,13 @@ mergeInto(LibraryManager.library, { throw new FS.ErrnoError(err); } } + try { + if (FS.trackingDelegate['willMovePath']) { + FS.trackingDelegate['willMovePath'](old_path, new_path); + } + } catch(e) { + console.log("FS.trackingDelegate['willMovePath']('"+old_path+"', '"+new_path+"') threw an exception: " + e.message); + } // remove the node from the lookup hash FS.hashRemoveNode(old_node); // do the underlying fs rename @@ -729,6 +738,11 @@ mergeInto(LibraryManager.library, { // changed its name) FS.hashAddNode(old_node); } + try { + if (FS.trackingDelegate['onMovePath']) FS.trackingDelegate['onMovePath'](old_path, new_path); + } catch(e) { + console.log("FS.trackingDelegate['onMovePath']('"+old_path+"', '"+new_path+"') threw an exception: " + e.message); + } }, rmdir: function(path) { var lookup = FS.lookupPath(path, { parent: true }); @@ -745,8 +759,20 @@ mergeInto(LibraryManager.library, { if (FS.isMountpoint(node)) { throw new FS.ErrnoError(ERRNO_CODES.EBUSY); } + try { + if (FS.trackingDelegate['willDeletePath']) { + FS.trackingDelegate['willDeletePath'](path); + } + } catch(e) { + console.log("FS.trackingDelegate['willDeletePath']('"+path+"') threw an exception: " + e.message); + } parent.node_ops.rmdir(parent, name); FS.destroyNode(node); + try { + if (FS.trackingDelegate['onDeletePath']) FS.trackingDelegate['onDeletePath'](path); + } catch(e) { + console.log("FS.trackingDelegate['onDeletePath']('"+path+"') threw an exception: " + e.message); + } }, readdir: function(path) { var lookup = FS.lookupPath(path, { follow: true }); @@ -773,8 +799,20 @@ mergeInto(LibraryManager.library, { if (FS.isMountpoint(node)) { throw new FS.ErrnoError(ERRNO_CODES.EBUSY); } + try { + if (FS.trackingDelegate['willDeletePath']) { + FS.trackingDelegate['willDeletePath'](path); + } + } catch(e) { + console.log("FS.trackingDelegate['willDeletePath']('"+path+"') threw an exception: " + e.message); + } parent.node_ops.unlink(parent, name); FS.destroyNode(node); + try { + if (FS.trackingDelegate['onDeletePath']) FS.trackingDelegate['onDeletePath'](path); + } catch(e) { + console.log("FS.trackingDelegate['onDeletePath']('"+path+"') threw an exception: " + e.message); + } }, readlink: function(path) { var lookup = FS.lookupPath(path); @@ -969,6 +1007,20 @@ mergeInto(LibraryManager.library, { Module['printErr']('read file: ' + path); } } + try { + if (FS.trackingDelegate['onOpenFile']) { + var trackingFlags = 0; + if ((flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_WRONLY') }}}) { + trackingFlags |= FS.tracking.openFlags.READ; + } + if ((flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_RDONLY') }}}) { + trackingFlags |= FS.tracking.openFlags.WRITE; + } + FS.trackingDelegate['onOpenFile'](path, trackingFlags); + } + } catch(e) { + console.log("FS.trackingDelegate['onOpenFile']('"+path+"', flags) threw an exception: " + e.message); + } return stream; }, close: function(stream) { @@ -1038,6 +1090,11 @@ mergeInto(LibraryManager.library, { } var bytesWritten = stream.stream_ops.write(stream, buffer, offset, length, position, canOwn); if (!seeking) stream.position += bytesWritten; + try { + if (stream.path && FS.trackingDelegate['onWriteToFile']) FS.trackingDelegate['onWriteToFile'](stream.path); + } catch(e) { + console.log("FS.trackingDelegate['onWriteToFile']('"+path+"') threw an exception: " + e.message); + } return bytesWritten; }, allocate: function(stream, offset, length) { diff --git a/src/library_gl.js b/src/library_gl.js index 851b01b1..2659a9d9 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -431,21 +431,42 @@ var LibraryGL = { sizePerPixel = 2; break; default: - throw 'Invalid format (' + format + ')'; + GL.recordError(0x0500); // GL_INVALID_ENUM +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glTex[Sub]Image, type: ' + type + ', format: ' + format); +#endif + return { + pixels: null, + internalFormat: 0x0 + }; } break; case 0x1403 /* GL_UNSIGNED_SHORT */: if (format == 0x1902 /* GL_DEPTH_COMPONENT */) { sizePerPixel = 2; } else { - throw 'Invalid format (' + format + ')'; + GL.recordError(0x0500); // GL_INVALID_ENUM +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glTex[Sub]Image, type: ' + type + ', format: ' + format); +#endif + return { + pixels: null, + internalFormat: 0x0 + }; } break; case 0x1405 /* GL_UNSIGNED_INT */: if (format == 0x1902 /* GL_DEPTH_COMPONENT */) { sizePerPixel = 4; } else { - throw 'Invalid format (' + format + ')'; + GL.recordError(0x0500); // GL_INVALID_ENUM +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glTex[Sub]Image, type: ' + type + ', format: ' + format); +#endif + return { + pixels: null, + internalFormat: 0x0 + }; } break; case 0x84FA /* UNSIGNED_INT_24_8_WEBGL */: @@ -468,12 +489,26 @@ var LibraryGL = { sizePerPixel = 4*4; break; default: - throw 'Invalid format (' + format + ')'; + GL.recordError(0x0500); // GL_INVALID_ENUM +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glTex[Sub]Image, type: ' + type + ', format: ' + format); +#endif + return { + pixels: null, + internalFormat: 0x0 + }; } internalFormat = GLctx.RGBA; break; default: - throw 'Invalid type (' + type + ')'; + GL.recordError(0x0500); // GL_INVALID_ENUM +#if GL_ASSERTIONS + Module.printErr('GL_INVALID_ENUM in glTex[Sub]Image, type: ' + type + ', format: ' + format); +#endif + return { + pixels: null, + internalFormat: 0x0 + }; } var bytes = GL.computeImageSize(width, height, sizePerPixel, GL.unpackAlignment); if (type == 0x1401 /* GL_UNSIGNED_BYTE */) { @@ -488,7 +523,7 @@ var LibraryGL = { return { pixels: pixels, internalFormat: internalFormat - } + }; }, #if GL_FFP_ONLY diff --git a/src/library_glfw.js b/src/library_glfw.js index 04dd4a0a..6dfea101 100644 --- a/src/library_glfw.js +++ b/src/library_glfw.js @@ -211,7 +211,7 @@ var LibraryGLFW = { }, onMouseWheel: function(event) { - GLFW.wheelPos += Browser.getMouseWheelDelta(event); + GLFW.wheelPos -= Browser.getMouseWheelDelta(event); if (GLFW.mouseWheelFunc && event.target == Module["canvas"]) { Runtime.dynCall('vi', GLFW.mouseWheelFunc, [GLFW.wheelPos]); diff --git a/src/library_html5.js b/src/library_html5.js index d9376c2a..d5d0cd66 100644 --- a/src/library_html5.js +++ b/src/library_html5.js @@ -1307,6 +1307,12 @@ var LibraryJSEvents = { JSEvents.registerWebGlEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefine('EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED') }}}, "webglcontextrestored"); return {{{ cDefine('EMSCRIPTEN_RESULT_SUCCESS') }}}; }, + + emscripten_is_webgl_context_lost: function(target) { + // TODO: In the future if multiple GL contexts are supported, use the 'target' parameter to find the canvas to query. + if (!Module['ctx']) return true; // No context ~> lost context. + return Module['ctx'].isContextLost(); + } }; autoAddDeps(LibraryJSEvents, '$JSEvents'); diff --git a/src/library_sdl.js b/src/library_sdl.js index eedb8c48..a01b3c6c 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -430,6 +430,15 @@ var LibrarySDL = { savedKeydown: null, receiveEvent: function(event) { + function unpressAllPressedKeys() { + // Un-press all pressed keys: TODO + for (var code in SDL.keyboardMap) { + SDL.events.push({ + type: 'keyup', + keyCode: SDL.keyboardMap[code] + }); + } + }; switch(event.type) { case 'touchstart': case 'touchmove': { event.preventDefault(); @@ -532,7 +541,7 @@ var LibrarySDL = { } } // fall through - case 'keydown': case 'keyup': case 'keypress': case 'mousedown': case 'mouseup': case 'DOMMouseScroll': case 'mousewheel': + case 'keydown': case 'keyup': case 'keypress': case 'mousedown': case 'mouseup': case 'DOMMouseScroll': case 'mousewheel': case 'wheel': // If we preventDefault on keydown events, the subsequent keypress events // won't fire. However, it's fine (and in some cases necessary) to // preventDefault for keys that don't generate a character. Otherwise, @@ -541,21 +550,40 @@ var LibrarySDL = { event.preventDefault(); } - if (event.type == 'DOMMouseScroll' || event.type == 'mousewheel') { + if (event.type == 'DOMMouseScroll' || event.type == 'mousewheel' || event.type == 'wheel') { + // Simulate old-style SDL events representing mouse wheel input as buttons var button = Browser.getMouseWheelDelta(event) > 0 ? 4 : 3; - var event2 = { + var event1 = { type: 'mousedown', button: button, pageX: event.pageX, pageY: event.pageY }; - SDL.events.push(event2); - event = { + SDL.events.push(event1); + var event2 = { type: 'mouseup', button: button, pageX: event.pageX, pageY: event.pageY }; + SDL.events.push(event2); + + // Convert DOMMouseScroll events to wheel events for new style SDL events. + if (event.type == 'DOMMouseScroll') { + SDL.events.push({ + type: 'wheel', + deltaX: 0, + deltaY: -event.detail, + }); + break; + } else if (event.type == 'mousewheel') { + SDL.events.push({ + type: 'wheel', + deltaX: 0, + deltaY: event.wheelDelta, + }); + break; + } } else if (event.type == 'mousedown') { SDL.DOMButtons[event.button] = 1; SDL.events.push({ @@ -635,18 +663,23 @@ var LibrarySDL = { } event.preventDefault(); break; + case 'focus': + SDL.events.push(event); + event.preventDefault(); + break; case 'blur': - case 'visibilitychange': { - // Un-press all pressed keys: TODO - for (var code in SDL.keyboardMap) { - SDL.events.push({ - type: 'keyup', - keyCode: SDL.keyboardMap[code] - }); - } + SDL.events.push(event); + unpressAllPressedKeys(); + event.preventDefault(); + break; + case 'visibilitychange': + SDL.events.push({ + type: 'visibilitychange', + visible: !document.hidden + }); + unpressAllPressedKeys(); event.preventDefault(); break; - } case 'unload': if (Browser.mainLoop.runner) { SDL.events.push(event); @@ -686,7 +719,6 @@ var LibrarySDL = { } else { code = SDL.keyCodes[event.keyCode] || event.keyCode; } - {{{ makeSetValue('SDL.keyboardState', 'code', 'down', 'i8') }}}; // TODO: lmeta, rmeta, numlock, capslock, KMOD_MODE, KMOD_RESERVED SDL.modState = ({{{ makeGetValue('SDL.keyboardState', '1248', 'i8') }}} ? 0x0040 | 0x0080 : 0) | // KMOD_LCTRL & KMOD_RCTRL @@ -788,8 +820,15 @@ var LibrarySDL = { } break; } + case 'wheel': { + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.x, 'event.deltaX', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.y, 'event.deltaY', 'i32') }}}; + break; + } case 'touchstart': case 'touchend': case 'touchmove': { var touch = event.touch; + if (!Browser.touches[touch.identifier]) break; var w = Module['canvas'].width; var h = Module['canvas'].height; var x = Browser.touches[touch.identifier].x / w; @@ -840,6 +879,29 @@ var LibrarySDL = { {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.value, 'SDL.joystickAxisValueConversion(event.value)', 'i32') }}}; break; } + case 'focus': { + var SDL_WINDOWEVENT_FOCUS_GAINED = 12 /* SDL_WINDOWEVENT_FOCUS_GAINED */; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, '0', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'SDL_WINDOWEVENT_FOCUS_GAINED', 'i8') }}}; + break; + } + case 'blur': { + var SDL_WINDOWEVENT_FOCUS_LOST = 13 /* SDL_WINDOWEVENT_FOCUS_LOST */; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, '0', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'SDL_WINDOWEVENT_FOCUS_LOST', 'i8') }}}; + break; + } + case 'visibilitychange': { + var SDL_WINDOWEVENT_SHOWN = 1 /* SDL_WINDOWEVENT_SHOWN */; + var SDL_WINDOWEVENT_HIDDEN = 2 /* SDL_WINDOWEVENT_HIDDEN */; + var visibilityEventID = event.visible ? SDL_WINDOWEVENT_SHOWN : SDL_WINDOWEVENT_HIDDEN; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, 0, 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'visibilityEventID' , 'i8') }}}; + break; + } default: throw 'Unhandled SDL event: ' + event.type; } }, @@ -880,12 +942,80 @@ var LibrarySDL = { if (!info) return 0; var ret = info.volume * 128; // MIX_MAX_VOLUME if (volume != -1) { - info.volume = volume / 128; - if (info.audio) info.audio.volume = info.volume; + info.volume = Math.min(Math.max(volume, 0), 128) / 128; + if (info.audio) { + try { + info.audio.volume = info.volume; // For <audio> element + if (info.audio.webAudioGainNode) info.audio.webAudioGainNode['gain']['value'] = info.volume; // For WebAudio playback + } catch(e) { + Module.printErr('setGetVolume failed to set audio volume: ' + e); + } + } } return ret; }, + // Plays out an SDL audio resource that was loaded with the Mix_Load APIs, when using Web Audio.. + playWebAudio: function(audio) { + if (!audio) return; + if (audio.webAudioNode) return; // This instance is already playing, don't start again. + if (!SDL.webAudioAvailable()) return; + try { + var webAudio = audio.resource.webAudio; + audio.paused = false; + if (!webAudio.decodedBuffer) { + if (webAudio.onDecodeComplete === undefined) abort("Cannot play back audio object that was not loaded"); + webAudio.onDecodeComplete.push(function() { if (!audio.paused) SDL.playWebAudio(audio); }); + return; + } + audio.webAudioNode = SDL.audioContext['createBufferSource'](); + audio.webAudioNode['buffer'] = webAudio.decodedBuffer; + audio.webAudioNode['loop'] = audio.loop; + audio.webAudioNode['onended'] = function() { audio.onended(); } // For <media> element compatibility, route the onended signal to the instance. + + // Add an intermediate gain node to control volume. + audio.webAudioGainNode = SDL.audioContext['createGain'](); + audio.webAudioGainNode['gain']['value'] = audio.volume; + audio.webAudioNode['connect'](audio.webAudioGainNode); + audio.webAudioGainNode['connect'](SDL.audioContext['destination']); + audio.webAudioNode['start'](0, audio.currentPosition); + audio.startTime = SDL.audioContext['currentTime'] - audio.currentPosition; + } catch(e) { + Module.printErr('playWebAudio failed: ' + e); + } + }, + + // Pausea an SDL audio resource that was played with Web Audio.. + pauseWebAudio: function(audio) { + if (!audio) return; + if (audio.webAudioNode) { + try { + // Remember where we left off, so that if/when we resume, we can restart the playback at a proper place. + audio.currentPosition = (SDL.audioContext['currentTime'] - audio.startTime) % audio.resource.webAudio.decodedBuffer.duration; + // Important: When we reach here, the audio playback is stopped by the user. But when calling .stop() below, the Web Audio + // graph will send the onended signal, but we don't want to process that, since pausing should not clear/destroy the audio + // channel. + audio.webAudioNode['onended'] = undefined; + audio.webAudioNode.stop(); + audio.webAudioNode = undefined; + } catch(e) { + Module.printErr('pauseWebAudio failed: ' + e); + } + } + audio.paused = true; + }, + + openAudioContext: function() { + // Initialize Web Audio API if we haven't done so yet. Note: Only initialize Web Audio context ever once on the web page, + // since initializing multiple times fails on Chrome saying 'audio resources have been exhausted'. + if (!SDL.audioContext) { + if (typeof(AudioContext) !== 'undefined') SDL.audioContext = new AudioContext(); + else if (typeof(webkitAudioContext) !== 'undefined') SDL.audioContext = new webkitAudioContext(); + } + }, + + webAudioAvailable: function() { return !!SDL.audioContext; }, + fillWebAudioBufferFromHeap: function(heapPtr, sizeSamplesPerChannel, dstAudioBuffer) { // The input audio data is interleaved across the channels, i.e. [L, R, L, R, L, R, ...] and is either 8-bit or 16-bit as // supported by the SDL API. The output audio wave data for Web Audio API must be in planar buffers of [-1,1]-normalized Float32 data, @@ -1043,6 +1173,7 @@ var LibrarySDL = { document.addEventListener("keydown", SDL.receiveEvent); document.addEventListener("keyup", SDL.receiveEvent); document.addEventListener("keypress", SDL.receiveEvent); + window.addEventListener("focus", SDL.receiveEvent); window.addEventListener("blur", SDL.receiveEvent); document.addEventListener("visibilitychange", SDL.receiveEvent); } @@ -1065,11 +1196,16 @@ var LibrarySDL = { SDL.DOMEventToSDLEvent['mousedown'] = 0x401 /* SDL_MOUSEBUTTONDOWN */; SDL.DOMEventToSDLEvent['mouseup'] = 0x402 /* SDL_MOUSEBUTTONUP */; SDL.DOMEventToSDLEvent['mousemove'] = 0x400 /* SDL_MOUSEMOTION */; + SDL.DOMEventToSDLEvent['wheel'] = 0x403 /* SDL_MOUSEWHEEL */; SDL.DOMEventToSDLEvent['touchstart'] = 0x700 /* SDL_FINGERDOWN */; SDL.DOMEventToSDLEvent['touchend'] = 0x701 /* SDL_FINGERUP */; SDL.DOMEventToSDLEvent['touchmove'] = 0x702 /* SDL_FINGERMOTION */; SDL.DOMEventToSDLEvent['unload'] = 0x100 /* SDL_QUIT */; SDL.DOMEventToSDLEvent['resize'] = 0x7001 /* SDL_VIDEORESIZE/SDL_EVENT_COMPAT2 */; + SDL.DOMEventToSDLEvent['visibilitychange'] = 0x200 /* SDL_WINDOWEVENT */; + SDL.DOMEventToSDLEvent['focus'] = 0x200 /* SDL_WINDOWEVENT */; + SDL.DOMEventToSDLEvent['blur'] = 0x200 /* SDL_WINDOWEVENT */; + // These are not technically DOM events; the HTML gamepad API is poll-based. // However, we define them here, as the rest of the SDL code assumes that // all SDL events originate as DOM events. @@ -1139,7 +1275,7 @@ var LibrarySDL = { }, SDL_SetVideoMode: function(width, height, depth, flags) { - ['touchstart', 'touchend', 'touchmove', 'mousedown', 'mouseup', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mouseout'].forEach(function(event) { + ['touchstart', 'touchend', 'touchmove', 'mousedown', 'mouseup', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'wheel', 'mouseout'].forEach(function(event) { Module['canvas'].addEventListener(event, SDL.receiveEvent, true); }); @@ -1182,11 +1318,11 @@ var LibrarySDL = { for (var i = 0; i < SDL.numChannels; ++i) { if (SDL.channels[i].audio) { SDL.channels[i].audio.pause(); + SDL.channels[i].audio = undefined; } } - if (SDL.music.audio) { - SDL.music.audio.pause(); - } + if (SDL.music.audio) SDL.music.audio.pause(); + SDL.music.audio = undefined; Module.print('SDL_Quit called (and ignored)'); }, @@ -1936,15 +2072,8 @@ var LibrarySDL = { } else { // Initialize Web Audio API if we haven't done so yet. Note: Only initialize Web Audio context ever once on the web page, // since initializing multiple times fails on Chrome saying 'audio resources have been exhausted'. - if (!SDL.audioContext) { - if (typeof(AudioContext) !== 'undefined') { - SDL.audioContext = new AudioContext(); - } else if (typeof(webkitAudioContext) !== 'undefined') { - SDL.audioContext = new webkitAudioContext(); - } else { - throw 'Web Audio API is not available!'; - } - } + SDL.openAudioContext(); + if (!SDL.audioContext) throw 'Web Audio API is not available!'; SDL.audio.soundSource = new Array(); // Use an array of sound sources as a ring buffer to queue blocks of synthesized audio to Web Audio API. SDL.audio.nextSoundSource = 0; // Index of the next sound buffer in the ring buffer queue to play. SDL.audio.nextPlayTime = 0; // Time in seconds when the next audio block is due to start. @@ -2127,6 +2256,7 @@ var LibrarySDL = { Mix_Quit: function(){}, Mix_OpenAudio: function(frequency, format, channels, chunksize) { + SDL.openAudioContext(); SDL.allocateChannels(32); // Just record the values for a later call to Mix_QuickLoad_RAW SDL.mixerFrequency = frequency; @@ -2169,6 +2299,7 @@ var LibrarySDL = { var filename = ''; var audio; + var webAudio; var bytes; if (rwops.filename !== undefined) { @@ -2176,7 +2307,7 @@ var LibrarySDL = { var raw = Module["preloadedAudios"][filename]; if (!raw) { if (raw === null) Module.printErr('Trying to reuse preloaded audio, but freePreloadedMediaOnUse is set!'); - Runtime.warnOnce('Cannot find preloaded audio ' + filename); + if (!Module.noAudioDecoding) Runtime.warnOnce('Cannot find preloaded audio ' + filename); // see if we can read the file-contents from the in-memory FS try { @@ -2192,45 +2323,84 @@ var LibrarySDL = { audio = raw; } else if (rwops.bytes !== undefined) { - bytes = HEAPU8.subarray(rwops.bytes, rwops.bytes + rwops.count); + // For Web Audio context buffer decoding, we must make a clone of the audio data, but for <media> element, + // a view to existing data is sufficient. + if (SDL.webAudioAvailable()) bytes = HEAPU8.buffer.slice(rwops.bytes, rwops.bytes + rwops.count); + else bytes = HEAPU8.subarray(rwops.bytes, rwops.bytes + rwops.count); } else { return 0; } - // Here, we didn't find a preloaded audio but we either were passed a filepath for - // which we loaded bytes, or we were passed some bytes - if (audio === undefined && bytes) { + var arrayBuffer = bytes ? bytes.buffer || bytes : bytes; + + // To allow user code to work around browser bugs with audio playback on <audio> elements an Web Audio, enable + // the user code to hook in a callback to decide on a file basis whether each file should use Web Audio or <audio> for decoding and playback. + // In particular, see https://bugzilla.mozilla.org/show_bug.cgi?id=654787 and ?id=1012801 for tradeoffs. + var canPlayWithWebAudio = Module['SDL_canPlayWithWebAudio'] === undefined || Module['SDL_canPlayWithWebAudio'](filename, arrayBuffer); + + if (bytes !== undefined && SDL.webAudioAvailable() && canPlayWithWebAudio) { + audio = undefined; + webAudio = {}; + // The audio decoding process is asynchronous, which gives trouble if user code plays the audio data back immediately + // after loading. Therefore prepare an array of callback handlers to run when this audio decoding is complete, which + // will then start the playback (with some delay). + webAudio.onDecodeComplete = []; // While this member array exists, decoding hasn't finished yet. + function onDecodeComplete(data) { + webAudio.decodedBuffer = data; + // Call all handlers that were waiting for this decode to finish, and clear the handler list. + webAudio.onDecodeComplete.forEach(function(e) { e(); }); + webAudio.onDecodeComplete = undefined; // Don't allow more callback handlers since audio has finished decoding. + } + + SDL.audioContext['decodeAudioData'](arrayBuffer, onDecodeComplete); + } else if (audio === undefined && bytes) { + // Here, we didn't find a preloaded audio but we either were passed a filepath for + // which we loaded bytes, or we were passed some bytes var blob = new Blob([bytes], {type: rwops.mimetype}); var url = URL.createObjectURL(blob); audio = new Audio(); audio.src = url; + audio.mozAudioChannelType = 'content'; // bugzilla 910340 } var id = SDL.audios.length; // Keep the loaded audio in the audio arrays, ready for playback SDL.audios.push({ source: filename, - audio: audio + audio: audio, // Points to the <audio> element, if loaded + webAudio: webAudio // Points to a Web Audio -specific resource object, if loaded }); return id; }, Mix_QuickLoad_RAW: function(mem, len) { - var audio = new Audio(); - // Record the number of channels and frequency for later usage - audio.numChannels = SDL.mixerNumChannels; - audio.frequency = SDL.mixerFrequency; + var audio; + var webAudio; + var numSamples = len >> 1; // len is the length in bytes, and the array contains 16-bit PCM values var buffer = new Float32Array(numSamples); for (var i = 0; i < numSamples; ++i) { buffer[i] = ({{{ makeGetValue('mem', 'i*2', 'i16', 0, 0) }}}) / 0x8000; // hardcoded 16-bit audio, signed (TODO: reSign if not ta2?) } - // FIXME: doesn't make sense to keep the audio element in the buffer + + if (SDL.webAudioAvailable()) { + webAudio = {}; + webAudio.decodedBuffer = buffer; + } else { + var audio = new Audio(); + audio.mozAudioChannelType = 'content'; // bugzilla 910340 + // Record the number of channels and frequency for later usage + audio.numChannels = SDL.mixerNumChannels; + audio.frequency = SDL.mixerFrequency; + // FIXME: doesn't make sense to keep the audio element in the buffer + } + var id = SDL.audios.length; SDL.audios.push({ source: '', audio: audio, + webAudio: webAudio, buffer: buffer }); return id; @@ -2243,13 +2413,12 @@ var LibrarySDL = { SDL.channelMinimumNumber = num; }, Mix_PlayChannel: function(channel, id, loops) { - // TODO: handle loops + // TODO: handle fixed amount of N loops. Currently loops either 0 or infinite times. // Get the audio element associated with the ID var info = SDL.audios[id]; if (!info) return -1; - var audio = info.audio; - if (!audio) return -1; + if (!info.audio && !info.webAudio) return -1; // If the user asks us to allocate a channel automatically, get the first // free one. @@ -2265,73 +2434,33 @@ var LibrarySDL = { return -1; } } - // We clone the audio node to utilize the preloaded audio buffer, since - // the browser has already preloaded the audio file. var channelInfo = SDL.channels[channel]; - channelInfo.audio = audio = audio.cloneNode(true); - audio.numChannels = info.audio.numChannels; - audio.frequency = info.audio.frequency; - // TODO: handle N loops. Behavior matches Mix_PlayMusic - audio.loop = loops != 0; - audio['onended'] = function SDL_audio_onended() { // TODO: cache these - channelInfo.audio = null; - if (SDL.channelFinished) { - Runtime.getFuncWrapper(SDL.channelFinished, 'vi')(channel); - } - } - // Either play the element, or load the dynamic data into it - if (info.buffer) { - var contextCtor = null; - if (audio && ('mozSetup' in audio)) { // Audio Data API - try { - audio['mozSetup'](audio.numChannels, audio.frequency); - audio["mozWriteAudio"](info.buffer); - } catch (e) { - // Workaround for Firefox bug 783052 - // ignore this exception! - } - /* - } else if (contextCtor = (window.AudioContext || // WebAudio API - window.webkitAudioContext)) { - var currentIndex = 0; - var numChannels = parseInt(audio.numChannels); - var context = new contextCtor(); - var source = context.createBufferSource(); - source.loop = false; - source.buffer = context.createBuffer(numChannels, 1, audio.frequency); - var jsNode = context.createJavaScriptNode(2048, numChannels, numChannels); - jsNode.onaudioprocess = function jsNode_onaudioprocess(event) { - var buffers = new Array(numChannels); - for (var i = 0; i < numChannels; ++i) { - buffers[i] = event.outputBuffer.getChannelData(i); - } - var remaining = info.buffer.length - currentIndex; - if (remaining > 2048) { - remaining = 2048; - } - for (var i = 0; i < remaining;) { - for (var j = 0; j < numChannels; ++j) { - buffers[j][i] = info.buffer[currentIndex + i + j] * audio.volume; - } - i += j; - } - currentIndex += remaining * numChannels; - for (var i = remaining; i < 2048;) { - for (var j = 0; j < numChannels; ++j) { - buffers[j][i] = 0; // silence - } - i += j; - } - }; - source.connect(jsNode); - jsNode.connect(context.destination); - source.noteOn(0); - */ - } + var audio; + if (info.webAudio) { + // Create an instance of the WebAudio object. + audio = {}; + audio.resource = info; // This new object is an instance that refers to this existing resource. + audio.paused = false; + audio.currentPosition = 0; + // Make our instance look similar to the instance of a <media> to make api simple. + audio.play = function() { SDL.playWebAudio(this); } + audio.pause = function() { SDL.pauseWebAudio(this); } } else { - audio.play(); + // We clone the audio node to utilize the preloaded audio buffer, since + // the browser has already preloaded the audio file. + audio = info.audio.cloneNode(true); + audio.numChannels = info.audio.numChannels; + audio.frequency = info.audio.frequency; } + audio['onended'] = function SDL_audio_onended() { // TODO: cache these + if (channelInfo.audio == this) { channelInfo.audio.paused = true; channelInfo.audio = null; } + if (SDL.channelFinished) Runtime.getFuncWrapper(SDL.channelFinished, 'vi')(channel); + } + channelInfo.audio = audio; + // TODO: handle N loops. Behavior matches Mix_PlayMusic + audio.loop = loops != 0; audio.volume = channelInfo.volume; + audio.play(); return channel; }, Mix_PlayChannelTimed: 'Mix_PlayChannel', // XXX ignore Timing @@ -2384,46 +2513,51 @@ var LibrarySDL = { Mix_PlayMusic__deps: ['Mix_HaltMusic'], Mix_PlayMusic: function(id, loops) { - loops = Math.max(loops, 1); - var audio = SDL.audios[id].audio; - if (!audio) return 0; - audio.loop = loops != 0; // TODO: handle N loops for finite N - if (SDL.audios[id].buffer) { - audio["mozWriteAudio"](SDL.audios[id].buffer); - } else { - audio.play(); - } - audio.volume = SDL.music.volume; - audio['onended'] = _Mix_HaltMusic; // will send callback + // Pause old music if it exists. if (SDL.music.audio) { - if (!SDL.music.audio.paused) { - Module.printErr('Music is already playing. ' + SDL.music.source); - } + if (!SDL.music.audio.paused) Module.printErr('Music is already playing. ' + SDL.music.source); SDL.music.audio.pause(); } + var info = SDL.audios[id]; + var audio; + if (info.webAudio) { // Play via Web Audio API + // Create an instance of the WebAudio object. + audio = {}; + audio.resource = info; // This new webAudio object is an instance that refers to this existing resource. + audio.paused = false; + audio.currentPosition = 0; + audio.play = function() { SDL.playWebAudio(this); } + audio.pause = function() { SDL.pauseWebAudio(this); } + } else if (info.audio) { // Play via the <audio> element + audio = info.audio; + } + audio['onended'] = function() { if (SDL.music.audio == this) _Mix_HaltMusic(); } // will send callback + audio.loop = loops != 0; // TODO: handle N loops for finite N + audio.volume = SDL.music.volume; SDL.music.audio = audio; + audio.play(); return 0; }, Mix_PauseMusic: function() { var audio = SDL.music.audio; - if (!audio) return 0; - audio.pause(); + if (audio) audio.pause(); return 0; }, Mix_ResumeMusic: function() { var audio = SDL.music.audio; - if (!audio) return 0; - audio.play(); + if (audio) audio.play(); return 0; }, Mix_HaltMusic: function() { var audio = SDL.music.audio; - if (!audio) return 0; - audio.src = audio.src; // rewind - audio.pause(); + if (audio) { + audio.src = audio.src; // rewind <media> element + audio.currentPosition = 0; // rewind Web Audio graph playback. + audio.pause(); + } SDL.music.audio = null; if (SDL.hookMusicFinished) { Runtime.dynCall('v', SDL.hookMusicFinished); @@ -2500,9 +2634,7 @@ var LibrarySDL = { return; } var info = SDL.channels[channel]; - if (info && info.audio) { - info.audio.play(); - } + if (info && info.audio) info.audio.play(); }, // SDL TTF diff --git a/src/modules.js b/src/modules.js index 2d2a75d0..fd5c23cd 100644 --- a/src/modules.js +++ b/src/modules.js @@ -21,6 +21,7 @@ var LLVM = { CONVERSIONS: set('inttoptr', 'ptrtoint', 'uitofp', 'sitofp', 'fptosi', 'fptoui', 'fpext', 'fptrunc'), INTRINSICS_32: set('_llvm_memcpy_p0i8_p0i8_i64', '_llvm_memmove_p0i8_p0i8_i64', '_llvm_memset_p0i8_i64'), // intrinsics that need args converted to i32 in USE_TYPED_ARRAYS == 2 MATHOP_IGNORABLES: set('exact', 'nnan', 'ninf', 'nsz', 'arcp', 'fast'), + PARAM_IGNORABLES: set('nocapture', 'readonly', 'readnone'), }; LLVM.GLOBAL_MODIFIERS = set(keys(LLVM.LINKAGES).concat(['constant', 'global', 'hidden'])); diff --git a/src/parseTools.js b/src/parseTools.js index 4fb76196..ececf477 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -467,7 +467,7 @@ function parseParamTokens(params) { // handle 'byval' and 'byval align X'. We store the alignment in 'byVal' byVal = QUANTUM_SIZE; segment.splice(1, 1); - if (segment[1] && (segment[1].text === 'nocapture' || segment[1].text === 'readonly')) { + if (segment[1] && (segment[1].text in LLVM.PARAM_IGNORABLES)) { segment.splice(1, 1); } if (segment[1] && segment[1].text === 'align') { @@ -476,7 +476,7 @@ function parseParamTokens(params) { segment.splice(1, 2); } } - if (segment[1] && (segment[1].text === 'nocapture' || segment[1].text === 'readonly')) { + if (segment[1] && (segment[1].text in LLVM.PARAM_IGNORABLES)) { segment.splice(1, 1); } if (segment.length == 1) { @@ -1168,15 +1168,10 @@ function getHeapOffset(offset, type, forceAsm) { var sz = Runtime.getNativeTypeSize(type); var shifts = Math.log(sz)/Math.LN2; offset = '(' + offset + ')'; - if (shifts != 0) { - if (CHECK_HEAP_ALIGN) { - return '((CHECK_ALIGN_' + sz + '(' + offset + '|0)|0)>>' + shifts + ')'; - } else { - return '(' + offset + '>>' + shifts + ')'; - } + if (CHECK_HEAP_ALIGN && shifts > 0) { + return '((CHECK_ALIGN_' + sz + '(' + offset + '|0)|0)>>' + shifts + ')'; } else { - // we need to guard against overflows here, HEAP[U]8 expects a guaranteed int - return isJSVar(offset) ? offset : '(' + offset + '|0)'; + return '(' + offset + '>>' + shifts + ')'; } } @@ -2040,7 +2035,7 @@ function finalizeLLVMParameter(param, noIndexizeFunctions) { } else if (param.intertype == 'mathop') { return processMathop(param); } else if (param.intertype === 'vector') { - return getVectorBaseType(param.type) + '32x4(' + param.idents.join(',') + ')'; + return 'SIMD.' + getVectorBaseType(param.type) + '32x4(' + param.idents.join(',') + ')'; } else { throw 'invalid llvm parameter: ' + param.intertype; } @@ -2705,7 +2700,7 @@ var simdLane = ['x', 'y', 'z', 'w']; function ensureVector(ident, base) { Types.usesSIMD = true; - return ident == 0 ? base + '32x4.splat(0)' : ident; + return ident == 0 ? 'SIMD.' + base + '32x4.splat(0)' : ident; } function ensureValidFFIType(type) { diff --git a/src/preamble.js b/src/preamble.js index 89ab5026..ae58e7e0 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -41,13 +41,13 @@ var ACCEPTABLE_SAFE_HEAP_ERRORS = 0; function SAFE_HEAP_ACCESS(dest, type, store, ignore, storeValue) { //if (dest === A_NUMBER) Module.print ([dest, type, store, ignore, storeValue] + ' ' + stackTrace()); // Something like this may be useful, in debugging - assert(dest > 0, 'segmentation fault'); + if (dest <= 0) abort('segmentation fault ' + (store ? ('storing value ' + storeValue) : 'loading') + ' type ' + type + ' at address ' + dest); #if USE_TYPED_ARRAYS // When using typed arrays, reads over the top of TOTAL_MEMORY will fail silently, so we must // correct that by growing TOTAL_MEMORY as needed. Without typed arrays, memory is a normal // JS array so it will work (potentially slowly, depending on the engine). - assert(ignore || dest < Math.max(DYNAMICTOP, STATICTOP)); + if (!ignore && dest >= Math.max(DYNAMICTOP, STATICTOP)) abort('segmentation fault ' + (store ? ('storing value ' + storeValue) : 'loading') + ' type ' + type + ' at address ' + dest + '. Heap ends at address ' + Math.max(DYNAMICTOP, STATICTOP)); assert(ignore || DYNAMICTOP <= TOTAL_MEMORY); #endif @@ -186,17 +186,17 @@ function SAFE_HEAP_STORE(dest, value, bytes, isFloat) { #if SAFE_HEAP_LOG Module.print('SAFE_HEAP store: ' + [dest, value, bytes, isFloat]); #endif - assert(dest > 0, 'segmentation fault'); - assert(dest % bytes === 0, 'alignment error'); - assert(dest < Math.max(DYNAMICTOP, STATICTOP), 'segmentation fault (high)'); + if (dest <= 0) abort('segmentation fault storing ' + bytes + ' bytes to address ' + dest); + if (dest % bytes !== 0) abort('alignment error storing to address ' + dest + ', which was expected to be aligned to a multiple of ' + bytes); + if (dest + bytes > Math.max(DYNAMICTOP, STATICTOP)) abort('segmentation fault, exceeded the top of the available heap when storing ' + bytes + ' bytes to address ' + dest + '. STATICTOP=' + STATICTOP + ', DYNAMICTOP=' + DYNAMICTOP); assert(DYNAMICTOP <= TOTAL_MEMORY); setValue(dest, value, getSafeHeapType(bytes, isFloat), 1); } function SAFE_HEAP_LOAD(dest, bytes, isFloat, unsigned) { - assert(dest > 0, 'segmentation fault'); - assert(dest % bytes === 0, 'alignment error'); - assert(dest < Math.max(DYNAMICTOP, STATICTOP), 'segmentation fault (high)'); + if (dest <= 0) abort('segmentation fault loading ' + bytes + ' bytes from address ' + dest); + if (dest % bytes !== 0) abort('alignment error loading from address ' + dest + ', which was expected to be aligned to a multiple of ' + bytes); + if (dest + bytes > Math.max(DYNAMICTOP, STATICTOP)) abort('segmentation fault, exceeded the top of the available heap when loading ' + bytes + ' bytes from address ' + dest + '. STATICTOP=' + STATICTOP + ', DYNAMICTOP=' + DYNAMICTOP); assert(DYNAMICTOP <= TOTAL_MEMORY); var type = getSafeHeapType(bytes, isFloat); var ret = getValue(dest, type, 1); @@ -207,6 +207,14 @@ function SAFE_HEAP_LOAD(dest, bytes, isFloat, unsigned) { return ret; } +function SAFE_FT_MASK(value, mask) { + var ret = value & mask; + if (ret !== value) { + abort('Function table mask error: function pointer is ' + value + ' which is masked by ' + mask + ', the likely cause of this is that the function pointer is being called by the wrong type.'); + } + return ret; +} + #endif #endif @@ -302,86 +310,168 @@ function assert(condition, text) { var globalScope = this; -// C calling interface. A convenient way to call C functions (in C files, or -// defined with extern "C"). -// -// Note: LLVM optimizations can inline and remove functions, after which you will not be -// able to call them. Closure can also do so. To avoid that, add your function to -// the exports using something like -// -// -s EXPORTED_FUNCTIONS='["_main", "_myfunc"]' -// -// @param ident The name of the C function (note that C++ functions will be name-mangled - use extern "C") -// @param returnType The return type of the function, one of the JS types 'number', 'string' or 'array' (use 'number' for any C pointer, and -// 'array' for JavaScript arrays and typed arrays; note that arrays are 8-bit). -// @param argTypes An array of the types of arguments for the function (if there are no arguments, this can be ommitted). Types are as in returnType, -// except that 'array' is not possible (there is no way for us to know the length of the array) -// @param args An array of the arguments to the function, as native JS values (as in returnType) -// Note that string arguments will be stored on the stack (the JS string will become a C string on the stack). -// @return The return value, as a native JS value (as in returnType) -function ccall(ident, returnType, argTypes, args) { - return ccallFunc(getCFunc(ident), returnType, argTypes, args); -} -Module["ccall"] = ccall; - // Returns the C function with a specified identifier (for C++, you need to do manual name mangling) function getCFunc(ident) { - try { - var func = Module['_' + ident]; // closure exported function - if (!func) func = eval('_' + ident); // explicit lookup - } catch(e) { + var func = Module['_' + ident]; // closure exported function + if (!func) { +#if NO_DYNAMIC_EXECUTION == 0 + try { + func = eval('_' + ident); // explicit lookup + } catch(e) {} +#else + abort('NO_DYNAMIC_EXECUTION was set, cannot eval - ccall/cwrap are not functional'); +#endif } assert(func, 'Cannot call unknown function ' + ident + ' (perhaps LLVM optimizations or closure removed it?)'); return func; } -// Internal function that does a C call using a function, not an identifier -function ccallFunc(func, returnType, argTypes, args) { +var cwrap, ccall; +(function(){ var stack = 0; - function toC(value, type) { - if (type == 'string') { - if (value === null || value === undefined || value === 0) return 0; // null string - value = intArrayFromString(value); - type = 'array'; - } - if (type == 'array') { - if (!stack) stack = Runtime.stackSave(); - var ret = Runtime.stackAlloc(value.length); - writeArrayToMemory(value, ret); + var JSfuncs = { + 'stackSave' : function() { + stack = Runtime.stackSave(); + }, + 'stackRestore' : function() { + Runtime.stackRestore(stack); + }, + // type conversion from js to c + 'arrayToC' : function(arr) { + var ret = Runtime.stackAlloc(arr.length); + writeArrayToMemory(arr, ret); + return ret; + }, + 'stringToC' : function(str) { + var ret = 0; + if (str !== null && str !== undefined && str !== 0) { // null string + ret = Runtime.stackAlloc(str.length + 1); // +1 for the trailing '\0' + writeStringToMemory(str, ret); + } return ret; } - return value; - } - function fromC(value, type) { - if (type == 'string') { - return Pointer_stringify(value); + }; + // For fast lookup of conversion functions + var toC = {'string' : JSfuncs['stringToC'], 'array' : JSfuncs['arrayToC']}; + + // C calling interface. A convenient way to call C functions (in C files, or + // defined with extern "C"). + // + // Note: ccall/cwrap use the C stack for temporary values. If you pass a string + // then it is only alive until the call is complete. If the code being + // called saves the pointer to be used later, it may point to invalid + // data. If you need a string to live forever, you can create it (and + // must later delete it manually!) using malloc and writeStringToMemory, + // for example. + // + // Note: LLVM optimizations can inline and remove functions, after which you will not be + // able to call them. Closure can also do so. To avoid that, add your function to + // the exports using something like + // + // -s EXPORTED_FUNCTIONS='["_main", "_myfunc"]' + // + // @param ident The name of the C function (note that C++ functions will be name-mangled - use extern "C") + // @param returnType The return type of the function, one of the JS types 'number', 'string' or 'array' (use 'number' for any C pointer, and + // 'array' for JavaScript arrays and typed arrays; note that arrays are 8-bit). + // @param argTypes An array of the types of arguments for the function (if there are no arguments, this can be ommitted). Types are as in returnType, + // except that 'array' is not possible (there is no way for us to know the length of the array) + // @param args An array of the arguments to the function, as native JS values (as in returnType) + // Note that string arguments will be stored on the stack (the JS string will become a C string on the stack). + // @return The return value, as a native JS value (as in returnType) + ccall = function ccallFunc(ident, returnType, argTypes, args) { + var func = getCFunc(ident); + var cArgs = []; +#if ASSERTIONS + assert(returnType !== 'array', 'Return type should not be "array".'); +#endif + if (args) { + for (var i = 0; i < args.length; i++) { + var converter = toC[argTypes[i]]; + if (converter) { + if (stack === 0) stack = Runtime.stackSave(); + cArgs[i] = converter(args[i]); + } else { + cArgs[i] = args[i]; + } + } } - assert(type != 'array'); - return value; + var ret = func.apply(null, cArgs); + if (returnType === 'string') ret = Pointer_stringify(ret); + if (stack !== 0) JSfuncs['stackRestore'](); + return ret; } - var i = 0; - var cArgs = args ? args.map(function(arg) { - return toC(arg, argTypes[i++]); - }) : []; - var ret = fromC(func.apply(null, cArgs), returnType); - if (stack) Runtime.stackRestore(stack); - return ret; -} -// Returns a native JS wrapper for a C function. This is similar to ccall, but -// returns a function you can call repeatedly in a normal way. For example: -// -// var my_function = cwrap('my_c_function', 'number', ['number', 'number']); -// alert(my_function(5, 22)); -// alert(my_function(99, 12)); -// -function cwrap(ident, returnType, argTypes) { - var func = getCFunc(ident); - return function() { - return ccallFunc(func, returnType, argTypes, Array.prototype.slice.call(arguments)); + var sourceRegex = /^function\s*\(([^)]*)\)\s*{\s*([^*]*?)[\s;]*(?:return\s*(.*?)[;\s]*)?}$/; + function parseJSFunc(jsfunc) { + // Match the body and the return value of a javascript function source + var parsed = jsfunc.toString().match(sourceRegex).slice(1); + return {arguments : parsed[0], body : parsed[1], returnValue: parsed[2]} } -} + var JSsource = {}; + for (var fun in JSfuncs) { + if (JSfuncs.hasOwnProperty(fun)) { + // Elements of toCsource are arrays of three items: + // the code, and the return value + JSsource[fun] = parseJSFunc(JSfuncs[fun]); + } + } + // Returns a native JS wrapper for a C function. This is similar to ccall, but + // returns a function you can call repeatedly in a normal way. For example: + // + // var my_function = cwrap('my_c_function', 'number', ['number', 'number']); + // alert(my_function(5, 22)); + // alert(my_function(99, 12)); + // + cwrap = function cwrap(ident, returnType, argTypes) { + var cfunc = getCFunc(ident); + // When the function takes numbers and returns a number, we can just return + // the original function + var numericArgs = argTypes.every(function(type){ return type === 'number'}); + var numericRet = (returnType !== 'string'); + if ( numericRet && numericArgs) { + return cfunc; + } + // Creation of the arguments list (["$1","$2",...,"$nargs"]) + var argNames = argTypes.map(function(x,i){return '$'+i}); + var funcstr = "(function(" + argNames.join(',') + ") {"; + var nargs = argTypes.length; + if (!numericArgs) { + // Generate the code needed to convert the arguments from javascript + // values to pointers + funcstr += JSsource['stackSave'].body + ';'; + for (var i = 0; i < nargs; i++) { + var arg = argNames[i], type = argTypes[i]; + if (type === 'number') continue; + var convertCode = JSsource[type + 'ToC']; // [code, return] + funcstr += 'var ' + convertCode.arguments + ' = ' + arg + ';'; + funcstr += convertCode.body + ';'; + funcstr += arg + '=' + convertCode.returnValue + ';'; + } + } + + // When the code is compressed, the name of cfunc is not literally 'cfunc' anymore + var cfuncname = parseJSFunc(function(){return cfunc}).returnValue; + // Call the function + funcstr += 'var ret = ' + cfuncname + '(' + argNames.join(',') + ');'; + if (!numericRet) { // Return type can only by 'string' or 'number' + // Convert the result to a string + var strgfy = parseJSFunc(function(){return Pointer_stringify}).returnValue; + funcstr += 'ret = ' + strgfy + '(ret);'; + } + if (!numericArgs) { + // If we had a stack, restore it + funcstr += JSsource['stackRestore'].body + ';'; + } + funcstr += 'return ret})'; +#if NO_DYNAMIC_EXECUTION == 0 + return eval(funcstr); +#else + abort('NO_DYNAMIC_EXECUTION was set, cannot eval - ccall is not functional'); +#endif + }; +})(); Module["cwrap"] = cwrap; +Module["ccall"] = ccall; // Sets a value in memory in a dynamic way at run-time. Uses the // type data. This is the same as makeSetValue, except that diff --git a/src/relooper/Relooper.cpp b/src/relooper/Relooper.cpp index 780a6d59..9e469ec4 100644 --- a/src/relooper/Relooper.cpp +++ b/src/relooper/Relooper.cpp @@ -17,6 +17,10 @@ typedef std::string ministring; #endif +// uncomment these out to get LLVM errs() debugging support +//#include <llvm/Support/raw_ostream.h> +//using namespace llvm; + template <class T, class U> static bool contains(const T& container, const U& contained) { return container.count(contained); } @@ -202,6 +206,7 @@ void Block::Render(bool InLoop) { if (Fused) { PrintDebug("Fusing Multiple to Simple\n"); Parent->Next = Parent->Next->Next; + Fused->UseSwitch = false; // TODO: emit switches here Fused->RenderLoopPrefix(); // When the Multiple has the same number of groups as we have branches, @@ -243,7 +248,7 @@ void Block::Render(bool InLoop) { Details = ProcessedBranchesOut[DefaultTarget]; } bool SetCurrLabel = (SetLabel && Target->IsCheckedMultipleEntry) || ForceSetLabel; - bool HasFusedContent = Fused && contains(Fused->InnerMap, Target); + bool HasFusedContent = Fused && contains(Fused->InnerMap, Target->Id); bool HasContent = SetCurrLabel || Details->Type != Branch::Direct || HasFusedContent || Details->Code; if (iter != ProcessedBranchesOut.end()) { // If there is nothing to show in this branch, omit the condition @@ -286,7 +291,7 @@ void Block::Render(bool InLoop) { if (!First) Indenter::Indent(); Details->Render(Target, SetCurrLabel); if (HasFusedContent) { - Fused->InnerMap.find(Target)->second->Render(InLoop); + Fused->InnerMap.find(Target->Id)->second->Render(InLoop); } else if (Details->Type == Branch::Nested) { // Nest the parent content here, and remove it from showing up afterwards as Next assert(Parent->Next); @@ -312,18 +317,24 @@ void Block::Render(bool InLoop) { // MultipleShape void MultipleShape::RenderLoopPrefix() { - if (NeedLoop) { - if (Labeled) { - PrintIndented("L%d: do {\n", Id); + if (Breaks) { + if (UseSwitch) { + if (Labeled) { + PrintIndented("L%d: ", Id); + } } else { - PrintIndented("do {\n"); + if (Labeled) { + PrintIndented("L%d: do {\n", Id); + } else { + PrintIndented("do {\n"); + } + Indenter::Indent(); } - Indenter::Indent(); } } void MultipleShape::RenderLoopPostfix() { - if (NeedLoop) { + if (Breaks && !UseSwitch) { Indenter::Unindent(); PrintIndented("} while(0);\n"); } @@ -332,32 +343,41 @@ void MultipleShape::RenderLoopPostfix() { void MultipleShape::Render(bool InLoop) { RenderLoopPrefix(); - // We know that blocks with the same Id were split from the same source, so their contents are identical and they are logically the same, so re-merge them here - typedef std::map<int, Shape*> IdShapeMap; - IdShapeMap IdMap; - for (BlockShapeMap::iterator iter = InnerMap.begin(); iter != InnerMap.end(); iter++) { - int Id = iter->first->Id; - IdShapeMap::iterator Test = IdMap.find(Id); - if (Test != IdMap.end()) { - assert(Shape::IsSimple(iter->second) && Shape::IsSimple(Test->second)); // we can only merge simple blocks, something horrible has gone wrong if we see anything else - continue; + if (!UseSwitch) { + // emit an if-else chain + bool First = true; + for (IdShapeMap::iterator iter = InnerMap.begin(); iter != InnerMap.end(); iter++) { + if (AsmJS) { + PrintIndented("%sif ((label|0) == %d) {\n", First ? "" : "else ", iter->first); + } else { + PrintIndented("%sif (label == %d) {\n", First ? "" : "else ", iter->first); + } + First = false; + Indenter::Indent(); + iter->second->Render(InLoop); + Indenter::Unindent(); + PrintIndented("}\n"); } - IdMap[iter->first->Id] = iter->second; - } - - bool First = true; - for (IdShapeMap::iterator iter = IdMap.begin(); iter != IdMap.end(); iter++) { + } else { + // emit a switch if (AsmJS) { - PrintIndented("%sif ((label|0) == %d) {\n", First ? "" : "else ", iter->first); + PrintIndented("switch (label|0) {\n"); } else { - PrintIndented("%sif (label == %d) {\n", First ? "" : "else ", iter->first); + PrintIndented("switch (label) {\n"); } - First = false; Indenter::Indent(); - iter->second->Render(InLoop); + for (IdShapeMap::iterator iter = InnerMap.begin(); iter != InnerMap.end(); iter++) { + PrintIndented("case %d: {\n", iter->first); + Indenter::Indent(); + iter->second->Render(InLoop); + PrintIndented("break;\n"); + Indenter::Unindent(); + PrintIndented("}\n"); + } Indenter::Unindent(); PrintIndented("}\n"); } + RenderLoopPostfix(); if (Next) Next->Render(InLoop); } @@ -547,7 +567,7 @@ void Relooper::Calculate(Block *Entry) { PriorOut->Ancestor = Ancestor; PriorOut->Type = Type; if (MultipleShape *Multiple = Shape::IsMultiple(Ancestor)) { - Multiple->NeedLoop++; // We are breaking out of this Multiple, so need a loop + Multiple->Breaks++; // We are breaking out of this Multiple, so need a loop } iter++; // carefully increment iter before erasing Target->BranchesIn.erase(Prior); @@ -853,7 +873,7 @@ void Relooper::Calculate(Block *Entry) { iter = Next; // increment carefully because Solipsize can remove us } } - Multiple->InnerMap[CurrEntry] = Process(CurrBlocks, CurrEntries, NULL); + Multiple->InnerMap[CurrEntry->Id] = Process(CurrBlocks, CurrEntries, NULL); // If we are not fused, then our entries will actually be checked if (!Fused) { CurrEntry->IsCheckedMultipleEntry = true; @@ -867,6 +887,11 @@ void Relooper::Calculate(Block *Entry) { NextEntries.insert(Entry); } } + // The multiple has been created, we can decide how to implement it + if (Multiple->InnerMap.size() >= 10) { + Multiple->UseSwitch = true; + Multiple->Breaks++; // switch captures breaks + } return Multiple; } @@ -1021,7 +1046,7 @@ void Relooper::Calculate(Block *Entry) { PostOptimizer(Relooper *ParentInit) : Parent(ParentInit), Closure(NULL) {} #define RECURSE_Multiple(shape, func) \ - for (BlockShapeMap::iterator iter = shape->InnerMap.begin(); iter != shape->InnerMap.end(); iter++) { \ + for (IdShapeMap::iterator iter = shape->InnerMap.begin(); iter != shape->InnerMap.end(); iter++) { \ func(iter->second); \ } #define RECURSE_Loop(shape, func) \ @@ -1042,7 +1067,7 @@ void Relooper::Calculate(Block *Entry) { SHAPE_SWITCH(S, { Out.insert(Simple->Inner); }, { - for (BlockShapeMap::iterator iter = Multiple->InnerMap.begin(); iter != Multiple->InnerMap.end(); iter++) { + for (IdShapeMap::iterator iter = Multiple->InnerMap.begin(); iter != Multiple->InnerMap.end(); iter++) { FollowNaturalFlow(iter->second, Out); } FollowNaturalFlow(Multiple->Next, Out); @@ -1061,7 +1086,7 @@ void Relooper::Calculate(Block *Entry) { SHAPE_SWITCH(Root, { }, { - for (BlockShapeMap::iterator iter = Multiple->InnerMap.begin(); iter != Multiple->InnerMap.end(); iter++) { + for (IdShapeMap::iterator iter = Multiple->InnerMap.begin(); iter != Multiple->InnerMap.end(); iter++) { FindNaturals(iter->second, Root->Natural); } }, { @@ -1072,7 +1097,7 @@ void Relooper::Calculate(Block *Entry) { // Remove unneeded breaks and continues. // A flow operation is trivially unneeded if the shape we naturally get to by normal code // execution is the same as the flow forces us to. - void RemoveUnneededFlows(Shape *Root, Shape *Natural=NULL, LoopShape *LastLoop=NULL) { + void RemoveUnneededFlows(Shape *Root, Shape *Natural=NULL, LoopShape *LastLoop=NULL, unsigned Depth=0) { BlockSet NaturalBlocks; FollowNaturalFlow(Natural, NaturalBlocks); Shape *Next = Root; @@ -1083,7 +1108,7 @@ void Relooper::Calculate(Block *Entry) { if (Simple->Inner->BranchVar) LastLoop = NULL; // a switch clears out the loop (TODO: only for breaks, not continue) if (Simple->Next) { - if (!Simple->Inner->BranchVar && Simple->Inner->ProcessedBranchesOut.size() == 2) { + if (!Simple->Inner->BranchVar && Simple->Inner->ProcessedBranchesOut.size() == 2 && Depth < 20) { // If there is a next block, we already know at Simple creation time to make direct branches, // and we can do nothing more in general. But, we try to optimize the case of a break and // a direct: This would normally be if (break?) { break; } .. but if we @@ -1111,7 +1136,7 @@ void Relooper::Calculate(Block *Entry) { if (Details->Type == Branch::Break) { Details->Type = Branch::Direct; if (MultipleShape *Multiple = Shape::IsMultiple(Details->Ancestor)) { - Multiple->NeedLoop--; + Multiple->Breaks--; } } else { assert(Details->Type == Branch::Direct); @@ -1119,6 +1144,7 @@ void Relooper::Calculate(Block *Entry) { } } } + Depth++; // this optimization increases depth, for us and all our next chain (i.e., until this call returns) } Next = Simple->Next; } else { @@ -1130,24 +1156,24 @@ void Relooper::Calculate(Block *Entry) { if (Details->Type != Branch::Direct && contains(NaturalBlocks, Target)) { // note: cannot handle split blocks Details->Type = Branch::Direct; if (MultipleShape *Multiple = Shape::IsMultiple(Details->Ancestor)) { - Multiple->NeedLoop--; + Multiple->Breaks--; } } else if (Details->Type == Branch::Break && LastLoop && LastLoop->Natural == Details->Ancestor->Natural) { // it is important to simplify breaks, as simpler breaks enable other optimizations Details->Labeled = false; if (MultipleShape *Multiple = Shape::IsMultiple(Details->Ancestor)) { - Multiple->NeedLoop--; + Multiple->Breaks--; } } } } }, { - for (BlockShapeMap::iterator iter = Multiple->InnerMap.begin(); iter != Multiple->InnerMap.end(); iter++) { - RemoveUnneededFlows(iter->second, Multiple->Next, Multiple->NeedLoop ? NULL : LastLoop); + for (IdShapeMap::iterator iter = Multiple->InnerMap.begin(); iter != Multiple->InnerMap.end(); iter++) { + RemoveUnneededFlows(iter->second, Multiple->Next, Multiple->Breaks ? NULL : LastLoop, Depth+1); } Next = Multiple->Next; }, { - RemoveUnneededFlows(Loop->Inner, Loop->Inner, Loop); + RemoveUnneededFlows(Loop->Inner, Loop->Inner, Loop, Depth+1); Next = Loop->Next; }); } @@ -1169,13 +1195,16 @@ void Relooper::Calculate(Block *Entry) { SHAPE_SWITCH(Root, { MultipleShape *Fused = Shape::IsMultiple(Root->Next); // If we are fusing a Multiple with a loop into this Simple, then visit it now - if (Fused && Fused->NeedLoop) { + if (Fused && Fused->Breaks) { LoopStack.push(Fused); } if (Simple->Inner->BranchVar) { LoopStack.push(NULL); // a switch means breaks are now useless, push a dummy } if (Fused) { + if (Fused->UseSwitch) { + LoopStack.push(NULL); // a switch means breaks are now useless, push a dummy + } RECURSE_Multiple(Fused, FindLabeledLoops); } for (BlockBranchMap::iterator iter = Simple->Inner->ProcessedBranchesOut.begin(); iter != Simple->Inner->ProcessedBranchesOut.end(); iter++) { @@ -1191,10 +1220,13 @@ void Relooper::Calculate(Block *Entry) { } } } + if (Fused && Fused->UseSwitch) { + LoopStack.pop(); + } if (Simple->Inner->BranchVar) { LoopStack.pop(); } - if (Fused && Fused->NeedLoop) { + if (Fused && Fused->Breaks) { LoopStack.pop(); } if (Fused) { @@ -1203,11 +1235,11 @@ void Relooper::Calculate(Block *Entry) { Next = Root->Next; } }, { - if (Multiple->NeedLoop) { + if (Multiple->Breaks) { LoopStack.push(Multiple); } RECURSE(Multiple, FindLabeledLoops); - if (Multiple->NeedLoop) { + if (Multiple->Breaks) { LoopStack.pop(); } Next = Root->Next; @@ -1290,8 +1322,8 @@ void Debugging::Dump(Shape *S, const char *prefix) { printf("<< Simple with block %d\n", Simple->Inner->Id); }, { printf("<< Multiple\n"); - for (BlockShapeMap::iterator iter = Multiple->InnerMap.begin(); iter != Multiple->InnerMap.end(); iter++) { - printf(" with entry %d\n", iter->first->Id); + for (IdShapeMap::iterator iter = Multiple->InnerMap.begin(); iter != Multiple->InnerMap.end(); iter++) { + printf(" with entry %d\n", iter->first); } }, { printf("<< Loop\n"); diff --git a/src/relooper/Relooper.h b/src/relooper/Relooper.h index 152bae0e..c86e63ac 100644 --- a/src/relooper/Relooper.h +++ b/src/relooper/Relooper.h @@ -132,8 +132,6 @@ struct SimpleShape : public Shape { } }; -typedef std::map<Block*, Shape*> BlockShapeMap; - // A shape that may be implemented with a labeled loop. struct LabeledShape : public Shape { bool Labeled; // If we have a loop, whether it needs to be labeled @@ -141,12 +139,16 @@ struct LabeledShape : public Shape { LabeledShape(ShapeType TypeInit) : Shape(TypeInit), Labeled(false) {} }; +// Blocks with the same id were split and are identical, so we just care about ids in Multiple entries +typedef std::map<int, Shape*> IdShapeMap; + struct MultipleShape : public LabeledShape { - BlockShapeMap InnerMap; // entry block -> shape - int NeedLoop; // If we have branches, we need a loop. This is a counter of loop requirements, - // if we optimize it to 0, the loop is unneeded + IdShapeMap InnerMap; // entry block ID -> shape + int Breaks; // If we have branches on us, we need a loop (or a switch). This is a counter of requirements, + // if we optimize it to 0, the loop is unneeded + bool UseSwitch; // Whether to switch on label as opposed to an if-else chain - MultipleShape() : LabeledShape(Multiple), NeedLoop(0) {} + MultipleShape() : LabeledShape(Multiple), Breaks(0), UseSwitch(false) {} void RenderLoopPrefix(); void RenderLoopPostfix(); diff --git a/src/relooper/fuzzer.py b/src/relooper/fuzzer.py index 18db997e..313f4d5e 100644 --- a/src/relooper/fuzzer.py +++ b/src/relooper/fuzzer.py @@ -3,7 +3,7 @@ import random, subprocess, difflib while True: # Random decisions - num = random.randint(2, 250) + num = random.randint(2, 500) density = random.random() * random.random() decisions = [random.randint(1, num*20) for x in range(num*3)] branches = [0]*num @@ -123,14 +123,14 @@ int main() { open('fuzz.slow.js', 'w').write(slow) open('fuzz.cpp', 'w').write(fast) print '_' - slow_out = subprocess.Popen(['mozjs', '-m', '-n', 'fuzz.slow.js'], stdout=subprocess.PIPE).communicate()[0] + slow_out = subprocess.Popen(['mozjs', 'fuzz.slow.js'], stdout=subprocess.PIPE).communicate()[0] print '.' subprocess.call(['g++', 'fuzz.cpp', 'Relooper.o', '-o', 'fuzz', '-g']) print '*' subprocess.call(['./fuzz'], stdout=open('fuzz.fast.js', 'w')) print '-' - fast_out = subprocess.Popen(['mozjs', '-m', '-n', 'fuzz.fast.js'], stdout=subprocess.PIPE).communicate()[0] + fast_out = subprocess.Popen(['mozjs', 'fuzz.fast.js'], stdout=subprocess.PIPE).communicate()[0] print if slow_out != fast_out: diff --git a/src/relooper/test.cpp b/src/relooper/test.cpp index 9f3ddceb..f319757b 100644 --- a/src/relooper/test.cpp +++ b/src/relooper/test.cpp @@ -1,4 +1,6 @@ +#include <vector> + #include "Relooper.h" int main() { @@ -435,5 +437,34 @@ int main() { puts(r.GetOutputBuffer()); } + + if (1) { + Relooper::MakeOutputBuffer(10); + + printf("\n\n-- lots of exits to an unwind block, possible nesting --\n\n"); + + const int DEPTH = 40; + + std::vector<Block*> blocks; + for (int i = 0; i < DEPTH; i++) blocks.push_back(new Block("// block\n", NULL)); + Block *last = new Block("// last\nreturn;\n", NULL); + Block *UW = new Block("// UW\nresumeException();\n\n", NULL); + + for (int i = 0; i < DEPTH; i++) { + Block *b = blocks[i]; + b->AddBranchTo(i+1 < DEPTH ? blocks[i+1] : last, "check()", NULL); + b->AddBranchTo(UW, NULL, NULL); + } + + Relooper r; + for (int i = 0; i < DEPTH; i++) r.AddBlock(blocks[i]); + r.AddBlock(last); + r.AddBlock(UW); + + r.Calculate(blocks[0]); + r.Render(); + + puts(r.GetOutputBuffer()); + } } diff --git a/src/relooper/test.txt b/src/relooper/test.txt index d53aeeb1..2ae331c0 100644 --- a/src/relooper/test.txt +++ b/src/relooper/test.txt @@ -417,3 +417,156 @@ } // block D + + +-- lots of exits to an unwind block, possible nesting -- + + // block + do { + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // last + return; + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } while(0); + // UW + resumeException(); + + diff --git a/src/runtime.js b/src/runtime.js index 63610d3b..96b12294 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -96,6 +96,15 @@ function unInline(name_, params) { } var Runtime = { + // When a 64 bit long is returned from a compiled function the least significant + // 32 bit word is passed in the return value, but the most significant 32 bit + // word is placed in tempRet0. This provides an accessor for that value. + setTempRet0: function(value) { + tempRet0 = value; + }, + getTempRet0: function() { + return tempRet0; + }, stackSave: function() { return STACKTOP; }, @@ -409,12 +418,16 @@ var Runtime = { abort('invalid EM_ASM input |' + source + '|. Please use EM_ASM(..code..) (no quotes) or EM_ASM({ ..code($0).. }, input) (to input values)'); } } +#if NO_DYNAMIC_EXECUTION == 0 try { var evalled = eval('(function(' + args.join(',') + '){ ' + source + ' })'); // new Function does not allow upvars in node } catch(e) { Module.printErr('error in executing inline EM_ASM code: ' + e + ' on: \n\n' + source + '\n\nwith args |' + args + '| (make sure to use the right one out of EM_ASM, EM_ASM_ARGS, etc.)'); throw e; } +#else + abort('NO_DYNAMIC_EXECUTION was set, cannot eval, so EM_ASM is not functional'); +#endif return Runtime.asmConstCache[code] = evalled; }, diff --git a/src/settings.js b/src/settings.js index 8b046e95..bdb149e3 100644 --- a/src/settings.js +++ b/src/settings.js @@ -39,7 +39,7 @@ var CHECK_SIGNS = 0; // Runtime errors for signing issues that need correcting. var ASSERTIONS = 1; // Whether we should add runtime assertions, for example to // check that each allocation to the stack does not - // exceed it's size, whether all allocations (stack and static) are + // exceed its size, whether all allocations (stack and static) are // of positive size, etc., whether we should throw if we encounter a bad __label__, i.e., // if code flow runs into a fault // ASSERTIONS == 2 gives even more runtime checks @@ -124,13 +124,20 @@ var PRECISE_I32_MUL = 1; // If enabled, i32 multiplication is done with full pre var PRECISE_F32 = 0; // 0: Use JS numbers for floating-point values. These are 64-bit and do not model C++ // floats exactly, which are 32-bit. // 1: Model C++ floats precisely, using Math.fround, polyfilling when necessary. This - // can be slow if the polyfill is used on heavy float32 computation. + // can be slow if the polyfill is used on heavy float32 computation. See note on + // browser support below. // 2: Model C++ floats precisely using Math.fround if available in the JS engine, otherwise // use an empty polyfill. This will have much less of a speed penalty than using the full // polyfill in cases where engine support is not present. In addition, we can // remove the empty polyfill calls themselves on the client when generating html, // which should mean that this gives you the best of both worlds of 0 and 1, and is // therefore recommended. + // XXX Note: To optimize float32-using code, we use the 'const' keyword in the emitted + // code. This allows us to avoid unnecessary calls to Math.fround, which would + // slow down engines not yet supporting that function. 'const' is present in + // all modern browsers, including Firefox, Chrome and Safari, but in IE is only + // present in IE11 and above. Therefore if you need to support legacy versions of + // IE, you should not enable PRECISE_F32 1 or 2. var SIMD = 0; // Whether to emit SIMD code ( https://github.com/johnmccutchan/ecmascript_simd ) var CLOSURE_COMPILER = 0; // Whether closure compiling is being run on this output @@ -338,7 +345,7 @@ var EXPORTED_FUNCTIONS = ['_main', '_malloc']; var EXPORT_ALL = 0; // If true, we export all the symbols. Note that this does *not* affect LLVM, so it can // still eliminate functions as dead. This just exports them on the Module object. var EXPORT_BINDINGS = 0; // Export all bindings generator functions (prefixed with emscripten_bind_). This - // is necessary to use the bindings generator with asm.js + // is necessary to use the WebIDL binder or bindings generator with asm.js var RETAIN_COMPILER_SETTINGS = 0; // Remembers the values of these settings, and makes them accessible // through Runtime.getCompilerSetting and emscripten_get_compiler_setting. // To see what is retained, look for compilerSettings in the generated code. @@ -495,6 +502,11 @@ var JS_CHUNK_SIZE = 10240; // Used as a maximum size before breaking up expressi var EXPORT_NAME = 'Module'; // Global variable to export the module as for environments without a standardized module // loading system (e.g. the browser and SM shell). +var NO_DYNAMIC_EXECUTION = 0; // When enabled, we do not emit eval() and new Function(), which disables some functionality + // (causing runtime errors if attempted to be used), but allows the emitted code to be + // acceptable in places that disallow dynamic code execution (chrome packaged app, non- + // privileged firefox app, etc.) + var RUNNING_JS_OPTS = 0; // whether js opts will be run, after the main compiler var COMPILER_ASSERTIONS = 0; // costly (slow) compile-time assertions diff --git a/src/shell.html b/src/shell.html index 226f12b9..d204bfa6 100644 --- a/src/shell.html +++ b/src/shell.html @@ -1245,7 +1245,16 @@ console.error(text); } }, - canvas: document.getElementById('canvas'), + canvas: (function() { + var canvas = document.getElementById('canvas'); + + // As a default initial behavior, pop up an alert when webgl context is lost. To make your + // application robust, you may want to override this behavior before shipping! + // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 + canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false); + + return canvas; + })(), setStatus: function(text) { if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' }; if (text === Module.setStatus.text) return; @@ -1273,6 +1282,13 @@ } }; Module.setStatus('Downloading...'); + window.onerror = function() { + Module.setStatus('Exception thrown, see JavaScript console'); + spinnerElement.style.display = 'none'; + Module.setStatus = function(text) { + if (text) Module.printErr('[post-exception status] ' + text); + }; + }; </script> {{{ SCRIPT }}} </body> diff --git a/src/shell.js b/src/shell.js index e1c0eb54..279a3461 100644 --- a/src/shell.js +++ b/src/shell.js @@ -96,7 +96,9 @@ else if (ENVIRONMENT_IS_SHELL) { this['{{{ EXPORT_NAME }}}'] = Module; +#if CLOSURE_COMPILER eval("if (typeof gc === 'function' && gc.toString().indexOf('[native code]') > 0) var gc = undefined"); // wipe out the SpiderMonkey shell 'gc' function, which can confuse closure (uses it as a minified name, and it is then initted to a non-falsey value unexpectedly) +#endif } else if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { Module['read'] = function read(url) { @@ -139,7 +141,11 @@ else { } function globalEval(x) { +#if NO_DYNAMIC_EXECUTION == 0 eval.call(null, x); +#else + throw 'NO_DYNAMIC_EXECUTION was set, cannot eval'; +#endif } if (!Module['load'] == 'undefined' && Module['read']) { Module['load'] = function load(f) { diff --git a/src/shell_minimal.html b/src/shell_minimal.html index 6f483719..b67c6fdd 100644 --- a/src/shell_minimal.html +++ b/src/shell_minimal.html @@ -101,7 +101,16 @@ console.error(text); } }, - canvas: document.getElementById('canvas'), + canvas: (function() { + var canvas = document.getElementById('canvas'); + + // As a default initial behavior, pop up an alert when webgl context is lost. To make your + // application robust, you may want to override this behavior before shipping! + // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 + canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false); + + return canvas; + })(), setStatus: function(text) { if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' }; if (text === Module.setStatus.text) return; @@ -129,6 +138,13 @@ } }; Module.setStatus('Downloading...'); + window.onerror = function() { + Module.setStatus('Exception thrown, see JavaScript console'); + spinnerElement.style.display = 'none'; + Module.setStatus = function(text) { + if (text) Module.printErr('[post-exception status] ' + text); + }; + }; </script> {{{ SCRIPT }}} </body> diff --git a/src/simd.js b/src/simd.js index 6e3e3675..d5ff8b15 100644 --- a/src/simd.js +++ b/src/simd.js @@ -22,6 +22,9 @@ "use strict"; +// SIMD module. +var SIMD = {}; + /** * Construct a new instance of float32x4 number. * @param {double} value used for x lane. @@ -30,9 +33,9 @@ * @param {double} value used for w lane. * @constructor */ -function float32x4(x, y, z, w) { - if (!(this instanceof float32x4)) { - return new float32x4(x, y, z, w); +SIMD.float32x4 = function(x, y, z, w) { + if (!(this instanceof SIMD.float32x4)) { + return new SIMD.float32x4(x, y, z, w); } this.storage_ = new Float32Array(4); this.storage_[0] = x; @@ -45,8 +48,8 @@ function float32x4(x, y, z, w) { * Construct a new instance of float32x4 number with 0.0 in all lanes. * @constructor */ -float32x4.zero = function() { - return float32x4(0.0, 0.0, 0.0, 0.0); +SIMD.float32x4.zero = function() { + return SIMD.float32x4(0.0, 0.0, 0.0, 0.0); } /** @@ -55,38 +58,10 @@ float32x4.zero = function() { * @param {double} value used for all lanes. * @constructor */ -float32x4.splat = function(s) { - return float32x4(s, s, s, s); +SIMD.float32x4.splat = function(s) { + return SIMD.float32x4(s, s, s, s); } -Object.defineProperty(float32x4.prototype, 'x', { - get: function() { return this.storage_[0]; } -}); - -Object.defineProperty(float32x4.prototype, 'y', { - get: function() { return this.storage_[1]; } -}); - -Object.defineProperty(float32x4.prototype, 'z', { - get: function() { return this.storage_[2]; } -}); - -Object.defineProperty(float32x4.prototype, 'w', - { get: function() { return this.storage_[3]; } -}); - -/** - * Extract the sign bit from each lane return them in the first 4 bits. - */ -Object.defineProperty(float32x4.prototype, 'signMask', { - get: function() { - var mx = this.x < 0.0 ? 1 : 0; - var my = this.y < 0.0 ? 1 : 0; - var mz = this.z < 0.0 ? 1 : 0; - var mw = this.w < 0.0 ? 1 : 0; - return mx | my << 1 | mz << 2 | mw << 3; - } -}); /** * Construct a new instance of int32x4 number. @@ -96,9 +71,9 @@ Object.defineProperty(float32x4.prototype, 'signMask', { * @param {integer} 32-bit unsigned value used for w lane. * @constructor */ -function int32x4(x, y, z, w) { - if (!(this instanceof int32x4)) { - return new int32x4(x, y, z, w); +SIMD.int32x4 = function(x, y, z, w) { + if (!(this instanceof SIMD.int32x4)) { + return new SIMD.int32x4(x, y, z, w); } this.storage_ = new Int32Array(4); this.storage_[0] = x; @@ -108,6 +83,14 @@ function int32x4(x, y, z, w) { } /** + * Construct a new instance of int32x4 number with 0 in all lanes. + * @constructor + */ +SIMD.int32x4.zero = function() { + return SIMD.int32x4(0, 0, 0, 0); +} + +/** * Construct a new instance of int32x4 number with 0xFFFFFFFF or 0x0 in each * lane, depending on the truth value in x, y, z, and w. * @param {boolean} flag used for x lane. @@ -116,11 +99,11 @@ function int32x4(x, y, z, w) { * @param {boolean} flag used for w lane. * @constructor */ -int32x4.bool = function(x, y, z, w) { - return int32x4(x ? -1 : 0x0, - y ? -1 : 0x0, - z ? -1 : 0x0, - w ? -1 : 0x0); +SIMD.int32x4.bool = function(x, y, z, w) { + return SIMD.int32x4(x ? -1 : 0x0, + y ? -1 : 0x0, + z ? -1 : 0x0, + w ? -1 : 0x0); } /** @@ -129,746 +112,637 @@ int32x4.bool = function(x, y, z, w) { * @param {integer} value used for all lanes. * @constructor */ -int32x4.splat = function(s) { - return int32x4(s, s, s, s); +SIMD.int32x4.splat = function(s) { + return SIMD.int32x4(s, s, s, s); } -Object.defineProperty(int32x4.prototype, 'x', { - get: function() { return this.storage_[0]; } -}); +/** +* @return {float32x4} New instance of float32x4 with absolute values of +* t. +*/ +SIMD.float32x4.abs = function(t) { + return SIMD.float32x4(Math.abs(t.x), Math.abs(t.y), Math.abs(t.z), + Math.abs(t.w)); +} -Object.defineProperty(int32x4.prototype, 'y', { - get: function() { return this.storage_[1]; } -}); +/** + * @return {float32x4} New instance of float32x4 with negated values of + * t. + */ +SIMD.float32x4.neg = function(t) { + return SIMD.float32x4(-t.x, -t.y, -t.z, -t.w); +} -Object.defineProperty(int32x4.prototype, 'z', { - get: function() { return this.storage_[2]; } -}); +/** + * @return {float32x4} New instance of float32x4 with a + b. + */ +SIMD.float32x4.add = function(a, b) { + return SIMD.float32x4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} -Object.defineProperty(int32x4.prototype, 'w', - { get: function() { return this.storage_[3]; } -}); +/** + * @return {float32x4} New instance of float32x4 with a - b. + */ +SIMD.float32x4.sub = function(a, b) { + return SIMD.float32x4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} -Object.defineProperty(int32x4.prototype, 'flagX', { - get: function() { return this.storage_[0] != 0x0; } -}); +/** + * @return {float32x4} New instance of float32x4 with a * b. + */ +SIMD.float32x4.mul = function(a, b) { + return SIMD.float32x4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); +} -Object.defineProperty(int32x4.prototype, 'flagY', { - get: function() { return this.storage_[1] != 0x0; } -}); +/** + * @return {float32x4} New instance of float32x4 with a / b. + */ +SIMD.float32x4.div = function(a, b) { + return SIMD.float32x4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); +} -Object.defineProperty(int32x4.prototype, 'flagZ', { - get: function() { return this.storage_[2] != 0x0; } -}); +/** + * @return {float32x4} New instance of float32x4 with t's values clamped + * between lowerLimit and upperLimit. + */ +SIMD.float32x4.clamp = function(t, lowerLimit, upperLimit) { + var cx = t.x < lowerLimit.x ? lowerLimit.x : t.x; + var cy = t.y < lowerLimit.y ? lowerLimit.y : t.y; + var cz = t.z < lowerLimit.z ? lowerLimit.z : t.z; + var cw = t.w < lowerLimit.w ? lowerLimit.w : t.w; + cx = cx > upperLimit.x ? upperLimit.x : cx; + cy = cy > upperLimit.y ? upperLimit.y : cy; + cz = cz > upperLimit.z ? upperLimit.z : cz; + cw = cw > upperLimit.w ? upperLimit.w : cw; + return SIMD.float32x4(cx, cy, cz, cw); +} -Object.defineProperty(int32x4.prototype, 'flagW', - { get: function() { return this.storage_[3] != 0x0; } -}); +/** + * @return {float32x4} New instance of float32x4 with the minimum value of + * t and other. + */ +SIMD.float32x4.min = function(t, other) { + var cx = t.x > other.x ? other.x : t.x; + var cy = t.y > other.y ? other.y : t.y; + var cz = t.z > other.z ? other.z : t.z; + var cw = t.w > other.w ? other.w : t.w; + return SIMD.float32x4(cx, cy, cz, cw); +} /** - * Extract the sign bit from each lane return them in the first 4 bits. + * @return {float32x4} New instance of float32x4 with the maximum value of + * t and other. */ -Object.defineProperty(int32x4.prototype, 'signMask', { - get: function() { - var mx = (this.storage_[0] & 0x80000000) >>> 31; - var my = (this.storage_[1] & 0x80000000) >>> 31; - var mz = (this.storage_[2] & 0x80000000) >>> 31; - var mw = (this.storage_[3] & 0x80000000) >>> 31; - return mx | my << 1 | mz << 2 | mw << 3; - } -}); +SIMD.float32x4.max = function(t, other) { + var cx = t.x < other.x ? other.x : t.x; + var cy = t.y < other.y ? other.y : t.y; + var cz = t.z < other.z ? other.z : t.z; + var cw = t.w < other.w ? other.w : t.w; + return SIMD.float32x4(cx, cy, cz, cw); +} -function isNumber(o) { - return typeof o == "number" || (typeof o == "object" && o.constructor === Number); +/** + * @return {float32x4} New instance of float32x4 with reciprocal value of + * t. + */ +SIMD.float32x4.reciprocal = function(t) { + return SIMD.float32x4(1.0 / t.x, 1.0 / t.y, 1.0 / t.z, 1.0 / t.w); } -function isTypedArray(o) { - return (o instanceof Int8Array) || - (o instanceof Uint8Array) || - (o instanceof Uint8ClampedArray) || - (o instanceof Int16Array) || - (o instanceof Uint16Array) || - (o instanceof Int32Array) || - (o instanceof Uint32Array) || - (o instanceof Float32Array) || - (o instanceof Float64Array) || - (o instanceof Float32x4Array); +/** + * @return {float32x4} New instance of float32x4 with square root of the + * reciprocal value of t. + */ +SIMD.float32x4.reciprocalSqrt = function(t) { + return SIMD.float32x4(Math.sqrt(1.0 / t.x), Math.sqrt(1.0 / t.y), + Math.sqrt(1.0 / t.z), Math.sqrt(1.0 / t.w)); +} +/** + * @return {float32x4} New instance of float32x4 with values of t + * scaled by s. + */ +SIMD.float32x4.scale = function(t, s) { + return SIMD.float32x4(s * t.x, s * t.y, s * t.z, s * t.w); } -function isArrayBuffer(o) { - return (o instanceof ArrayBuffer); +/** + * @return {float32x4} New instance of float32x4 with square root of + * values of t. + */ +SIMD.float32x4.sqrt = function(t) { + return SIMD.float32x4(Math.sqrt(t.x), Math.sqrt(t.y), + Math.sqrt(t.z), Math.sqrt(t.w)); } -function Float32x4Array(a, b, c) { - if (isNumber(a)) { - this.storage_ = new Float32Array(a*4); - this.length_ = a; - this.byteOffset_ = 0; - return; - } else if (isTypedArray(a)) { - if (!(a instanceof Float32x4Array)) { - throw "Copying typed array of non-Float32x4Array is unimplemented."; - } - this.storage_ = new Float32Array(a.length * 4); - this.length_ = a.length; - this.byteOffset_ = 0; - // Copy floats. - for (var i = 0; i < a.length*4; i++) { - this.storage_[i] = a.storage_[i]; - } - } else if (isArrayBuffer(a)) { - if ((b != undefined) && (b % Float32x4Array.BYTES_PER_ELEMENT) != 0) { - throw "byteOffset must be a multiple of 16."; - } - if (c != undefined) { - c *= 4; - this.storage_ = new Float32Array(a, b, c); - } - else { - // Note: new Float32Array(a, b) is NOT equivalent to new Float32Array(a, b, undefined) - this.storage_ = new Float32Array(a, b); - } - this.length_ = this.storage_.length / 4; - this.byteOffset_ = b != undefined ? b : 0; - } else { - throw "Unknown type of first argument."; - } +/** + * @param {float32x4} t An instance of float32x4 to be shuffled. + * @param {integer} mask One of the 256 shuffle masks, for example, SIMD.XXXX. + * @return {float32x4} New instance of float32x4 with lanes shuffled. + */ +SIMD.float32x4.shuffle = function(t, mask) { + var _x = (mask) & 0x3; + var _y = (mask >> 2) & 0x3; + var _z = (mask >> 4) & 0x3; + var _w = (mask >> 6) & 0x3; + return SIMD.float32x4(t.storage_[_x], t.storage_[_y], t.storage_[_z], + t.storage_[_w]); } -Object.defineProperty(Float32x4Array.prototype, 'length', - { get: function() { return this.length_; } -}); +/** + * @param {float32x4} t1 An instance of float32x4 to be shuffled. XY lanes in result + * @param {float32x4} t2 An instance of float32x4 to be shuffled. ZW lanes in result + * @param {integer} mask One of the 256 shuffle masks, for example, SIMD.XXXX. + * @return {float32x4} New instance of float32x4 with lanes shuffled. + */ +SIMD.float32x4.shuffleMix = function(t1, t2, mask) { + var _x = (mask) & 0x3; + var _y = (mask >> 2) & 0x3; + var _z = (mask >> 4) & 0x3; + var _w = (mask >> 6) & 0x3; + return SIMD.float32x4(t1.storage_[_x], t1.storage_[_y], t2.storage_[_z], + t2.storage_[_w]); +} -Object.defineProperty(Float32x4Array.prototype, 'byteLength', - { get: function() { return this.length_ * Float32x4Array.BYTES_PER_ELEMENT; } -}); +/** + * @param {double} value used for x lane. + * @return {float32x4} New instance of float32x4 with the values in t and + * x replaced with {x}. + */ +SIMD.float32x4.withX = function(t, x) { + return SIMD.float32x4(x, t.y, t.z, t.w); +} -Object.defineProperty(Float32x4Array, 'BYTES_PER_ELEMENT', - { get: function() { return 16; } -}); +/** + * @param {double} value used for y lane. + * @return {float32x4} New instance of float32x4 with the values in t and + * y replaced with {y}. + */ +SIMD.float32x4.withY = function(t, y) { + return SIMD.float32x4(t.x, y, t.z, t.w); +} -Object.defineProperty(Float32x4Array.prototype, 'BYTES_PER_ELEMENT', - { get: function() { return 16; } -}); +/** + * @param {double} value used for z lane. + * @return {float32x4} New instance of float32x4 with the values in t and + * z replaced with {z}. + */ +SIMD.float32x4.withZ = function(t, z) { + return SIMD.float32x4(t.x, t.y, z, t.w); +} -Object.defineProperty(Float32x4Array.prototype, 'byteOffset', - { get: function() { return this.byteOffset_; } -}); +/** + * @param {double} value used for w lane. + * @return {float32x4} New instance of float32x4 with the values in t and + * w replaced with {w}. + */ +SIMD.float32x4.withW = function(t, w) { + return SIMD.float32x4(t.x, t.y, t.z, w); +} -Object.defineProperty(Float32x4Array.prototype, 'buffer', - { get: function() { return this.storage_.buffer; } -}); +/** + * @param {float32x4} t An instance of float32x4. + * @param {float32x4} other An instance of float32x4. + * @return {int32x4} 0xFFFFFFFF or 0x0 in each lane depending on + * the result of t < other. + */ +SIMD.float32x4.lessThan = function(t, other) { + var cx = t.x < other.x; + var cy = t.y < other.y; + var cz = t.z < other.z; + var cw = t.w < other.w; + return SIMD.int32x4.bool(cx, cy, cz, cw); +} -Float32x4Array.prototype.getAt = function(i) { - if (i < 0) { - throw "Index must be >= 0."; - } - if (i >= this.length) { - throw "Index out of bounds."; - } - var x = this.storage_[i*4+0]; - var y = this.storage_[i*4+1]; - var z = this.storage_[i*4+2]; - var w = this.storage_[i*4+3]; - return float32x4(x, y, z, w); +/** + * @param {float32x4} t An instance of float32x4. + * @param {float32x4} other An instance of float32x4. + * @return {int32x4} 0xFFFFFFFF or 0x0 in each lane depending on + * the result of t <= other. + */ +SIMD.float32x4.lessThanOrEqual = function(t, other) { + var cx = t.x <= other.x; + var cy = t.y <= other.y; + var cz = t.z <= other.z; + var cw = t.w <= other.w; + return SIMD.int32x4.bool(cx, cy, cz, cw); } -Float32x4Array.prototype.setAt = function(i, v) { - if (i < 0) { - throw "Index must be >= 0."; - } - if (i >= this.length) { - throw "Index out of bounds."; - } - if (!(v instanceof float32x4)) { - throw "Value is not a float32x4."; - } - this.storage_[i*4+0] = v.x; - this.storage_[i*4+1] = v.y; - this.storage_[i*4+2] = v.z; - this.storage_[i*4+3] = v.w; +/** + * @param {float32x4} t An instance of float32x4. + * @param {float32x4} other An instance of float32x4. + * @return {int32x4} 0xFFFFFFFF or 0x0 in each lane depending on + * the result of t == other. + */ +SIMD.float32x4.equal = function(t, other) { + var cx = t.x == other.x; + var cy = t.y == other.y; + var cz = t.z == other.z; + var cw = t.w == other.w; + return SIMD.int32x4.bool(cx, cy, cz, cw); } +/** + * @param {float32x4} t An instance of float32x4. + * @param {float32x4} other An instance of float32x4. + * @return {int32x4} 0xFFFFFFFF or 0x0 in each lane depending on + * the result of t != other. + */ +SIMD.float32x4.notEqual = function(t, other) { + var cx = t.x != other.x; + var cy = t.y != other.y; + var cz = t.z != other.z; + var cw = t.w != other.w; + return SIMD.int32x4.bool(cx, cy, cz, cw); +} -function Int32x4Array(a, b, c) { +/** + * @param {float32x4} t An instance of float32x4. + * @param {float32x4} other An instance of float32x4. + * @return {int32x4} 0xFFFFFFFF or 0x0 in each lane depending on + * the result of t >= other. + */ +SIMD.float32x4.greaterThanOrEqual = function(t, other) { + var cx = t.x >= other.x; + var cy = t.y >= other.y; + var cz = t.z >= other.z; + var cw = t.w >= other.w; + return SIMD.int32x4.bool(cx, cy, cz, cw); +} - function isNumber(o) { - return typeof o == "number" || (typeof o == "object" && o.constructor === Number); - } +/** + * @param {float32x4} t An instance of float32x4. + * @param {float32x4} other An instance of float32x4. + * @return {int32x4} 0xFFFFFFFF or 0x0 in each lane depending on + * the result of t > other. + */ +SIMD.float32x4.greaterThan = function(t, other) { + var cx = t.x > other.x; + var cy = t.y > other.y; + var cz = t.z > other.z; + var cw = t.w > other.w; + return SIMD.int32x4.bool(cx, cy, cz, cw); +} - function isTypedArray(o) { - return (o instanceof Int8Array) || - (o instanceof Uint8Array) || - (o instanceof Uint8ClampedArray) || - (o instanceof Int16Array) || - (o instanceof Uint16Array) || - (o instanceof Int32Array) || - (o instanceof Uint32Array) || - (o instanceof Float32Array) || - (o instanceof Float64Array) || - (o instanceof Int32x4Array) || - (o instanceof Float32x4Array); - } +/** + * @param {float32x4} t An instance of float32x4. + * @return {int32x4} a bit-wise copy of t as a int32x4. + */ +SIMD.float32x4.bitsToInt32x4 = function(t) { + var alias = new Int32Array(t.storage_.buffer); + return SIMD.int32x4(alias[0], alias[1], alias[2], alias[3]); +} - function isArrayBuffer(o) { - return (o instanceof ArrayBuffer); - } +/** + * @param {float32x4} t An instance of float32x4. + * @return {int32x4} with a integer to float conversion of t. + */ +SIMD.float32x4.toInt32x4 = function(t) { + var a = SIMD.int32x4(t.storage_[0], t.storage_[1], t.storage_[2], + t.storage_[3]); + return a; +} - if (isNumber(a)) { - this.storage_ = new Int32Array(a*4); - this.length_ = a; - this.byteOffset_ = 0; - return; - } else if (isTypedArray(a)) { - if (!(a instanceof Int32x4Array)) { - throw "Copying typed array of non-Int32x4Array is unimplemented."; - } - this.storage_ = new Int32Array(a.length * 4); - this.length_ = a.length; - this.byteOffset_ = 0; - // Copy floats. - for (var i = 0; i < a.length*4; i++) { - this.storage_[i] = a.storage_[i]; - } - } else if (isArrayBuffer(a)) { - if ((b != undefined) && (b % Int32x4Array.BYTES_PER_ELEMENT) != 0) { - throw "byteOffset must be a multiple of 16."; - } - if (c != undefined) { - c *= 4; - this.storage_ = new Int32Array(a, b, c); - } - else { - // Note: new Int32Array(a, b) is NOT equivalent to new Float32Array(a, b, undefined) - this.storage_ = new Int32Array(a, b); - } - this.length_ = this.storage_.length / 4; - this.byteOffset_ = b != undefined ? b : 0; - } else { - throw "Unknown type of first argument."; - } +/** + * @param {float32x4} a An instance of float32x4. + * @param {float32x4} b An instance of float32x4. + * @return {float32x4} New instance of float32x4 with values of a & b. + */ +SIMD.float32x4.and = function(a, b) { + var aInt = SIMD.float32x4.bitsToInt32x4(a); + var bInt = SIMD.float32x4.bitsToInt32x4(b); + return SIMD.int32x4.bitsToFloat32x4(SIMD.int32x4.and(aInt, bInt)); } -Object.defineProperty(Int32x4Array.prototype, 'length', - { get: function() { return this.length_; } -}); +/** + * @param {float32x4} a An instance of float32x4. + * @param {float32x4} b An instance of float32x4. + * @return {float32x4} New instance of float32x4 with values of a | b. + */ +SIMD.float32x4.or = function(a, b) { + var aInt = SIMD.float32x4.bitsToInt32x4(a); + var bInt = SIMD.float32x4.bitsToInt32x4(b); + return SIMD.int32x4.bitsToFloat32x4(SIMD.int32x4.or(aInt, bInt)); +} -Object.defineProperty(Int32x4Array.prototype, 'byteLength', - { get: function() { return this.length_ * Int32x4Array.BYTES_PER_ELEMENT; } -}); +/** + * @param {float32x4} a An instance of float32x4. + * @param {float32x4} b An instance of float32x4. + * @return {float32x4} New instance of float32x4 with values of a ^ b. + */ +SIMD.float32x4.xor = function(a, b) { + var aInt = SIMD.float32x4.bitsToInt32x4(a); + var bInt = SIMD.float32x4.bitsToInt32x4(b); + return SIMD.int32x4.bitsToFloat32x4(SIMD.int32x4.xor(aInt, bInt)); +} -Object.defineProperty(Int32x4Array, 'BYTES_PER_ELEMENT', - { get: function() { return 16; } -}); +/** + * @param {float32x4} a An instance of float32x4. + * @return {float32x4} New instance of float32x4 with values of ~a. + */ +SIMD.float32x4.not = function(a) { + var aInt = SIMD.float32x4.bitsToInt32x4(a); + return SIMD.int32x4.bitsToFloat32x4(SIMD.int32x4.not(aInt)); +} -Object.defineProperty(Int32x4Array.prototype, 'BYTES_PER_ELEMENT', - { get: function() { return 16; } -}); +/** + * @param {int32x4} a An instance of int32x4. + * @param {int32x4} b An instance of int32x4. + * @return {int32x4} New instance of int32x4 with values of a & b. + */ +SIMD.int32x4.and = function(a, b) { + return SIMD.int32x4(a.x & b.x, a.y & b.y, a.z & b.z, a.w & b.w); +} -Object.defineProperty(Int32x4Array.prototype, 'byteOffset', - { get: function() { return this.byteOffset_; } -}); +/** + * @param {int32x4} a An instance of int32x4. + * @param {int32x4} b An instance of int32x4. + * @return {int32x4} New instance of int32x4 with values of a | b. + */ +SIMD.int32x4.or = function(a, b) { + return SIMD.int32x4(a.x | b.x, a.y | b.y, a.z | b.z, a.w | b.w); +} -Object.defineProperty(Int32x4Array.prototype, 'buffer', - { get: function() { return this.storage_.buffer; } -}); +/** + * @param {int32x4} a An instance of int32x4. + * @param {int32x4} b An instance of int32x4. + * @return {int32x4} New instance of int32x4 with values of a ^ b. + */ +SIMD.int32x4.xor = function(a, b) { + return SIMD.int32x4(a.x ^ b.x, a.y ^ b.y, a.z ^ b.z, a.w ^ b.w); +} -Int32x4Array.prototype.getAt = function(i) { - if (i < 0) { - throw "Index must be >= 0."; - } - if (i >= this.length) { - throw "Index out of bounds."; - } - var x = this.storage_[i*4+0]; - var y = this.storage_[i*4+1]; - var z = this.storage_[i*4+2]; - var w = this.storage_[i*4+3]; - return float32x4(x, y, z, w); +/** + * @param {int32x4} t An instance of int32x4. + * @return {int32x4} New instance of int32x4 with values of ~t + */ +SIMD.int32x4.not = function(t) { + return SIMD.int32x4(~t.x, ~t.y, ~t.z, ~t.w); } -Int32x4Array.prototype.setAt = function(i, v) { - if (i < 0) { - throw "Index must be >= 0."; - } - if (i >= this.length) { - throw "Index out of bounds."; - } - if (!(v instanceof int32x4)) { - throw "Value is not a int32x4."; - } - this.storage_[i*4+0] = v.x; - this.storage_[i*4+1] = v.y; - this.storage_[i*4+2] = v.z; - this.storage_[i*4+3] = v.w; +/** + * @param {int32x4} t An instance of int32x4. + * @return {int32x4} New instance of int32x4 with values of -t + */ +SIMD.int32x4.neg = function(t) { + return SIMD.int32x4(-t.x, -t.y, -t.z, -t.w); } -var SIMD = (function () { - return { - float32x4: { - /** - * @return {float32x4} New instance of float32x4 with absolute values of - * t. - */ - abs: function(t) { - return new float32x4(Math.abs(t.x), Math.abs(t.y), Math.abs(t.z), - Math.abs(t.w)); - }, - /** - * @return {float32x4} New instance of float32x4 with negated values of - * t. - */ - neg: function(t) { - return new float32x4(-t.x, -t.y, -t.z, -t.w); - }, - /** - * @return {float32x4} New instance of float32x4 with a + b. - */ - add: function(a, b) { - return new float32x4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); - }, - /** - * @return {float32x4} New instance of float32x4 with a - b. - */ - sub: function(a, b) { - return new float32x4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); - }, - /** - * @return {float32x4} New instance of float32x4 with a * b. - */ - mul: function(a, b) { - return new float32x4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); - }, - /** - * @return {float32x4} New instance of float32x4 with a / b. - */ - div: function(a, b) { - return new float32x4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); - }, - /** - * @return {float32x4} New instance of float32x4 with t's values clamped - * between lowerLimit and upperLimit. - */ - clamp: function(t, lowerLimit, upperLimit) { - var cx = t.x < lowerLimit.x ? lowerLimit.x : t.x; - var cy = t.y < lowerLimit.y ? lowerLimit.y : t.y; - var cz = t.z < lowerLimit.z ? lowerLimit.z : t.z; - var cw = t.w < lowerLimit.w ? lowerLimit.w : t.w; - cx = cx > upperLimit.x ? upperLimit.x : cx; - cy = cy > upperLimit.y ? upperLimit.y : cy; - cz = cz > upperLimit.z ? upperLimit.z : cz; - cw = cw > upperLimit.w ? upperLimit.w : cw; - return new float32x4(cx, cy, cz, cw); - }, - /** - * @return {float32x4} New instance of float32x4 with the minimum value of - * t and other. - */ - min: function(t, other) { - var cx = t.x > other.x ? other.x : t.x; - var cy = t.y > other.y ? other.y : t.y; - var cz = t.z > other.z ? other.z : t.z; - var cw = t.w > other.w ? other.w : t.w; - return new float32x4(cx, cy, cz, cw); - }, - /** - * @return {float32x4} New instance of float32x4 with the maximum value of - * t and other. - */ - max: function(t, other) { - var cx = t.x < other.x ? other.x : t.x; - var cy = t.y < other.y ? other.y : t.y; - var cz = t.z < other.z ? other.z : t.z; - var cw = t.w < other.w ? other.w : t.w; - return new float32x4(cx, cy, cz, cw); - }, - /** - * @return {float32x4} New instance of float32x4 with reciprocal value of - * t. - */ - reciprocal: function(t) { - return new float32x4(1.0 / t.x, 1.0 / t.y, 1.0 / t.z, 1.0 / t.w); - }, - /** - * @return {float32x4} New instance of float32x4 with square root of the - * reciprocal value of t. - */ - reciprocalSqrt: function(t) { - return new float32x4(Math.sqrt(1.0 / t.x), Math.sqrt(1.0 / t.y), - Math.sqrt(1.0 / t.z), Math.sqrt(1.0 / t.w)); - }, - /** - * @return {float32x4} New instance of float32x4 with values of t - * scaled by s. - */ - scale: function(t, s) { - return new float32x4(s * t.x, s * t.y, s * t.z, s * t.w); - }, - /** - * @return {float32x4} New instance of float32x4 with square root of - * values of t. - */ - sqrt: function(t) { - return new float32x4(Math.sqrt(t.x), Math.sqrt(t.y), - Math.sqrt(t.z), Math.sqrt(t.w)); - }, - /** - * @param {float32x4} t An instance of float32x4 to be shuffled. - * @param {integer} mask One of the 256 shuffle masks, for example, SIMD.XXXX. - * @return {float32x4} New instance of float32x4 with lanes shuffled. - */ - shuffle: function(t, mask) { - var _x = (mask) & 0x3; - var _y = (mask >> 2) & 0x3; - var _z = (mask >> 4) & 0x3; - var _w = (mask >> 6) & 0x3; - return new float32x4(t.storage_[_x], t.storage_[_y], t.storage_[_z], - t.storage_[_w]); - }, - /** - * @param {float32x4} t1 An instance of float32x4 to be shuffled. XY lanes in result - * @param {float32x4} t2 An instance of float32x4 to be shuffled. ZW lanes in result - * @param {integer} mask One of the 256 shuffle masks, for example, SIMD.XXXX. - * @return {float32x4} New instance of float32x4 with lanes shuffled. - */ - shuffleMix: function(t1, t2, mask) { - var _x = (mask) & 0x3; - var _y = (mask >> 2) & 0x3; - var _z = (mask >> 4) & 0x3; - var _w = (mask >> 6) & 0x3; - return new float32x4(t1.storage_[_x], t1.storage_[_y], t2.storage_[_z], - t2.storage_[_w]); - }, - /** - * @param {double} value used for x lane. - * @return {float32x4} New instance of float32x4 with the values in t and - * x replaced with {x}. - */ - withX: function(t, x) { - return new float32x4(x, t.y, t.z, t.w); - }, - /** - * @param {double} value used for y lane. - * @return {float32x4} New instance of float32x4 with the values in t and - * y replaced with {y}. - */ - withY: function(t, y) { - return new float32x4(t.x, y, t.z, t.w); - }, - /** - * @param {double} value used for z lane. - * @return {float32x4} New instance of float32x4 with the values in t and - * z replaced with {z}. - */ - withZ: function(t, z) { - return new float32x4(t.x, t.y, z, t.w); - }, - /** - * @param {double} value used for w lane. - * @return {float32x4} New instance of float32x4 with the values in t and - * w replaced with {w}. - */ - withW: function(t, w) { - return new float32x4(t.x, t.y, t.z, w); - }, - /** - * @param {float32x4} t An instance of float32x4. - * @param {float32x4} other An instance of float32x4. - * @return {int32x4} 0xFFFFFFFF or 0x0 in each lane depending on - * the result of t < other. - */ - lessThan: function(t, other) { - var cx = t.x < other.x; - var cy = t.y < other.y; - var cz = t.z < other.z; - var cw = t.w < other.w; - return int32x4.bool(cx, cy, cz, cw); - }, - /** - * @param {float32x4} t An instance of float32x4. - * @param {float32x4} other An instance of float32x4. - * @return {int32x4} 0xFFFFFFFF or 0x0 in each lane depending on - * the result of t <= other. - */ - lessThanOrEqual: function(t, other) { - var cx = t.x <= other.x; - var cy = t.y <= other.y; - var cz = t.z <= other.z; - var cw = t.w <= other.w; - return int32x4.bool(cx, cy, cz, cw); - }, - /** - * @param {float32x4} t An instance of float32x4. - * @param {float32x4} other An instance of float32x4. - * @return {int32x4} 0xFFFFFFFF or 0x0 in each lane depending on - * the result of t == other. - */ - equal: function(t, other) { - var cx = t.x == other.x; - var cy = t.y == other.y; - var cz = t.z == other.z; - var cw = t.w == other.w; - return int32x4.bool(cx, cy, cz, cw); - }, - /** - * @param {float32x4} t An instance of float32x4. - * @param {float32x4} other An instance of float32x4. - * @return {int32x4} 0xFFFFFFFF or 0x0 in each lane depending on - * the result of t != other. - */ - notEqual: function(t, other) { - var cx = t.x != other.x; - var cy = t.y != other.y; - var cz = t.z != other.z; - var cw = t.w != other.w; - return int32x4.bool(cx, cy, cz, cw); - }, - /** - * @param {float32x4} t An instance of float32x4. - * @param {float32x4} other An instance of float32x4. - * @return {int32x4} 0xFFFFFFFF or 0x0 in each lane depending on - * the result of t >= other. - */ - greaterThanOrEqual: function(t, other) { - var cx = t.x >= other.x; - var cy = t.y >= other.y; - var cz = t.z >= other.z; - var cw = t.w >= other.w; - return int32x4.bool(cx, cy, cz, cw); - }, - /** - * @param {float32x4} t An instance of float32x4. - * @param {float32x4} other An instance of float32x4. - * @return {int32x4} 0xFFFFFFFF or 0x0 in each lane depending on - * the result of t > other. - */ - greaterThan: function(t, other) { - var cx = t.x > other.x; - var cy = t.y > other.y; - var cz = t.z > other.z; - var cw = t.w > other.w; - return int32x4.bool(cx, cy, cz, cw); - }, - /** - * @param {float32x4} t An instance of float32x4. - * @return {int32x4} a bit-wise copy of t as a int32x4. - */ - bitsToInt32x4: function(t) { - var alias = new Int32Array(t.storage_.buffer); - return new int32x4(alias[0], alias[1], alias[2], alias[3]); - }, - /** - * @param {float32x4} t An instance of float32x4. - * @return {int32x4} with a integer to float conversion of t. - */ - toInt32x4: function(t) { - var a = new int32x4(t.storage_[0], t.storage_[1], t.storage_[2], - t.storage_[3]); - return a; - } - }, - int32x4: { - /** - * @param {int32x4} a An instance of int32x4. - * @param {int32x4} b An instance of int32x4. - * @return {int32x4} New instance of int32x4 with values of a & b. - */ - and: function(a, b) { - return new int32x4(a.x & b.x, a.y & b.y, a.z & b.z, a.w & b.w); - }, - /** - * @param {int32x4} a An instance of int32x4. - * @param {int32x4} b An instance of int32x4. - * @return {int32x4} New instance of int32x4 with values of a | b. - */ - or: function(a, b) { - return new int32x4(a.x | b.x, a.y | b.y, a.z | b.z, a.w | b.w); - }, - /** - * @param {int32x4} a An instance of int32x4. - * @param {int32x4} b An instance of int32x4. - * @return {int32x4} New instance of int32x4 with values of a ^ b. - */ - xor: function(a, b) { - return new int32x4(a.x ^ b.x, a.y ^ b.y, a.z ^ b.z, a.w ^ b.w); - }, - /** - * @param {int32x4} t An instance of int32x4. - * @return {int32x4} New instance of int32x4 with values of ~t - */ - not: function(t) { - return new int32x4(~t.x, ~t.y, ~t.z, ~t.w); - }, - /** - * @param {int32x4} t An instance of int32x4. - * @return {int32x4} New instance of int32x4 with values of -t - */ - neg: function(t) { - return new int32x4(-t.x, -t.y, -t.z, -t.w); - }, - /** - * @param {int32x4} a An instance of int32x4. - * @param {int32x4} b An instance of int32x4. - * @return {int32x4} New instance of int32x4 with values of a + b. - */ - add: function(a, b) { - return new int32x4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); - }, - /** - * @param {int32x4} a An instance of int32x4. - * @param {int32x4} b An instance of int32x4. - * @return {int32x4} New instance of int32x4 with values of a - b. - */ - sub: function(a, b) { - return new int32x4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); - }, - /** - * @param {int32x4} a An instance of int32x4. - * @param {int32x4} b An instance of int32x4. - * @return {int32x4} New instance of int32x4 with values of a * b. - */ - mul: function(a, b) { - return new int32x4(Math.imul(a.x, b.x), Math.imul(a.y, b.y), - Math.imul(a.z, b.z), Math.imul(a.w, b.w)); - }, - /** - * @param {int32x4} t An instance of float32x4 to be shuffled. - * @param {integer} mask One of the 256 shuffle masks, for example, SIMD.XXXX. - * @return {int32x4} New instance of float32x4 with lanes shuffled. - */ - shuffle: function(t, mask) { - var _x = (mask) & 0x3; - var _y = (mask >> 2) & 0x3; - var _z = (mask >> 4) & 0x3; - var _w = (mask >> 6) & 0x3; - return new int32x4(t.storage_[_x], t.storage_[_y], t.storage_[_z], - t.storage_[_w]); - }, - /** - * @param {int32x4} t1 An instance of float32x4 to be shuffled. XY lanes in result - * @param {int32x4} t2 An instance of float32x4 to be shuffled. ZW lanes in result - * @param {integer} mask One of the 256 shuffle masks, for example, SIMD.XXXX. - * @return {int32x4} New instance of float32x4 with lanes shuffled. - */ - shuffleMix: function(t1, t2, mask) { - var _x = (mask) & 0x3; - var _y = (mask >> 2) & 0x3; - var _z = (mask >> 4) & 0x3; - var _w = (mask >> 6) & 0x3; - return new int32x4(t1.storage_[_x], t1.storage_[_y], t2.storage_[_z], - t2.storage_[_w]); - }, - /** - * @param {float32x4} - */ - select: function(t, trueValue, falseValue) { - var tv = SIMD.float32x4.bitsToInt32x4(trueValue); - var fv = SIMD.float32x4.bitsToInt32x4(falseValue); - var tr = SIMD.int32x4.and(t, tv); - var fr = SIMD.int32x4.and(SIMD.int32x4.not(t), fv); - return SIMD.int32x4.bitsToFloat32x4(SIMD.int32x4.or(tr, fr)); - }, - /** - * @param {int32x4} t An instance of int32x4. - * @param {integer} 32-bit value used for x lane. - * @return {int32x4} New instance of int32x4 with the values in t and - * x lane replaced with {x}. - */ - withX: function(t, x) { - return new int32x4(x, t.y, t.z, t.w); - }, - /** - * param {int32x4} t An instance of int32x4. - * @param {integer} 32-bit value used for y lane. - * @return {int32x4} New instance of int32x4 with the values in t and - * y lane replaced with {y}. - */ - withY: function(t, y) { - return new int32x4(t.x, y, t.z, t.w); - }, - /** - * @param {int32x4} t An instance of int32x4. - * @param {integer} 32-bit value used for z lane. - * @return {int32x4} New instance of int32x4 with the values in t and - * z lane replaced with {z}. - */ - withZ: function(t, z) { - return new int32x4(t.x, t.y, z, t.w); - }, - /** - * @param {integer} 32-bit value used for w lane. - * @return {int32x4} New instance of int32x4 with the values in t and - * w lane replaced with {w}. - */ - withW: function(t, w) { - return new int32x4(t.x, t.y, t.z, w); - }, - /** - * @param {int32x4} t An instance of int32x4. - * @param {boolean} x flag used for x lane. - * @return {int32x4} New instance of int32x4 with the values in t and - * x lane replaced with {x}. - */ - withFlagX: function(t, flagX) { - var x = flagX ? 0xFFFFFFFF : 0x0; - return new int32x4(x, t.y, t.z, t.w); - }, - /** - * @param {int32x4} t An instance of int32x4. - * @param {boolean} y flag used for y lane. - * @return {int32x4} New instance of int32x4 with the values in t and - * y lane replaced with {y}. - */ - withFlagY: function(t, flagY) { - var y = flagY ? 0xFFFFFFFF : 0x0; - return new int32x4(t.x, y, t.z, t.w); - }, - /** - * @param {int32x4} t An instance of int32x4. - * @param {boolean} z flag used for z lane. - * @return {int32x4} New instance of int32x4 with the values in t and - * z lane replaced with {z}. - */ - withFlagZ: function(t, flagZ) { - var z = flagZ ? 0xFFFFFFFF : 0x0; - return new int32x4(t.x, t.y, z, t.w); - }, - /** - * @param {int32x4} t An instance of int32x4. - * @param {boolean} w flag used for w lane. - * @return {int32x4} New instance of int32x4 with the values in t and - * w lane replaced with {w}. - */ - withFlagW: function(t, flagW) { - var w = flagW ? 0xFFFFFFFF : 0x0; - return new int32x4(t.x, t.y, t.z, w); - }, - /** - * @param {int32x4} t An instance of int32x4. - * @return {float32x4} a bit-wise copy of t as a float32x4. - */ - bitsToFloat32x4: function(t) { - var temp_storage = new Int32Array([t.storage_[0], t.storage_[1], t.storage_[2], t.storage_[3]]); - var alias = new Float32Array(temp_storage.buffer); - var fx4 = float32x4.zero(); - fx4.storage_ = alias; - return fx4; - }, - /** - * @param {int32x4} t An instance of int32x4. - * @return {float32x4} with a float to integer conversion copy of t. - */ - toFloat32x4: function(t) { - var a = float32x4.zero(); - a.storage_[0] = t.storage_[0]; - a.storage_[1] = t.storage_[1]; - a.storage_[2] = t.storage_[2]; - a.storage_[3] = t.storage_[3]; - return a; - } - } - } -})(); +/** + * @param {int32x4} a An instance of int32x4. + * @param {int32x4} b An instance of int32x4. + * @return {int32x4} New instance of int32x4 with values of a + b. + */ +SIMD.int32x4.add = function(a, b) { + return SIMD.int32x4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); +} + +/** + * @param {int32x4} a An instance of int32x4. + * @param {int32x4} b An instance of int32x4. + * @return {int32x4} New instance of int32x4 with values of a - b. + */ +SIMD.int32x4.sub = function(a, b) { + return SIMD.int32x4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); +} + +/** + * @param {int32x4} a An instance of int32x4. + * @param {int32x4} b An instance of int32x4. + * @return {int32x4} New instance of int32x4 with values of a * b. + */ +SIMD.int32x4.mul = function(a, b) { + return SIMD.int32x4(Math.imul(a.x, b.x), Math.imul(a.y, b.y), + Math.imul(a.z, b.z), Math.imul(a.w, b.w)); +} + +/** + * @param {int32x4} t An instance of float32x4 to be shuffled. + * @param {integer} mask One of the 256 shuffle masks, for example, SIMD.XXXX. + * @return {int32x4} New instance of float32x4 with lanes shuffled. + */ +SIMD.int32x4.shuffle = function(t, mask) { + var _x = (mask) & 0x3; + var _y = (mask >> 2) & 0x3; + var _z = (mask >> 4) & 0x3; + var _w = (mask >> 6) & 0x3; + return SIMD.int32x4(t.storage_[_x], t.storage_[_y], t.storage_[_z], + t.storage_[_w]); +} + +/** + * @param {int32x4} t1 An instance of float32x4 to be shuffled. XY lanes in result + * @param {int32x4} t2 An instance of float32x4 to be shuffled. ZW lanes in result + * @param {integer} mask One of the 256 shuffle masks, for example, SIMD.XXXX. + * @return {int32x4} New instance of float32x4 with lanes shuffled. + */ +SIMD.int32x4.shuffleMix = function(t1, t2, mask) { + var _x = (mask) & 0x3; + var _y = (mask >> 2) & 0x3; + var _z = (mask >> 4) & 0x3; + var _w = (mask >> 6) & 0x3; + return SIMD.int32x4(t1.storage_[_x], t1.storage_[_y], t2.storage_[_z], + t2.storage_[_w]); +} + +/** + * @param {float32x4} + */ +SIMD.int32x4.select = function(t, trueValue, falseValue) { + var tv = SIMD.float32x4.bitsToInt32x4(trueValue); + var fv = SIMD.float32x4.bitsToInt32x4(falseValue); + var tr = SIMD.int32x4.and(t, tv); + var fr = SIMD.int32x4.and(SIMD.int32x4.not(t), fv); + return SIMD.int32x4.bitsToFloat32x4(SIMD.int32x4.or(tr, fr)); +} + +/** + * @param {int32x4} t An instance of int32x4. + * @param {integer} 32-bit value used for x lane. + * @return {int32x4} New instance of int32x4 with the values in t and + * x lane replaced with {x}. + */ +SIMD.int32x4.withX = function(t, x) { + return SIMD.int32x4(x, t.y, t.z, t.w); +} + +/** + * param {int32x4} t An instance of int32x4. + * @param {integer} 32-bit value used for y lane. + * @return {int32x4} New instance of int32x4 with the values in t and + * y lane replaced with {y}. + */ +SIMD.int32x4.withY = function(t, y) { + return SIMD.int32x4(t.x, y, t.z, t.w); +} + +/** + * @param {int32x4} t An instance of int32x4. + * @param {integer} 32-bit value used for z lane. + * @return {int32x4} New instance of int32x4 with the values in t and + * z lane replaced with {z}. + */ +SIMD.int32x4.withZ = function(t, z) { + return SIMD.int32x4(t.x, t.y, z, t.w); +} + +/** + * @param {integer} 32-bit value used for w lane. + * @return {int32x4} New instance of int32x4 with the values in t and + * w lane replaced with {w}. + */ +SIMD.int32x4.withW = function(t, w) { + return SIMD.int32x4(t.x, t.y, t.z, w); +} + +/** + * @param {int32x4} t An instance of int32x4. + * @param {boolean} x flag used for x lane. + * @return {int32x4} New instance of int32x4 with the values in t and + * x lane replaced with {x}. + */ +SIMD.int32x4.withFlagX = function(t, flagX) { + var x = flagX ? 0xFFFFFFFF : 0x0; + return SIMD.int32x4(x, t.y, t.z, t.w); +} + +/** + * @param {int32x4} t An instance of int32x4. + * @param {boolean} y flag used for y lane. + * @return {int32x4} New instance of int32x4 with the values in t and + * y lane replaced with {y}. + */ +SIMD.int32x4.withFlagY = function(t, flagY) { + var y = flagY ? 0xFFFFFFFF : 0x0; + return SIMD.int32x4(t.x, y, t.z, t.w); +} + +/** + * @param {int32x4} t An instance of int32x4. + * @param {boolean} z flag used for z lane. + * @return {int32x4} New instance of int32x4 with the values in t and + * z lane replaced with {z}. + */ +SIMD.int32x4.withFlagZ = function(t, flagZ) { + var z = flagZ ? 0xFFFFFFFF : 0x0; + return SIMD.int32x4(t.x, t.y, z, t.w); +} + +/** + * @param {int32x4} t An instance of int32x4. + * @param {boolean} w flag used for w lane. + * @return {int32x4} New instance of int32x4 with the values in t and + * w lane replaced with {w}. + */ +SIMD.int32x4.withFlagW = function(t, flagW) { + var w = flagW ? 0xFFFFFFFF : 0x0; + return SIMD.int32x4(t.x, t.y, t.z, w); +} + +/** + * @param {int32x4} t An instance of int32x4. + * @param {int32x4} other An instance of int32x4. + * @return {int32x4} 0xFFFFFFFF or 0x0 in each lane depending on + * the result of t == other. + */ +SIMD.int32x4.equal = function(t, other) { + var cx = t.x == other.x; + var cy = t.y == other.y; + var cz = t.z == other.z; + var cw = t.w == other.w; + return SIMD.int32x4.bool(cx, cy, cz, cw); +} + +/** + * @param {int32x4} t An instance of int32x4. + * @param {int32x4} other An instance of int32x4. + * @return {int32x4} 0xFFFFFFFF or 0x0 in each lane depending on + * the result of t > other. + */ +SIMD.int32x4.greaterThan = function(t, other) { + var cx = t.x > other.x; + var cy = t.y > other.y; + var cz = t.z > other.z; + var cw = t.w > other.w; + return SIMD.int32x4.bool(cx, cy, cz, cw); +} + +/** + * @param {int32x4} t An instance of int32x4. + * @param {int32x4} other An instance of int32x4. + * @return {int32x4} 0xFFFFFFFF or 0x0 in each lane depending on + * the result of t < other. + */ +SIMD.int32x4.lessThan = function(t, other) { + var cx = t.x < other.x; + var cy = t.y < other.y; + var cz = t.z < other.z; + var cw = t.w < other.w; + return SIMD.int32x4.bool(cx, cy, cz, cw); +} + +/** + * @param {int32x4} a An instance of int32x4. + * @param {int} bits Bit count to shift by. + * @return {int32x4} lanes in a shifted by bits. + */ +SIMD.int32x4.shiftLeft = function(a, bits) { + var x = a.x << bits; + var y = a.y << bits; + var z = a.z << bits; + var w = a.w << bits; + return SIMD.int32x4(x, y, z, w); +} + +/** + * @param {int32x4} a An instance of int32x4. + * @param {int} bits Bit count to shift by. + * @return {int32x4} lanes in a shifted by bits. + */ +SIMD.int32x4.shiftRightLogical = function(a, bits) { + var x = a.x >>> bits; + var y = a.y >>> bits; + var z = a.z >>> bits; + var w = a.w >>> bits; + return SIMD.int32x4(x, y, z, w); +} + +/** + * @param {int32x4} a An instance of int32x4. + * @param {int} bits Bit count to shift by. + * @return {int32x4} lanes in a shifted by bits. + */ +SIMD.int32x4.shiftRightArithmetic = function(a, bits) { + var x = a.x >> bits; + var y = a.y >> bits; + var z = a.z >> bits; + var w = a.w >> bits; + return SIMD.int32x4(x, y, z, w); +} + +/** + * @param {int32x4} t An instance of int32x4. + * @return {float32x4} a bit-wise copy of t as a float32x4. + */ +SIMD.int32x4.bitsToFloat32x4 = function(t) { + var temp_storage = new Int32Array([t.storage_[0], t.storage_[1], t.storage_[2], t.storage_[3]]); + var alias = new Float32Array(temp_storage.buffer); + var fx4 = SIMD.float32x4.zero(); + fx4.storage_ = alias; + return fx4; +} + +/** + * @param {int32x4} t An instance of int32x4. + * @return {float32x4} with a float to integer conversion copy of t. + */ +SIMD.int32x4.toFloat32x4 = function(t) { + var a = float32x4.zero(); + a.storage_[0] = t.storage_[0]; + a.storage_[1] = t.storage_[1]; + a.storage_[2] = t.storage_[2]; + a.storage_[3] = t.storage_[3]; + return a; +} Object.defineProperty(SIMD, 'XXXX', { get: function() { return 0x0; } }); Object.defineProperty(SIMD, 'XXXY', { get: function() { return 0x40; } }); @@ -1126,3 +1000,302 @@ Object.defineProperty(SIMD, 'WWWX', { get: function() { return 0x3F; } }); Object.defineProperty(SIMD, 'WWWY', { get: function() { return 0x7F; } }); Object.defineProperty(SIMD, 'WWWZ', { get: function() { return 0xBF; } }); Object.defineProperty(SIMD, 'WWWW', { get: function() { return 0xFF; } }); + +Object.defineProperty(SIMD.float32x4.prototype, 'x', { + get: function() { return this.storage_[0]; } +}); + +Object.defineProperty(SIMD.float32x4.prototype, 'y', { + get: function() { return this.storage_[1]; } +}); + +Object.defineProperty(SIMD.float32x4.prototype, 'z', { + get: function() { return this.storage_[2]; } +}); + +Object.defineProperty(SIMD.float32x4.prototype, 'w', + { get: function() { return this.storage_[3]; } +}); + +/** + * Extract the sign bit from each lane return them in the first 4 bits. + */ +Object.defineProperty(SIMD.float32x4.prototype, 'signMask', { + get: function() { + var mx = this.x < 0.0 ? 1 : 0; + var my = this.y < 0.0 ? 1 : 0; + var mz = this.z < 0.0 ? 1 : 0; + var mw = this.w < 0.0 ? 1 : 0; + return mx | my << 1 | mz << 2 | mw << 3; + } +}); + +Object.defineProperty(SIMD.int32x4.prototype, 'x', { + get: function() { return this.storage_[0]; } +}); + +Object.defineProperty(SIMD.int32x4.prototype, 'y', { + get: function() { return this.storage_[1]; } +}); + +Object.defineProperty(SIMD.int32x4.prototype, 'z', { + get: function() { return this.storage_[2]; } +}); + +Object.defineProperty(SIMD.int32x4.prototype, 'w', + { get: function() { return this.storage_[3]; } +}); + +Object.defineProperty(SIMD.int32x4.prototype, 'flagX', { + get: function() { return this.storage_[0] != 0x0; } +}); + +Object.defineProperty(SIMD.int32x4.prototype, 'flagY', { + get: function() { return this.storage_[1] != 0x0; } +}); + +Object.defineProperty(SIMD.int32x4.prototype, 'flagZ', { + get: function() { return this.storage_[2] != 0x0; } +}); + +Object.defineProperty(SIMD.int32x4.prototype, 'flagW', + { get: function() { return this.storage_[3] != 0x0; } +}); + +/** + * Extract the sign bit from each lane return them in the first 4 bits. + */ +Object.defineProperty(SIMD.int32x4.prototype, 'signMask', { + get: function() { + var mx = (this.storage_[0] & 0x80000000) >>> 31; + var my = (this.storage_[1] & 0x80000000) >>> 31; + var mz = (this.storage_[2] & 0x80000000) >>> 31; + var mw = (this.storage_[3] & 0x80000000) >>> 31; + return mx | my << 1 | mz << 2 | mw << 3; + } +}); + +function isNumber(o) { + return typeof o == "number" || (typeof o == "object" && o.constructor === Number); +} + +function isTypedArray(o) { + return (o instanceof Int8Array) || + (o instanceof Uint8Array) || + (o instanceof Uint8ClampedArray) || + (o instanceof Int16Array) || + (o instanceof Uint16Array) || + (o instanceof Int32Array) || + (o instanceof Uint32Array) || + (o instanceof Float32Array) || + (o instanceof Float64Array) || + (o instanceof Float32x4Array); +} + +function isArrayBuffer(o) { + return (o instanceof ArrayBuffer); +} + +function Float32x4Array(a, b, c) { + if (isNumber(a)) { + this.storage_ = new Float32Array(a*4); + this.length_ = a; + this.byteOffset_ = 0; + return; + } else if (isTypedArray(a)) { + if (!(a instanceof Float32x4Array)) { + throw "Copying typed array of non-Float32x4Array is unimplemented."; + } + this.storage_ = new Float32Array(a.length * 4); + this.length_ = a.length; + this.byteOffset_ = 0; + // Copy floats. + for (var i = 0; i < a.length*4; i++) { + this.storage_[i] = a.storage_[i]; + } + } else if (isArrayBuffer(a)) { + if ((b != undefined) && (b % Float32x4Array.BYTES_PER_ELEMENT) != 0) { + throw "byteOffset must be a multiple of 16."; + } + if (c != undefined) { + c *= 4; + this.storage_ = new Float32Array(a, b, c); + } + else { + // Note = new Float32Array(a, b) is NOT equivalent to new Float32Array(a, b, undefined) + this.storage_ = new Float32Array(a, b); + } + this.length_ = this.storage_.length / 4; + this.byteOffset_ = b != undefined ? b : 0; + } else { + throw "Unknown type of first argument."; + } +} + +Object.defineProperty(Float32x4Array.prototype, 'length', + { get: function() { return this.length_; } +}); + +Object.defineProperty(Float32x4Array.prototype, 'byteLength', + { get: function() { return this.length_ * Float32x4Array.BYTES_PER_ELEMENT; } +}); + +Object.defineProperty(Float32x4Array, 'BYTES_PER_ELEMENT', + { get: function() { return 16; } +}); + +Object.defineProperty(Float32x4Array.prototype, 'BYTES_PER_ELEMENT', + { get: function() { return 16; } +}); + +Object.defineProperty(Float32x4Array.prototype, 'byteOffset', + { get: function() { return this.byteOffset_; } +}); + +Object.defineProperty(Float32x4Array.prototype, 'buffer', + { get: function() { return this.storage_.buffer; } +}); + +Float32x4Array.prototype.getAt = function(i) { + if (i < 0) { + throw "Index must be >= 0."; + } + if (i >= this.length) { + throw "Index out of bounds."; + } + var x = this.storage_[i*4+0]; + var y = this.storage_[i*4+1]; + var z = this.storage_[i*4+2]; + var w = this.storage_[i*4+3]; + return SIMD.float32x4(x, y, z, w); +} + +Float32x4Array.prototype.setAt = function(i, v) { + if (i < 0) { + throw "Index must be >= 0."; + } + if (i >= this.length) { + throw "Index out of bounds."; + } + if (!(v instanceof SIMD.float32x4)) { + throw "Value is not a float32x4."; + } + this.storage_[i*4+0] = v.x; + this.storage_[i*4+1] = v.y; + this.storage_[i*4+2] = v.z; + this.storage_[i*4+3] = v.w; +} + + +function Int32x4Array(a, b, c) { + + function isNumber(o) { + return typeof o == "number" || (typeof o == "object" && o.constructor === Number); + } + + function isTypedArray(o) { + return (o instanceof Int8Array) || + (o instanceof Uint8Array) || + (o instanceof Uint8ClampedArray) || + (o instanceof Int16Array) || + (o instanceof Uint16Array) || + (o instanceof Int32Array) || + (o instanceof Uint32Array) || + (o instanceof Float32Array) || + (o instanceof Float64Array) || + (o instanceof Int32x4Array) || + (o instanceof Float32x4Array); + } + + function isArrayBuffer(o) { + return (o instanceof ArrayBuffer); + } + + if (isNumber(a)) { + this.storage_ = new Int32Array(a*4); + this.length_ = a; + this.byteOffset_ = 0; + return; + } else if (isTypedArray(a)) { + if (!(a instanceof Int32x4Array)) { + throw "Copying typed array of non-Int32x4Array is unimplemented."; + } + this.storage_ = new Int32Array(a.length * 4); + this.length_ = a.length; + this.byteOffset_ = 0; + // Copy ints. + for (var i = 0; i < a.length*4; i++) { + this.storage_[i] = a.storage_[i]; + } + } else if (isArrayBuffer(a)) { + if ((b != undefined) && (b % Int32x4Array.BYTES_PER_ELEMENT) != 0) { + throw "byteOffset must be a multiple of 16."; + } + if (c != undefined) { + c *= 4; + this.storage_ = new Int32Array(a, b, c); + } + else { + // Note = new Int32Array(a, b) is NOT equivalent to new Float32Array(a, b, undefined) + this.storage_ = new Int32Array(a, b); + } + this.length_ = this.storage_.length / 4; + this.byteOffset_ = b != undefined ? b : 0; + } else { + throw "Unknown type of first argument."; + } +} + +Object.defineProperty(Int32x4Array.prototype, 'length', + { get: function() { return this.length_; } +}); + +Object.defineProperty(Int32x4Array.prototype, 'byteLength', + { get: function() { return this.length_ * Int32x4Array.BYTES_PER_ELEMENT; } +}); + +Object.defineProperty(Int32x4Array, 'BYTES_PER_ELEMENT', + { get: function() { return 16; } +}); + +Object.defineProperty(Int32x4Array.prototype, 'BYTES_PER_ELEMENT', + { get: function() { return 16; } +}); + +Object.defineProperty(Int32x4Array.prototype, 'byteOffset', + { get: function() { return this.byteOffset_; } +}); + +Object.defineProperty(Int32x4Array.prototype, 'buffer', + { get: function() { return this.storage_.buffer; } +}); + +Int32x4Array.prototype.getAt = function(i) { + if (i < 0) { + throw "Index must be >= 0."; + } + if (i >= this.length) { + throw "Index out of bounds."; + } + var x = this.storage_[i*4+0]; + var y = this.storage_[i*4+1]; + var z = this.storage_[i*4+2]; + var w = this.storage_[i*4+3]; + return SIMD.int32x4(x, y, z, w); +} + +Int32x4Array.prototype.setAt = function(i, v) { + if (i < 0) { + throw "Index must be >= 0."; + } + if (i >= this.length) { + throw "Index out of bounds."; + } + if (!(v instanceof SIMD.int32x4)) { + throw "Value is not a int32x4."; + } + this.storage_[i*4+0] = v.x; + this.storage_[i*4+1] = v.y; + this.storage_[i*4+2] = v.z; + this.storage_[i*4+3] = v.w; +}
\ No newline at end of file diff --git a/src/struct_info.json b/src/struct_info.json index f762bf2b..54c89fd7 100644 --- a/src/struct_info.json +++ b/src/struct_info.json @@ -932,6 +932,16 @@ "file": "SDL/SDL_events.h", "defines": [], "structs": { + "SDL_WindowEvent": [ + "type", + "windowID", + "event", + "padding1", + "padding2", + "padding3", + "data1", + "data2" + ], "SDL_KeyboardEvent": [ "type", "windowID", @@ -969,6 +979,14 @@ "x", "y" ], + "SDL_MouseWheelEvent": [ + "type", + "timestamp", + "windowID", + "which", + "x", + "y" + ], "SDL_JoyAxisEvent": [ "type", "which", |