blob: 675db1b2ac0b9503e81fd9a2c20f592e7f88eb67 [file] [log] [blame]
// Copyright 2012 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.
/*global Module:true, Runtime*/
/*global HEAP32*/
/*global newFunc*/
/*global createNamedFunction*/
/*global readLatin1String, stringToUTF8*/
/*global requireRegisteredType, throwBindingError, runDestructors*/
/*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. */
// -- jshint doesn't understand library syntax, so we need to mark the symbols exposed here
/*global getStringOrSymbol, emval_freelist, emval_handles, Emval, __emval_unregister, count_emval_handles, emval_symbols, __emval_decref*/
/*global emval_addMethodCaller, emval_methodCallers, addToLibrary, global, emval_lookupTypes, makeLegalFunctionName*/
/*global emval_get_global*/
// Number of handles reserved for non-use (0) or common values w/o refcount.
{{{
globalThis.EMVAL_RESERVED_HANDLES = 5;
globalThis.EMVAL_LAST_RESERVED_HANDLE = globalThis.EMVAL_RESERVED_HANDLES * 2 - 1;
null;
}}}
var LibraryEmVal = {
// Stack of handles available for reuse.
$emval_freelist: [],
// Array of alternating pairs (value, refcount).
$emval_handles: [],
$emval_symbols: {}, // address -> string
$init_emval__deps: ['$count_emval_handles', '$emval_handles'],
$init_emval__postset: 'init_emval();',
$init_emval: () => {
// reserve 0 and some special values. These never get de-allocated.
emval_handles.push(
0, 1,
undefined, 1,
null, 1,
true, 1,
false, 1,
);
#if ASSERTIONS
assert(emval_handles.length === {{{ EMVAL_RESERVED_HANDLES }}} * 2);
#endif
Module['count_emval_handles'] = count_emval_handles;
},
$count_emval_handles__deps: ['$emval_freelist', '$emval_handles'],
$count_emval_handles: () => {
return emval_handles.length / 2 - {{{ EMVAL_RESERVED_HANDLES }}} - emval_freelist.length;
},
_emval_register_symbol__deps: ['$emval_symbols', '$readLatin1String'],
_emval_register_symbol: (address) => {
emval_symbols[address] = readLatin1String(address);
},
$getStringOrSymbol__deps: ['$emval_symbols', '$readLatin1String'],
$getStringOrSymbol: (address) => {
var symbol = emval_symbols[address];
if (symbol === undefined) {
return readLatin1String(address);
}
return symbol;
},
$Emval__deps: ['$emval_freelist', '$emval_handles', '$throwBindingError', '$init_emval'],
$Emval: {
toValue: (handle) => {
if (!handle) {
throwBindingError('Cannot use deleted val. handle = ' + handle);
}
#if ASSERTIONS
// handle 2 is supposed to be `undefined`.
assert(handle === 2 || emval_handles[handle] !== undefined && handle % 2 === 0, `invalid handle: ${handle}`);
#endif
return emval_handles[handle];
},
toHandle: (value) => {
switch (value) {
case undefined: return 2;
case null: return 4;
case true: return 6;
case false: return 8;
default:{
const handle = emval_freelist.pop() || emval_handles.length;
emval_handles[handle] = value;
emval_handles[handle + 1] = 1;
return handle;
}
}
}
},
_emval_incref__deps: ['$emval_handles'],
_emval_incref: (handle) => {
if (handle > {{{ EMVAL_LAST_RESERVED_HANDLE }}}) {
emval_handles[handle + 1] += 1;
}
},
_emval_decref__deps: ['$emval_freelist', '$emval_handles'],
_emval_decref: (handle) => {
if (handle > {{{ EMVAL_LAST_RESERVED_HANDLE }}} && 0 === --emval_handles[handle + 1]) {
#if ASSERTIONS
assert(emval_handles[handle] !== undefined, `Decref for unallocated handle.`);
#endif
emval_handles[handle] = undefined;
emval_freelist.push(handle);
}
},
_emval_run_destructors__deps: ['_emval_decref', '$Emval', '$runDestructors'],
_emval_run_destructors: (handle) => {
var destructors = Emval.toValue(handle);
runDestructors(destructors);
__emval_decref(handle);
},
_emval_new_array__deps: ['$Emval'],
_emval_new_array: () => Emval.toHandle([]),
_emval_new_array_from_memory_view__deps: ['$Emval'],
_emval_new_array_from_memory_view: (view) => {
view = Emval.toValue(view);
// using for..loop is faster than Array.from
var a = new Array(view.length);
for (var i = 0; i < view.length; i++) a[i] = view[i];
return Emval.toHandle(a);
},
_emval_new_object__deps: ['$Emval'],
_emval_new_object: () => Emval.toHandle({}),
_emval_new_cstring__deps: ['$getStringOrSymbol', '$Emval'],
_emval_new_cstring: (v) => Emval.toHandle(getStringOrSymbol(v)),
_emval_new_u8string__deps: ['$Emval'],
_emval_new_u8string: (v) => Emval.toHandle(UTF8ToString(v)),
_emval_new_u16string__deps: ['$Emval'],
_emval_new_u16string: (v) => Emval.toHandle(UTF16ToString(v)),
_emval_take_value__deps: ['$Emval', '$requireRegisteredType'],
_emval_take_value: (type, arg) => {
type = requireRegisteredType(type, '_emval_take_value');
var v = type['readValueFromPointer'](arg);
return Emval.toHandle(v);
},
#if !DYNAMIC_EXECUTION
$emval_get_global: () => {
if (typeof globalThis == 'object') {
return globalThis;
}
function testGlobal(obj) {
obj['$$$embind_global$$$'] = obj;
var success = typeof $$$embind_global$$$ == 'object' && obj['$$$embind_global$$$'] == obj;
if (!success) {
delete obj['$$$embind_global$$$'];
}
return success;
}
if (typeof $$$embind_global$$$ == 'object') {
return $$$embind_global$$$;
}
if (typeof global == 'object' && testGlobal(global)) {
$$$embind_global$$$ = global;
} else if (typeof self == 'object' && testGlobal(self)) {
$$$embind_global$$$ = self; // This works for both "window" and "self" (Web Workers) global objects
}
if (typeof $$$embind_global$$$ == 'object') {
return $$$embind_global$$$;
}
throw Error('unable to get global object.');
},
#else
// appease jshint (technically this code uses eval)
$emval_get_global: () => {
if (typeof globalThis == 'object') {
return globalThis;
}
return (function(){
return Function;
})()('return this')();
},
#endif
_emval_get_global__deps: ['$Emval', '$getStringOrSymbol', '$emval_get_global'],
_emval_get_global: (name) => {
if (name===0) {
return Emval.toHandle(emval_get_global());
} else {
name = getStringOrSymbol(name);
return Emval.toHandle(emval_get_global()[name]);
}
},
_emval_get_module_property__deps: ['$getStringOrSymbol', '$Emval'],
_emval_get_module_property: (name) => {
name = getStringOrSymbol(name);
return Emval.toHandle(Module[name]);
},
_emval_get_property__deps: ['$Emval'],
_emval_get_property: (handle, key) => {
handle = Emval.toValue(handle);
key = Emval.toValue(key);
return Emval.toHandle(handle[key]);
},
_emval_set_property__deps: ['$Emval'],
_emval_set_property: (handle, key, value) => {
handle = Emval.toValue(handle);
key = Emval.toValue(key);
value = Emval.toValue(value);
handle[key] = value;
},
$emval_returnValue__deps: ['$Emval'],
$emval_returnValue: (returnType, destructorsRef, handle) => {
var destructors = [];
var result = returnType['toWireType'](destructors, handle);
if (destructors.length) {
// void, primitives and any other types w/o destructors don't need to allocate a handle
{{{ makeSetValue('destructorsRef', '0', 'Emval.toHandle(destructors)', '*') }}};
}
return result;
},
_emval_as__deps: ['$Emval', '$requireRegisteredType', '$emval_returnValue'],
_emval_as: (handle, returnType, destructorsRef) => {
handle = Emval.toValue(handle);
returnType = requireRegisteredType(returnType, 'emval::as');
return emval_returnValue(returnType, destructorsRef, handle);
},
_emval_as_int64__deps: ['$Emval', '$requireRegisteredType'],
_emval_as_int64: (handle, returnType) => {
handle = Emval.toValue(handle);
returnType = requireRegisteredType(returnType, 'emval::as');
return returnType['toWireType'](null, handle);
},
_emval_as_uint64__deps: ['$Emval', '$requireRegisteredType'],
_emval_as_uint64: (handle, returnType) => {
handle = Emval.toValue(handle);
returnType = requireRegisteredType(returnType, 'emval::as');
return returnType['toWireType'](null, handle);
},
_emval_equals__deps: ['$Emval'],
_emval_equals: (first, second) => {
first = Emval.toValue(first);
second = Emval.toValue(second);
return first == second;
},
_emval_strictly_equals__deps: ['$Emval'],
_emval_strictly_equals: (first, second) => {
first = Emval.toValue(first);
second = Emval.toValue(second);
return first === second;
},
_emval_greater_than__deps: ['$Emval'],
_emval_greater_than: (first, second) => {
first = Emval.toValue(first);
second = Emval.toValue(second);
return first > second;
},
_emval_less_than__deps: ['$Emval'],
_emval_less_than: (first, second) => {
first = Emval.toValue(first);
second = Emval.toValue(second);
return first < second;
},
_emval_not__deps: ['$Emval'],
_emval_not: (object) => {
object = Emval.toValue(object);
return !object;
},
_emval_call__deps: ['$emval_methodCallers', '$Emval'],
_emval_call: (caller, handle, destructorsRef, args) => {
caller = emval_methodCallers[caller];
handle = Emval.toValue(handle);
return caller(null, handle, destructorsRef, args);
},
$emval_lookupTypes__deps: ['$requireRegisteredType'],
$emval_lookupTypes: (argCount, argTypes) => {
var a = new Array(argCount);
for (var i = 0; i < argCount; ++i) {
a[i] = requireRegisteredType({{{ makeGetValue('argTypes', 'i * ' + POINTER_SIZE, '*') }}},
"parameter " + i);
}
return a;
},
// Leave id 0 undefined. It's not a big deal, but might be confusing
// to have null be a valid method caller.
$emval_methodCallers: [undefined],
$emval_addMethodCaller__deps: ['$emval_methodCallers'],
$emval_addMethodCaller: (caller) => {
var id = emval_methodCallers.length;
emval_methodCallers.push(caller);
return id;
},
#if MIN_CHROME_VERSION < 49 || MIN_FIREFOX_VERSION < 42 || MIN_SAFARI_VERSION < 100101
$reflectConstruct: null,
$reflectConstruct__postset: `
if (typeof Reflect != 'undefined') {
reflectConstruct = Reflect.construct;
} else {
reflectConstruct = function(target, args) {
// limited polyfill for Reflect.construct that handles variadic args and native objects, but not new.target
return new (target.bind.apply(target, [null].concat(args)))();
};
}
`,
#else
$reflectConstruct: 'Reflect.construct',
#endif
_emval_get_method_caller__deps: [
'$emval_addMethodCaller', '$emval_lookupTypes',
'$createNamedFunction',
'$reflectConstruct', '$emval_returnValue',
#if DYNAMIC_EXECUTION
'$newFunc',
#endif
],
_emval_get_method_caller: (argCount, argTypes, kind) => {
var types = emval_lookupTypes(argCount, argTypes);
var retType = types.shift();
argCount--; // remove the shifted off return type
#if !DYNAMIC_EXECUTION
var argN = new Array(argCount);
var invokerFunction = (obj, func, destructorsRef, args) => {
var offset = 0;
for (var i = 0; i < argCount; ++i) {
argN[i] = types[i]['readValueFromPointer'](args + offset);
offset += types[i].argPackAdvance;
}
var rv = kind === /* CONSTRUCTOR */ 1 ? reflectConstruct(func, argN) : func.apply(obj, argN);
return emval_returnValue(retType, destructorsRef, rv);
};
#else
var functionBody =
`return function (obj, func, destructorsRef, args) {\n`;
var offset = 0;
var argsList = []; // 'obj?, arg0, arg1, arg2, ... , argN'
if (kind === /* FUNCTION */ 0) {
argsList.push("obj");
}
var params = ["retType"];
var args = [retType];
for (var i = 0; i < argCount; ++i) {
argsList.push("arg" + i);
params.push("argType" + i);
args.push(types[i]);
functionBody +=
` var arg${i} = argType${i}.readValueFromPointer(args${offset ? "+" + offset : ""});\n`;
offset += types[i].argPackAdvance;
}
var invoker = kind === /* CONSTRUCTOR */ 1 ? 'new func' : 'func.call';
functionBody +=
` var rv = ${invoker}(${argsList.join(", ")});\n`;
if (!retType.isVoid) {
params.push("emval_returnValue");
args.push(emval_returnValue);
functionBody +=
" return emval_returnValue(retType, destructorsRef, rv);\n";
}
functionBody +=
"};\n";
params.push(functionBody);
var invokerFunction = newFunc(Function, params)(...args);
#endif
var functionName = `methodCaller<(${types.map(t => t.name).join(', ')}) => ${retType.name}>`;
return emval_addMethodCaller(createNamedFunction(functionName, invokerFunction));
},
_emval_call_method__deps: ['$getStringOrSymbol', '$emval_methodCallers', '$Emval'],
_emval_call_method: (caller, objHandle, methodName, destructorsRef, args) => {
caller = emval_methodCallers[caller];
objHandle = Emval.toValue(objHandle);
methodName = getStringOrSymbol(methodName);
return caller(objHandle, objHandle[methodName], destructorsRef, args);
},
_emval_typeof__deps: ['$Emval'],
_emval_typeof: (handle) => {
handle = Emval.toValue(handle);
return Emval.toHandle(typeof handle);
},
_emval_instanceof__deps: ['$Emval'],
_emval_instanceof: (object, constructor) => {
object = Emval.toValue(object);
constructor = Emval.toValue(constructor);
return object instanceof constructor;
},
_emval_is_number__deps: ['$Emval'],
_emval_is_number: (handle) => {
handle = Emval.toValue(handle);
return typeof handle == 'number';
},
_emval_is_string__deps: ['$Emval'],
_emval_is_string: (handle) => {
handle = Emval.toValue(handle);
return typeof handle == 'string';
},
_emval_in__deps: ['$Emval'],
_emval_in: (item, object) => {
item = Emval.toValue(item);
object = Emval.toValue(object);
return item in object;
},
_emval_delete__deps: ['$Emval'],
_emval_delete: (object, property) => {
object = Emval.toValue(object);
property = Emval.toValue(property);
return delete object[property];
},
_emval_throw__deps: ['$Emval'],
_emval_throw: (object) => {
object = Emval.toValue(object);
throw object;
},
#if ASYNCIFY
_emval_await__deps: ['$Emval', '$Asyncify'],
_emval_await__async: true,
_emval_await: (promise) => {
return Asyncify.handleAsync(async () => {
var value = await Emval.toValue(promise);
return Emval.toHandle(value);
});
},
#endif
_emval_iter_begin__deps: ['$Emval'],
_emval_iter_begin: (iterable) => {
iterable = Emval.toValue(iterable);
return Emval.toHandle(iterable[Symbol.iterator]());
},
_emval_iter_next__deps: ['$Emval'],
_emval_iter_next: (iterator) => {
iterator = Emval.toValue(iterator);
var result = iterator.next();
return result.done ? 0 : Emval.toHandle(result.value);
},
_emval_coro_suspend__deps: ['$Emval', '_emval_coro_resume'],
_emval_coro_suspend: async (promiseHandle, awaiterPtr) => {
var result = await Emval.toValue(promiseHandle);
__emval_coro_resume(awaiterPtr, Emval.toHandle(result));
},
_emval_coro_make_promise__deps: ['$Emval', '__cxa_rethrow'],
_emval_coro_make_promise: (resolveHandlePtr, rejectHandlePtr) => {
return Emval.toHandle(new Promise((resolve, reject) => {
const rejectWithCurrentException = () => {
try {
// Use __cxa_rethrow which already has mechanism for generating
// user-friendly error message and stacktrace from C++ exception
// if EXCEPTION_STACK_TRACES is enabled and numeric exception
// with metadata optimised out otherwise.
___cxa_rethrow();
} catch (e) {
// But catch it so that it rejects the promise instead of throwing
// in an unpredictable place during async execution.
reject(e);
}
};
{{{ makeSetValue('resolveHandlePtr', '0', 'Emval.toHandle(resolve)', '*') }}};
{{{ makeSetValue('rejectHandlePtr', '0', 'Emval.toHandle(rejectWithCurrentException)', '*') }}};
}));
},
};
addToLibrary(LibraryEmVal);