diff options
-rwxr-xr-x | emcc | 2 | ||||
-rwxr-xr-x | emscripten.py | 12 | ||||
-rw-r--r-- | src/analyzer.js | 16 | ||||
-rwxr-xr-x | src/embind/embind.js | 631 | ||||
-rwxr-xr-x | src/embind/emval.js | 60 | ||||
-rw-r--r-- | src/jsifier.js | 60 | ||||
-rw-r--r-- | src/library.js | 63 | ||||
-rw-r--r-- | src/library_browser.js | 6 | ||||
-rw-r--r-- | src/library_gl.js | 8 | ||||
-rw-r--r-- | src/preamble.js | 2 | ||||
-rw-r--r-- | src/settings.js | 1 | ||||
-rwxr-xr-x | system/include/emscripten/bind.h | 192 | ||||
-rw-r--r-- | system/include/emscripten/val.h | 57 | ||||
-rwxr-xr-x | system/include/emscripten/wire.h | 34 | ||||
-rwxr-xr-x | system/lib/embind/bind.cpp | 23 | ||||
-rw-r--r-- | tests/cases/legalizer_ta2.ll | 1 | ||||
-rw-r--r-- | tests/cases/longjmp_tiny.ll (renamed from tests/cases/longjmp_tiny_noasm.ll) | 0 | ||||
-rw-r--r-- | tests/cases/longjmp_tiny.txt (renamed from tests/cases/longjmp_tiny_noasm.txt) | 0 | ||||
-rw-r--r-- | tests/cases/longjmp_tiny_invoke.ll (renamed from tests/cases/longjmp_tiny_noasm_invoke.ll) | 0 | ||||
-rw-r--r-- | tests/cases/longjmp_tiny_invoke.txt (renamed from tests/cases/longjmp_tiny_noasm_invoke.txt) | 0 | ||||
-rw-r--r-- | tests/cases/longjmp_tiny_phi.ll (renamed from tests/cases/longjmp_tiny_phi_noasm.ll) | 0 | ||||
-rw-r--r-- | tests/cases/longjmp_tiny_phi.txt (renamed from tests/cases/longjmp_tiny_phi_noasm.txt) | 0 | ||||
-rw-r--r-- | tests/cases/longjmp_tiny_phi2.ll (renamed from tests/cases/longjmp_tiny_phi2_noasm.ll) | 0 | ||||
-rw-r--r-- | tests/cases/longjmp_tiny_phi2.txt (renamed from tests/cases/longjmp_tiny_phi2_noasm.txt) | 0 | ||||
-rw-r--r-- | tests/embind/embind.benchmark.js | 201 | ||||
-rwxr-xr-x | tests/embind/embind.test.js | 164 | ||||
-rw-r--r-- | tests/embind/embind_benchmark.cpp | 344 | ||||
-rw-r--r-- | tests/embind/embind_test.cpp | 124 | ||||
-rw-r--r-- | tests/embind/shell.html | 94 | ||||
-rw-r--r-- | tests/gl_stride.c | 152 | ||||
-rw-r--r-- | tests/gl_stride.png | bin | 0 -> 345620 bytes | |||
-rwxr-xr-x | tests/runner.py | 24 | ||||
-rw-r--r-- | tools/eliminator/asm-eliminator-test-output.js | 15 | ||||
-rw-r--r-- | tools/eliminator/asm-eliminator-test.js | 15 | ||||
-rw-r--r-- | tools/js-optimizer.js | 13 | ||||
-rw-r--r-- | tools/shared.py | 4 |
36 files changed, 1879 insertions, 439 deletions
@@ -1002,7 +1002,7 @@ try: # Apply effects from settings if shared.Settings.ASM_JS: - assert opt_level == 2, 'asm.js requires -O2' + assert opt_level >= 1, 'asm.js requires -O1 or above' if closure: print >> sys.stderr, 'emcc: warning: disabling closure because it is not compatible with asm.js code generation' diff --git a/emscripten.py b/emscripten.py index 6e5f1e7c..6d384a96 100755 --- a/emscripten.py +++ b/emscripten.py @@ -426,7 +426,7 @@ function invoke_%s(%s) { try { %sModule.dynCall_%s(%s); } catch(e) { - asm.setThrew(1); + asm.setThrew(1, 0); } } ''' % (sig, args, 'return ' if sig[0] != 'v' else '', sig, args) @@ -489,6 +489,8 @@ var asm = (function(global, env, buffer) { var HEAPF64 = new global.Float64Array(buffer); ''' % (asm_setup,) + '\n' + asm_global_vars + ''' var __THREW__ = 0; + var threwValue = 0; + var setjmpId = 0; var undef = 0; var tempInt = 0, tempBigInt = 0, tempBigIntP = 0, tempBigIntS = 0, tempBigIntR = 0.0, tempBigIntI = 0, tempBigIntD = 0, tempValue = 0, tempDouble = 0.0; ''' + ''.join([''' @@ -509,9 +511,13 @@ var asm = (function(global, env, buffer) { top = top|0; STACKTOP = top; } - function setThrew(threw) { + function setThrew(threw, value) { threw = threw|0; - __THREW__ = threw; + value = value|0; + if ((__THREW__|0) == 0) { + __THREW__ = threw; + threwValue = value; + } } ''' + ''.join([''' function setTempRet%d(value) { diff --git a/src/analyzer.js b/src/analyzer.js index df5a435e..3278139b 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -1389,21 +1389,21 @@ function analyzer(data, sidePass) { var line = label.lines[j]; if ((line.intertype == 'call' || line.intertype == 'invoke') && line.ident == setjmp) { // Add a new label - var oldIdent = label.ident; - var newIdent = func.labelIdCounter++; + var oldLabel = label.ident; + var newLabel = func.labelIdCounter++; if (!func.setjmpTable) func.setjmpTable = []; - func.setjmpTable.push([oldIdent, newIdent, line.assignTo]); + func.setjmpTable.push({ oldLabel: oldLabel, newLabel: newLabel, assignTo: line.assignTo }); func.labels.splice(i+1, 0, { intertype: 'label', - ident: newIdent, + ident: newLabel, lineNum: label.lineNum + 0.5, lines: label.lines.slice(j+1) }); - func.labelsDict[newIdent] = func.labels[i+1]; + func.labelsDict[newLabel] = func.labels[i+1]; label.lines = label.lines.slice(0, j+1); label.lines.push({ intertype: 'branch', - label: toNiceIdent(newIdent), + label: toNiceIdent(newLabel), lineNum: line.lineNum + 0.01, // XXX legalizing might confuse this }); // Correct phis @@ -1412,8 +1412,8 @@ function analyzer(data, sidePass) { if (phi.intertype == 'phi') { for (var i = 0; i < phi.params.length; i++) { var sourceLabelId = getActualLabelId(phi.params[i].label); - if (sourceLabelId == oldIdent) { - phi.params[i].label = newIdent; + if (sourceLabelId == oldLabel) { + phi.params[i].label = newLabel; } } } diff --git a/src/embind/embind.js b/src/embind/embind.js index ee717f4b..988526b4 100755 --- a/src/embind/embind.js +++ b/src/embind/embind.js @@ -1,10 +1,10 @@ /*global Module*/ /*global _malloc, _free, _memcpy*/ -/*global FUNCTION_TABLE, HEAP32, HEAPU8*/ -/*global Pointer_stringify*/ +/*global FUNCTION_TABLE, HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32*/ +/*global readLatin1String*/ /*global __emval_register, _emval_handle_array, __emval_decref*/ /*global ___getTypeName*/ - +/*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'); @@ -223,9 +223,26 @@ function whenDependentTypesAreResolved(myTypes, dependentTypes, getTypeConverter } } +var __charCodes = (function() { + var codes = new Array(256); + for (var i = 0; i < 256; ++i) { + codes[i] = String.fromCharCode(i); + } + return codes; +})(); + +function readLatin1String(ptr) { + var ret = ""; + var c = ptr; + while (HEAPU8[c]) { + ret += __charCodes[HEAPU8[c++]]; + } + return ret; +} + function getTypeName(type) { var ptr = ___getTypeName(type); - var rv = Pointer_stringify(ptr); + var rv = readLatin1String(ptr); _free(ptr); return rv; } @@ -247,34 +264,35 @@ function requireRegisteredType(rawType, humanName) { } function __embind_register_void(rawType, name) { - name = Pointer_stringify(name); + name = readLatin1String(name); registerType(rawType, { name: name, - fromWireType: function() { + 'fromWireType': function() { return undefined; }, }); } function __embind_register_bool(rawType, name, trueValue, falseValue) { - name = Pointer_stringify(name); + name = readLatin1String(name); registerType(rawType, { name: name, - fromWireType: function(wt) { + 'fromWireType': function(wt) { // ambiguous emscripten ABI: sometimes return values are // true or false, and sometimes integers (0 or 1) return !!wt; }, - toWireType: function(destructors, o) { + 'toWireType': function(destructors, o) { return o ? trueValue : falseValue; }, + destructorFunction: null, // This type does not need a destructor }); } // When converting a number from JS to C++ side, the valid range of the number is // [minRange, maxRange], inclusive. function __embind_register_integer(primitiveType, name, minRange, maxRange) { - name = Pointer_stringify(name); + name = readLatin1String(name); if (maxRange === -1) { // LLVM doesn't have signed and unsigned 32-bit types, so u32 literals come out as 'i32 -1'. Always treat those as max u32. maxRange = 4294967295; } @@ -282,10 +300,10 @@ function __embind_register_integer(primitiveType, name, minRange, maxRange) { name: name, minRange: minRange, maxRange: maxRange, - fromWireType: function(value) { + 'fromWireType': function(value) { return value; }, - toWireType: function(destructors, value) { + 'toWireType': function(destructors, value) { // todo: Here we have an opportunity for -O3 level "unsafe" optimizations: we could // avoid the following two if()s and assume value is of proper type. if (typeof value !== "number") { @@ -296,17 +314,18 @@ function __embind_register_integer(primitiveType, name, minRange, maxRange) { } return value | 0; }, + destructorFunction: null, // This type does not need a destructor }); } function __embind_register_float(rawType, name) { - name = Pointer_stringify(name); + name = readLatin1String(name); registerType(rawType, { name: name, - fromWireType: function(value) { + 'fromWireType': function(value) { return value; }, - toWireType: function(destructors, value) { + 'toWireType': function(destructors, value) { // todo: Here we have an opportunity for -O3 level "unsafe" optimizations: we could // avoid the following if() and assume value is of proper type. if (typeof value !== "number") { @@ -314,15 +333,16 @@ function __embind_register_float(rawType, name) { } return value; }, + destructorFunction: null, // This type does not need a destructor }); } -function __embind_register_cstring(rawType, name) { - name = Pointer_stringify(name); +function __embind_register_std_string(rawType, name) { + name = readLatin1String(name); registerType(rawType, { name: name, - fromWireType: function(value) { - var length = HEAP32[value >> 2]; + 'fromWireType': function(value) { + var length = HEAPU32[value >> 2]; var a = new Array(length); for (var i = 0; i < length; ++i) { a[i] = String.fromCharCode(HEAPU8[value + 4 + i]); @@ -330,32 +350,102 @@ function __embind_register_cstring(rawType, name) { _free(value); return a.join(''); }, - toWireType: function(destructors, value) { + 'toWireType': function(destructors, value) { + if (value instanceof ArrayBuffer) { + value = new Uint8Array(value); + } + + function getTAElement(ta, index) { + return ta[index]; + } + function getStringElement(string, index) { + return string.charCodeAt(index); + } + var getElement; + if (value instanceof Uint8Array) { + getElement = getTAElement; + } else if (value instanceof Int8Array) { + getElement = getTAElement; + } else if (typeof value === 'string') { + getElement = getStringElement; + } else { + throwBindingError('Cannot pass non-string to std::string'); + } + // assumes 4-byte alignment var length = value.length; var ptr = _malloc(4 + length); - HEAP32[ptr >> 2] = length; + HEAPU32[ptr >> 2] = length; + for (var i = 0; i < length; ++i) { + var charCode = getElement(value, i); + if (charCode > 255) { + _free(ptr); + throwBindingError('String has UTF-16 code units that do not fit in 8 bits'); + } + HEAPU8[ptr + 4 + i] = charCode; + } + if (destructors !== null) { + destructors.push(_free, ptr); + } + return ptr; + }, + destructorFunction: function(ptr) { _free(ptr); }, + }); +} + +function __embind_register_std_wstring(rawType, charSize, name) { + name = readLatin1String(name); + var HEAP, shift; + if (charSize === 2) { + HEAP = HEAPU16; + shift = 1; + } else if (charSize === 4) { + HEAP = HEAPU32; + shift = 2; + } + registerType(rawType, { + name: name, + 'fromWireType': function(value) { + var length = HEAPU32[value >> 2]; + var a = new Array(length); + var start = (value + 4) >> shift; + for (var i = 0; i < length; ++i) { + a[i] = String.fromCharCode(HEAP[start + i]); + } + _free(value); + return a.join(''); + }, + 'toWireType': function(destructors, value) { + // assumes 4-byte alignment + var length = value.length; + var ptr = _malloc(4 + length * charSize); + HEAPU32[ptr >> 2] = length; + var start = (ptr + 4) >> shift; for (var i = 0; i < length; ++i) { - HEAPU8[ptr + 4 + i] = value.charCodeAt(i); + HEAP[start + i] = value.charCodeAt(i); + } + if (destructors !== null) { + destructors.push(_free, ptr); } - destructors.push(_free, ptr); return ptr; }, + destructorFunction: function(ptr) { _free(ptr); }, }); } function __embind_register_emval(rawType, name) { - name = Pointer_stringify(name); + name = readLatin1String(name); registerType(rawType, { name: name, - fromWireType: function(handle) { + 'fromWireType': function(handle) { var rv = _emval_handle_array[handle].value; __emval_decref(handle); return rv; }, - toWireType: function(destructors, value) { + 'toWireType': function(destructors, value) { return __emval_register(value); }, + destructorFunction: null, // This type does not need a destructor }); } @@ -367,30 +457,142 @@ function runDestructors(destructors) { } } -function makeInvoker(name, argCount, argTypes, invoker, fn) { - if (!FUNCTION_TABLE[fn]) { - throwBindingError('function '+name+' is not defined'); +// Function implementation of operator new, per +// http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf +// 13.2.2 +// ES3 +function new_(constructor, argumentList) { + if (!(constructor instanceof Function)) { + throw new TypeError('new_ called with constructor type ' + typeof(constructor) + " which is not a function"); + } + + /* + * Previously, the following line was just: + + function dummy() {}; + + * Unfortunately, Chrome was preserving 'dummy' as the object's name, even though at creation, the 'dummy' has the + * correct constructor name. Thus, objects created with IMVU.new would show up in the debugger as 'dummy', which + * isn't very helpful. Using IMVU.createNamedFunction addresses the issue. Doublely-unfortunately, there's no way + * to write a test for this behavior. -NRD 2013.02.22 + */ + var dummy = createNamedFunction(constructor.name, function(){}); + dummy.prototype = constructor.prototype; + var obj = new dummy; + + var r = constructor.apply(obj, argumentList); + return (r instanceof Object) ? r : obj; +} + +// The path to interop from JS code to C++ code: +// (hand-written JS code) -> (autogenerated JS invoker) -> (template-generated C++ invoker) -> (target C++ function) +// craftInvokerFunction generates the JS invoker function for each function exposed to JS through embind. +function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cppTargetFunc) { + // humanName: a human-readable string name for the function to be generated. + // argTypes: An array that contains the embind type objects for all types in the function signature. + // argTypes[0] is the type object for the function return value. + // argTypes[1] is the type object for function this object/class type, or null if not crafting an invoker for a class method. + // argTypes[2...] are the actual function parameters. + // classType: The embind type object for the class to be bound, or null if this is not a method of a class. + // cppInvokerFunc: JS Function object to the C++-side function that interops into C++ code. + // cppTargetFunc: Function pointer (an integer to FUNCTION_TABLE) to the target C++ function the cppInvokerFunc will end up calling. + var argCount = argTypes.length; + + if (argCount < 2) { + throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!"); + } + + 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) { +// return FUNCTION_TABLE[fn]; +// } + + var argsList = ""; + var argsListWired = ""; + for(var i = 0; i < argCount-2; ++i) { + argsList += (i!==0?", ":"")+"arg"+i; + argsListWired += (i!==0?", ":"")+"arg"+i+"Wired"; } - return createNamedFunction(makeLegalFunctionName(name), function() { - if (arguments.length !== argCount - 1) { - throwBindingError('function ' + name + ' called with ' + arguments.length + ' arguments, expected ' + (argCount - 1)); + + var invokerFnBody = + "return function "+makeLegalFunctionName(humanName)+"("+argsList+") {\n" + + "if (arguments.length !== "+(argCount - 2)+") {\n" + + "throwBindingError('function "+humanName+" called with ' + arguments.length + ' arguments, expected "+(argCount - 2)+" args!');\n" + + "}\n"; + + // Determine if we need to use a dynamic stack to store the destructors for the function parameters. + // TODO: Remove this completely once all function invokers are being dynamically generated. + var needsDestructorStack = false; + + for(var i = 1; i < argTypes.length; ++i) { // Skip return value at index 0 - it's not deleted here. + if (argTypes[i] !== null && argTypes[i].destructorFunction === undefined) { // The type does not define a destructor function - must use dynamic stack + needsDestructorStack = true; + break; } - var destructors = []; - var args = new Array(argCount); - args[0] = fn; - for (var i = 1; i < argCount; ++i) { - args[i] = argTypes[i].toWireType(destructors, arguments[i - 1]); + } + + if (needsDestructorStack) { + invokerFnBody += + "var destructors = [];\n"; + } + + 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]]; + + if (isClassMethodFunc) { + invokerFnBody += "var thisWired = classParam.toWireType("+dtorStack+", this);\n"; + } + + 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]); + } + + if (isClassMethodFunc) { + argsListWired = "thisWired" + (argsListWired.length > 0 ? ", " : "") + argsListWired; + } + + var returns = (argTypes[0].name !== "void"); + + invokerFnBody += + (returns?"var rv = ":"") + "invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n"; + + if (needsDestructorStack) { + 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")); + if (argTypes[i].destructorFunction !== null) { + invokerFnBody += paramName+"_dtor("+paramName+"); // "+argTypes[i].name+"\n"; + args1.push(paramName+"_dtor"); + args2.push(argTypes[i].destructorFunction); + } } - var rv = invoker.apply(null, args); - rv = argTypes[0].fromWireType(rv); - runDestructors(destructors); - return rv; - }); + } + + if (returns) { + invokerFnBody += "return retType.fromWireType(rv);\n"; + } + invokerFnBody += "}\n"; + + args1.push(invokerFnBody); + + var invokerFunction = new_(Function, args1).apply(null, args2); + return invokerFunction; } function __embind_register_function(name, argCount, rawArgTypesAddr, rawInvoker, fn) { var argTypes = heap32VectorToArray(argCount, rawArgTypesAddr); - name = Pointer_stringify(name); + name = readLatin1String(name); rawInvoker = FUNCTION_TABLE[rawInvoker]; exposePublicSymbol(name, function() { @@ -398,7 +600,8 @@ function __embind_register_function(name, argCount, rawArgTypesAddr, rawInvoker, }, argCount - 1); whenDependentTypesAreResolved([], argTypes, function(argTypes) { - replacePublicSymbol(name, makeInvoker(name, argCount, argTypes, rawInvoker, fn), argCount - 1); + var invokerArgsArray = [argTypes[0] /* return value */, null /* no class 'this'*/].concat(argTypes.slice(1) /* actual params */); + replacePublicSymbol(name, craftInvokerFunction(name, invokerArgsArray, null /* no class 'this'*/, rawInvoker, fn), argCount - 1); return []; }); } @@ -407,7 +610,7 @@ var tupleRegistrations = {}; function __embind_register_tuple(rawType, name, rawConstructor, rawDestructor) { tupleRegistrations[rawType] = { - name: Pointer_stringify(name), + name: readLatin1String(name), rawConstructor: FUNCTION_TABLE[rawConstructor], rawDestructor: FUNCTION_TABLE[rawDestructor], elements: [], @@ -453,18 +656,18 @@ function __embind_finalize_tuple(rawTupleType) { var setter = elt.setter; var setterContext = elt.setterContext; elt.read = function(ptr) { - return getterReturnType.fromWireType(getter(getterContext, ptr)); + return getterReturnType['fromWireType'](getter(getterContext, ptr)); }; elt.write = function(ptr, o) { var destructors = []; - setter(setterContext, ptr, setterArgumentType.toWireType(destructors, o)); + setter(setterContext, ptr, setterArgumentType['toWireType'](destructors, o)); runDestructors(destructors); }; }); return [{ name: reg.name, - fromWireType: function(ptr) { + 'fromWireType': function(ptr) { var rv = new Array(elementsLength); for (var i = 0; i < elementsLength; ++i) { rv[i] = elements[i].read(ptr); @@ -472,7 +675,7 @@ function __embind_finalize_tuple(rawTupleType) { rawDestructor(ptr); return rv; }, - toWireType: function(destructors, o) { + 'toWireType': function(destructors, o) { if (elementsLength !== o.length) { throw new TypeError("Incorrect number of tuple elements"); } @@ -480,9 +683,12 @@ function __embind_finalize_tuple(rawTupleType) { for (var i = 0; i < elementsLength; ++i) { elements[i].write(ptr, o[i]); } - destructors.push(rawDestructor, ptr); + if (destructors !== null) { + destructors.push(rawDestructor, ptr); + } return ptr; }, + destructorFunction: rawDestructor, }]; }); } @@ -496,7 +702,7 @@ function __embind_register_struct( rawDestructor ) { structRegistrations[rawType] = { - name: Pointer_stringify(name), + name: readLatin1String(name), rawConstructor: FUNCTION_TABLE[rawConstructor], rawDestructor: FUNCTION_TABLE[rawDestructor], fields: [], @@ -514,7 +720,7 @@ function __embind_register_struct_field( setterContext ) { structRegistrations[structType].fields.push({ - fieldName: Pointer_stringify(fieldName), + fieldName: readLatin1String(fieldName), getterReturnType: getterReturnType, getter: FUNCTION_TABLE[getter], getterContext: getterContext, @@ -545,12 +751,12 @@ function __embind_finalize_struct(structType) { var setterContext = field.setterContext; fields[fieldName] = { read: function(ptr) { - return getterReturnType.fromWireType( + return getterReturnType['fromWireType']( getter(getterContext, ptr)); }, write: function(ptr, o) { var destructors = []; - setter(setterContext, ptr, setterArgumentType.toWireType(destructors, o)); + setter(setterContext, ptr, setterArgumentType['toWireType'](destructors, o)); runDestructors(destructors); } }; @@ -558,7 +764,7 @@ function __embind_finalize_struct(structType) { return [{ name: reg.name, - fromWireType: function(ptr) { + 'fromWireType': function(ptr) { var rv = {}; for (var i in fields) { rv[i] = fields[i].read(ptr); @@ -566,7 +772,7 @@ function __embind_finalize_struct(structType) { rawDestructor(ptr); return rv; }, - toWireType: function(destructors, o) { + 'toWireType': function(destructors, o) { // todo: Here we have an opportunity for -O3 level "unsafe" optimizations: // assume all fields are present without checking. for (var fieldName in fields) { @@ -578,55 +784,17 @@ function __embind_finalize_struct(structType) { for (fieldName in fields) { fields[fieldName].write(ptr, o[fieldName]); } - destructors.push(rawDestructor, ptr); + if (destructors !== null) { + destructors.push(rawDestructor, ptr); + } return ptr; }, + destructorFunction: rawDestructor, }]; }); } -function RegisteredPointer( - name, - registeredClass, - isReference, - isConst, - - // smart pointer properties - isSmartPointer, - pointeeType, - sharingPolicy, - rawGetPointee, - rawConstructor, - rawShare, - rawDestructor -) { - this.name = name; - this.registeredClass = registeredClass; - this.isReference = isReference; - this.isConst = isConst; - - // smart pointer properties - this.isSmartPointer = isSmartPointer; - this.pointeeType = pointeeType; - this.sharingPolicy = sharingPolicy; - this.rawGetPointee = rawGetPointee; - this.rawConstructor = rawConstructor; - this.rawShare = rawShare; - this.rawDestructor = rawDestructor; -} - -RegisteredPointer.prototype.toWireType = function(destructors, handle) { - var self = this; - function throwCannotConvert() { - var name; - if (handle.$$.smartPtrType) { - name = handle.$$.smartPtrType.name; - } else { - name = handle.$$.ptrType.name; - } - throwBindingError('Cannot convert argument of type ' + name + ' to parameter type ' + self.name); - } - +var genericPointerToWireType = function(destructors, handle) { if (handle === null) { if (this.isReference) { throwBindingError('null is not a valid ' + this.name); @@ -640,33 +808,39 @@ RegisteredPointer.prototype.toWireType = function(destructors, handle) { return 0; } } - if (!(handle instanceof this.registeredClass.constructor)) { - throwBindingError('Expected null or instance of ' + this.name + ', got ' + _embind_repr(handle)); + + if (!handle.$$) { + throwBindingError('Cannot pass "' + _embind_repr(handle) + '" as a ' + this.name); } - // TODO: this is not strictly true - // We could support BY_EMVAL conversions from raw pointers to smart pointers - // because the smart pointer can hold a reference to the handle - if (this.isSmartPointer && undefined === handle.$$.smartPtr) { - throwBindingError('Passing raw pointer to smart pointer is illegal'); + if (!handle.$$.ptr) { + throwBindingError('Cannot pass deleted object as a pointer of type ' + this.name); } if (!this.isConst && handle.$$.ptrType.isConst) { - throwCannotConvert(); + throwBindingError('Cannot convert argument of type ' + (handle.$$.smartPtrType ? handle.$$.smartPtrType.name : handle.$$.ptrType.name) + ' to parameter type ' + this.name); } var handleClass = handle.$$.ptrType.registeredClass; var ptr = upcastPointer(handle.$$.ptr, handleClass, this.registeredClass); + if (this.isSmartPointer) { + // TODO: this is not strictly true + // We could support BY_EMVAL conversions from raw pointers to smart pointers + // because the smart pointer can hold a reference to the handle + if (undefined === handle.$$.smartPtr) { + throwBindingError('Passing raw pointer to smart pointer is illegal'); + } + switch (this.sharingPolicy) { case 0: // NONE // no upcasting if (handle.$$.smartPtrType === this) { ptr = handle.$$.smartPtr; } else { - throwCannotConvert(); + throwBindingError('Cannot convert argument of type ' + (handle.$$.smartPtrType ? handle.$$.smartPtrType.name : handle.$$.ptrType.name) + ' to parameter type ' + this.name); } break; case 1: // INTRUSIVE - throwBindingError('INTRUSIVE sharing policy not yet supported'); + ptr = handle.$$.smartPtr; break; case 2: // BY_EMVAL @@ -691,6 +865,97 @@ RegisteredPointer.prototype.toWireType = function(destructors, handle) { return ptr; }; +// If we know a pointer type is not going to have SmartPtr logic in it, we can +// special-case optimize it a bit (compare to genericPointerToWireType) +var constNoSmartPtrRawPointerToWireType = function(destructors, handle) { + if (handle === null) { + if (this.isReference) { + throwBindingError('null is not a valid ' + this.name); + } + return 0; + } + + if (!handle.$$) { + throwBindingError('Cannot pass "' + _embind_repr(handle) + '" as a ' + this.name); + } + if (!handle.$$.ptr) { + throwBindingError('Cannot pass deleted object as a pointer of type ' + this.name); + } + var handleClass = handle.$$.ptrType.registeredClass; + var ptr = upcastPointer(handle.$$.ptr, handleClass, this.registeredClass); + return ptr; +}; + +// An optimized version for non-const method accesses - there we must additionally restrict that +// the pointer is not a const-pointer. +var nonConstNoSmartPtrRawPointerToWireType = function(destructors, handle) { + if (handle === null) { + if (this.isReference) { + throwBindingError('null is not a valid ' + this.name); + } + return 0; + } + + if (!handle.$$) { + throwBindingError('Cannot pass "' + _embind_repr(handle) + '" as a ' + this.name); + } + if (!handle.$$.ptr) { + throwBindingError('Cannot pass deleted object as a pointer of type ' + this.name); + } + if (handle.$$.ptrType.isConst) { + throwBindingError('Cannot convert argument of type ' + handle.$$.ptrType.name + ' to parameter type ' + this.name); + } + var handleClass = handle.$$.ptrType.registeredClass; + var ptr = upcastPointer(handle.$$.ptr, handleClass, this.registeredClass); + return ptr; +}; + +function RegisteredPointer( + name, + registeredClass, + isReference, + isConst, + + // smart pointer properties + isSmartPointer, + pointeeType, + sharingPolicy, + rawGetPointee, + rawConstructor, + rawShare, + rawDestructor +) { + this.name = name; + this.registeredClass = registeredClass; + this.isReference = isReference; + this.isConst = isConst; + + // smart pointer properties + this.isSmartPointer = isSmartPointer; + this.pointeeType = pointeeType; + this.sharingPolicy = sharingPolicy; + this.rawGetPointee = rawGetPointee; + this.rawConstructor = rawConstructor; + this.rawShare = rawShare; + this.rawDestructor = rawDestructor; + + if (!isSmartPointer && registeredClass.baseClass === undefined) { + if (isConst) { + this['toWireType'] = constNoSmartPtrRawPointerToWireType; + this.destructorFunction = null; + } else { + this['toWireType'] = nonConstNoSmartPtrRawPointerToWireType; + this.destructorFunction = null; + } + } else { + this['toWireType'] = genericPointerToWireType; + // Here we must leave this.destructorFunction undefined, since whether genericPointerToWireType returns + // a pointer that needs to be freed up is runtime-dependent, and cannot be evaluated at registration time. + // TODO: Create an alternative mechanism that allows removing the use of var destructors = []; array in + // craftInvokerFunction altogether. + } +} + RegisteredPointer.prototype.getPointee = function(ptr) { if (this.rawGetPointee) { ptr = this.rawGetPointee(ptr); @@ -704,7 +969,7 @@ RegisteredPointer.prototype.destructor = function(ptr) { } }; -RegisteredPointer.prototype.fromWireType = function(ptr) { +RegisteredPointer.prototype['fromWireType'] = function(ptr) { // ptr is a raw pointer (or a raw smartpointer) // rawPointer is a maybe-null raw pointer @@ -775,7 +1040,7 @@ function makeClassHandle(prototype, record) { } record.count = { value: 1 }; return Object.create(prototype, { - '$$': { + $$: { value: record, }, }); @@ -795,7 +1060,7 @@ ClassHandle.prototype.clone = function() { } var clone = Object.create(Object.getPrototypeOf(this), { - '$$': { + $$: { value: shallowCopy(this.$$), } }); @@ -865,7 +1130,7 @@ function __embind_register_class( name, rawDestructor ) { - name = Pointer_stringify(name); + name = readLatin1String(name); rawDestructor = FUNCTION_TABLE[rawDestructor]; getActualType = FUNCTION_TABLE[getActualType]; upcast = FUNCTION_TABLE[upcast]; @@ -988,13 +1253,13 @@ function __embind_register_class_constructor( var args = new Array(argCount); args[0] = rawConstructor; for (var i = 1; i < argCount; ++i) { - args[i] = argTypes[i].toWireType(destructors, arguments[i - 1]); + args[i] = argTypes[i]['toWireType'](destructors, arguments[i - 1]); } var ptr = invoker.apply(null, args); runDestructors(destructors); - return argTypes[0].fromWireType(ptr); + return argTypes[0]['fromWireType'](ptr); }; return []; }); @@ -1016,6 +1281,9 @@ function downcastPointer(ptr, ptrClass, desiredClass) { function upcastPointer(ptr, ptrClass, desiredClass) { while (ptrClass !== desiredClass) { + if (!ptrClass.upcast) { + throwBindingError("Expected null or instance of " + desiredClass.name + ", got an instance of " + ptrClass.name); + } ptr = ptrClass.upcast(ptr); ptrClass = ptrClass.baseClass; } @@ -1049,7 +1317,7 @@ function __embind_register_class_function( context ) { var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); - methodName = Pointer_stringify(methodName); + methodName = readLatin1String(methodName); rawInvoker = FUNCTION_TABLE[rawInvoker]; whenDependentTypesAreResolved([], [rawClassType], function(classType) { @@ -1057,8 +1325,8 @@ function __embind_register_class_function( var humanName = classType.name + '.' + methodName; var unboundTypesHandler = function() { - throwUnboundTypeError('Cannot call ' + humanName + ' due to unbound types', rawArgTypes); - }; + throwUnboundTypeError('Cannot call ' + humanName + ' due to unbound types', rawArgTypes); + }; var proto = classType.registeredClass.instancePrototype; var method = proto[methodName]; @@ -1074,25 +1342,8 @@ function __embind_register_class_function( } whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { - var memberFunction = createNamedFunction(makeLegalFunctionName(humanName), function() { - if (arguments.length !== argCount - 2) { - throwBindingError(humanName + ' called with ' + arguments.length + ' arguments, expected ' + (argCount-2)); - } - - validateThis(this, classType, humanName); - - var destructors = []; - var args = new Array(argCount + 1); - args[0] = context; - args[1] = argTypes[1].toWireType(destructors, this); - for (var i = 2; i < argCount; ++i) { - args[i] = argTypes[i].toWireType(destructors, arguments[i - 2]); - } - var rv = rawInvoker.apply(null, args); - rv = argTypes[0].fromWireType(rv); - runDestructors(destructors); - return rv; - }); + + var memberFunction = craftInvokerFunction(humanName, argTypes, classType, rawInvoker, context); // Replace the initial unbound-handler-stub function with the appropriate member function, now that all types // are resolved. If multiple overloads are registered for this function, the function goes into an overload table. @@ -1117,7 +1368,7 @@ function __embind_register_class_class_function( fn ) { var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); - methodName = Pointer_stringify(methodName); + methodName = readLatin1String(methodName); rawInvoker = FUNCTION_TABLE[rawInvoker]; whenDependentTypesAreResolved([], [rawClassType], function(classType) { classType = classType[0]; @@ -1141,7 +1392,8 @@ function __embind_register_class_class_function( 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 func = makeInvoker(humanName, argCount, argTypes, rawInvoker, fn); + 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 { @@ -1154,46 +1406,65 @@ function __embind_register_class_class_function( } function __embind_register_class_property( - rawClassType, + classType, fieldName, - rawFieldType, + getterReturnType, getter, + getterContext, + setterArgumentType, setter, - context + setterContext ) { - fieldName = Pointer_stringify(fieldName); + fieldName = readLatin1String(fieldName); getter = FUNCTION_TABLE[getter]; - setter = FUNCTION_TABLE[setter]; - whenDependentTypesAreResolved([], [rawClassType], function(classType) { + + whenDependentTypesAreResolved([], [classType], function(classType) { classType = classType[0]; var humanName = classType.name + '.' + fieldName; - - Object.defineProperty(classType.registeredClass.instancePrototype, fieldName, { + var desc = { get: function() { - throwUnboundTypeError('Cannot access ' + humanName + ' due to unbound types', [rawFieldType]); - }, - set: function() { - throwUnboundTypeError('Cannot access ' + humanName + ' due to unbound types', [rawFieldType]); + throwUnboundTypeError('Cannot access ' + humanName + ' due to unbound types', [getterReturnType, setterArgumentType]); }, enumerable: true, configurable: true - }); + }; + if (setter) { + desc.set = function() { + throwUnboundTypeError('Cannot access ' + humanName + ' due to unbound types', [getterReturnType, setterArgumentType]); + }; + } else { + desc.set = function(v) { + throwBindingError(humanName + ' is a read-only property'); + }; + } + + Object.defineProperty(classType.registeredClass.instancePrototype, fieldName, desc); - whenDependentTypesAreResolved([], [rawFieldType], function(fieldType) { - fieldType = fieldType[0]; - Object.defineProperty(classType.registeredClass.instancePrototype, fieldName, { + whenDependentTypesAreResolved( + [], + (setter ? [getterReturnType, setterArgumentType] : [getterReturnType]), + function(types) { + var getterReturnType = types[0]; + var desc = { get: function() { var ptr = validateThis(this, classType, humanName + ' getter'); - return fieldType.fromWireType(getter(context, ptr)); + return getterReturnType['fromWireType'](getter(getterContext, ptr)); }, - set: function(v) { + enumerable: true + }; + + if (setter) { + setter = FUNCTION_TABLE[setter]; + var setterArgumentType = types[1]; + desc.set = function(v) { var ptr = validateThis(this, classType, humanName + ' setter'); var destructors = []; - setter(context, ptr, fieldType.toWireType(destructors, v)); + setter(setterContext, ptr, setterArgumentType['toWireType'](destructors, v)); runDestructors(destructors); - }, - enumerable: true - }); + }; + } + + Object.defineProperty(classType.registeredClass.instancePrototype, fieldName, desc); return []; }); @@ -1223,7 +1494,7 @@ function __embind_register_smart_ptr( rawShare, rawDestructor ) { - name = Pointer_stringify(name); + name = readLatin1String(name); rawGetPointee = FUNCTION_TABLE[rawGetPointee]; rawConstructor = FUNCTION_TABLE[rawConstructor]; rawShare = FUNCTION_TABLE[rawShare]; @@ -1253,7 +1524,7 @@ function __embind_register_enum( rawType, name ) { - name = Pointer_stringify(name); + name = readLatin1String(name); function constructor() { } @@ -1262,12 +1533,13 @@ function __embind_register_enum( registerType(rawType, { name: name, constructor: constructor, - fromWireType: function(c) { + 'fromWireType': function(c) { return this.constructor.values[c]; }, - toWireType: function(destructors, c) { + 'toWireType': function(destructors, c) { return c.value; }, + destructorFunction: null, }); exposePublicSymbol(name, constructor); } @@ -1278,7 +1550,7 @@ function __embind_register_enum_value( enumValue ) { var enumType = requireRegisteredType(rawEnumType, 'enum'); - name = Pointer_stringify(name); + name = readLatin1String(name); var Enum = enumType.constructor; @@ -1290,34 +1562,11 @@ function __embind_register_enum_value( Enum[name] = Value; } -function __embind_register_interface( - rawType, - name, - rawConstructor, - rawDestructor -) { - name = Pointer_stringify(name); - rawConstructor = FUNCTION_TABLE[rawConstructor]; - rawDestructor = FUNCTION_TABLE[rawDestructor]; - - registerType(rawType, { - name: name, - rawConstructor: rawConstructor, - rawDestructor: rawDestructor, - toWireType: function(destructors, o) { - var handle = __emval_register(o); - var ptr = this.rawConstructor(handle); - destructors.push(this.rawDestructor, ptr); - return ptr; - }, - }); -} - function __embind_register_constant(name, type, value) { - name = Pointer_stringify(name); + name = readLatin1String(name); whenDependentTypesAreResolved([], [type], function(type) { type = type[0]; - Module[name] = type.fromWireType(value); + Module[name] = type['fromWireType'](value); return []; }); } diff --git a/src/embind/emval.js b/src/embind/emval.js index 45030b99..c02ffa92 100755 --- a/src/embind/emval.js +++ b/src/embind/emval.js @@ -1,9 +1,9 @@ /*global Module*/ /*global HEAP32*/ -/*global Pointer_stringify, writeStringToMemory*/ -/*global requireRegisteredType*/ +/*global readLatin1String, writeStringToMemory*/ +/*global requireRegisteredType, throwBindingError*/ -var _emval_handle_array = []; +var _emval_handle_array = [{}]; // reserve zero var _emval_free_list = []; // Public JS API @@ -11,7 +11,7 @@ var _emval_free_list = []; /** @expose */ Module.count_emval_handles = function() { var count = 0; - for (var i = 0; i < _emval_handle_array.length; ++i) { + for (var i = 1; i < _emval_handle_array.length; ++i) { if (_emval_handle_array[i] !== undefined) { ++count; } @@ -21,7 +21,7 @@ Module.count_emval_handles = function() { /** @expose */ Module.get_first_emval = function() { - for (var i = 0; i < _emval_handle_array.length; ++i) { + for (var i = 1; i < _emval_handle_array.length; ++i) { if (_emval_handle_array[i] !== undefined) { return _emval_handle_array[i]; } @@ -31,6 +31,27 @@ Module.get_first_emval = function() { // Private C++ API +var _emval_symbols = {}; // address -> string + +function __emval_register_symbol(address) { + _emval_symbols[address] = readLatin1String(address); +} + +function getStringOrSymbol(address) { + var symbol = _emval_symbols[address]; + if (symbol === undefined) { + return readLatin1String(address); + } else { + return symbol; + } +} + +function requireHandle(handle) { + if (!handle) { + throwBindingError('Cannot use deleted val. handle = ' + handle); + } +} + function __emval_register(value) { var handle = _emval_free_list.length ? _emval_free_list.pop() : @@ -41,11 +62,13 @@ function __emval_register(value) { } function __emval_incref(handle) { - _emval_handle_array[handle].refcount += 1; + if (handle) { + _emval_handle_array[handle].refcount += 1; + } } function __emval_decref(handle) { - if (0 === --_emval_handle_array[handle].refcount) { + if (handle && 0 === --_emval_handle_array[handle].refcount) { delete _emval_handle_array[handle]; _emval_free_list.push(handle); @@ -74,7 +97,7 @@ function __emval_null() { } function __emval_new_cstring(v) { - return __emval_register(Pointer_stringify(v)); + return __emval_register(getStringOrSymbol(v)); } function __emval_take_value(type, v) { @@ -86,6 +109,8 @@ function __emval_take_value(type, v) { var __newers = {}; // arity -> function function __emval_new(handle, argCount, argTypes) { + requireHandle(handle); + var args = parseParameters( argCount, argTypes, @@ -127,24 +152,27 @@ function __emval_new(handle, argCount, argTypes) { var global = (function(){return Function;})()('return this')(); function __emval_get_global(name) { - name = Pointer_stringify(name); + name = getStringOrSymbol(name); return __emval_register(global[name]); } function __emval_get_module_property(name) { - name = Pointer_stringify(name); + name = getStringOrSymbol(name); return __emval_register(Module[name]); } function __emval_get_property(handle, key) { + requireHandle(handle); return __emval_register(_emval_handle_array[handle].value[_emval_handle_array[key].value]); } function __emval_set_property(handle, key, value) { + requireHandle(handle); _emval_handle_array[handle].value[_emval_handle_array[key].value] = _emval_handle_array[value].value; } function __emval_as(handle, returnType) { + requireHandle(handle); returnType = requireRegisteredType(returnType, 'emval::as'); var destructors = []; // caller owns destructing @@ -163,6 +191,7 @@ function parseParameters(argCount, argTypes, argWireTypes) { } function __emval_call(handle, argCount, argTypes) { + requireHandle(handle); var fn = _emval_handle_array[handle].value; var args = parseParameters( argCount, @@ -173,7 +202,8 @@ function __emval_call(handle, argCount, argTypes) { } function __emval_call_method(handle, name, argCount, argTypes) { - name = Pointer_stringify(name); + requireHandle(handle); + name = getStringOrSymbol(name); var args = parseParameters( argCount, @@ -185,7 +215,8 @@ function __emval_call_method(handle, name, argCount, argTypes) { } function __emval_call_void_method(handle, name, argCount, argTypes) { - name = Pointer_stringify(name); + requireHandle(handle); + name = getStringOrSymbol(name); var args = parseParameters( argCount, @@ -194,3 +225,8 @@ function __emval_call_void_method(handle, name, argCount, argTypes) { var obj = _emval_handle_array[handle].value; obj[name].apply(obj, args); } + +function __emval_has_function(handle, name) { + name = getStringOrSymbol(name); + return _emval_handle_array[handle].value[name] instanceof Function; +} diff --git a/src/jsifier.js b/src/jsifier.js index 926be71a..a01b2655 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -701,18 +701,22 @@ function JSify(data, functionsOnly, givenFunctions) { ret += indent + 'label = ' + getLabelId(block.entries[0]) + '; ' + (SHOW_LABELS ? '/* ' + getOriginalLabelId(block.entries[0]) + ' */' : '') + '\n'; } // otherwise, should have been set before! if (func.setjmpTable) { - assert(!ASM_JS, 'asm.js mode does not support setjmp yet'); - var setjmpTable = {}; - ret += indent + 'var mySetjmpIds = {};\n'; - ret += indent + 'var setjmpTable = {'; - func.setjmpTable.forEach(function(triple) { // original label, label we created for right after the setjmp, variable setjmp result goes into - ret += '"' + getLabelId(triple[0]) + '": ' + 'function(value) { label = ' + getLabelId(triple[1]) + '; ' + triple[2] + ' = value },'; - }); - ret += 'dummy: 0'; - ret += '};\n'; + if (!ASM_JS) { + var setjmpTable = {}; + ret += indent + 'var mySetjmpIds = {};\n'; + ret += indent + 'var setjmpTable = {'; + func.setjmpTable.forEach(function(triple) { // original label, label we created for right after the setjmp, variable setjmp result goes into + ret += '"' + getLabelId(triple.oldLabel) + '": ' + 'function(value) { label = ' + getLabelId(triple.newLabel) + '; ' + triple.assignTo + ' = value },'; + }); + ret += 'dummy: 0'; + ret += '};\n'; + } else { + ret += 'var setjmpLabel = 0;\n'; + ret += 'var setjmpTable = ' + RuntimeGenerator.stackAlloc(4 * (MAX_SETJMPS + 1) * 2) + ';\n'; + } } ret += indent + 'while(1) '; - if (func.setjmpTable) { + if (func.setjmpTable && !ASM_JS) { ret += 'try { '; } ret += 'switch(' + asmCoercion('label', 'i32') + ') {\n'; @@ -720,9 +724,19 @@ function JSify(data, functionsOnly, givenFunctions) { return indent + ' case ' + getLabelId(label.ident) + ': ' + (SHOW_LABELS ? '// ' + getOriginalLabelId(label.ident) : '') + '\n' + getLabelLines(label, indent + ' '); }).join('\n') + '\n'; + if (func.setjmpTable && ASM_JS) { + // emit a label in which we write to the proper local variable, before jumping to the actual label + ret += ' case -1111: '; + ret += func.setjmpTable.map(function(triple) { // original label, label we created for right after the setjmp, variable setjmp result goes into + return 'if ((setjmpLabel|0) == ' + getLabelId(triple.oldLabel) + ') { ' + triple.assignTo + ' = threwValue; label = ' + triple.newLabel + ' }\n'; + }).join(' else '); + if (ASSERTIONS) ret += 'else abort(-3);\n'; + ret += '__THREW__ = threwValue = 0;\n'; + ret += 'break;\n'; + } if (ASSERTIONS) ret += indent + ' default: assert(0' + (ASM_JS ? '' : ', "bad label: " + label') + ');\n'; ret += indent + '}\n'; - if (func.setjmpTable) { + if (func.setjmpTable && !ASM_JS) { ret += ' } catch(e) { if (!e.longjmp || !(e.id in mySetjmpIds)) throw(e); setjmpTable[setjmpLabels[e.id]](e.value) }'; } } else { @@ -1186,7 +1200,7 @@ function JSify(data, functionsOnly, givenFunctions) { } item.reloopingJS = ret; // everything but the actual branching (which the relooper will do for us) item.toLabelJS = getPhiSetsForLabel(phiSets, item.toLabel); - item.unwindLabelJS = getPhiSetsForLabel(phiSets, item.unwindLabel); + item.unwindLabelJS = (ASM_JS ? '__THREW__ = 0;' : '') + getPhiSetsForLabel(phiSets, item.unwindLabel); ret += 'if (!__THREW__) { ' + item.toLabelJS + makeBranch(item.toLabel, item.currLabelId) + ' } else { ' + item.unwindLabelJS + makeBranch(item.unwindLabel, item.currLabelId) + ' }'; return ret; @@ -1210,6 +1224,12 @@ function JSify(data, functionsOnly, givenFunctions) { } }); makeFuncLineActor('landingpad', function(item) { + if (DISABLE_EXCEPTION_CATCHING && USE_TYPED_ARRAYS == 2) { + ret = makeVarDef(item.assignTo) + '$0 = 0; ' + item.assignTo + '$1 = 0;'; + item.assignTo = null; + if (ASSERTIONS) warnOnce('landingpad, but exceptions are disabled!'); + return ret; + } var catchTypeArray = item.catchables.map(finalizeLLVMParameter).map(function(element) { return asmCoercion(element, 'i32') }).join(','); var ret = asmCoercion('___cxa_find_matching_catch(-1, -1' + (catchTypeArray.length > 0 ? ',' + catchTypeArray : '') +')', 'i32'); if (USE_TYPED_ARRAYS == 2) { @@ -1295,6 +1315,8 @@ function JSify(data, functionsOnly, givenFunctions) { // We cannot compile assembly. See comment in intertyper.js:'Call' assert(ident != 'asm', 'Inline assembly cannot be compiled to JavaScript!'); + if (ASM_JS && funcData.setjmpTable) forceByPointer = true; // in asm.js mode, we must do an invoke for each call + ident = Variables.resolveAliasToIdent(ident); var shortident = ident.slice(1); var simpleIdent = shortident; @@ -1449,6 +1471,13 @@ function JSify(data, functionsOnly, givenFunctions) { ret += '; return 0'; // special case: abort() can happen without return, breaking the return type of asm functions. ensure a return } } + + if (ASM_JS && funcData.setjmpTable) { + // check if a longjmp was done. If a setjmp happened, check if ours. If ours, go to -111 to handle it. + // otherwise, just return - the call to us must also have been an invoke, so the setjmp propagates that way + ret += '; if (((__THREW__|0) != 0) & ((threwValue|0) > 0)) { setjmpLabel = _testSetjmp(' + makeGetValue('__THREW__', 0, 'i32') + ', setjmpTable); if ((setjmpLabel|0) > 0) { label = -1111; break } else return ' + (funcData.returnType != 'void' ? asmCoercion('0', funcData.returnType) : '') + ' } __THREW__ = threwValue = 0;\n'; + } + return ret; } makeFuncLineActor('getelementptr', function(item) { return finalizeLLVMFunctionCall(item) }); @@ -1458,11 +1487,12 @@ function JSify(data, functionsOnly, givenFunctions) { }); makeFuncLineActor('unreachable', function(item) { + var ret = ''; + if (ASM_JS && item.funcData.returnType != 'void') ret = 'return ' + asmCoercion('0', item.funcData.returnType) + ';'; if (ASSERTIONS) { - return ASM_JS ? 'abort()' : 'throw "Reached an unreachable!"'; - } else { - return ';'; + ret = (ASM_JS ? 'abort()' : 'throw "Reached an unreachable!"') + ';' + ret; } + return ret || ';'; }); // Final combiner diff --git a/src/library.js b/src/library.js index 11f30a1c..2663512d 100644 --- a/src/library.js +++ b/src/library.js @@ -573,7 +573,7 @@ LibraryManager.library = { eof: false, ungotten: [] }; - assert(Math.max(_stdin, _stdout, _stderr) < 128); // make sure these are low, we flatten arrays with these + assert(Math.max(_stdin, _stdout, _stderr) < 1024); // make sure these are low, we flatten arrays with these {{{ makeSetValue(makeGlobalUse('_stdin'), 0, 1, 'void*') }}}; {{{ makeSetValue(makeGlobalUse('_stdout'), 0, 2, 'void*') }}}; {{{ makeSetValue(makeGlobalUse('_stderr'), 0, 3, 'void*') }}}; @@ -6217,13 +6217,74 @@ LibraryManager.library = { // related functionality so the slowdown is more limited. // ========================================================================== + saveSetjmp__asm: true, + saveSetjmp__sig: 'iii', + saveSetjmp: function(env, label, table) { + // Not particularly fast: slow table lookup of setjmpId to label. But setjmp + // prevents relooping anyhow, so slowness is to be expected. And typical case + // is 1 setjmp per invocation, or less. + env = env|0; + label = label|0; + table = table|0; + var i = 0; +#if ASSERTIONS + if ((label|0) == 0) abort(121); +#endif + setjmpId = (setjmpId+1)|0; + {{{ makeSetValueAsm('env', '0', 'setjmpId', 'i32') }}}; + while ((i|0) < {{{ MAX_SETJMPS }}}) { + if ({{{ makeGetValueAsm('table', 'i*4', 'i32') }}} == 0) { + {{{ makeSetValueAsm('table', 'i*4', 'setjmpId', 'i32') }}}; + {{{ makeSetValueAsm('table', 'i*4+4', 'label', 'i32') }}}; + // prepare next slot + {{{ makeSetValueAsm('table', 'i*4+8', '0', 'i32') }}}; + return 0; + } + i = (i+2)|0; + } + abort(987); // if you hit this, adjust MAX_SETJMPS + return 0; + }, + + testSetjmp__asm: true, + testSetjmp__sig: 'iii', + testSetjmp: function(id, table) { + id = id|0; + table = table|0; + var i = 0, curr = 0; + while ((i|0) < {{{ MAX_SETJMPS }}}) { + curr = {{{ makeGetValueAsm('table', 'i*4', 'i32') }}}; + if ((curr|0) == 0) break; + if ((curr|0) == (id|0)) { + return {{{ makeGetValueAsm('table', 'i*4+4', 'i32') }}}; + } + i = (i+2)|0; + } + return 0; + }, + +#if ASM_JS + setjmp__deps: ['saveSetjmp', 'testSetjmp'], +#endif setjmp__inline: function(env) { // Save the label +#if ASM_JS + return '_saveSetjmp(' + env + ', label, setjmpTable)'; +#else return '(tempInt = setjmpId++, mySetjmpIds[tempInt] = 1, setjmpLabels[tempInt] = label,' + makeSetValue(env, '0', 'tempInt', 'i32', undefined, undefined, undefined, undefined, ',') + ', 0)'; +#endif }, +#if ASM_JS + longjmp__deps: ['saveSetjmp', 'testSetjmp'], +#endif longjmp: function(env, value) { +#if ASM_JS + asm.setThrew(env, value || 1); + throw 'longjmp'; +#else throw { longjmp: true, id: {{{ makeGetValue('env', '0', 'i32') }}}, value: value || 1 }; +#endif }, // ========================================================================== diff --git a/src/library_browser.js b/src/library_browser.js index c1add740..85eb93f7 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -704,7 +704,11 @@ mergeInto(LibraryManager.library, { }, emscripten_get_now: function() { - if (window['performance'] && window['performance']['now']) { + if (ENVIRONMENT_IS_NODE) { + var t = process['hrtime'](); + return t[0] * 1e3 + t[1] / 1e6; + } + else if (window['performance'] && window['performance']['now']) { return window['performance']['now'](); } else { return Date.now(); diff --git a/src/library_gl.js b/src/library_gl.js index 0912b5da..b91d782c 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -2282,14 +2282,14 @@ var LibraryGL = { } attributes.sort(function(x, y) { return !x ? (!y ? 0 : 1) : (!y ? -1 : (x.pointer - y.pointer)) }); start = GL.currArrayBuffer ? 0 : attributes[0].pointer; + var multiStrides = false; for (var i = 0; i < attributes.length; i++) { var attribute = attributes[i]; if (!attribute) break; -#if ASSERTIONS - assert(stride == 0 || stride == attribute.stride); // must all be in the same buffer -#endif + if (stride != 0 && stride != attribute.stride) multiStrides = true; if (attribute.stride) stride = attribute.stride; } + if (multiStrides) stride = 0; // we will need to restride var bytes = 0; // total size in bytes if (!stride && !beginEnd) { // beginEnd can not have stride in the attributes, that is fine. otherwise, @@ -2297,7 +2297,7 @@ var LibraryGL = { // our emulation code simple, we perform unpacking/restriding here. this adds overhead, so // it is a good idea to not hit this! #if ASSERTIONS - Runtime.warnOnce('Unpacking/restriding attributes, this is not fast'); + Runtime.warnOnce('Unpacking/restriding attributes, this is slow and dangerous'); #endif if (!GL.immediate.restrideBuffer) GL.immediate.restrideBuffer = _malloc(GL.MAX_TEMP_BUFFER_SIZE); start = GL.immediate.restrideBuffer; diff --git a/src/preamble.js b/src/preamble.js index 592363f9..17b74cd9 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -217,8 +217,10 @@ var START_TIME = Date.now(); //======================================== var __THREW__ = 0; // Used in checking for thrown exceptions. +#if ASM_JS == 0 var setjmpId = 1; // Used in setjmp/longjmp var setjmpLabels = {}; +#endif var ABORT = false; diff --git a/src/settings.js b/src/settings.js index 8f31c2d8..db24cca4 100644 --- a/src/settings.js +++ b/src/settings.js @@ -55,6 +55,7 @@ var ALLOW_MEMORY_GROWTH = 0; // If false, we abort with an error if we try to al // that case we must be careful about optimizations, in particular the // eliminator). Note that memory growth is only supported with typed // arrays. +var MAX_SETJMPS = 10; // size of setjmp table allocated in each function invocation (that has setjmp) // Code embetterments var MICRO_OPTS = 1; // Various micro-optimizations, like nativizing variables diff --git a/system/include/emscripten/bind.h b/system/include/emscripten/bind.h index 57e44c0c..4d2f4ac8 100755 --- a/system/include/emscripten/bind.h +++ b/system/include/emscripten/bind.h @@ -47,10 +47,15 @@ namespace emscripten { TYPEID floatType, const char* name); - void _embind_register_cstring( + void _embind_register_std_string( TYPEID stringType, const char* name); + void _embind_register_std_wstring( + TYPEID stringType, + size_t charSize, + const char* name); + void _embind_register_emval( TYPEID emvalType, const char* name); @@ -136,10 +141,12 @@ namespace emscripten { void _embind_register_class_property( TYPEID classType, const char* fieldName, - TYPEID fieldType, + TYPEID getterReturnType, GenericFunction getter, + void* getterContext, + TYPEID setterArgumentType, GenericFunction setter, - void* context); + void* setterContext); void _embind_register_class_class_function( TYPEID classType, @@ -223,6 +230,18 @@ namespace emscripten { } namespace internal { + template<typename ClassType, typename Signature> + struct MemberFunctionType { + typedef Signature (ClassType::*type); + }; + } + + template<typename Signature, typename ClassType> + typename internal::MemberFunctionType<ClassType, Signature>::type select_overload(Signature (ClassType::*fn)) { + return fn; + } + + namespace internal { template<typename ReturnType, typename... Args> struct Invoker { static typename internal::BindingType<ReturnType>::WireType invoke( @@ -277,8 +296,8 @@ namespace emscripten { } template<typename WrapperType, typename ClassType, typename... Args> - WrapperType wrapped_new(Args... args) { - return WrapperType(new ClassType(args...)); + WrapperType wrapped_new(Args&&... args) { + return WrapperType(new ClassType(std::forward<Args>(args)...)); } template<typename ClassType, typename... Args> @@ -711,28 +730,41 @@ namespace emscripten { template<typename T> class wrapper : public T { public: - wrapper(const val& wrapped) - : wrapped(wrapped) + explicit wrapper(val&& wrapped) + : wrapped(std::forward<val>(wrapped)) {} template<typename ReturnType, typename... Args> - ReturnType call(const char* name, Args... args) const { - return Caller<ReturnType, Args...>::call(wrapped, name, args...); + ReturnType call(const char* name, Args&&... args) const { + return Caller<ReturnType, Args...>::call(wrapped, name, std::forward<Args>(args)...); + } + + template<typename ReturnType, typename... Args, typename Default> + ReturnType optional_call(const char* name, Default def, Args&&... args) const { + if (has_function(name)) { + return Caller<ReturnType, Args...>::call(wrapped, name, std::forward<Args>(args)...); + } else { + return def(); + } } private: + bool has_function(const char* name) const { + return wrapped.has_function(name); + } + // this class only exists because you can't partially specialize function templates template<typename ReturnType, typename... Args> struct Caller { - static ReturnType call(const val& v, const char* name, Args... args) { - return v.call(name, args...).template as<ReturnType>(); + static ReturnType call(const val& v, const char* name, Args&&... args) { + return v.call(name, std::forward<Args>(args)...).template as<ReturnType>(); } }; template<typename... Args> struct Caller<void, Args...> { - static void call(const val& v, const char* name, Args... args) { - v.call_void(name, args...); + static void call(const val& v, const char* name, Args&&... args) { + v.call_void(name, std::forward<Args>(args)...); } }; @@ -740,7 +772,7 @@ namespace emscripten { }; #define EMSCRIPTEN_WRAPPER(T) \ - T(const ::emscripten::val& v): wrapper(v) {} + T(::emscripten::val&& v): wrapper(std::forward<::emscripten::val>(v)) {} namespace internal { struct NoBaseClass { @@ -971,7 +1003,23 @@ namespace emscripten { return *this; } - template<typename FieldType> + template<typename FieldType, typename = typename std::enable_if<!std::is_function<FieldType>::value>::type> + class_& property(const char* fieldName, const FieldType ClassType::*field) { + using namespace internal; + + _embind_register_class_property( + TypeID<ClassType>::get(), + fieldName, + TypeID<FieldType>::get(), + reinterpret_cast<GenericFunction>(&MemberAccess<ClassType, FieldType>::template getWire<ClassType>), + getContext(field), + 0, + 0, + 0); + return *this; + } + + template<typename FieldType, typename = typename std::enable_if<!std::is_function<FieldType>::value>::type> class_& property(const char* fieldName, FieldType ClassType::*field) { using namespace internal; @@ -980,11 +1028,46 @@ namespace emscripten { fieldName, TypeID<FieldType>::get(), reinterpret_cast<GenericFunction>(&MemberAccess<ClassType, FieldType>::template getWire<ClassType>), + getContext(field), + TypeID<FieldType>::get(), reinterpret_cast<GenericFunction>(&MemberAccess<ClassType, FieldType>::template setWire<ClassType>), getContext(field)); return *this; } + template<typename Getter> + class_& property(const char* fieldName, Getter getter) { + using namespace internal; + typedef GetterPolicy<Getter> GP; + _embind_register_class_property( + TypeID<ClassType>::get(), + fieldName, + TypeID<typename GP::ReturnType>::get(), + reinterpret_cast<GenericFunction>(&GP::template get<ClassType>), + GP::getContext(getter), + 0, + 0, + 0); + return *this; + } + + template<typename Getter, typename Setter> + class_& property(const char* fieldName, Getter getter, Setter setter) { + using namespace internal; + typedef GetterPolicy<Getter> GP; + typedef SetterPolicy<Setter> SP; + _embind_register_class_property( + TypeID<ClassType>::get(), + fieldName, + TypeID<typename GP::ReturnType>::get(), + reinterpret_cast<GenericFunction>(&GP::template get<ClassType>), + GP::getContext(getter), + TypeID<typename SP::ArgumentType>::get(), + reinterpret_cast<GenericFunction>(&SP::template set<ClassType>), + SP::getContext(setter)); + return *this; + } + template<typename ReturnType, typename... Args, typename... Policies> class_& class_function(const char* methodName, ReturnType (*classMethod)(Args...), Policies...) { using namespace internal; @@ -999,11 +1082,6 @@ namespace emscripten { reinterpret_cast<GenericFunction>(classMethod)); return *this; } - - template<typename ReturnType, typename... Args, typename... Policies> - class_& calloperator(const char* methodName, Policies... policies) { - return function(methodName, &ClassType::operator(), policies...); - } }; //////////////////////////////////////////////////////////////////////////////// @@ -1210,80 +1288,6 @@ namespace emscripten { typename std::aligned_storage<sizeof(T)>::type data; }; } - - //////////////////////////////////////////////////////////////////////////////// - // NEW INTERFACE - //////////////////////////////////////////////////////////////////////////////// - - class JSInterface { - public: - JSInterface(internal::EM_VAL handle) { - initialize(handle); - } - - JSInterface(const JSInterface& obj) - : jsobj(obj.jsobj) - {} - - template<typename ReturnType, typename... Args> - ReturnType call(const char* name, Args... args) { - assertInitialized(); - return Caller<ReturnType, Args...>::call(*jsobj, name, args...); - } - - static std::shared_ptr<JSInterface> cloneToSharedPtr(const JSInterface& i) { - return std::make_shared<JSInterface>(i); - } - - private: - void initialize(internal::EM_VAL handle) { - if (jsobj) { - internal::_embind_fatal_error( - "Cannot initialize interface wrapper twice", - "JSInterface"); - } - jsobj = val::take_ownership(handle); - } - - // this class only exists because you can't partially specialize function templates - template<typename ReturnType, typename... Args> - struct Caller { - static ReturnType call(val& v, const char* name, Args... args) { - return v.call(name, args...).template as<ReturnType>(); - } - }; - - template<typename... Args> - struct Caller<void, Args...> { - static void call(val& v, const char* name, Args... args) { - v.call_void(name, args...); - } - }; - - void assertInitialized() { - if (!jsobj) { - internal::_embind_fatal_error( - "Cannot invoke call on uninitialized Javascript interface wrapper.", "JSInterface"); - } - } - - internal::optional<val> jsobj; - }; - - namespace internal { - extern JSInterface* create_js_interface(EM_VAL e); - } - - class register_js_interface { - public: - register_js_interface() { - _embind_register_interface( - internal::TypeID<JSInterface>::get(), - "JSInterface", - reinterpret_cast<internal::GenericFunction>(&internal::create_js_interface), - reinterpret_cast<internal::GenericFunction>(&internal::raw_destructor<JSInterface>)); - } - }; } namespace emscripten { diff --git a/system/include/emscripten/val.h b/system/include/emscripten/val.h index 91f775a7..09cad80e 100644 --- a/system/include/emscripten/val.h +++ b/system/include/emscripten/val.h @@ -8,6 +8,8 @@ namespace emscripten { namespace internal { // Implemented in JavaScript. Don't call these directly. extern "C" { + void _emval_register_symbol(const char*); + typedef struct _EM_VAL* EM_VAL; void _emval_incref(EM_VAL value); @@ -48,9 +50,23 @@ namespace emscripten { unsigned argCount, internal::TYPEID argTypes[] /*, ...*/); + bool _emval_has_function( + EM_VAL value, + const char* methodName); } } + template<const char* address> + struct symbol_registrar { + symbol_registrar() { + internal::_emval_register_symbol(address); + } + }; + +#define EMSCRIPTEN_SYMBOL(name) \ + static const char name##_symbol[] = #name; \ + static const symbol_registrar<name##_symbol> name##_registrar + class val { public: // missing operators: @@ -98,18 +114,24 @@ namespace emscripten { } template<typename T> - explicit val(const T& value) { + explicit val(T&& value) { typedef internal::BindingType<T> BT; auto taker = reinterpret_cast<internal::EM_VAL (*)(internal::TYPEID, typename BT::WireType)>(&internal::_emval_take_value); - handle = taker(internal::TypeID<T>::get(), BT::toWireType(value)); + handle = taker(internal::TypeID<T>::get(), BT::toWireType(std::forward<T>(value))); } val() = delete; - val(const char* v) + explicit val(const char* v) : handle(internal::_emval_new_cstring(v)) {} + val(val&& v) + : handle(v.handle) + { + v.handle = 0; + } + val(const val& v) : handle(v.handle) { @@ -120,6 +142,13 @@ namespace emscripten { internal::_emval_decref(handle); } + val& operator=(val&& v) { + internal::_emval_decref(handle); + handle = v.handle; + v.handle = 0; + return *this; + } + val& operator=(const val& v) { internal::_emval_incref(v.handle); internal::_emval_decref(handle); @@ -132,7 +161,7 @@ namespace emscripten { } template<typename... Args> - val new_(Args... args) const { + val new_(Args&&... args) const { using namespace internal; WithPolicies<>::ArgTypeList<Args...> argList; @@ -149,7 +178,7 @@ namespace emscripten { handle, argList.count, argList.types, - toWireType(args)...)); + toWireType(std::forward<Args>(args))...)); } template<typename T> @@ -163,7 +192,7 @@ namespace emscripten { } template<typename... Args> - val operator()(Args... args) { + val operator()(Args&&... args) { using namespace internal; WithPolicies<>::ArgTypeList<Args...> argList; @@ -178,11 +207,11 @@ namespace emscripten { handle, argList.count, argList.types, - toWireType(args)...)); + toWireType(std::forward<Args>(args))...)); } template<typename ...Args> - val call(const char* name, Args... args) const { + val call(const char* name, Args&&... args) const { using namespace internal; WithPolicies<>::ArgTypeList<Args...> argList; @@ -199,11 +228,11 @@ namespace emscripten { name, argList.count, argList.types, - toWireType(args)...)); + toWireType(std::forward<Args>(args))...)); } template<typename ...Args> - void call_void(const char* name, Args... args) const { + void call_void(const char* name, Args&&... args) const { using namespace internal; WithPolicies<>::ArgTypeList<Args...> argList; @@ -219,7 +248,11 @@ namespace emscripten { name, argList.count, argList.types, - toWireType(args)...); + toWireType(std::forward<Args>(args))...); + } + + bool has_function(const char* name) const { + return _emval_has_function(handle, name); } template<typename T> @@ -253,7 +286,7 @@ namespace emscripten { template<> struct BindingType<val> { typedef internal::EM_VAL WireType; - static WireType toWireType(val v) { + static WireType toWireType(const val& v) { _emval_incref(v.handle); return v.handle; } diff --git a/system/include/emscripten/wire.h b/system/include/emscripten/wire.h index 64114491..6fb15fc7 100755 --- a/system/include/emscripten/wire.h +++ b/system/include/emscripten/wire.h @@ -181,11 +181,35 @@ namespace emscripten { } }; + template<> + struct BindingType<std::wstring> { + typedef struct { + size_t length; + wchar_t data[1]; // trailing data + }* WireType; + static WireType toWireType(const std::wstring& v) { + WireType wt = (WireType)malloc(sizeof(size_t) + v.length() * sizeof(wchar_t)); + wt->length = v.length(); + wmemcpy(wt->data, v.data(), v.length()); + return wt; + } + static std::wstring fromWireType(WireType v) { + return std::wstring(v->data, v->length); + } + static void destroy(WireType v) { + free(v); + } + }; + template<typename T> struct BindingType<const T> : public BindingType<T> { }; template<typename T> + struct BindingType<T&> : public BindingType<T> { + }; + + template<typename T> struct BindingType<const T&> : public BindingType<T> { }; @@ -216,10 +240,14 @@ namespace emscripten { typedef typename std::remove_reference<T>::type ActualT; typedef ActualT* WireType; - static WireType toWireType(T v) { + static WireType toWireType(const T& v) { return new T(v); } + static WireType toWireType(T&& v) { + return new T(std::forward<T>(v)); + } + static ActualT& fromWireType(WireType p) { return *p; } @@ -262,8 +290,8 @@ namespace emscripten { {}; template<typename T> - auto toWireType(const T& v) -> typename BindingType<T>::WireType { - return BindingType<T>::toWireType(v); + auto toWireType(T&& v) -> typename BindingType<T>::WireType { + return BindingType<T>::toWireType(std::forward<T>(v)); } template<typename T> diff --git a/system/lib/embind/bind.cpp b/system/lib/embind/bind.cpp index deb55138..35d99dad 100755 --- a/system/lib/embind/bind.cpp +++ b/system/lib/embind/bind.cpp @@ -36,12 +36,22 @@ extern "C" { }
}
-namespace emscripten {
- namespace internal {
- JSInterface* create_js_interface(EM_VAL e) {
- return new JSInterface(e);
- }
+// TODO: fix in library.js or a proper emscripten libc
+extern "C" wchar_t *wmemset(wchar_t *dest, wchar_t c, size_t count) {
+ wchar_t *o = dest;
+ while (count--) {
+ *o++ = c;
+ }
+ return dest;
+}
+
+// TODO: fix in library.js or a proper emscripten libc
+extern "C" wchar_t *wmemcpy(wchar_t *dest, const wchar_t *src, size_t count) {
+ wchar_t *o = dest;
+ while (count--) {
+ *dest++ = *src++;
}
+ return dest;
}
EMSCRIPTEN_BINDINGS(native_and_builtin_types) {
@@ -64,6 +74,7 @@ EMSCRIPTEN_BINDINGS(native_and_builtin_types) { _embind_register_float(TypeID<float>::get(), "float");
_embind_register_float(TypeID<double>::get(), "double");
- _embind_register_cstring(TypeID<std::string>::get(), "std::string");
+ _embind_register_std_string(TypeID<std::string>::get(), "std::string");
+ _embind_register_std_wstring(TypeID<std::wstring>::get(), sizeof(wchar_t), "std::wstring");
_embind_register_emval(TypeID<val>::get(), "emscripten::val");
}
diff --git a/tests/cases/legalizer_ta2.ll b/tests/cases/legalizer_ta2.ll index 7e17c707..89ebcef6 100644 --- a/tests/cases/legalizer_ta2.ll +++ b/tests/cases/legalizer_ta2.ll @@ -188,4 +188,5 @@ done: declare i32 @puts(i8*) declare i32 @__gxx_personality_v0(...) +declare void @__cxa_throw(i32, i32, i32) ; for asm1, where exceptions are enabled but this test needs a throw to bring in lib stuff diff --git a/tests/cases/longjmp_tiny_noasm.ll b/tests/cases/longjmp_tiny.ll index 0045847c..0045847c 100644 --- a/tests/cases/longjmp_tiny_noasm.ll +++ b/tests/cases/longjmp_tiny.ll diff --git a/tests/cases/longjmp_tiny_noasm.txt b/tests/cases/longjmp_tiny.txt index 8a0aa386..8a0aa386 100644 --- a/tests/cases/longjmp_tiny_noasm.txt +++ b/tests/cases/longjmp_tiny.txt diff --git a/tests/cases/longjmp_tiny_noasm_invoke.ll b/tests/cases/longjmp_tiny_invoke.ll index e1a72e00..e1a72e00 100644 --- a/tests/cases/longjmp_tiny_noasm_invoke.ll +++ b/tests/cases/longjmp_tiny_invoke.ll diff --git a/tests/cases/longjmp_tiny_noasm_invoke.txt b/tests/cases/longjmp_tiny_invoke.txt index 8a0aa386..8a0aa386 100644 --- a/tests/cases/longjmp_tiny_noasm_invoke.txt +++ b/tests/cases/longjmp_tiny_invoke.txt diff --git a/tests/cases/longjmp_tiny_phi_noasm.ll b/tests/cases/longjmp_tiny_phi.ll index cced7cab..cced7cab 100644 --- a/tests/cases/longjmp_tiny_phi_noasm.ll +++ b/tests/cases/longjmp_tiny_phi.ll diff --git a/tests/cases/longjmp_tiny_phi_noasm.txt b/tests/cases/longjmp_tiny_phi.txt index 16f5a93e..16f5a93e 100644 --- a/tests/cases/longjmp_tiny_phi_noasm.txt +++ b/tests/cases/longjmp_tiny_phi.txt diff --git a/tests/cases/longjmp_tiny_phi2_noasm.ll b/tests/cases/longjmp_tiny_phi2.ll index 1d7761c3..1d7761c3 100644 --- a/tests/cases/longjmp_tiny_phi2_noasm.ll +++ b/tests/cases/longjmp_tiny_phi2.ll diff --git a/tests/cases/longjmp_tiny_phi2_noasm.txt b/tests/cases/longjmp_tiny_phi2.txt index 37e85737..37e85737 100644 --- a/tests/cases/longjmp_tiny_phi2_noasm.txt +++ b/tests/cases/longjmp_tiny_phi2.txt diff --git a/tests/embind/embind.benchmark.js b/tests/embind/embind.benchmark.js new file mode 100644 index 00000000..4ce9355c --- /dev/null +++ b/tests/embind/embind.benchmark.js @@ -0,0 +1,201 @@ +function _increment_counter_benchmark_js(N) { + var ctr = _get_counter(); + var a = _emscripten_get_now(); + for(i = 0; i < N; ++i) { + _increment_counter(); + _increment_counter(); + _increment_counter(); + _increment_counter(); + _increment_counter(); + _increment_counter(); + _increment_counter(); + _increment_counter(); + _increment_counter(); + _increment_counter(); + } + var b = _emscripten_get_now(); + var ctr2 = _get_counter(); + Module.print("JS increment_counter " + N + " iters: " + (b-a) + " msecs. result: " + (ctr2-ctr)); +} + +function _increment_class_counter_benchmark_embind_js(N) { + var foo = new Module['Foo'](); + var a = _emscripten_get_now(); + for(i = 0; i < N; ++i) { + foo['incr_class_counter'](); + foo['incr_class_counter'](); + foo['incr_class_counter'](); + foo['incr_class_counter'](); + foo['incr_class_counter'](); + foo['incr_class_counter'](); + foo['incr_class_counter'](); + foo['incr_class_counter'](); + foo['incr_class_counter'](); + foo['incr_class_counter'](); + } + var b = _emscripten_get_now(); + Module.print("JS embind increment_class_counter " + N + " iters: " + (b-a) + " msecs. result: " + foo['class_counter_val']()); + foo['delete'](); +} + +function _returns_input_benchmark_js() { + var a = _emscripten_get_now(); + var t = 0; + for(i = 0; i < 100000; ++i) { + t += _returns_input(i); + t += _returns_input(i); + t += _returns_input(i); + t += _returns_input(i); + t += _returns_input(i); + t += _returns_input(i); + t += _returns_input(i); + t += _returns_input(i); + t += _returns_input(i); + t += _returns_input(i); + } + var b = _emscripten_get_now(); + Module.print("JS returns_input 100000 iters: " + (b-a) + " msecs. result: " + t); +} + +function _sum_int_benchmark_js() { + var a = _emscripten_get_now(); + var r = 0; + for(i = 0; i < 100000; ++i) { + r += _sum_int(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_int(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_int(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_int(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_int(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_int(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_int(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_int(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_int(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_int(i, 2, 3, 4, 5, 6, 7, 8, 9); + } + var b = _emscripten_get_now(); + Module.print("JS sum_int 100000 iters: " + (b-a) + " msecs. result: " + r); +} + +function _sum_float_benchmark_js() { + var a = _emscripten_get_now(); + var r = 0; + for(i = 0; i < 100000; ++i) { + r += _sum_float(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_float(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_float(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_float(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_float(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_float(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_float(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_float(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_float(i, 2, 3, 4, 5, 6, 7, 8, 9); + r += _sum_float(i, 2, 3, 4, 5, 6, 7, 8, 9); + } + var b = _emscripten_get_now(); + Module.print("JS sum_float 100000 iters: " + (b-a) + " msecs. result: " + r); +} + +function _increment_counter_benchmark_embind_js(N) { + var ctr = _get_counter(); + var a = _emscripten_get_now(); + for(i = 0; i < N; ++i) { + Module['increment_counter'](); + Module['increment_counter'](); + Module['increment_counter'](); + Module['increment_counter'](); + Module['increment_counter'](); + Module['increment_counter'](); + Module['increment_counter'](); + Module['increment_counter'](); + Module['increment_counter'](); + Module['increment_counter'](); + } + var b = _emscripten_get_now(); + var ctr2 = _get_counter(); + Module.print("JS embind increment_counter " + N + " iters: " + (b-a) + " msecs. result: " + (ctr2-ctr)); +} + +function _returns_input_benchmark_embind_js() { + var a = _emscripten_get_now(); + var t = 0; + for(i = 0; i < 100000; ++i) { + t += Module['returns_input'](i); + t += Module['returns_input'](i); + t += Module['returns_input'](i); + t += Module['returns_input'](i); + t += Module['returns_input'](i); + t += Module['returns_input'](i); + t += Module['returns_input'](i); + t += Module['returns_input'](i); + t += Module['returns_input'](i); + t += Module['returns_input'](i); + } + var b = _emscripten_get_now(); + Module.print("JS embind returns_input 100000 iters: " + (b-a) + " msecs. result: " + t); +} + +function _sum_int_benchmark_embind_js() { + var a = _emscripten_get_now(); + var r = 0; + for(i = 0; i < 100000; ++i) { + r += Module['sum_int'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_int'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_int'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_int'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_int'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_int'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_int'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_int'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_int'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_int'](i, 2, 3, 4, 5, 6, 7, 8, 9); + } + var b = _emscripten_get_now(); + Module.print("JS embind sum_int 100000 iters: " + (b-a) + " msecs. result: " + r); +} + +function _sum_float_benchmark_embind_js() { + var a = _emscripten_get_now(); + var r = 0; + for(i = 0; i < 100000; ++i) { + r += Module['sum_float'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_float'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_float'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_float'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_float'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_float'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_float'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_float'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_float'](i, 2, 3, 4, 5, 6, 7, 8, 9); + r += Module['sum_float'](i, 2, 3, 4, 5, 6, 7, 8, 9); + } + var b = _emscripten_get_now(); + Module.print("JS embind sum_float 100000 iters: " + (b-a) + " msecs. result: " + r); +} + +function _move_gameobjects_benchmark_embind_js() { + var N = 10000; + var objects = []; + for(i = 0; i < N; ++i) { + objects.push(Module['create_game_object']()); + } + + var a = _emscripten_get_now(); + for(i = 0; i < N; ++i) { + var t = objects[i]['GetTransform'](); + var pos = Module['add'](t['GetPosition'](), [2, 0, 1]); + var rot = Module['add'](t['GetRotation'](), [0.1, 0.2, 0.3]); + t['SetPosition'](pos); + t['SetRotation'](rot); + t['delete'](); + } + var b = _emscripten_get_now(); + + var accum = [0,0,0]; + for(i = 0; i < N; ++i) { + var t = objects[i]['GetTransform'](); + accum = Module['add'](Module['add'](accum, t['GetPosition']()), t['GetRotation']()); + t['delete'](); + } + + Module.print("JS embind move_gameobjects " + N + " iters: " + (b-a) + " msecs. Result: " + (accum[0] + accum[1] + accum[2])); +} diff --git a/tests/embind/embind.test.js b/tests/embind/embind.test.js index 290fed72..8ef46ad8 100755 --- a/tests/embind/embind.test.js +++ b/tests/embind/embind.test.js @@ -143,33 +143,49 @@ module({ var e = assert.throws(cm.BindingError, function() { cm.Derived.prototype.setMember.call(a, "foo"); }); - assert.equal('Derived.setMember incompatible with "this" of type HasTwoBases', e.message); + assert.equal('Expected null or instance of Derived, got an instance of Base2', e.message); a.delete(); + + // Base1 and Base2 both have the method 'getField()' exposed - make sure + // that calling the Base2 function with a 'this' instance of Base1 doesn't accidentally work! + var b = new cm.Base1; + var e = assert.throws(cm.BindingError, function() { + cm.Base2.prototype.getField.call(b); + }); + assert.equal('Expected null or instance of Base2, got an instance of Base1', e.message); + b.delete(); }); test("calling method with invalid this throws error", function() { var e = assert.throws(cm.BindingError, function() { cm.Derived.prototype.setMember.call(undefined, "foo"); }); - if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well! - // got Error: expected: Derived.setMember with invalid "this": undefined, actual: Derived.setMember incompatible with "this" of type Object - assert.equal('Derived.setMember with invalid "this": undefined', e.message); - } + assert.equal('Cannot pass "[object global]" as a Derived*', e.message); + + var e = assert.throws(cm.BindingError, function() { + cm.Derived.prototype.setMember.call(true, "foo"); + }); + assert.equal('Cannot pass "true" as a Derived*', e.message); + + var e = assert.throws(cm.BindingError, function() { + cm.Derived.prototype.setMember.call(null, "foo"); + }); + assert.equal('Cannot pass "[object global]" as a Derived*', e.message); + + var e = assert.throws(cm.BindingError, function() { + cm.Derived.prototype.setMember.call(42, "foo"); + }); + assert.equal('Cannot pass "42" as a Derived*', e.message); var e = assert.throws(cm.BindingError, function() { cm.Derived.prototype.setMember.call("this", "foo"); }); - if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well! - // TODO got 'Derived.setMember incompatible with "this" of type Object' - assert.equal('Derived.setMember with invalid "this": this', e.message); - } + assert.equal('Cannot pass "this" as a Derived*', e.message); var e = assert.throws(cm.BindingError, function() { cm.Derived.prototype.setMember.call({}, "foo"); }); - if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well! - assert.equal('Derived.setMember incompatible with "this" of type Object', e.message); - } + assert.equal('Cannot pass "[object Object]" as a Derived*', e.message); }); test("setting and getting property on unrelated class throws error", function() { @@ -388,6 +404,59 @@ module({ }); }); + BaseFixture.extend("string", function() { + test("non-ascii strings", function() { + var expected = ''; + for (var i = 0; i < 128; ++i) { + expected += String.fromCharCode(128 + i); + } + assert.equal(expected, cm.get_non_ascii_string()); + }); + + test("passing non-8-bit strings from JS to std::string throws", function() { + assert.throws(cm.BindingError, function() { + cm.emval_test_take_and_return_std_string("\u1234"); + }); + }); + + test("can't pass integers as strings", function() { + var e = assert.throws(cm.BindingError, function() { + cm.emval_test_take_and_return_std_string(10); + }); + }); + + test("can pass Uint8Array to std::string", function() { + var e = cm.emval_test_take_and_return_std_string(new Uint8Array([65, 66, 67, 68])); + assert.equal('ABCD', e); + }); + + test("can pass Int8Array to std::string", function() { + var e = cm.emval_test_take_and_return_std_string(new Int8Array([65, 66, 67, 68])); + assert.equal('ABCD', e); + }); + + test("can pass ArrayBuffer to std::string", function() { + var e = cm.emval_test_take_and_return_std_string((new Int8Array([65, 66, 67, 68])).buffer); + assert.equal('ABCD', e); + }); + + test("non-ascii wstrings", function() { + var expected = String.fromCharCode(10) + + String.fromCharCode(1234) + + String.fromCharCode(2345) + + String.fromCharCode(65535); + assert.equal(expected, cm.get_non_ascii_wstring()); + }); + + test("passing unicode string into C++", function() { + var expected = String.fromCharCode(10) + + String.fromCharCode(1234) + + String.fromCharCode(2345) + + String.fromCharCode(65535); + assert.equal(expected, cm.take_and_return_std_wstring(expected)); + }); + }); + BaseFixture.extend("embind", function() { test("value creation", function() { assert.equal(15, cm.emval_test_new_integer()); @@ -837,6 +906,37 @@ module({ assert.equal(0, cm.count_emval_handles()); }); + test("class properties can be methods", function() { + var a = {}; + var b = {foo: 'foo'}; + var c = new cm.ValHolder(a); + assert.equal(a, c.val); + c.val = b; + assert.equal(b, c.val); + c.delete(); + }); + + test("class properties can be read-only", function() { + var a = {}; + var h = new cm.ValHolder(a); + assert.equal(a, h.val_readonly); + var e = assert.throws(cm.BindingError, function() { + h.val_readonly = 10; + }); + assert.equal('ValHolder.val_readonly is a read-only property', e.message); + h.delete(); + }); + + test("read-only member field", function() { + var a = new cm.HasReadOnlyProperty(10); + assert.equal(10, a.i); + var e = assert.throws(cm.BindingError, function() { + a.i = 20; + }); + assert.equal('HasReadOnlyProperty.i is a read-only property', e.message); + a.delete(); + }); + test("class instance $$ property is non-enumerable", function() { var c = new cm.ValHolder(undefined); assert.deepEqual([], Object.keys(c)); @@ -1415,24 +1515,6 @@ module({ }); }); - BaseFixture.extend("JavaScript interface", function() { - this.setUp(function() { - this.testobj = { - "method1": function() { return 111; }, - "method2": function() { return 222; } - }; - }); - - test("pass js object to c++ and call its method", function() { - var obj = new cm.JSInterfaceHolder(this.testobj); - assert.equal(111, obj.callMethod("method1")); - assert.equal(222, obj.callMethod("method2")); - assert.equal(111, obj.callMethodUsingSharedPtr("method1")); - assert.equal(222, obj.callMethodUsingSharedPtr("method2")); - obj.delete(); - }); - }); - BaseFixture.extend("abstract methods", function() { test("can call abstract methods", function() { var obj = cm.getAbstractClass(); @@ -1454,6 +1536,28 @@ module({ assert.equal(expected, cm.callAbstractMethod(impl)); impl.delete(); }); + + test("can implement optional methods in JavaScript", function() { + var expected = "my JS string"; + function MyImplementation() { + this.rv = expected; + } + MyImplementation.prototype.optionalMethod = function() { + return this.rv; + }; + + var impl = cm.AbstractClass.implement(new MyImplementation); + assert.equal(expected, impl.optionalMethod(expected)); + assert.equal(expected, cm.callOptionalMethod(impl, expected)); + impl.delete(); + }); + + test("if not implemented then optional method runs default", function() { + var impl = cm.AbstractClass.implement({}); + assert.equal("optionalfoo", impl.optionalMethod("foo")); + assert.equal("optionalfoo", cm.callOptionalMethod(impl, "foo")); + impl.delete(); + }); }); BaseFixture.extend("registration order", function() { diff --git a/tests/embind/embind_benchmark.cpp b/tests/embind/embind_benchmark.cpp new file mode 100644 index 00000000..80abc7e7 --- /dev/null +++ b/tests/embind/embind_benchmark.cpp @@ -0,0 +1,344 @@ +#include <stdio.h> +#include <emscripten.h> +#include <bind.h> +#include <memory> + +int counter = 0; + +extern "C" +{ + +int __attribute__((noinline)) get_counter() +{ + return counter; +} + +void __attribute__((noinline)) increment_counter() +{ + ++counter; +} + +int __attribute__((noinline)) sum_int(int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8, int v9) +{ + return v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9; +} + +float __attribute__((noinline)) sum_float(float v1, float v2, float v3, float v4, float v5, float v6, float v7, float v8, float v9) +{ + return v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9; +} + +int __attribute__((noinline)) returns_input(int i) +{ + return i; +} + +extern void increment_counter_benchmark_js(int N); +extern void returns_input_benchmark_js(); +extern void sum_int_benchmark_js(); +extern void sum_float_benchmark_js(); + +extern void increment_counter_benchmark_embind_js(int N); +extern void returns_input_benchmark_embind_js(); +extern void sum_int_benchmark_embind_js(); +extern void sum_float_benchmark_embind_js(); + +extern void increment_class_counter_benchmark_embind_js(int N); +extern void move_gameobjects_benchmark_embind_js(); +} + +class Vec3 +{ +public: + Vec3():x(0),y(0),z(0) {} + Vec3(float x_, float y_, float z_):x(x_),y(y_),z(z_) {} + float x,y,z; +}; + +Vec3 add(const Vec3 &lhs, const Vec3 &rhs) { return Vec3(lhs.x+rhs.x, lhs.y+rhs.y, lhs.z+rhs.z); } + +class Transform +{ +public: + Transform():scale(1) {} + + Vec3 pos; + Vec3 rot; + float scale; + + Vec3 __attribute__((noinline)) GetPosition() const { return pos; } + Vec3 __attribute__((noinline)) GetRotation() const { return rot; } + float __attribute__((noinline)) GetScale() const { return scale; } + + void __attribute__((noinline)) SetPosition(const Vec3 &pos_) { pos = pos_; } + void __attribute__((noinline)) SetRotation(const Vec3 &rot_) { rot = rot_; } + void __attribute__((noinline)) SetScale(float scale_) { scale = scale_; } +}; +typedef std::shared_ptr<Transform> TransformPtr; + +class GameObject +{ +public: + GameObject() + { + transform = std::make_shared<Transform>(); + } + std::shared_ptr<Transform> transform; + + TransformPtr __attribute__((noinline)) GetTransform() const { return transform; } +}; +typedef std::shared_ptr<GameObject> GameObjectPtr; + +GameObjectPtr create_game_object() +{ + return std::make_shared<GameObject>(); +} + +class Foo +{ +public: + Foo() + :class_counter(0) + { + } + + void __attribute__((noinline)) incr_global_counter() + { + ++counter; + } + + void __attribute__((noinline)) incr_class_counter() + { + ++class_counter; + } + + int class_counter_val() const + { + return class_counter; + } + + int class_counter; +}; + +EMSCRIPTEN_BINDINGS(benchmark) +{ + using namespace emscripten; + + class_<GameObject>("GameObject") + .smart_ptr<GameObjectPtr>() + .function("GetTransform", &GameObject::GetTransform); + + class_<Transform>("Transform") + .smart_ptr<TransformPtr>() + .function("GetPosition", &Transform::GetPosition) + .function("GetRotation", &Transform::GetRotation) + .function("GetScale", &Transform::GetScale) + .function("SetPosition", &Transform::SetPosition) + .function("SetRotation", &Transform::SetRotation) + .function("SetScale", &Transform::SetScale); + + value_tuple<Vec3>("Vec3") + .element(&Vec3::x) + .element(&Vec3::y) + .element(&Vec3::z); + + function("create_game_object", &create_game_object); + function("add", &add); + + function("get_counter", &get_counter); + function("increment_counter", &increment_counter); + function("returns_input", &returns_input); + function("sum_int", &sum_int); + function("sum_float", &sum_float); + + class_<Foo>("Foo") + .constructor<>() + .function("incr_global_counter", &Foo::incr_global_counter) + .function("incr_class_counter", &Foo::incr_class_counter) + .function("class_counter_val", &Foo::class_counter_val); +} + +void __attribute__((noinline)) emscripten_get_now_benchmark(int N) +{ + volatile float t = emscripten_get_now(); + for(int i = 0; i < N; ++i) + { + emscripten_get_now(); + emscripten_get_now(); + emscripten_get_now(); + emscripten_get_now(); + emscripten_get_now(); + emscripten_get_now(); + emscripten_get_now(); + emscripten_get_now(); + emscripten_get_now(); + emscripten_get_now(); + } + volatile float t2 = emscripten_get_now(); + printf("C++ emscripten_get_now %d iters: %f msecs.\n", N, (t2-t)); +} + +void __attribute__((noinline)) increment_counter_benchmark(int N) +{ + volatile float t = emscripten_get_now(); + for(int i = 0; i < N; ++i) + { + increment_counter(); + increment_counter(); + increment_counter(); + increment_counter(); + increment_counter(); + increment_counter(); + increment_counter(); + increment_counter(); + increment_counter(); + increment_counter(); + } + volatile float t2 = emscripten_get_now(); + printf("C++ increment_counter %d iters: %f msecs.\n", N, (t2-t)); +} + +void __attribute__((noinline)) increment_class_counter_benchmark(int N) +{ + Foo foo; + volatile float t = emscripten_get_now(); + for(int i = 0; i < N; ++i) + { + foo.incr_class_counter(); + foo.incr_class_counter(); + foo.incr_class_counter(); + foo.incr_class_counter(); + foo.incr_class_counter(); + foo.incr_class_counter(); + foo.incr_class_counter(); + foo.incr_class_counter(); + foo.incr_class_counter(); + foo.incr_class_counter(); + } + volatile float t2 = emscripten_get_now(); + printf("C++ increment_class_counter %d iters: %f msecs. result: %d\n", N, (t2-t), foo.class_counter); +} + +void __attribute__((noinline)) returns_input_benchmark() +{ + volatile int r = 0; + volatile float t = emscripten_get_now(); + for(int i = 0; i < 100000; ++i) + { + r += returns_input(i); + r += returns_input(i); + r += returns_input(i); + r += returns_input(i); + r += returns_input(i); + r += returns_input(i); + r += returns_input(i); + r += returns_input(i); + r += returns_input(i); + r += returns_input(i); + } + volatile float t2 = emscripten_get_now(); + printf("C++ returns_input 100000 iters: %f msecs.\n", (t2-t)); +} + +void __attribute__((noinline)) sum_int_benchmark() +{ + volatile float t = emscripten_get_now(); + volatile int r = 0; + for(int i = 0; i < 100000; ++i) + { + r += sum_int(i,2,3,4,5,6,7,8,9); + r += sum_int(i,2,3,4,5,6,7,8,9); + r += sum_int(i,2,3,4,5,6,7,8,9); + r += sum_int(i,2,3,4,5,6,7,8,9); + r += sum_int(i,2,3,4,5,6,7,8,9); + r += sum_int(i,2,3,4,5,6,7,8,9); + r += sum_int(i,2,3,4,5,6,7,8,9); + r += sum_int(i,2,3,4,5,6,7,8,9); + r += sum_int(i,2,3,4,5,6,7,8,9); + r += sum_int(i,2,3,4,5,6,7,8,9); + } + volatile float t2 = emscripten_get_now(); + printf("C++ sum_int 100000 iters: %f msecs.\n", (t2-t)); +} + +void __attribute__((noinline)) sum_float_benchmark() +{ + volatile float f = 0.f; + volatile float t = emscripten_get_now(); + for(int i = 0; i < 100000; ++i) + { + f += sum_float((float)i,2.f,3.f,4.f,5.f,6.f,7.f,8.f,9.f); + f += sum_float((float)i,2.f,3.f,4.f,5.f,6.f,7.f,8.f,9.f); + f += sum_float((float)i,2.f,3.f,4.f,5.f,6.f,7.f,8.f,9.f); + f += sum_float((float)i,2.f,3.f,4.f,5.f,6.f,7.f,8.f,9.f); + f += sum_float((float)i,2.f,3.f,4.f,5.f,6.f,7.f,8.f,9.f); + f += sum_float((float)i,2.f,3.f,4.f,5.f,6.f,7.f,8.f,9.f); + f += sum_float((float)i,2.f,3.f,4.f,5.f,6.f,7.f,8.f,9.f); + f += sum_float((float)i,2.f,3.f,4.f,5.f,6.f,7.f,8.f,9.f); + f += sum_float((float)i,2.f,3.f,4.f,5.f,6.f,7.f,8.f,9.f); + f += sum_float((float)i,2.f,3.f,4.f,5.f,6.f,7.f,8.f,9.f); + } + volatile float t2 = emscripten_get_now(); + printf("C++ sum_float 100000 iters: %f msecs.\n", (t2-t)); +} + +void __attribute__((noinline)) move_gameobjects_benchmark() +{ + const int N = 10000; + GameObjectPtr objects[N]; + for(int i = 0; i < N; ++i) + objects[i] = create_game_object(); + + volatile float t = emscripten_get_now(); + for(int i = 0; i < N; ++i) + { + TransformPtr t = objects[i]->GetTransform(); + Vec3 pos = add(t->GetPosition(), Vec3(2.f, 0.f, 1.f)); + Vec3 rot = add(t->GetRotation(), Vec3(0.1f, 0.2f, 0.3f)); + t->SetPosition(pos); + t->SetRotation(rot); + } + volatile float t2 = emscripten_get_now(); + + Vec3 accum; + for(int i = 0; i < N; ++i) + accum = add(add(accum, objects[i]->GetTransform()->GetPosition()), objects[i]->GetTransform()->GetRotation()); + printf("C++ move_gameobjects %d iters: %f msecs. Result: %f\n", N, (t2-t), accum.x+accum.y+accum.z); +} + +int main() +{ + for(int i = 1000; i <= 100000; i *= 10) + emscripten_get_now_benchmark(i); + + printf("\n"); + for(int i = 1000; i <= 100000; i *= 10) + { + increment_counter_benchmark(i); + increment_counter_benchmark_js(i); + increment_counter_benchmark_embind_js(i); + printf("\n"); + } + + for(int i = 1000; i <= 100000; i *= 10) + { + increment_class_counter_benchmark(i); + increment_class_counter_benchmark_embind_js(i); + printf("\n"); + } + + returns_input_benchmark(); + returns_input_benchmark_js(); + returns_input_benchmark_embind_js(); + printf("\n"); + sum_int_benchmark(); + sum_int_benchmark_js(); + sum_int_benchmark_embind_js(); + printf("\n"); + sum_float_benchmark(); + sum_float_benchmark_js(); + sum_float_benchmark_embind_js(); + printf("\n"); + move_gameobjects_benchmark(); + move_gameobjects_benchmark_embind_js(); +} diff --git a/tests/embind/embind_test.cpp b/tests/embind/embind_test.cpp index 1384406a..f2359955 100644 --- a/tests/embind/embind_test.cpp +++ b/tests/embind/embind_test.cpp @@ -79,6 +79,24 @@ unsigned emval_test_sum(val v) { return rv;
}
+std::string get_non_ascii_string() {
+ char c[128 + 1];
+ c[128] = 0;
+ for (int i = 0; i < 128; ++i) {
+ c[i] = 128 + i;
+ }
+ return c;
+}
+
+std::wstring get_non_ascii_wstring() {
+ std::wstring ws(4, 0);
+ ws[0] = 10;
+ ws[1] = 1234;
+ ws[2] = 2345;
+ ws[3] = 65535;
+ return ws;
+}
+
std::string emval_test_take_and_return_const_char_star(const char* str) {
return str;
}
@@ -91,6 +109,10 @@ std::string emval_test_take_and_return_std_string_const_ref(const std::string& s return str;
}
+std::wstring take_and_return_std_wstring(std::wstring str) {
+ return str;
+}
+
std::function<std::string (std::string)> emval_test_get_function_ptr() {
return emval_test_take_and_return_std_string;
}
@@ -651,8 +673,6 @@ private: std::string name_;
};
-// todo: does it need to be polymorphic?
-// todo: virtual diamond pattern
class PolyDiamondBase {
public:
PolyDiamondBase():
@@ -1043,20 +1063,6 @@ std::vector<std::shared_ptr<StringHolder>> emval_test_return_shared_ptr_vector() return sharedStrVector;
}
-class JSInterfaceHolder {
-public:
- JSInterfaceHolder(JSInterface &jsobj) : jsobj_(jsobj) {
- ptr_ = JSInterface::cloneToSharedPtr(jsobj_);
- }
-
- int callMethod(std::string method) { return jsobj_.call<int>(method.c_str()); }
- int callMethodUsingSharedPtr(std::string method) { return ptr_->call<int>(method.c_str()); }
-
-private:
- JSInterface jsobj_;
- std::shared_ptr<JSInterface> ptr_;
-};
-
void test_string_with_vec(const std::string& p1, std::vector<std::string>& v1) {
// THIS DOES NOT WORK -- need to get as val and then call vecFromJSArray
printf("%s\n", p1.c_str());
@@ -1074,8 +1080,13 @@ class AbstractClass { public:
virtual ~AbstractClass() {}
virtual std::string abstractMethod() const = 0;
+ virtual std::string optionalMethod(std::string s) const {
+ return "optional" + s;
+ }
};
+EMSCRIPTEN_SYMBOL(optionalMethod);
+
class AbstractClassWrapper : public wrapper<AbstractClass> {
public:
EMSCRIPTEN_WRAPPER(AbstractClassWrapper);
@@ -1083,6 +1094,11 @@ public: std::string abstractMethod() const {
return call<std::string>("abstractMethod");
}
+ std::string optionalMethod(std::string s) const {
+ return optional_call<std::string>(optionalMethod_symbol, [&] {
+ return AbstractClass::optionalMethod(s);
+ }, s);
+ }
};
class ConcreteClass : public AbstractClass {
@@ -1099,6 +1115,10 @@ std::string callAbstractMethod(AbstractClass& ac) { return ac.abstractMethod();
}
+std::string callOptionalMethod(AbstractClass& ac, std::string s) {
+ return ac.optionalMethod(s);
+}
+
class HasExternalConstructor {
public:
HasExternalConstructor(const std::string& str)
@@ -1454,8 +1474,6 @@ EMSCRIPTEN_BINDINGS(constants) { }
EMSCRIPTEN_BINDINGS(tests) {
- register_js_interface();
-
register_vector<int>("IntegerVector");
register_vector<char>("CharVector");
register_vector<unsigned>("VectorUnsigned");
@@ -1481,9 +1499,12 @@ EMSCRIPTEN_BINDINGS(tests) { function("const_ref_adder", &const_ref_adder);
function("emval_test_sum", &emval_test_sum);
+ function("get_non_ascii_string", &get_non_ascii_string);
+ function("get_non_ascii_wstring", &get_non_ascii_wstring);
//function("emval_test_take_and_return_const_char_star", &emval_test_take_and_return_const_char_star);
function("emval_test_take_and_return_std_string", &emval_test_take_and_return_std_string);
function("emval_test_take_and_return_std_string_const_ref", &emval_test_take_and_return_std_string_const_ref);
+ function("take_and_return_std_wstring", &take_and_return_std_wstring);
//function("emval_test_take_and_return_CustomStruct", &emval_test_take_and_return_CustomStruct);
@@ -1527,6 +1548,8 @@ EMSCRIPTEN_BINDINGS(tests) { .function("getConstVal", &ValHolder::getConstVal)
.function("getValConstRef", &ValHolder::getValConstRef)
.function("setVal", &ValHolder::setVal)
+ .property("val", &ValHolder::getVal, &ValHolder::setVal)
+ .property("val_readonly", &ValHolder::getVal)
.class_function("makeConst", &ValHolder::makeConst, allow_raw_pointer<ret_val>())
.class_function("makeValHolder", &ValHolder::makeValHolder)
.class_function("some_class_method", &ValHolder::some_class_method)
@@ -1550,7 +1573,7 @@ EMSCRIPTEN_BINDINGS(tests) { class_<std::function<std::string(std::string)>>("StringFunctorString")
.constructor<>()
- .calloperator<std::string, std::string>("opcall")
+ .function("opcall", &std::function<std::string(std::string)>::operator())
;
function("emval_test_get_function_ptr", &emval_test_get_function_ptr);
@@ -1790,6 +1813,11 @@ EMSCRIPTEN_BINDINGS(tests) { function("embind_attempt_to_modify_smart_pointer_when_passed_by_value", embind_attempt_to_modify_smart_pointer_when_passed_by_value);
function("embind_save_smart_base_pointer", embind_save_smart_base_pointer);
+ class_<Base1>("Base1")
+ .constructor()
+ .function("getField", &Base1::getField)
+ ;
+
class_<Base2>("Base2")
.function("getField", &Base2::getField)
.property("field", &Base2::field2)
@@ -1845,12 +1873,6 @@ EMSCRIPTEN_BINDINGS(tests) { register_map<std::string, int>("StringIntMap");
function("embind_test_get_string_int_map", embind_test_get_string_int_map);
- class_<JSInterfaceHolder>("JSInterfaceHolder")
- .constructor<JSInterface&>()
- .function("callMethod", &JSInterfaceHolder::callMethod)
- .function("callMethodUsingSharedPtr", &JSInterfaceHolder::callMethodUsingSharedPtr)
- ;
-
function("embind_test_new_Object", &embind_test_new_Object);
function("embind_test_new_factory", &embind_test_new_factory);
@@ -1858,10 +1880,12 @@ EMSCRIPTEN_BINDINGS(tests) { .smart_ptr<std::shared_ptr<AbstractClass>>()
.allow_subclass<AbstractClassWrapper>()
.function("abstractMethod", &AbstractClass::abstractMethod)
+ .function("optionalMethod", &AbstractClass::optionalMethod)
;
function("getAbstractClass", &getAbstractClass);
function("callAbstractMethod", &callAbstractMethod);
+ function("callOptionalMethod", &callOptionalMethod);
class_<HasExternalConstructor>("HasExternalConstructor")
.constructor(&createHasExternalConstructor)
@@ -1901,8 +1925,8 @@ EMSCRIPTEN_BINDINGS(tests) { function("long_to_string", &long_to_string);
function("unsigned_long_to_string", &unsigned_long_to_string);
- function("overloaded_function", (int(*)(int))&overloaded_function);
- function("overloaded_function", (int(*)(int, int))&overloaded_function);
+ function("overloaded_function", select_overload<int(int)>(&overloaded_function));
+ function("overloaded_function", select_overload<int(int, int)>(&overloaded_function));
class_<MultipleCtors>("MultipleCtors")
.constructor<int>()
@@ -1912,19 +1936,21 @@ EMSCRIPTEN_BINDINGS(tests) { class_<MultipleOverloads>("MultipleOverloads")
.constructor<>()
- .function("Func", (int(MultipleOverloads::*)(int))&MultipleOverloads::Func)
- .function("Func", (int(MultipleOverloads::*)(int,int))&MultipleOverloads::Func)
+ .function("Func", select_overload<int(int)>(&MultipleOverloads::Func))
+ .function("Func", select_overload<int(int, int)>(&MultipleOverloads::Func))
.function("WhichFuncCalled", &MultipleOverloads::WhichFuncCalled)
- .class_function("StaticFunc", (int(*)(int))&MultipleOverloads::StaticFunc)
- .class_function("StaticFunc", (int(*)(int,int))&MultipleOverloads::StaticFunc)
- .class_function("WhichStaticFuncCalled", &MultipleOverloads::WhichStaticFuncCalled);
+ .class_function("StaticFunc", select_overload<int(int)>(&MultipleOverloads::StaticFunc))
+ .class_function("StaticFunc", select_overload<int(int,int)>(&MultipleOverloads::StaticFunc))
+ .class_function("WhichStaticFuncCalled", &MultipleOverloads::WhichStaticFuncCalled)
+ ;
class_<MultipleOverloadsDerived, base<MultipleOverloads> >("MultipleOverloadsDerived")
.constructor<>()
- .function("Func", (int(MultipleOverloadsDerived::*)(int,int,int))&MultipleOverloadsDerived::Func)
- .function("Func", (int(MultipleOverloadsDerived::*)(int,int,int,int))&MultipleOverloadsDerived::Func)
- .class_function("StaticFunc", (int(*)(int,int,int))&MultipleOverloadsDerived::StaticFunc)
- .class_function("StaticFunc", (int(*)(int,int,int,int))&MultipleOverloadsDerived::StaticFunc);
+ .function("Func", select_overload<int(int,int,int)>(&MultipleOverloadsDerived::Func))
+ .function("Func", select_overload<int(int,int,int,int)>(&MultipleOverloadsDerived::Func))
+ .class_function("StaticFunc", select_overload<int(int,int,int)>(&MultipleOverloadsDerived::StaticFunc))
+ .class_function("StaticFunc", select_overload<int(int,int,int,int)>(&MultipleOverloadsDerived::StaticFunc))
+ ;
}
// tests for out-of-order registration
@@ -2048,15 +2074,41 @@ class Noncopyable { public:
Noncopyable() {}
+ Noncopyable(Noncopyable&& other) {
+ other.valid = false;
+ }
std::string method() const {
return "foo";
}
+
+ bool valid = true;
};
+Noncopyable getNoncopyable() {
+ return Noncopyable();
+}
+
EMSCRIPTEN_BINDINGS(noncopyable) {
class_<Noncopyable>("Noncopyable")
.constructor<>()
.function("method", &Noncopyable::method)
;
+
+ function("getNoncopyable", &getNoncopyable);
+}
+
+struct HasReadOnlyProperty {
+ HasReadOnlyProperty(int i)
+ : i(i)
+ {}
+
+ const int i;
+};
+
+EMSCRIPTEN_BINDINGS(read_only_properties) {
+ class_<HasReadOnlyProperty>("HasReadOnlyProperty")
+ .constructor<int>()
+ .property("i", &HasReadOnlyProperty::i)
+ ;
}
diff --git a/tests/embind/shell.html b/tests/embind/shell.html new file mode 100644 index 00000000..6664ec78 --- /dev/null +++ b/tests/embind/shell.html @@ -0,0 +1,94 @@ +<!doctype html> +<html lang="en-us"> + <head> + <meta charset="utf-8"> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <title>Emscripten-Generated Code</title> + <style> + .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } + textarea.emscripten { font-family: monospace; width: 80%; } + div.emscripten { text-align: center; } + div.emscripten_border { border: 1px solid black; } + /* the canvas *must not* have any border or padding, or mouse coords will be wrong */ + canvas.emscripten { border: 0px none; } + </style> + </head> + <body> + <div class="emscripten" id="status">Downloading...</div> + <div class="emscripten"> + <progress value="0" max="100" id="progress" hidden=1></progress> + </div> + <div class="emscripten_border" style="display:none;"> + <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas> + </div> + <div class="emscripten" style="display:none;"> + <input type="checkbox" id="resize">Resize canvas + <input type="checkbox" id="pointerLock" checked>Lock/hide mouse pointer + + <input type="button" value="Fullscreen" onclick="Module.requestFullScreen(document.getElementById('pointerLock').checked, + document.getElementById('resize').checked)"> + </div> + + <hr/> + <textarea class="emscripten" id="output" rows="45"></textarea> + <hr> + <script type='text/javascript'> + // connect to canvas + var Module = { + preRun: [], + postRun: [], + print: (function() { + var element = document.getElementById('output'); + element.value = ''; // clear browser cache + return function(text) { + text = Array.prototype.slice.call(arguments).join(' '); + // These replacements are necessary if you render to raw HTML + //text = text.replace(/&/g, "&"); + //text = text.replace(/</g, "<"); + //text = text.replace(/>/g, ">"); + //text = text.replace('\n', '<br>', 'g'); + element.value += text + "\n"; + element.scrollTop = 99999; // focus on bottom + }; + })(), + printErr: function(text) { + text = Array.prototype.slice.call(arguments).join(' '); + if (0) { // XXX disabled for safety typeof dump == 'function') { + dump(text + '\n'); // fast, straight to the real console + } else { + console.log(text); + } + }, + canvas: document.getElementById('canvas'), + setStatus: function(text) { + if (Module.setStatus.interval) clearInterval(Module.setStatus.interval); + var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/); + var statusElement = document.getElementById('status'); + var progressElement = document.getElementById('progress'); + if (m) { + text = m[1]; + progressElement.value = parseInt(m[2])*100; + progressElement.max = parseInt(m[4])*100; + progressElement.hidden = false; + } else { + progressElement.value = null; + progressElement.max = null; + progressElement.hidden = true; + } + statusElement.innerHTML = text; + }, + totalDependencies: 0, + monitorRunDependencies: function(left) { + this.totalDependencies = Math.max(this.totalDependencies, left); + Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.'); + } + }; + Module.setStatus('Downloading...'); + </script> + <script type='text/javascript'> + + {{{ SCRIPT_CODE }}} + + </script> + </body> +</html> diff --git a/tests/gl_stride.c b/tests/gl_stride.c new file mode 100644 index 00000000..c254ad5a --- /dev/null +++ b/tests/gl_stride.c @@ -0,0 +1,152 @@ +/******************************************************************* + * * + * Using SDL With OpenGL * + * * + * Tutorial by Kyle Foley (sdw) * + * * + * http://gpwiki.org/index.php/SDL:Tutorials:Using_SDL_with_OpenGL * + * * + *******************************************************************/ + +/* +THIS WORK, INCLUDING THE SOURCE CODE, DOCUMENTATION +AND RELATED MEDIA AND DATA, IS PLACED INTO THE PUBLIC DOMAIN. + +THE ORIGINAL AUTHOR IS KYLE FOLEY. + +THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY +OF ANY KIND, NOT EVEN THE IMPLIED WARRANTY OF +MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE, +ASSUMES _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE +RESULTING FROM THE USE, MODIFICATION, OR +REDISTRIBUTION OF THIS SOFTWARE. +*/ + +#if !EMSCRIPTEN +#define USE_GLEW 0 +#endif + +#if USE_GLEW +#include "GL/glew.h" +#endif + +#include "SDL/SDL.h" +#include "SDL/SDL_image.h" +#if !USE_GLEW +#include "SDL/SDL_opengl.h" +#endif + +#include <stdio.h> +#include <string.h> +#include <assert.h> + +int main(int argc, char *argv[]) +{ + SDL_Surface *screen; + + // Slightly different SDL initialization + if ( SDL_Init(SDL_INIT_VIDEO) != 0 ) { + printf("Unable to initialize SDL: %s\n", SDL_GetError()); + return 1; + } + + SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); // *new* + + screen = SDL_SetVideoMode( 640, 480, 16, SDL_OPENGL ); // *changed* + if ( !screen ) { + printf("Unable to set video mode: %s\n", SDL_GetError()); + return 1; + } + + // Set the OpenGL state after creating the context with SDL_SetVideoMode + + glClearColor( 0, 0, 0, 0 ); + +#if !EMSCRIPTEN + glEnable( GL_TEXTURE_2D ); // Need this to display a texture XXX unnecessary in OpenGL ES 2.0/WebGL +#endif + + glViewport( 0, 0, 640, 480 ); + + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); + + // Clear the screen before drawing + glClear( GL_COLOR_BUFFER_BIT ); + + typedef struct Vertex { + GLfloat x; + GLfloat y; + } Vertex; + + typedef struct Color { + GLubyte r; + GLubyte g; + GLubyte b; + GLubyte a; + } Color; + + Vertex vertices[3] = { + {-1.0, 0.0}, + { 0.0, 1.0}, + { 1.0, 0.0} + }; + + Color colors[3] = { + {0xFF, 0x00, 0x00, 0xFF}, + {0x00, 0xFF, 0x00, 0xFF}, + {0x00, 0x00, 0xFF, 0xFF} + }; + + Vertex vertices2[3] = { + {-1.0, 0.0}, + { 1.0, 0.0}, + { 0.0, -1.0} + }; + + Color colors2[3] = { + {0xFF, 0x00, 0x00, 0xFF}, + {0x00, 0x00, 0xFF, 0xFF}, + {0x00, 0xFF, 0x00, 0xFF} + }; + + // DRAW + + // Clear the screen before drawing + glClear( GL_COLOR_BUFFER_BIT ); + + // This test ensures that we can use two separate arrays in memory for different + // attributes, and that they each can have different stride. + // The first test shows implicit striding (the zero indicates tightly packed) + // The second test shows explicit striding where the stride is passed in + // even though it also is tightly packed + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + // TEST 1 + + glVertexPointer(2, GL_FLOAT, 0, vertices); + glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 3); + + // TEST 2 + + glVertexPointer(2, GL_FLOAT, 8, vertices2); + glColorPointer(4, GL_UNSIGNED_BYTE, 4, colors2); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 3); + + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + + SDL_GL_SwapBuffers(); + +#if !EMSCRIPTEN + // Wait for 3 seconds to give us a chance to see the image + SDL_Delay(3000); +#endif + + SDL_Quit(); + + return 0; +} diff --git a/tests/gl_stride.png b/tests/gl_stride.png Binary files differnew file mode 100644 index 00000000..db1bc751 --- /dev/null +++ b/tests/gl_stride.png diff --git a/tests/runner.py b/tests/runner.py index db5ba108..aa2e1dc1 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -455,7 +455,7 @@ if 'benchmark' not in str(sys.argv) and 'sanity' not in str(sys.argv) and 'brows if len(sys.argv) == 2 and 'ALL.' in sys.argv[1]: ignore, test = sys.argv[1].split('.') print 'Running all test modes on test "%s"' % test - sys.argv = [sys.argv[0], 'default.'+test, 'o1.'+test, 'o2.'+test, 'asm2.'+test, 'asm2g.'+test, 's_0_0.'+test, 's_0_1.'+test, 's_1_0.'+test, 's_1_1.'+test] + sys.argv = [sys.argv[0], 'default.'+test, 'o1.'+test, 'o2.'+test, 'asm1.'+test, 'asm2.'+test, 'asm2g.'+test, 's_0_0.'+test, 's_0_1.'+test, 's_1_0.'+test, 's_1_1.'+test] class T(RunnerCore): # Short name, to make it more fun to use manually on the commandline ## Does a complete test - builds, runs, checks output, etc. @@ -2310,8 +2310,6 @@ cat |umber one top notchfi FI FO FUM WHEN WHERE WHY HOW WHO|''', ['wowie', 'too' self.do_run(src, 'Assertion failed: 1 == false') def test_longjmp(self): - if Settings.ASM_JS: return self.skip('asm does not support longjmp') - src = r''' #include <stdio.h> #include <setjmp.h> @@ -2343,8 +2341,6 @@ cat |umber one top notchfi FI FO FUM WHEN WHERE WHY HOW WHO|''', ['wowie', 'too' self.do_run(src, 'second\nmain: 1\n') def test_longjmp2(self): - if Settings.ASM_JS: return self.skip('asm does not support longjmp') - src = r''' #include <setjmp.h> #include <stdio.h> @@ -2391,8 +2387,6 @@ Exiting stack_manipulate_func, level: 0 ''') def test_longjmp3(self): - if Settings.ASM_JS: return self.skip('asm does not support longjmp') - src = r''' #include <setjmp.h> #include <stdio.h> @@ -2445,8 +2439,6 @@ Exiting setjmp function, level: 0 ''') def test_longjmp4(self): - if Settings.ASM_JS: return self.skip('asm does not support longjmp') - src = r''' #include <setjmp.h> #include <stdio.h> @@ -3541,7 +3533,7 @@ def process(filename): double get() { double ret = 0; - __asm __volatile__("12/3.3":"=a"(ret)); + __asm __volatile__("12/3.3":"=r"(ret)); return ret; } @@ -3709,6 +3701,7 @@ def process(filename): def test_bigswitch(self): if Settings.RELOOP: return self.skip('TODO: switch in relooper, issue #781') + if Settings.ASM_JS: return self.skip('TODO: switch too large for asm') src = open(path_from_root('tests', 'bigswitch.cpp')).read() self.do_run(src, '''34962: GL_ARRAY_BUFFER (0x8892) @@ -9001,6 +8994,7 @@ TT = %s exec('o2 = make_run("o2", compiler=CLANG, emcc_args=["-O2", "-s", "JS_CHUNK_SIZE=1024"])') # asm.js + exec('asm1 = make_run("asm1", compiler=CLANG, emcc_args=["-O1", "-s", "ASM_JS=1"])') exec('asm2 = make_run("asm2", compiler=CLANG, emcc_args=["-O2", "-s", "ASM_JS=1"])') exec('asm2g = make_run("asm2g", compiler=CLANG, emcc_args=["-O2", "-s", "ASM_JS=1", "-g", "-s", "ASSERTIONS=1", "--memory-init-file", "1"])') @@ -10241,8 +10235,8 @@ f.close() for args, fail in [ ([], True), # without --bind, we fail (['--bind'], False), - (['--bind', '-O1'], False) - # XXX TODO (['--bind', '-O2'], False) + (['--bind', '-O1'], False), + (['--bind', '-O2'], False) ]: print args, fail self.clear() @@ -11586,6 +11580,9 @@ elif 'browser' in str(sys.argv): def test_gl_renderers(self): self.btest('gl_renderers.c', reference='gl_renderers.png', args=['-s', 'GL_UNSAFE_OPTS=0']) + def test_gl_stride(self): + self.btest('gl_stride.c', reference='gl_stride.png', args=['-s', 'GL_UNSAFE_OPTS=0']) + def test_matrix_identity(self): self.btest('gl_matrix_identity.c', expected=['-1882984448', '460451840']) @@ -12102,8 +12099,7 @@ elif 'benchmark' in str(sys.argv): '-o', final_filename] + shared_args + emcc_args, stdout=PIPE, stderr=self.stderr_redirect).communicate() assert os.path.exists(final_filename), 'Failed to compile file: ' + output[0] - if self.save_JS: - self.hardcode_arguments(final_filename, args) + self.hardcode_arguments(final_filename, args) # Run JS global total_times, tests_done diff --git a/tools/eliminator/asm-eliminator-test-output.js b/tools/eliminator/asm-eliminator-test-output.js index 4cf15c62..e477c320 100644 --- a/tools/eliminator/asm-eliminator-test-output.js +++ b/tools/eliminator/asm-eliminator-test-output.js @@ -108,4 +108,19 @@ function label() { i(); } } +function switchy() { + var no = 0, yes = 0; + while (1) switch (label | 0) { + case x: + no = 100; + break; + case y: + yes = 111; + yes = yes * 2; + print(yes); + yes--; + print(yes / 2); + continue; + } +} diff --git a/tools/eliminator/asm-eliminator-test.js b/tools/eliminator/asm-eliminator-test.js index d2c0507c..acc07edb 100644 --- a/tools/eliminator/asm-eliminator-test.js +++ b/tools/eliminator/asm-eliminator-test.js @@ -141,5 +141,20 @@ function label() { i(); } } +function switchy() { + var no = 0, yes = 0; + while (1) switch (label | 0) { + case x: + no = 100; // eliminatable in theory, but eliminator does not look into switch. must leave def above as well. + break; + case y: + yes = 111; + yes = yes*2; + print(yes); + yes--; + print(yes/2); + continue; + } +} // EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "__Z11printResultPiS_j", "_segment_holding", "__ZN5identC2EiPKcPci", "_vec2Length", "exc", "label"] diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 5ede0ce8..598287db 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -1765,6 +1765,7 @@ function eliminate(ast, memSafe) { var values = {}; var locals = {}; var varsToRemove = {}; // variables being removed, that we can eliminate all 'var x;' of (this refers to 'var' nodes we should remove) + // 1 means we should remove it, 2 means we successfully removed it var varsToTryToRemove = {}; // variables that have 0 uses, but have side effects - when we scan we can try to remove them // add arguments as locals if (func[2]) { @@ -1822,7 +1823,7 @@ function eliminate(ast, memSafe) { }); } if (!hasSideEffects) { - varsToRemove[name] = 1; // remove it normally + varsToRemove[name] = !definitions[name] ? 2 : 1; // remove it normally sideEffectFree[name] = true; } else { varsToTryToRemove[name] = 1; // try to remove it later during scanning @@ -1979,7 +1980,7 @@ function eliminate(ast, memSafe) { for (var i = 0; i < value.length; i++) { node[i] = value[i]; } - varsToRemove[name] = 1; + varsToRemove[name] = 2; } } else { if (allowTracking) track(name, node[3], node); @@ -2019,7 +2020,7 @@ function eliminate(ast, memSafe) { for (var i = 0; i < value.length; i++) { node[i] = value[i]; } - varsToRemove[name] = 1; + varsToRemove[name] = 2; } } } @@ -2123,7 +2124,7 @@ function eliminate(ast, memSafe) { function doEliminate(name, node) { //printErr('elim!!!!! ' + name); // yes, eliminate! - varsToRemove[name] = 1; // both assign and var definitions can have other vars we must clean up + varsToRemove[name] = 2; // both assign and var definitions can have other vars we must clean up var info = tracked[name]; delete tracked[name]; var defNode = info.defNode; @@ -2181,7 +2182,7 @@ function eliminate(ast, memSafe) { //printErr('cleaning up ' + JSON.stringify(varsToRemove)); traverse(func, function(node, type) { if (type === 'var') { - node[1] = node[1].filter(function(pair) { return !(pair[0] in varsToRemove) }); + node[1] = node[1].filter(function(pair) { return !varsToRemove[pair[0]] }); if (node[1].length == 0) { // wipe out an empty |var;| node[0] = 'toplevel'; @@ -2192,7 +2193,7 @@ function eliminate(ast, memSafe) { if (asm) { for (var v in varsToRemove) { - delete asmData.vars[v]; + if (varsToRemove[v] == 2) delete asmData.vars[v]; } denormalizeAsm(func, asmData); } diff --git a/tools/shared.py b/tools/shared.py index a53c0a7b..eedb2e8b 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -729,13 +729,13 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e basename = os.path.basename(f) cache[cache_name].append((basename, open(f, 'rb').read())) break - except: + except Exception, e: if i > 0: # Due to the ugly hack above our best guess is to output the first run with open_make_err(0) as ferr: for line in ferr: sys.stderr.write(line) - raise Exception('could not build library ' + name) + raise Exception('could not build library ' + name + ' due to exception ' + str(e)) if old_dir: os.chdir(old_dir) return generated_libs |