diff options
Diffstat (limited to 'src/embind')
-rwxr-xr-x[-rw-r--r--] | src/embind/embind.js | 1816 | ||||
-rwxr-xr-x[-rw-r--r--] | src/embind/emval.js | 195 |
2 files changed, 1542 insertions, 469 deletions
diff --git a/src/embind/embind.js b/src/embind/embind.js index d40d6ca2..988526b4 100644..100755 --- a/src/embind/embind.js +++ b/src/embind/embind.js @@ -1,14 +1,141 @@ /*global Module*/ /*global _malloc, _free, _memcpy*/ -/*global FUNCTION_TABLE, HEAP32*/ -/*global Pointer_stringify, writeStringToMemory*/ +/*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'); +function throwInternalError(message) { + throw new InternalError(message); +} + +function throwBindingError(message) { + throw new BindingError(message); +} + +function throwUnboundTypeError(message, types) { + var unboundTypes = []; + var seen = {}; + function visit(type) { + if (seen[type]) { + return; + } + if (registeredTypes[type]) { + return; + } + if (typeDependencies[type]) { + typeDependencies[type].forEach(visit); + return; + } + unboundTypes.push(type); + seen[type] = true; + } + types.forEach(visit); + + throw new UnboundTypeError(message + ': ' + unboundTypes.map(getTypeName).join([', '])); +} + +// Creates a function overload resolution table to the given method 'methodName' in the given prototype, +// if the overload table doesn't yet exist. +function ensureOverloadTable(proto, methodName, humanName) { + if (undefined === proto[methodName].overloadTable) { + var prevFunc = proto[methodName]; + // Inject an overload resolver function that routes to the appropriate overload based on the number of arguments. + proto[methodName] = function() { + // TODO This check can be removed in -O3 level "unsafe" optimizations. + if (!proto[methodName].overloadTable.hasOwnProperty(arguments.length)) { + throwBindingError("Function '" + humanName + "' called with an invalid number of arguments (" + arguments.length + ") - expects one of (" + proto[methodName].overloadTable + ")!"); + } + return proto[methodName].overloadTable[arguments.length].apply(this, arguments); + }; + // Move the previous function into the overload table. + proto[methodName].overloadTable = []; + proto[methodName].overloadTable[prevFunc.argCount] = prevFunc; + } +} + +/* Registers a symbol (function, class, enum, ...) as part of the Module JS object so that + hand-written code is able to access that symbol via 'Module.name'. + name: The name of the symbol that's being exposed. + value: The object itself to expose (function, class, ...) + numArguments: For functions, specifies the number of arguments the function takes in. For other types, unused and undefined. + + To implement support for multiple overloads of a function, an 'overload selector' function is used. That selector function chooses + the appropriate overload to call from an function overload table. This selector function is only used if multiple overloads are + actually registered, since it carries a slight performance penalty. */ +function exposePublicSymbol(name, value, numArguments) { + if (Module.hasOwnProperty(name)) { + if (undefined === numArguments || (undefined !== Module[name].overloadTable && undefined !== Module[name].overloadTable[numArguments])) { + throwBindingError("Cannot register public name '" + name + "' twice"); + } + + // We are exposing a function with the same name as an existing function. Create an overload table and a function selector + // that routes between the two. + ensureOverloadTable(Module, name, name); + if (Module.hasOwnProperty(numArguments)) { + throwBindingError("Cannot register multiple overloads of a function with the same number of arguments (" + numArguments + ")!"); + } + // Add the new function into the overload table. + Module[name].overloadTable[numArguments] = value; + } + else { + Module[name] = value; + if (undefined !== numArguments) { + Module[name].numArguments = numArguments; + } + } +} + +function replacePublicSymbol(name, value, numArguments) { + if (!Module.hasOwnProperty(name)) { + throwInternalError('Replacing nonexistant public symbol'); + } + // If there's an overload table for this symbol, replace the symbol in the overload table instead. + if (undefined !== Module[name].overloadTable && undefined !== numArguments) { + Module[name].overloadTable[numArguments] = value; + } + else { + Module[name] = value; + } +} + +// from https://github.com/imvu/imvujs/blob/master/src/error.js +function extendError(baseErrorType, errorName) { + var errorClass = createNamedFunction(errorName, function(message) { + this.name = errorName; + this.message = message; + + var stack = (new Error(message)).stack; + if (stack !== undefined) { + this.stack = this.toString() + '\n' + + stack.replace(/^Error(:[^\n]*)?\n/, ''); + } + }); + errorClass.prototype = Object.create(baseErrorType.prototype); + errorClass.prototype.constructor = errorClass; + errorClass.prototype.toString = function() { + if (this.message === undefined) { + return this.name; + } else { + return this.name + ': ' + this.message; + } + }; + + return errorClass; +} + + +// from https://github.com/imvu/imvujs/blob/master/src/function.js function createNamedFunction(name, body) { /*jshint evil:true*/ return new Function( "body", "return function " + name + "() {\n" + + " \"use strict\";" + " return body.apply(this, arguments);\n" + "};\n" )(body); @@ -23,568 +150,1407 @@ function _embind_repr(v) { } } -var typeRegistry = {}; +// typeID -> { toWireType: ..., fromWireType: ... } +var registeredTypes = {}; + +// typeID -> [callback] +var awaitingDependencies = {}; + +// typeID -> [dependentTypes] +var typeDependencies = {}; + +// class typeID -> {pointerType: ..., constPointerType: ...} +var registeredPointers = {}; + +function registerType(rawType, registeredInstance) { + var name = registeredInstance.name; + if (!rawType) { + throwBindingError('type "' + name + '" must have a positive integer typeid pointer'); + } + if (registeredTypes.hasOwnProperty(rawType)) { + throwBindingError("Cannot register type '" + name + "' twice"); + } + + registeredTypes[rawType] = registeredInstance; + delete typeDependencies[rawType]; + + if (awaitingDependencies.hasOwnProperty(rawType)) { + var callbacks = awaitingDependencies[rawType]; + delete awaitingDependencies[rawType]; + callbacks.forEach(function(cb) { + cb(); + }); + } +} + +function whenDependentTypesAreResolved(myTypes, dependentTypes, getTypeConverters) { + myTypes.forEach(function(type) { + typeDependencies[type] = dependentTypes; + }); + + function onComplete(typeConverters) { + var myTypeConverters = getTypeConverters(typeConverters); + if (myTypeConverters.length !== myTypes.length) { + throwInternalError('Mismatched type converter count'); + } + for (var i = 0; i < myTypes.length; ++i) { + registerType(myTypes[i], myTypeConverters[i]); + } + } + + var typeConverters = new Array(dependentTypes.length); + var unregisteredTypes = []; + var registered = 0; + dependentTypes.forEach(function(dt, i) { + if (registeredTypes.hasOwnProperty(dt)) { + typeConverters[i] = registeredTypes[dt]; + } else { + unregisteredTypes.push(dt); + if (!awaitingDependencies.hasOwnProperty(dt)) { + awaitingDependencies[dt] = []; + } + awaitingDependencies[dt].push(function() { + typeConverters[i] = registeredTypes[dt]; + ++registered; + if (registered === unregisteredTypes.length) { + onComplete(typeConverters); + } + }); + } + }); + if (0 === unregisteredTypes.length) { + onComplete(typeConverters); + } +} + +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 = readLatin1String(ptr); + _free(ptr); + return rv; +} -function validateType(type, name) { - if (!type) { - throw new BindingError('type "' + name + '" must have a positive integer typeid pointer'); +function heap32VectorToArray(count, firstElement) { + var array = []; + for (var i = 0; i < count; i++) { + array.push(HEAP32[(firstElement >> 2) + i]); } - if (undefined !== typeRegistry[type]) { - throw new BindingError('cannot register type "' + name + '" twice'); + return array; +} + +function requireRegisteredType(rawType, humanName) { + var impl = registeredTypes[rawType]; + if (undefined === impl) { + throwBindingError(humanName + " has unknown type " + getTypeName(rawType)); } + return impl; } -function __embind_register_void(voidType, name) { - name = Pointer_stringify(name); - validateType(voidType, name); - typeRegistry[voidType] = { +function __embind_register_void(rawType, name) { + name = readLatin1String(name); + registerType(rawType, { name: name, - fromWireType: function() { + 'fromWireType': function() { return undefined; - } - }; + }, + }); } -function __embind_register_bool(boolType, name, trueValue, falseValue) { - name = Pointer_stringify(name); - validateType(boolType, name); - typeRegistry[boolType] = { +function __embind_register_bool(rawType, name, trueValue, falseValue) { + name = readLatin1String(name); + registerType(rawType, { name: name, - toWireType: function(destructors, o) { - return o ? trueValue : falseValue; + 'fromWireType': function(wt) { + // ambiguous emscripten ABI: sometimes return values are + // true or false, and sometimes integers (0 or 1) + return !!wt; }, - fromWireType: function(wt) { - return wt === trueValue; + 'toWireType': function(destructors, o) { + return o ? trueValue : falseValue; }, - }; + destructorFunction: null, // This type does not need a destructor + }); } -function __embind_register_integer(primitiveType, name) { - name = Pointer_stringify(name); - validateType(primitiveType, name); - typeRegistry[primitiveType] = { +// 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 = 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; + } + registerType(primitiveType, { name: name, - toWireType: function(destructors, value) { + minRange: minRange, + maxRange: maxRange, + 'fromWireType': function(value) { + return 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") { - throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' + name); + throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' + this.name); + } + if (value < minRange || value > maxRange) { + throw new TypeError('Passing a number "' + _embind_repr(value) + '" from JS side to C/C++ side to an argument of type "' + name + '", which is outside the valid range [' + minRange + ', ' + maxRange + ']!'); } return value | 0; }, - fromWireType: function(value) { - return value; - } - }; + destructorFunction: null, // This type does not need a destructor + }); } -function __embind_register_float(primitiveType, name) { - name = Pointer_stringify(name); - validateType(primitiveType, name); - typeRegistry[primitiveType] = { +function __embind_register_float(rawType, name) { + name = readLatin1String(name); + registerType(rawType, { name: name, - toWireType: function(destructors, value) { + 'fromWireType': function(value) { + return 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") { - throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' + name); + throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' +this.name); } return value; }, - fromWireType: function(value) { - return value; - } - }; + destructorFunction: null, // This type does not need a destructor + }); } -function __embind_register_cstring(stringType, name) { - name = Pointer_stringify(name); - validateType(stringType, name); - typeRegistry[stringType] = { +function __embind_register_std_string(rawType, name) { + name = readLatin1String(name); + registerType(rawType, { name: name, - toWireType: function(destructors, value) { - var ptr = _malloc(value.length + 1); - writeStringToMemory(value, ptr); - destructors.push(_free); - destructors.push(ptr); + '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]); + } + _free(value); + return a.join(''); + }, + '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); + 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; }, - fromWireType: function(value) { - var rv = Pointer_stringify(value); - _free(value); - return rv; - } - }; + destructorFunction: function(ptr) { _free(ptr); }, + }); } -function __embind_register_emval(emvalType, name) { - name = Pointer_stringify(name); - validateType(emvalType, name); - typeRegistry[emvalType] = { +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, - toWireType: function(destructors, value) { - return __emval_register(value); + '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) { + HEAP[start + i] = value.charCodeAt(i); + } + if (destructors !== null) { + destructors.push(_free, ptr); + } + return ptr; }, - fromWireType: function(handle) { + destructorFunction: function(ptr) { _free(ptr); }, + }); +} + +function __embind_register_emval(rawType, name) { + name = readLatin1String(name); + registerType(rawType, { + name: name, + 'fromWireType': function(handle) { var rv = _emval_handle_array[handle].value; __emval_decref(handle); return rv; - } - }; + }, + 'toWireType': function(destructors, value) { + return __emval_register(value); + }, + destructorFunction: null, // This type does not need a destructor + }); } -var BindingError = Error; -/** @expose */ -Module.BindingError = BindingError; - -function typeName(typeID) { - // could use our carnal knowledge of RTTI but for now just return the pointer... - return typeID; +function runDestructors(destructors) { + while (destructors.length) { + var ptr = destructors.pop(); + var del = destructors.pop(); + del(ptr); + } } -function requireRegisteredType(type, humanName) { - var impl = typeRegistry[type]; - if (undefined === impl) { - throw new BindingError(humanName + " has unknown type: " + typeName(type)); +// 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"); } - return impl; + + /* + * 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; } -function requireArgumentTypes(argCount, argTypes, name) { - var argTypeImpls = new Array(argCount); - for (var i = 0; i < argCount; ++i) { - var argType = HEAP32[(argTypes >> 2) + i]; - argTypeImpls[i] = requireRegisteredType(argType, name + " parameter " + i); +// 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!"); } - return argTypeImpls; -} + + var isClassMethodFunc = (argTypes[1] !== null && classType !== null); -function runDestructors(destructors) { - while (destructors.length) { - var ptr = destructors.pop(); - var del = destructors.pop(); - del(ptr); + if (!isClassMethodFunc && !FUNCTION_TABLE[cppTargetFunc]) { + throwBindingError('Global function '+humanName+' is not defined!'); } -} -function __embind_register_function(name, returnType, argCount, argTypes, invoker, fn) { - name = Pointer_stringify(name); - returnType = requireRegisteredType(returnType, "Function " + name + " return value"); - invoker = FUNCTION_TABLE[invoker]; - argTypes = requireArgumentTypes(argCount, argTypes, name); + // 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]; +// } - Module[name] = function() { - if (arguments.length !== argCount) { - throw new BindingError('emscripten binding function ' + name + ' called with ' + arguments.length + ' arguments, expected ' + argCount); - } - var destructors = []; - var args = new Array(argCount + 1); - args[0] = fn; - for (var i = 0; i < argCount; ++i) { - args[i + 1] = argTypes[i].toWireType(destructors, arguments[i]); + var argsList = ""; + var argsListWired = ""; + for(var i = 0; i < argCount-2; ++i) { + argsList += (i!==0?", ":"")+"arg"+i; + argsListWired += (i!==0?", ":"")+"arg"+i+"Wired"; + } + + 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 rv = returnType.fromWireType(invoker.apply(null, args)); - runDestructors(destructors); - return rv; - }; -} + } -function __embind_register_tuple(tupleType, name, constructor, destructor) { - name = Pointer_stringify(name); - constructor = FUNCTION_TABLE[constructor]; - destructor = FUNCTION_TABLE[destructor]; + if (needsDestructorStack) { + invokerFnBody += + "var destructors = [];\n"; + } - var elements = []; + 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]]; - typeRegistry[tupleType] = { - name: name, - elements: elements, - fromWireType: function(ptr) { - var len = elements.length; - var rv = new Array(len); - for (var i = 0; i < len; ++i) { - rv[i] = elements[i].read(ptr); - } - destructor(ptr); - return rv; - }, - toWireType: function(destructors, o) { - var len = elements.length; - if (len !== o.length) { - throw new TypeError("Incorrect number of tuple elements"); - } - var ptr = constructor(); - for (var i = 0; i < len; ++i) { - elements[i].write(ptr, o[i]); + 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); } - destructors.push(destructor); - destructors.push(ptr); - return ptr; } - }; + } + + if (returns) { + invokerFnBody += "return retType.fromWireType(rv);\n"; + } + invokerFnBody += "}\n"; + + args1.push(invokerFnBody); + + var invokerFunction = new_(Function, args1).apply(null, args2); + return invokerFunction; } -function copyMemberPointer(memberPointer, memberPointerSize) { - var copy = _malloc(memberPointerSize); - if (!copy) { - throw new Error('Failed to allocate member pointer copy'); - } - _memcpy(copy, memberPointer, memberPointerSize); - return copy; +function __embind_register_function(name, argCount, rawArgTypesAddr, rawInvoker, fn) { + var argTypes = heap32VectorToArray(argCount, rawArgTypesAddr); + name = readLatin1String(name); + rawInvoker = FUNCTION_TABLE[rawInvoker]; + + exposePublicSymbol(name, function() { + throwUnboundTypeError('Cannot call ' + name + ' due to unbound types', argTypes); + }, argCount - 1); + + whenDependentTypesAreResolved([], argTypes, function(argTypes) { + 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 []; + }); +} + +var tupleRegistrations = {}; + +function __embind_register_tuple(rawType, name, rawConstructor, rawDestructor) { + tupleRegistrations[rawType] = { + name: readLatin1String(name), + rawConstructor: FUNCTION_TABLE[rawConstructor], + rawDestructor: FUNCTION_TABLE[rawDestructor], + elements: [], + }; } function __embind_register_tuple_element( - tupleType, - elementType, + rawTupleType, + getterReturnType, getter, + getterContext, + setterArgumentType, setter, - memberPointerSize, - memberPointer + setterContext ) { - tupleType = requireRegisteredType(tupleType, 'tuple'); - elementType = requireRegisteredType(elementType, "element " + tupleType.name + "[" + tupleType.elements.length + "]"); - getter = FUNCTION_TABLE[getter]; - setter = FUNCTION_TABLE[setter]; - memberPointer = copyMemberPointer(memberPointer, memberPointerSize); - - tupleType.elements.push({ - read: function(ptr) { - return elementType.fromWireType(getter(ptr, memberPointer)); - }, - write: function(ptr, o) { - var destructors = []; - setter(ptr, memberPointer, elementType.toWireType(destructors, o)); - runDestructors(destructors); - } + tupleRegistrations[rawTupleType].elements.push({ + getterReturnType: getterReturnType, + getter: FUNCTION_TABLE[getter], + getterContext: getterContext, + setterArgumentType: setterArgumentType, + setter: FUNCTION_TABLE[setter], + setterContext: setterContext, }); } -function __embind_register_tuple_element_accessor( - tupleType, - elementType, - staticGetter, - getterSize, - getter, - staticSetter, - setterSize, - setter -) { - tupleType = requireRegisteredType(tupleType, 'tuple'); - elementType = requireRegisteredType(elementType, "element " + tupleType.name + "[" + tupleType.elements.length + "]"); - staticGetter = FUNCTION_TABLE[staticGetter]; - getter = copyMemberPointer(getter, getterSize); - staticSetter = FUNCTION_TABLE[staticSetter]; - setter = copyMemberPointer(setter, setterSize); - - tupleType.elements.push({ - read: function(ptr) { - return elementType.fromWireType(staticGetter(ptr, HEAP32[getter >> 2])); - }, - write: function(ptr, o) { - var destructors = []; - staticSetter( - ptr, - HEAP32[setter >> 2], - elementType.toWireType(destructors, o)); - runDestructors(destructors); - } +function __embind_finalize_tuple(rawTupleType) { + var reg = tupleRegistrations[rawTupleType]; + delete tupleRegistrations[rawTupleType]; + var elements = reg.elements; + var elementsLength = elements.length; + var elementTypes = elements.map(function(elt) { return elt.getterReturnType; }). + concat(elements.map(function(elt) { return elt.setterArgumentType; })); + + var rawConstructor = reg.rawConstructor; + var rawDestructor = reg.rawDestructor; + + whenDependentTypesAreResolved([rawTupleType], elementTypes, function(elementTypes) { + elements.forEach(function(elt, i) { + var getterReturnType = elementTypes[i]; + var getter = elt.getter; + var getterContext = elt.getterContext; + var setterArgumentType = elementTypes[i + elementsLength]; + var setter = elt.setter; + var setterContext = elt.setterContext; + elt.read = function(ptr) { + return getterReturnType['fromWireType'](getter(getterContext, ptr)); + }; + elt.write = function(ptr, o) { + var destructors = []; + setter(setterContext, ptr, setterArgumentType['toWireType'](destructors, o)); + runDestructors(destructors); + }; + }); + + return [{ + name: reg.name, + 'fromWireType': function(ptr) { + var rv = new Array(elementsLength); + for (var i = 0; i < elementsLength; ++i) { + rv[i] = elements[i].read(ptr); + } + rawDestructor(ptr); + return rv; + }, + 'toWireType': function(destructors, o) { + if (elementsLength !== o.length) { + throw new TypeError("Incorrect number of tuple elements"); + } + var ptr = rawConstructor(); + for (var i = 0; i < elementsLength; ++i) { + elements[i].write(ptr, o[i]); + } + if (destructors !== null) { + destructors.push(rawDestructor, ptr); + } + return ptr; + }, + destructorFunction: rawDestructor, + }]; }); } +var structRegistrations = {}; + function __embind_register_struct( - structType, + rawType, name, - constructor, - destructor + rawConstructor, + rawDestructor ) { - name = Pointer_stringify(name); - constructor = FUNCTION_TABLE[constructor]; - destructor = FUNCTION_TABLE[destructor]; - - typeRegistry[structType] = { - fields: {}, - fromWireType: function(ptr) { - var fields = this.fields; - var rv = {}; - for (var i in fields) { - rv[i] = fields[i].read(ptr); - } - destructor(ptr); - return rv; - }, - toWireType: function(destructors, o) { - var fields = this.fields; - for (var fieldName in fields) { - if (!(fieldName in o)) { - throw new TypeError('Missing field'); - } - } - var ptr = constructor(); - for (var fieldName in fields) { - fields[fieldName].write(ptr, o[fieldName]); - } - destructors.push(destructor); - destructors.push(ptr); - return ptr; - } + structRegistrations[rawType] = { + name: readLatin1String(name), + rawConstructor: FUNCTION_TABLE[rawConstructor], + rawDestructor: FUNCTION_TABLE[rawDestructor], + fields: [], }; } function __embind_register_struct_field( structType, fieldName, - fieldType, + getterReturnType, getter, + getterContext, + setterArgumentType, setter, - memberPointerSize, - memberPointer + setterContext ) { - structType = requireRegisteredType(structType, 'struct'); - fieldName = Pointer_stringify(fieldName); - fieldType = requireRegisteredType(fieldType, 'field "' + structType.name + '.' + fieldName + '"'); - getter = FUNCTION_TABLE[getter]; - setter = FUNCTION_TABLE[setter]; - memberPointer = copyMemberPointer(memberPointer, memberPointerSize); + structRegistrations[structType].fields.push({ + fieldName: readLatin1String(fieldName), + getterReturnType: getterReturnType, + getter: FUNCTION_TABLE[getter], + getterContext: getterContext, + setterArgumentType: setterArgumentType, + setter: FUNCTION_TABLE[setter], + setterContext: setterContext, + }); +} + +function __embind_finalize_struct(structType) { + var reg = structRegistrations[structType]; + delete structRegistrations[structType]; + + var rawConstructor = reg.rawConstructor; + var rawDestructor = reg.rawDestructor; + var fieldRecords = reg.fields; + var fieldTypes = fieldRecords.map(function(field) { return field.getterReturnType; }). + concat(fieldRecords.map(function(field) { return field.setterArgumentType; })); + whenDependentTypesAreResolved([structType], fieldTypes, function(fieldTypes) { + var fields = {}; + fieldRecords.forEach(function(field, i) { + var fieldName = field.fieldName; + var getterReturnType = fieldTypes[i]; + var getter = field.getter; + var getterContext = field.getterContext; + var setterArgumentType = fieldTypes[i + fieldRecords.length]; + var setter = field.setter; + var setterContext = field.setterContext; + fields[fieldName] = { + read: function(ptr) { + return getterReturnType['fromWireType']( + getter(getterContext, ptr)); + }, + write: function(ptr, o) { + var destructors = []; + setter(setterContext, ptr, setterArgumentType['toWireType'](destructors, o)); + runDestructors(destructors); + } + }; + }); + + return [{ + name: reg.name, + 'fromWireType': function(ptr) { + var rv = {}; + for (var i in fields) { + rv[i] = fields[i].read(ptr); + } + rawDestructor(ptr); + return rv; + }, + '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) { + if (!(fieldName in o)) { + throw new TypeError('Missing field'); + } + } + var ptr = rawConstructor(); + for (fieldName in fields) { + fields[fieldName].write(ptr, o[fieldName]); + } + if (destructors !== null) { + destructors.push(rawDestructor, ptr); + } + return ptr; + }, + destructorFunction: rawDestructor, + }]; + }); +} + +var genericPointerToWireType = function(destructors, handle) { + if (handle === null) { + if (this.isReference) { + throwBindingError('null is not a valid ' + this.name); + } + + if (this.isSmartPointer) { + var ptr = this.rawConstructor(); + destructors.push(this.rawDestructor, ptr); + return ptr; + } else { + 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 (!this.isConst && handle.$$.ptrType.isConst) { + 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 { + throwBindingError('Cannot convert argument of type ' + (handle.$$.smartPtrType ? handle.$$.smartPtrType.name : handle.$$.ptrType.name) + ' to parameter type ' + this.name); + } + break; + + case 1: // INTRUSIVE + ptr = handle.$$.smartPtr; + break; + + case 2: // BY_EMVAL + if (handle.$$.smartPtrType === this) { + ptr = handle.$$.smartPtr; + } else { + var clonedHandle = handle.clone(); + ptr = this.rawShare( + ptr, + __emval_register(function() { + clonedHandle.delete(); + }) + ); + destructors.push(this.rawDestructor, ptr); + } + break; + + default: + throwBindingError('Unsupporting sharing policy'); + } + } + 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; + } - structType.fields[fieldName] = { - read: function(ptr) { - return fieldType.fromWireType(getter(ptr, memberPointer)); + 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); + } + return ptr; +}; + +RegisteredPointer.prototype.destructor = function(ptr) { + if (this.rawDestructor) { + this.rawDestructor(ptr); + } +}; + +RegisteredPointer.prototype['fromWireType'] = function(ptr) { + // ptr is a raw pointer (or a raw smartpointer) + + // rawPointer is a maybe-null raw pointer + var rawPointer = this.getPointee(ptr); + if (!rawPointer) { + this.destructor(ptr); + return null; + } + + function makeDefaultHandle() { + if (this.isSmartPointer) { + return makeClassHandle(this.registeredClass.instancePrototype, { + ptrType: this.pointeeType, + ptr: rawPointer, + smartPtrType: this, + smartPtr: ptr, + }); + } else { + return makeClassHandle(this.registeredClass.instancePrototype, { + ptrType: this, + ptr: ptr, + }); + } + } + + var actualType = this.registeredClass.getActualType(rawPointer); + var registeredPointerRecord = registeredPointers[actualType]; + if (!registeredPointerRecord) { + return makeDefaultHandle.call(this); + } + + var toType; + if (this.isConst) { + toType = registeredPointerRecord.constPointerType; + } else { + toType = registeredPointerRecord.pointerType; + } + var dp = downcastPointer( + rawPointer, + this.registeredClass, + toType.registeredClass); + if (dp === null) { + return makeDefaultHandle.call(this); + } + if (this.isSmartPointer) { + return makeClassHandle(toType.registeredClass.instancePrototype, { + ptrType: toType, + ptr: dp, + smartPtrType: this, + smartPtr: ptr, + }); + } else { + return makeClassHandle(toType.registeredClass.instancePrototype, { + ptrType: toType, + ptr: dp, + }); + } +}; + +function makeClassHandle(prototype, record) { + if (!record.ptrType || !record.ptr) { + throwInternalError('makeClassHandle requires ptr and ptrType'); + } + var hasSmartPtrType = !!record.smartPtrType; + var hasSmartPtr = !!record.smartPtr; + if (hasSmartPtrType !== hasSmartPtr) { + throwInternalError('Both smartPtrType and smartPtr must be specified'); + } + record.count = { value: 1 }; + return Object.create(prototype, { + $$: { + value: record, }, - write: function(ptr, o) { - var destructors = []; - setter(ptr, memberPointer, fieldType.toWireType(destructors, o)); - runDestructors(destructors); + }); +} + +// root of all pointer and smart pointer handles in embind +function ClassHandle() { +} + +function getInstanceTypeName(handle) { + return handle.$$.ptrType.registeredClass.name; +} + +ClassHandle.prototype.clone = function() { + if (!this.$$.ptr) { + throwBindingError(getInstanceTypeName(this) + ' instance already deleted'); + } + + var clone = Object.create(Object.getPrototypeOf(this), { + $$: { + value: shallowCopy(this.$$), } - }; + }); + + clone.$$.count.value += 1; + return clone; +}; + +function runDestructor(handle) { + var $$ = handle.$$; + if ($$.smartPtr) { + $$.smartPtrType.rawDestructor($$.smartPtr); + } else { + $$.ptrType.registeredClass.rawDestructor($$.ptr); + } +} + +ClassHandle.prototype['delete'] = function() { + if (!this.$$.ptr) { + throwBindingError(getInstanceTypeName(this) + ' instance already deleted'); + } + + this.$$.count.value -= 1; + if (0 === this.$$.count.value) { + runDestructor(this); + } + this.$$.smartPtr = undefined; + this.$$.ptr = undefined; +}; + +function RegisteredClass( + name, + constructor, + instancePrototype, + rawDestructor, + baseClass, + getActualType, + upcast, + downcast +) { + this.name = name; + this.constructor = constructor; + this.instancePrototype = instancePrototype; + this.rawDestructor = rawDestructor; + this.baseClass = baseClass; + this.getActualType = getActualType; + this.upcast = upcast; + this.downcast = downcast; +} + +function shallowCopy(o) { + var rv = {}; + for (var k in o) { + rv[k] = o[k]; + } + return rv; } function __embind_register_class( - classType, + rawType, + rawPointerType, + rawConstPointerType, + baseClassRawType, + getActualType, + upcast, + downcast, name, - destructor + rawDestructor ) { - name = Pointer_stringify(name); - destructor = FUNCTION_TABLE[destructor]; + name = readLatin1String(name); + rawDestructor = FUNCTION_TABLE[rawDestructor]; + getActualType = FUNCTION_TABLE[getActualType]; + upcast = FUNCTION_TABLE[upcast]; + downcast = FUNCTION_TABLE[downcast]; + var legalFunctionName = makeLegalFunctionName(name); - var Handle = createNamedFunction(name, function(ptr) { - this.count = {value: 1}; - this.ptr = ptr; + exposePublicSymbol(legalFunctionName, function() { + // this code cannot run if baseClassRawType is zero + throwUnboundTypeError('Cannot construct ' + name + ' due to unbound types', [baseClassRawType]); }); - Handle.prototype.clone = function() { - if (!this.ptr) { - throw new BindingError(classType.name + ' instance already deleted'); - } + whenDependentTypesAreResolved( + [rawType, rawPointerType, rawConstPointerType], + baseClassRawType ? [baseClassRawType] : [], + function(base) { + base = base[0]; + + var baseClass; + var basePrototype; + if (baseClassRawType) { + baseClass = base.registeredClass; + basePrototype = baseClass.instancePrototype; + } else { + basePrototype = ClassHandle.prototype; + } + + var constructor = createNamedFunction(legalFunctionName, function() { + if (Object.getPrototypeOf(this) !== instancePrototype) { + throw new BindingError("Use 'new' to construct " + name); + } + if (undefined === registeredClass.constructor_body) { + throw new BindingError(name + " has no accessible constructor"); + } + var body = registeredClass.constructor_body[arguments.length]; + if (undefined === body) { + throw new BindingError("Tried to invoke ctor of " + name + " with invalid number of parameters (" + arguments.length + ") - expected (" + Object.keys(registeredClass.constructor_body).toString() + ") parameters instead!"); + } + return body.apply(this, arguments); + }); + + var instancePrototype = Object.create(basePrototype, { + constructor: { value: constructor }, + }); + + constructor.prototype = instancePrototype; - var clone = Object.create(Handle.prototype); - clone.count = this.count; - clone.ptr = this.ptr; + var registeredClass = new RegisteredClass( + name, + constructor, + instancePrototype, + rawDestructor, + baseClass, + getActualType, + upcast, + downcast); + + var referenceConverter = new RegisteredPointer( + name, + registeredClass, + true, + false, + false); - clone.count.value += 1; - return clone; - }; + var pointerConverter = new RegisteredPointer( + name + '*', + registeredClass, + false, + false, + false); - Handle.prototype.move = function() { - var rv = this.clone(); - this.delete(); - return rv; - }; + var constPointerConverter = new RegisteredPointer( + name + ' const*', + registeredClass, + false, + true, + false); - Handle.prototype['delete'] = function() { - if (!this.ptr) { - throw new BindingError(classType.name + ' instance already deleted'); - } + registeredPointers[rawType] = { + pointerType: pointerConverter, + constPointerType: constPointerConverter + }; - this.count.value -= 1; - if (0 === this.count.value) { - destructor(this.ptr); - } - this.ptr = undefined; - }; + replacePublicSymbol(legalFunctionName, constructor); - var constructor = createNamedFunction(name, function() { - var body = constructor.body; - body.apply(this, arguments); - }); - constructor.prototype = Object.create(Handle.prototype); - - typeRegistry[classType] = { - name: name, - constructor: constructor, - Handle: Handle, - fromWireType: function(ptr) { - return new Handle(ptr); - }, - toWireType: function(destructors, o) { - return o.ptr; + return [referenceConverter, pointerConverter, constPointerConverter]; } - }; - - Module[name] = constructor; + ); } function __embind_register_class_constructor( - classType, + rawClassType, argCount, - argTypes, - constructor + rawArgTypesAddr, + invoker, + rawConstructor ) { - classType = requireRegisteredType(classType, 'class'); - var humanName = 'constructor ' + classType.name; - argTypes = requireArgumentTypes(argCount, argTypes, humanName); - constructor = FUNCTION_TABLE[constructor]; - - classType.constructor.body = function() { - if (arguments.length !== argCount) { - throw new BindingError('emscripten binding ' + humanName + ' called with ' + arguments.length + ' arguments, expected ' + argCount); + var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); + invoker = FUNCTION_TABLE[invoker]; + + whenDependentTypesAreResolved([], [rawClassType], function(classType) { + classType = classType[0]; + var humanName = 'constructor ' + classType.name; + + if (undefined === classType.registeredClass.constructor_body) { + classType.registeredClass.constructor_body = []; } - var destructors = []; - var args = new Array(argCount); - for (var i = 0; i < argCount; ++i) { - args[i] = argTypes[i].toWireType(destructors, arguments[i]); + if (undefined !== classType.registeredClass.constructor_body[argCount - 1]) { + throw new BindingError("Cannot register multiple constructors with identical number of parameters (" + (argCount-1) + ") for class '" + classType.name + "'! Overload resolution is currently only performed using the parameter count, not actual type info!"); } + classType.registeredClass.constructor_body[argCount - 1] = function() { + throwUnboundTypeError('Cannot construct ' + classType.name + ' due to unbound types', rawArgTypes); + }; - var ptr = constructor.apply(null, args); - runDestructors(destructors); - classType.Handle.call(this, ptr); - }; + whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { + classType.registeredClass.constructor_body[argCount - 1] = function() { + if (arguments.length !== argCount - 1) { + throwBindingError(humanName + ' called with ' + arguments.length + ' arguments, expected ' + (argCount-1)); + } + var destructors = []; + var args = new Array(argCount); + args[0] = rawConstructor; + for (var i = 1; i < argCount; ++i) { + args[i] = argTypes[i]['toWireType'](destructors, arguments[i - 1]); + } + + var ptr = invoker.apply(null, args); + runDestructors(destructors); + + return argTypes[0]['fromWireType'](ptr); + }; + return []; + }); + return []; + }); } -function __embind_register_class_method( - classType, +function downcastPointer(ptr, ptrClass, desiredClass) { + if (ptrClass === desiredClass) { + return ptr; + } + if (undefined === desiredClass.baseClass) { + return null; // no conversion + } + // O(depth) stack space used + return desiredClass.downcast( + downcastPointer(ptr, ptrClass, desiredClass.baseClass)); +} + +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; + } + return ptr; +} + +function validateThis(this_, classType, humanName) { + if (!(this_ instanceof Object)) { + throwBindingError(humanName + ' with invalid "this": ' + this_); + } + if (!(this_ instanceof classType.registeredClass.constructor)) { + throwBindingError(humanName + ' incompatible with "this" of type ' + this_.constructor.name); + } + if (!this_.$$.ptr) { + throwBindingError('cannot call emscripten binding method ' + humanName + ' on deleted object'); + } + + // todo: kill this + return upcastPointer( + this_.$$.ptr, + this_.$$.ptrType.registeredClass, + classType.registeredClass); +} + +function __embind_register_class_function( + rawClassType, methodName, - returnType, argCount, - argTypes, - invoker, - memberFunctionSize, - memberFunction + rawArgTypesAddr, // [ReturnType, ThisType, Args...] + rawInvoker, + context ) { - classType = requireRegisteredType(classType, 'class'); - methodName = Pointer_stringify(methodName); - var humanName = classType.name + '.' + methodName; - returnType = requireRegisteredType(returnType, 'method ' + humanName + ' return value'); - argTypes = requireArgumentTypes(argCount, argTypes, 'method ' + humanName); - invoker = FUNCTION_TABLE[invoker]; - memberFunction = copyMemberPointer(memberFunction, memberFunctionSize); + var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); + methodName = readLatin1String(methodName); + rawInvoker = FUNCTION_TABLE[rawInvoker]; - classType.Handle.prototype[methodName] = function() { - if (!this.ptr) { - throw new BindingError('cannot call emscripten binding method ' + humanName + ' on deleted object'); - } - if (arguments.length !== argCount) { - throw new BindingError('emscripten binding method ' + humanName + ' called with ' + arguments.length + ' arguments, expected ' + argCount); + whenDependentTypesAreResolved([], [rawClassType], function(classType) { + classType = classType[0]; + var humanName = classType.name + '.' + methodName; + + var unboundTypesHandler = function() { + throwUnboundTypeError('Cannot call ' + humanName + ' due to unbound types', rawArgTypes); + }; + + var proto = classType.registeredClass.instancePrototype; + var method = proto[methodName]; + if (undefined === method || (undefined === method.overloadTable && method.className !== classType.name && method.argCount === argCount-2)) { + // This is the first overload to be registered, OR we are replacing a function in the base class with a function in the derived class. + unboundTypesHandler.argCount = argCount-2; + unboundTypesHandler.className = classType.name; + proto[methodName] = unboundTypesHandler; + } else { + // There was an existing function with the same name registered. Set up a function overload routing table. + ensureOverloadTable(proto, methodName, humanName); + proto[methodName].overloadTable[argCount-2] = unboundTypesHandler; } + + whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { - var destructors = []; - var args = new Array(argCount + 2); - args[0] = this.ptr; - args[1] = memberFunction; - for (var i = 0; i < argCount; ++i) { - args[i + 2] = argTypes[i].toWireType(destructors, arguments[i]); - } + var memberFunction = craftInvokerFunction(humanName, argTypes, classType, rawInvoker, context); - var rv = returnType.fromWireType(invoker.apply(null, args)); - runDestructors(destructors); - return rv; - }; + // 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. + if (undefined === proto[methodName].overloadTable) { + proto[methodName] = memberFunction; + } else { + proto[methodName].overloadTable[argCount-2] = memberFunction; + } + + return []; + }); + return []; + }); } -function __embind_register_class_classmethod( - classType, +function __embind_register_class_class_function( + rawClassType, methodName, - returnType, argCount, - argTypes, - method + rawArgTypesAddr, + rawInvoker, + fn ) { - classType = requireRegisteredType(classType, 'class'); - methodName = Pointer_stringify(methodName); - var humanName = classType.name + '.' + methodName; - returnType = requireRegisteredType(returnType, 'classmethod ' + humanName + ' return value'); - argTypes = requireArgumentTypes(argCount, argTypes, 'classmethod ' + humanName); - method = FUNCTION_TABLE[method]; - - classType.constructor[methodName] = function() { - if (arguments.length !== argCount) { - throw new BindingError('emscripten binding method ' + humanName + ' called with ' + arguments.length + ' arguments, expected ' + argCount); - } + var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); + methodName = readLatin1String(methodName); + rawInvoker = FUNCTION_TABLE[rawInvoker]; + whenDependentTypesAreResolved([], [rawClassType], function(classType) { + classType = classType[0]; + var humanName = classType.name + '.' + methodName; + + var unboundTypesHandler = function() { + throwUnboundTypeError('Cannot call ' + humanName + ' due to unbound types', rawArgTypes); + }; - var destructors = []; - var args = new Array(argCount); - for (var i = 0; i < argCount; ++i) { - args[i] = argTypes[i].toWireType(destructors, arguments[i]); + var proto = classType.registeredClass.constructor; + if (undefined === proto[methodName]) { + // This is the first function to be registered with this name. + unboundTypesHandler.argCount = argCount-1; + proto[methodName] = unboundTypesHandler; + } else { + // There was an existing function with the same name registered. Set up a function overload routing table. + ensureOverloadTable(proto, methodName, humanName); + proto[methodName].overloadTable[argCount-1] = unboundTypesHandler; } - var rv = returnType.fromWireType(method.apply(null, args)); - runDestructors(destructors); - return rv; - }; + whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { + // Replace the initial unbound-types-handler stub with the proper function. If multiple overloads are registered, + // the function handlers go into an overload table. + var invokerArgsArray = [argTypes[0] /* return value */, null /* no class 'this'*/].concat(argTypes.slice(1) /* actual params */); + var func = craftInvokerFunction(humanName, invokerArgsArray, null /* no class 'this'*/, rawInvoker, fn); + if (undefined === proto[methodName].overloadTable) { + proto[methodName] = func; + } else { + proto[methodName].overloadTable[argCount-1] = func; + } + return []; + }); + return []; + }); } -function __embind_register_class_field( +function __embind_register_class_property( classType, fieldName, - fieldType, + getterReturnType, getter, + getterContext, + setterArgumentType, setter, - memberPointerSize, - memberPointer + setterContext ) { - classType = requireRegisteredType(classType, 'class'); - fieldName = Pointer_stringify(fieldName); - var humanName = classType.name + '.' + fieldName; - fieldType = requireRegisteredType(fieldType, 'field ' + humanName); + fieldName = readLatin1String(fieldName); getter = FUNCTION_TABLE[getter]; - setter = FUNCTION_TABLE[setter]; - memberPointer = copyMemberPointer(memberPointer, memberPointerSize); - Object.defineProperty(classType.Handle.prototype, fieldName, { - get: function() { - if (!this.ptr) { - throw new BindingError('cannot access emscripten binding field ' + humanName + ' on deleted object'); - } - return fieldType.fromWireType(getter(this.ptr, memberPointer)); - }, - set: function(v) { - if (!this.ptr) { - throw new BindingError('cannot modify emscripten binding field ' + humanName + ' on deleted object'); + whenDependentTypesAreResolved([], [classType], function(classType) { + classType = classType[0]; + var humanName = classType.name + '.' + fieldName; + var desc = { + get: function() { + 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( + [], + (setter ? [getterReturnType, setterArgumentType] : [getterReturnType]), + function(types) { + var getterReturnType = types[0]; + var desc = { + get: function() { + var ptr = validateThis(this, classType, humanName + ' getter'); + return getterReturnType['fromWireType'](getter(getterContext, ptr)); + }, + 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(setterContext, ptr, setterArgumentType['toWireType'](destructors, v)); + runDestructors(destructors); + }; } - var destructors = []; - setter(this.ptr, memberPointer, fieldType.toWireType(destructors, v)); - runDestructors(destructors); - }, - enumerable: true + + Object.defineProperty(classType.registeredClass.instancePrototype, fieldName, desc); + return []; + }); + + return []; + }); +} + +var char_0 = '0'.charCodeAt(0); +var char_9 = '9'.charCodeAt(0); +function makeLegalFunctionName(name) { + name = name.replace(/[^a-zA-Z0-9_]/g, '$'); + var f = name.charCodeAt(0); + if (f >= char_0 && f <= char_9) { + return '_' + name; + } else { + return name; + } +} + +function __embind_register_smart_ptr( + rawType, + rawPointeeType, + name, + sharingPolicy, + rawGetPointee, + rawConstructor, + rawShare, + rawDestructor +) { + name = readLatin1String(name); + rawGetPointee = FUNCTION_TABLE[rawGetPointee]; + rawConstructor = FUNCTION_TABLE[rawConstructor]; + rawShare = FUNCTION_TABLE[rawShare]; + rawDestructor = FUNCTION_TABLE[rawDestructor]; + + whenDependentTypesAreResolved([rawType], [rawPointeeType], function(pointeeType) { + pointeeType = pointeeType[0]; + + var registeredPointer = new RegisteredPointer( + name, + pointeeType.registeredClass, + false, + false, + // smart pointer properties + true, + pointeeType, + sharingPolicy, + rawGetPointee, + rawConstructor, + rawShare, + rawDestructor); + return [registeredPointer]; }); } function __embind_register_enum( - enumType, + rawType, name ) { - name = Pointer_stringify(name); + name = readLatin1String(name); - function Enum() { + function constructor() { } - Enum.values = {}; + constructor.values = {}; - typeRegistry[enumType] = { + registerType(rawType, { name: name, - constructor: Enum, - toWireType: function(destructors, c) { - return c.value; + constructor: constructor, + 'fromWireType': function(c) { + return this.constructor.values[c]; }, - fromWireType: function(c) { - return Enum.values[c]; + 'toWireType': function(destructors, c) { + return c.value; }, - }; - - Module[name] = Enum; + destructorFunction: null, + }); + exposePublicSymbol(name, constructor); } function __embind_register_enum_value( - enumType, + rawEnumType, name, enumValue ) { - enumType = requireRegisteredType(enumType, 'enum'); - name = Pointer_stringify(name); + var enumType = requireRegisteredType(rawEnumType, 'enum'); + name = readLatin1String(name); var Enum = enumType.constructor; @@ -596,25 +1562,11 @@ function __embind_register_enum_value( Enum[name] = Value; } -function __embind_register_interface( - interfaceType, - name, - constructor, - destructor -) { - name = Pointer_stringify(name); - constructor = FUNCTION_TABLE[constructor]; - destructor = FUNCTION_TABLE[destructor]; - - typeRegistry[interfaceType] = { - name: name, - toWireType: function(destructors, o) { - var handle = __emval_register(o); - var ptr = constructor(handle); - destructors.push(destructor); - destructors.push(ptr); - return ptr; - }, - }; +function __embind_register_constant(name, type, value) { + name = readLatin1String(name); + whenDependentTypesAreResolved([], [type], function(type) { + type = type[0]; + Module[name] = type['fromWireType'](value); + return []; + }); } - diff --git a/src/embind/emval.js b/src/embind/emval.js index 9574ab37..c02ffa92 100644..100755 --- a/src/embind/emval.js +++ b/src/embind/emval.js @@ -1,34 +1,74 @@ /*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 /** @expose */ Module.count_emval_handles = function() { - return _emval_handle_array.length; + var count = 0; + for (var i = 1; i < _emval_handle_array.length; ++i) { + if (_emval_handle_array[i] !== undefined) { + ++count; + } + } + return count; +}; + +/** @expose */ +Module.get_first_emval = function() { + for (var i = 1; i < _emval_handle_array.length; ++i) { + if (_emval_handle_array[i] !== undefined) { + return _emval_handle_array[i]; + } + } + return null; }; // 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() : _emval_handle_array.length; + _emval_handle_array[handle] = {refcount: 1, value: value}; return handle; } 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); @@ -40,72 +80,153 @@ function __emval_decref(handle) { } } +function __emval_new_array() { + return __emval_register([]); +} + function __emval_new_object() { return __emval_register({}); } -function __emval_new_long(value) { - return __emval_register(value); +function __emval_undefined() { + return __emval_register(undefined); } -function __emval_new_cstring(str) { - return __emval_register(Pointer_stringify(str)); +function __emval_null() { + return __emval_register(null); } -function __emval_get_property(handle, k) { - k = Pointer_stringify(k); - return __emval_register(_emval_handle_array[handle].value[k]); +function __emval_new_cstring(v) { + return __emval_register(getStringOrSymbol(v)); } -function __emval_get_property_by_long(handle, k) { - return __emval_register(_emval_handle_array[handle].value[k]); +function __emval_take_value(type, v) { + type = requireRegisteredType(type, '_emval_take_value'); + v = type.fromWireType(v); + return __emval_register(v); } -function __emval_get_property_by_unsigned_long(handle, k) { - return __emval_register(_emval_handle_array[handle].value[k]); +var __newers = {}; // arity -> function + +function __emval_new(handle, argCount, argTypes) { + requireHandle(handle); + + var args = parseParameters( + argCount, + argTypes, + Array.prototype.slice.call(arguments, 3)); + + // Alas, we are forced to use operator new until WebKit enables + // constructing typed arrays without new. + // In WebKit, Uint8Array(10) throws an error. + // In every other browser, it's identical to new Uint8Array(10). + + var newer = __newers[argCount]; + if (!newer) { + var parameters = new Array(argCount); + for (var i = 0; i < argCount; ++i) { + parameters[i] = 'a' + i; + } + /*jshint evil:true*/ + newer = __newers[argCount] = new Function( + ['c'].concat(parameters), + "return new c(" + parameters.join(',') + ");"); + } + + var constructor = _emval_handle_array[handle].value; + var obj = newer.apply(undefined, [constructor].concat(args)); +/* + // implement what amounts to operator new + function dummy(){} + dummy.prototype = constructor.prototype; + var obj = new constructor; + var rv = constructor.apply(obj, args); + if (typeof rv === 'object') { + obj = rv; + } +*/ + return __emval_register(obj); +} + +// appease jshint (technically this code uses eval) +var global = (function(){return Function;})()('return this')(); + +function __emval_get_global(name) { + name = getStringOrSymbol(name); + return __emval_register(global[name]); } -function __emval_set_property(handle, k, value) { - k = Pointer_stringify(k); - _emval_handle_array[handle].value[k] = _emval_handle_array[value].value; +function __emval_get_module_property(name) { + name = getStringOrSymbol(name); + return __emval_register(Module[name]); } -function __emval_set_property_by_int(handle, k, value) { - _emval_handle_array[handle].value[k] = _emval_handle_array[value].value; +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 return returnType.toWireType(destructors, _emval_handle_array[handle].value); } -function __emval_call(handle, argCount, argTypes) { - var args = Array.prototype.slice.call(arguments, 3); - var fn = _emval_handle_array[handle].value; +function parseParameters(argCount, argTypes, argWireTypes) { var a = new Array(argCount); for (var i = 0; i < argCount; ++i) { var argType = requireRegisteredType( HEAP32[(argTypes >> 2) + i], "parameter " + i); - a[i] = argType.fromWireType(args[i]); + a[i] = argType.fromWireType(argWireTypes[i]); } - var rv = fn.apply(undefined, a); + return a; +} + +function __emval_call(handle, argCount, argTypes) { + requireHandle(handle); + var fn = _emval_handle_array[handle].value; + var args = parseParameters( + argCount, + argTypes, + Array.prototype.slice.call(arguments, 3)); + var rv = fn.apply(undefined, args); return __emval_register(rv); } function __emval_call_method(handle, name, argCount, argTypes) { - name = Pointer_stringify(name); - var args = Array.prototype.slice.call(arguments, 4); + requireHandle(handle); + name = getStringOrSymbol(name); + + var args = parseParameters( + argCount, + argTypes, + Array.prototype.slice.call(arguments, 4)); var obj = _emval_handle_array[handle].value; - var a = new Array(argCount); - for (var i = 0; i < argCount; ++i) { - var argType = requireRegisteredType( - HEAP32[(argTypes >> 2) + i], - "parameter " + i); - a[i] = argType.fromWireType(args[i]); - } - var rv = obj[name].apply(obj, a); + var rv = obj[name].apply(obj, args); return __emval_register(rv); } + +function __emval_call_void_method(handle, name, argCount, argTypes) { + requireHandle(handle); + name = getStringOrSymbol(name); + + var args = parseParameters( + argCount, + argTypes, + Array.prototype.slice.call(arguments, 4)); + 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; +} |