| // Copyright 2023 The Emscripten Authors. All rights reserved. | |
| // Emscripten is available under two separate licenses, the MIT license and the | |
| // University of Illinois/NCSA Open Source License. Both these licenses can be | |
| // found in the LICENSE file. | |
| var LibraryEmbindShared = { | |
| $InternalError: class extends Error { | |
| constructor(message) { | |
| super(message); | |
| this.name = 'InternalError'; | |
| } | |
| }, | |
| $BindingError: class extends Error { | |
| constructor(message) { | |
| super(message); | |
| this.name = 'BindingError'; | |
| } | |
| }, | |
| $throwInternalError__deps: ['$InternalError'], | |
| $throwInternalError: (message) => { throw new InternalError(message); }, | |
| $throwBindingError__deps: ['$BindingError'], | |
| $throwBindingError: (message) => { throw new BindingError(message); }, | |
| // typeID -> { toWireType: ..., fromWireType: ... } | |
| $registeredTypes: {}, | |
| // typeID -> [callback] | |
| $awaitingDependencies: {}, | |
| // typeID -> [dependentTypes] | |
| $typeDependencies: {}, | |
| $tupleRegistrations: {}, | |
| $structRegistrations: {}, | |
| $sharedRegisterType__deps: [ | |
| '$awaitingDependencies', '$registeredTypes', | |
| '$typeDependencies', '$throwBindingError' ], | |
| $sharedRegisterType__docs: '/** @param {Object=} options */', | |
| $sharedRegisterType: function(rawType, registeredInstance, options = {}) { | |
| var name = registeredInstance.name; | |
| if (!rawType) { | |
| throwBindingError(`type "${name}" must have a positive integer typeid pointer`); | |
| } | |
| if (registeredTypes.hasOwnProperty(rawType)) { | |
| if (options.ignoreDuplicateRegistrations) { | |
| return; | |
| } else { | |
| 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((cb) => cb()); | |
| } | |
| }, | |
| $whenDependentTypesAreResolved__deps: [ | |
| '$awaitingDependencies', '$registeredTypes', | |
| '$typeDependencies', '$throwInternalError'], | |
| $whenDependentTypesAreResolved: (myTypes, dependentTypes, getTypeConverters) => { | |
| myTypes.forEach((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; | |
| for (let [i, dt] of dependentTypes.entries()) { | |
| if (registeredTypes.hasOwnProperty(dt)) { | |
| typeConverters[i] = registeredTypes[dt]; | |
| } else { | |
| unregisteredTypes.push(dt); | |
| if (!awaitingDependencies.hasOwnProperty(dt)) { | |
| awaitingDependencies[dt] = []; | |
| } | |
| awaitingDependencies[dt].push(() => { | |
| typeConverters[i] = registeredTypes[dt]; | |
| ++registered; | |
| if (registered === unregisteredTypes.length) { | |
| onComplete(typeConverters); | |
| } | |
| }); | |
| } | |
| } | |
| if (0 === unregisteredTypes.length) { | |
| onComplete(typeConverters); | |
| } | |
| }, | |
| $getTypeName__deps: ['$AsciiToString', '__getTypeName', 'free'], | |
| $getTypeName: (type) => { | |
| var ptr = ___getTypeName(type); | |
| var rv = AsciiToString(ptr); | |
| _free(ptr); | |
| return rv; | |
| }, | |
| $getFunctionName__deps: [], | |
| $getFunctionName: (signature) => { | |
| signature = signature.trim(); | |
| const argsIndex = signature.indexOf('('); | |
| if (argsIndex === -1) return signature; | |
| #if ASSERTIONS | |
| assert(signature.endsWith(')'), 'Parentheses for argument names should match.'); | |
| #endif | |
| return signature.slice(0, argsIndex); | |
| }, | |
| $getFunctionArgsName__deps: [], | |
| $getFunctionArgsName: (signature) => { | |
| signature = signature.trim(); | |
| const argsIndex = signature.indexOf('('); | |
| if (argsIndex == -1) return; // Return undefined to mean we don't have any argument names | |
| #if ASSERTIONS | |
| assert(signature.endsWith(')'), 'Parentheses for argument names should match.'); | |
| #endif | |
| return signature.slice(argsIndex + 1, -1).replaceAll(' ', '').split(',').filter(n => n.length); | |
| }, | |
| $heap32VectorToArray: (count, firstElement) => { | |
| var array = []; | |
| for (var i = 0; i < count; i++) { | |
| // TODO(https://github.com/emscripten-core/emscripten/issues/17310): | |
| // Find a way to hoist the `>> 2` or `>> 3` out of this loop. | |
| array.push({{{ makeGetValue('firstElement', `i * ${POINTER_SIZE}`, '*') }}}); | |
| } | |
| return array; | |
| }, | |
| $requireRegisteredType__deps: [ | |
| '$registeredTypes', '$getTypeName', '$throwBindingError'], | |
| $requireRegisteredType: (rawType, humanName) => { | |
| var impl = registeredTypes[rawType]; | |
| if (undefined === impl) { | |
| throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`); | |
| } | |
| return impl; | |
| }, | |
| $usesDestructorStack(argTypes) { | |
| // Skip return value at index 0 - it's not deleted here. | |
| for (var i = 1; i < argTypes.length; ++i) { | |
| // The type does not define a destructor function - must use dynamic stack | |
| if (argTypes[i] !== null && argTypes[i].destructorFunction === undefined) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| }, | |
| // Trivially constructible/destructible value types (argStackAlloc) place | |
| // their argument temporaries on the wasm stack when the invoker brackets | |
| // the call in stackSave/stackRestore, skipping the per-call heap temp and | |
| // destructor bookkeeping entirely. | |
| $argsUseStackAlloc(argTypes) { | |
| // Skip return value at index 0 - only arguments stack-allocate. | |
| for (var i = 1; i < argTypes.length; ++i) { | |
| if (argTypes[i] !== null && argTypes[i].argStackAlloc) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| }, | |
| // Many of the JS invoker functions are generic and can be reused for multiple | |
| // function bindings. This function needs to match createJsInvoker and create | |
| // a unique signature for any inputs that will create different invoker | |
| // function outputs. | |
| $createJsInvokerSignature(argTypes, isClassMethodFunc, returns, isAsync) { | |
| const signature = [ | |
| isClassMethodFunc ? 't' : 'f', | |
| returns ? 't' : 'f', | |
| isAsync ? 't' : 'f' | |
| ]; | |
| for (let i = isClassMethodFunc ? 1 : 2; i < argTypes.length; ++i) { | |
| const arg = argTypes[i]; | |
| let destructorSig = ''; | |
| if (arg.argStackAlloc) { | |
| // Stack-allocated trivial value type: needs no destructor, but the | |
| // invoker must bracket the call in a stack frame. | |
| destructorSig = 's'; | |
| } else if (arg.destructorFunction === undefined) { | |
| destructorSig = 'u'; | |
| } else if (arg.destructorFunction === null) { | |
| destructorSig = 'n'; | |
| } else { | |
| destructorSig = 't'; | |
| } | |
| signature.push(destructorSig); | |
| } | |
| return signature.join(''); | |
| }, | |
| $checkArgCount(numArgs, minArgs, maxArgs, humanName, throwBindingError) { | |
| if (numArgs < minArgs || numArgs > maxArgs) { | |
| var argCountMessage = minArgs == maxArgs ? minArgs : `${minArgs} to ${maxArgs}`; | |
| throwBindingError(`function ${humanName} called with ${numArgs} arguments, expected ${argCountMessage}`); | |
| } | |
| }, | |
| $getEnumValueType(rawValueType) { | |
| // This must match the values of enum_value_type in wire.h | |
| return !rawValueType ? 'object' : (rawValueType === 1 ? 'number' : 'string'); | |
| }, | |
| $getRequiredArgCount(argTypes) { | |
| var requiredArgCount = argTypes.length - 2; | |
| for (var i = argTypes.length - 1; i >= 2; --i) { | |
| if (!argTypes[i].optional) { | |
| break; | |
| } | |
| requiredArgCount--; | |
| } | |
| return requiredArgCount; | |
| }, | |
| $createJsInvoker__deps: ['$usesDestructorStack', '$argsUseStackAlloc', | |
| #if ASSERTIONS | |
| '$checkArgCount', | |
| #endif | |
| ], | |
| $createJsInvoker(argTypes, isClassMethodFunc, returns, isAsync) { | |
| var needsDestructorStack = usesDestructorStack(argTypes); | |
| var argsNeedStack = argsUseStackAlloc(argTypes); | |
| #if ASYNCIFY == 1 | |
| // Any call may suspend under Asyncify, and destructors run deferred in | |
| // onDone, after a stack frame would already be gone. | |
| var useStackFrame = false; | |
| #else | |
| // JSPI-async invokers resume after the frame would be gone, so they | |
| // defer through the destructors array instead. | |
| var useStackFrame = argsNeedStack && !isAsync && !needsDestructorStack; | |
| #endif | |
| if (argsNeedStack && !useStackFrame) { | |
| // A stack-allocating type must never see a null destructors argument | |
| // without a bracketing frame; route it through the destructors array | |
| // (it heap-allocates on that path). | |
| needsDestructorStack = true; | |
| } | |
| var argCount = argTypes.length - 2; | |
| var argsList = []; | |
| var argsListWired = ['fn']; | |
| if (isClassMethodFunc) { | |
| argsListWired.push('thisWired'); | |
| } | |
| for (var i = 0; i < argCount; ++i) { | |
| argsList.push(`arg${i}`) | |
| argsListWired.push(`arg${i}Wired`) | |
| } | |
| argsList = argsList.join() | |
| argsListWired = argsListWired.join() | |
| var invokerFnBody = `return function (${argsList}) {\n`; | |
| #if ASSERTIONS | |
| invokerFnBody += 'checkArgCount(arguments.length, minArgs, maxArgs, humanName, throwBindingError);\n'; | |
| #endif | |
| #if EMSCRIPTEN_TRACING | |
| invokerFnBody += `Module.emscripten_trace_enter_context('embind::' + humanName );\n`; | |
| #endif | |
| if (needsDestructorStack) { | |
| invokerFnBody += 'var destructors = [];\n'; | |
| } | |
| if (useStackFrame) { | |
| invokerFnBody += 'var sp = stackSave();\n'; | |
| } | |
| var dtorStack = needsDestructorStack ? 'destructors' : 'null'; | |
| var args1 = ['humanName', 'throwBindingError', 'invoker', 'fn', 'runDestructors', 'fromRetWire', 'toClassParamWire']; | |
| if (useStackFrame) { | |
| args1.push('stackSave', 'stackRestore'); | |
| } | |
| #if EMSCRIPTEN_TRACING | |
| args1.push('Module'); | |
| #endif | |
| if (isClassMethodFunc) { | |
| invokerFnBody += `var thisWired = toClassParamWire(${dtorStack}, this);\n`; | |
| } | |
| for (var i = 0; i < argCount; ++i) { | |
| var argName = `toArg${i}Wire`; | |
| invokerFnBody += `var arg${i}Wired = ${argName}(${dtorStack}, arg${i});\n`; | |
| args1.push(argName); | |
| } | |
| invokerFnBody += (returns || isAsync ? 'var rv = ' : '') + `invoker(${argsListWired});\n`; | |
| if (useStackFrame) { | |
| // The callee has consumed the stack-allocated argument temporaries; | |
| // release the frame before any post-call work. Not restored on an | |
| // exception path, matching the rest of emscripten (see the exceptions | |
| // docs on stackSave/stackRestore at JS catch sites). | |
| invokerFnBody += 'stackRestore(sp);\n'; | |
| } | |
| var returnVal = returns ? 'rv' : ''; | |
| #if ASYNCIFY == 1 | |
| args1.push('Asyncify'); | |
| #endif | |
| #if ASYNCIFY | |
| invokerFnBody += `function onDone(${returnVal}) {\n`; | |
| #endif | |
| 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});\n`; | |
| args1.push(`${paramName}_dtor`); | |
| } | |
| } | |
| } | |
| if (returns) { | |
| invokerFnBody += 'var ret = fromRetWire(rv);\n' + | |
| #if EMSCRIPTEN_TRACING | |
| 'Module.emscripten_trace_exit_context();\n' + | |
| #endif | |
| 'return ret;\n'; | |
| } else { | |
| #if EMSCRIPTEN_TRACING | |
| invokerFnBody += 'Module.emscripten_trace_exit_context();\n'; | |
| #endif | |
| } | |
| #if ASYNCIFY == 1 | |
| invokerFnBody += '}\n'; | |
| invokerFnBody += `return Asyncify.currData ? Asyncify.whenDone().then(onDone) : onDone(${returnVal});\n` | |
| #elif ASYNCIFY == 2 | |
| invokerFnBody += '}\n'; | |
| invokerFnBody += 'return ' + (isAsync ? 'rv.then(onDone)' : `onDone(${returnVal})`) + ';'; | |
| #endif | |
| invokerFnBody += '}\n'; | |
| #if ASSERTIONS | |
| args1.push('checkArgCount', 'minArgs', 'maxArgs'); | |
| invokerFnBody = `if (arguments.length !== ${args1.length}){ throw new Error(humanName + "Expected ${args1.length} closure arguments " + arguments.length + " given."); }\n${invokerFnBody}`; | |
| #endif | |
| return new Function(args1, invokerFnBody); | |
| } | |
| }; | |
| addToLibrary(LibraryEmbindShared); |