diff options
Diffstat (limited to 'src/embind/embind.js')
-rw-r--r-- | src/embind/embind.js | 1921 |
1 files changed, 1489 insertions, 432 deletions
diff --git a/src/embind/embind.js b/src/embind/embind.js index d40d6ca2..91386c69 100644 --- a/src/embind/embind.js +++ b/src/embind/embind.js @@ -1,14 +1,142 @@ /*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) { + name = makeLegalFunctionName(name); /*jshint evil:true*/ return new Function( "body", "return function " + name + "() {\n" + + " \"use strict\";" + " return body.apply(this, arguments);\n" + "};\n" )(body); @@ -23,136 +151,332 @@ 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 validateType(type, name) { - if (!type) { - throw new BindingError('type "' + name + '" must have a positive integer typeid pointer'); + 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]); + } } - if (undefined !== typeRegistry[type]) { - throw new BindingError('cannot register type "' + name + '" twice'); + + 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); } } -function __embind_register_void(voidType, name) { - name = Pointer_stringify(name); - validateType(voidType, name); - typeRegistry[voidType] = { +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 heap32VectorToArray(count, firstElement) { + var array = []; + for (var i = 0; i < count; i++) { + array.push(HEAP32[(firstElement >> 2) + i]); + } + 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(rawType, name) { + name = readLatin1String(name); + registerType(rawType, { name: name, - fromWireType: function() { + 'fromWireType': function() { return undefined; - } - }; + }, + 'toWireType': function(destructors, o) { + // TODO: assert if anything else is given? + 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) { - if (typeof value !== "number") { - throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' + name); + 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" && typeof value !== "boolean") { + 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) { - if (typeof value !== "number") { - throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' + name); - } + 'fromWireType': function(value) { return value; }, - fromWireType: function(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" && typeof value !== "boolean") { + throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' + this.name); + } 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; - } - }; -} - -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; + }, + 'toWireType': function(destructors, value) { + return __emval_register(value); + }, + destructorFunction: null, // This type does not need a destructor + }); } -function requireRegisteredType(type, humanName) { - var impl = typeRegistry[type]; - if (undefined === impl) { - throw new BindingError(humanName + " has unknown type: " + typeName(type)); - } - return impl; -} +function __embind_register_memory_view(rawType, name) { + var typeMapping = [ + Int8Array, + Uint8Array, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float32Array, + Float64Array, + ]; -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); - } - return argTypeImpls; + name = readLatin1String(name); + registerType(rawType, { + name: name, + 'fromWireType': function(handle) { + var type = HEAPU32[handle >> 2]; + var size = HEAPU32[(handle >> 2) + 1]; // in elements + var data = HEAPU32[(handle >> 2) + 2]; // byte offset into emscripten heap + var TA = typeMapping[type]; + return new TA(HEAP8.buffer, data, size); + }, + }); } function runDestructors(destructors) { @@ -163,428 +487,1175 @@ function runDestructors(destructors) { } } -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); +// 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"); + } - 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 rv = returnType.fromWireType(invoker.apply(null, args)); - runDestructors(destructors); - return rv; - }; + /* + * 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 __embind_register_tuple(tupleType, name, constructor, destructor) { - name = Pointer_stringify(name); - constructor = FUNCTION_TABLE[constructor]; - destructor = FUNCTION_TABLE[destructor]; +// 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; - var elements = []; + if (argCount < 2) { + throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!"); + } + + var isClassMethodFunc = (argTypes[1] !== null && classType !== null); - 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 && !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"; + } + + 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; + } + } + + 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); } - 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 for " + reg.name + ": expected=" + elementsLength + ", actual=" + o.length); + } + 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); - - structType.fields[fieldName] = { |