diff options
-rw-r--r-- | src/embind/embind.js | 154 | ||||
-rw-r--r-- | src/embind/emval.js | 156 | ||||
-rw-r--r-- | system/include/emscripten/bind.h | 15 | ||||
-rw-r--r-- | system/include/emscripten/val.h | 200 | ||||
-rw-r--r-- | system/include/emscripten/wire.h | 2 | ||||
-rw-r--r-- | system/lib/embind/bind.cpp | 41 | ||||
-rw-r--r-- | tests/embind/embind.test.js | 76 | ||||
-rw-r--r-- | tests/embind/embind_test.cpp | 59 |
8 files changed, 524 insertions, 179 deletions
diff --git a/src/embind/embind.js b/src/embind/embind.js index f0cd0c74..6ec07cd9 100644 --- a/src/embind/embind.js +++ b/src/embind/embind.js @@ -1,6 +1,6 @@ /*global Module*/ /*global _malloc, _free, _memcpy*/ -/*global FUNCTION_TABLE, HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32*/ +/*global FUNCTION_TABLE, HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAPF64*/ /*global readLatin1String*/ /*global __emval_register, _emval_handle_array, __emval_decref*/ /*global ___getTypeName*/ @@ -35,7 +35,7 @@ function throwUnboundTypeError(message, types) { seen[type] = true; } types.forEach(visit); - + throw new UnboundTypeError(message + ': ' + unboundTypes.map(getTypeName).join([', '])); } @@ -55,7 +55,7 @@ function ensureOverloadTable(proto, methodName, humanName) { // 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 @@ -72,7 +72,7 @@ function exposePublicSymbol(name, value, numArguments) { 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); @@ -164,6 +164,10 @@ var typeDependencies = {}; var registeredPointers = {}; function registerType(rawType, registeredInstance) { + if (!('argPackAdvance' in registeredInstance)) { + throw new TypeError('registerType registeredInstance requires argPackAdvance'); + } + var name = registeredInstance.name; if (!rawType) { throwBindingError('type "' + name + '" must have a positive integer typeid pointer'); @@ -268,6 +272,7 @@ function __embind_register_void(rawType, name) { name = readLatin1String(name); registerType(rawType, { name: name, + 'argPackAdvance': 0, 'fromWireType': function() { return undefined; }, @@ -278,7 +283,9 @@ function __embind_register_void(rawType, name) { }); } -function __embind_register_bool(rawType, name, trueValue, falseValue) { +function __embind_register_bool(rawType, name, size, trueValue, falseValue) { + var shift = getShiftFromSize(size); + name = readLatin1String(name); registerType(rawType, { name: name, @@ -290,21 +297,80 @@ function __embind_register_bool(rawType, name, trueValue, falseValue) { 'toWireType': function(destructors, o) { return o ? trueValue : falseValue; }, + 'argPackAdvance': 8, + 'readValueFromPointer': function(pointer) { + // TODO: if heap is fixed (like in asm.js) this could be executed outside + var heap; + if (size === 1) { + heap = HEAP8; + } else if (size === 2) { + heap = HEAP16; + } else if (size === 4) { + heap = HEAP32; + } else { + throw new TypeError("Unknown boolean type size: " + name); + } + return this['fromWireType'](heap[pointer >> shift]); + }, destructorFunction: null, // This type does not need a destructor }); } +function getShiftFromSize(size) { + switch (size) { + case 1: return 0; + case 2: return 1; + case 4: return 2; + case 8: return 3; + default: + throw new TypeError('Unknown type size: ' + size); + } +} + +function integerReadValueFromPointer(name, shift, signed) { + switch (shift) { + case 0: return function(pointer) { + var heap = signed ? HEAP8 : HEAPU8; + return this['fromWireType'](heap[pointer]); + }; + case 1: return function(pointer) { + var heap = signed ? HEAP16 : HEAPU16; + return this['fromWireType'](heap[pointer >> 1]); + }; + case 2: return function(pointer) { + var heap = signed ? HEAP32 : HEAPU32; + return this['fromWireType'](heap[pointer >> 2]); + }; + default: + throw new TypeError("Unknown integer type: " + name); + } +} + +function floatReadValueFromPointer(name, shift) { + switch (shift) { + case 2: return function(pointer) { + return this['fromWireType'](HEAPF32[pointer >> 2]); + }; + case 3: return function(pointer) { + return this['fromWireType'](HEAPF64[pointer >> 3]); + }; + default: + throw new TypeError("Unknown float type: " + name); + } +} + // 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) { +function __embind_register_integer(primitiveType, name, size, 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; } + + var shift = getShiftFromSize(size); + registerType(primitiveType, { name: name, - minRange: minRange, - maxRange: maxRange, 'fromWireType': function(value) { return value; }, @@ -319,11 +385,16 @@ function __embind_register_integer(primitiveType, name, minRange, maxRange) { } return value | 0; }, + 'argPackAdvance': 8, + 'readValueFromPointer': integerReadValueFromPointer(name, shift, minRange !== 0), destructorFunction: null, // This type does not need a destructor }); } -function __embind_register_float(rawType, name) { + + +function __embind_register_float(rawType, name, size) { + var shift = getShiftFromSize(size); name = readLatin1String(name); registerType(rawType, { name: name, @@ -338,10 +409,17 @@ function __embind_register_float(rawType, name) { } return value; }, + 'argPackAdvance': 8, + 'readValueFromPointer': floatReadValueFromPointer(name, shift), destructorFunction: null, // This type does not need a destructor }); } +// For types whose wire types are 32-bit pointers. +function simpleReadValueFromPointer(pointer) { + return this['fromWireType'](HEAPU32[pointer >> 2]); +} + function __embind_register_std_string(rawType, name) { name = readLatin1String(name); registerType(rawType, { @@ -394,6 +472,8 @@ function __embind_register_std_string(rawType, name) { } return ptr; }, + 'argPackAdvance': 8, + 'readValueFromPointer': simpleReadValueFromPointer, destructorFunction: function(ptr) { _free(ptr); }, }); } @@ -434,6 +514,8 @@ function __embind_register_std_wstring(rawType, charSize, name) { } return ptr; }, + 'argPackAdvance': 8, + 'readValueFromPointer': simpleReadValueFromPointer, destructorFunction: function(ptr) { _free(ptr); }, }); } @@ -450,6 +532,8 @@ function __embind_register_emval(rawType, name) { 'toWireType': function(destructors, value) { return __emval_register(value); }, + 'argPackAdvance': 8, + 'readValueFromPointer': simpleReadValueFromPointer, destructorFunction: null, // This type does not need a destructor }); } @@ -463,7 +547,7 @@ function __embind_register_memory_view(rawType, name) { Int32Array, Uint32Array, Float32Array, - Float64Array, + Float64Array, ]; name = readLatin1String(name); @@ -476,6 +560,10 @@ function __embind_register_memory_view(rawType, name) { var TA = typeMapping[type]; return new TA(HEAP8.buffer, data, size); }, + 'argPackAdvance': 16, + 'readValueFromPointer': function(ptr) { + return this['fromWireType'](ptr); + }, }); } @@ -531,7 +619,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp if (argCount < 2) { throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!"); } - + var isClassMethodFunc = (argTypes[1] !== null && classType !== null); if (!isClassMethodFunc && !FUNCTION_TABLE[cppTargetFunc]) { @@ -560,7 +648,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp // 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; @@ -595,7 +683,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp invokerFnBody += (returns?"var rv = ":"") + "invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n"; - + if (needsDestructorStack) { invokerFnBody += "runDestructors(destructors);\n"; } else { @@ -608,7 +696,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp } } } - + if (returns) { invokerFnBody += "return retType.fromWireType(rv);\n"; } @@ -676,7 +764,7 @@ function __embind_finalize_value_array(rawTupleType) { var rawConstructor = reg.rawConstructor; var rawDestructor = reg.rawDestructor; - + whenDependentTypesAreResolved([rawTupleType], elementTypes, function(elementTypes) { elements.forEach(function(elt, i) { var getterReturnType = elementTypes[i]; @@ -718,6 +806,8 @@ function __embind_finalize_value_array(rawTupleType) { } return ptr; }, + 'argPackAdvance': 8, + 'readValueFromPointer': simpleReadValueFromPointer, destructorFunction: rawDestructor, }]; }); @@ -819,6 +909,8 @@ function __embind_finalize_value_object(structType) { } return ptr; }, + 'argPackAdvance': 8, + 'readValueFromPointer': simpleReadValueFromPointer, destructorFunction: rawDestructor, }]; }); @@ -860,7 +952,7 @@ var genericPointerToWireType = function(destructors, handle) { if (undefined === handle.$$.smartPtr) { throwBindingError('Passing raw pointer to smart pointer is illegal'); } - + switch (this.sharingPolicy) { case 0: // NONE // no upcasting @@ -870,11 +962,11 @@ var genericPointerToWireType = function(destructors, handle) { 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; @@ -891,7 +983,7 @@ var genericPointerToWireType = function(destructors, handle) { } } break; - + default: throwBindingError('Unsupporting sharing policy'); } @@ -985,7 +1077,7 @@ function RegisteredPointer( 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 + // TODO: Create an alternative mechanism that allows removing the use of var destructors = []; array in // craftInvokerFunction altogether. } } @@ -1003,6 +1095,9 @@ RegisteredPointer.prototype.destructor = function(ptr) { } }; +RegisteredPointer.prototype['argPackAdvance'] = 8; +RegisteredPointer.prototype['readValueFromPointer'] = simpleReadValueFromPointer; + RegisteredPointer.prototype['fromWireType'] = function(ptr) { // ptr is a raw pointer (or a raw smartpointer) @@ -1110,7 +1205,7 @@ ClassHandle.prototype['isAliasOf'] = function(other) { right = rightClass.upcast(right); rightClass = rightClass.baseClass; } - + return leftClass === rightClass && left === right; }; @@ -1195,7 +1290,7 @@ Module['setDelayFunction'] = function setDelayFunction(fn) { delayFunction(flushPendingDeletes); } }; - + function RegisteredClass( name, constructor, @@ -1298,7 +1393,7 @@ function __embind_register_class( true, false, false); - + var pointerConverter = new RegisteredPointer( name + '*', registeredClass, @@ -1360,10 +1455,10 @@ function __embind_register_class_constructor( 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 []; @@ -1447,7 +1542,7 @@ function __embind_register_class_function( } whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { - + var memberFunction = craftInvokerFunction(humanName, argTypes, classType, rawInvoker, context); // Replace the initial unbound-handler-stub function with the appropriate member function, now that all types @@ -1627,8 +1722,11 @@ function __embind_register_smart_ptr( function __embind_register_enum( rawType, - name + name, + size, + isSigned ) { + var shift = getShiftFromSize(size); name = readLatin1String(name); function constructor() { @@ -1644,6 +1742,8 @@ function __embind_register_enum( 'toWireType': function(destructors, c) { return c.value; }, + 'argPackAdvance': 8, + 'readValueFromPointer': integerReadValueFromPointer(name, shift, isSigned), destructorFunction: null, }); exposePublicSymbol(name, constructor); diff --git a/src/embind/emval.js b/src/embind/emval.js index 039f1d61..4007701a 100644 --- a/src/embind/emval.js +++ b/src/embind/emval.js @@ -55,6 +55,7 @@ function requireHandle(handle) { if (!handle) { throwBindingError('Cannot use deleted val. handle = ' + handle); } + return _emval_handle_array[handle].value; } function __emval_register(value) { @@ -105,9 +106,9 @@ function __emval_new_cstring(v) { return __emval_register(getStringOrSymbol(v)); } -function __emval_take_value(type, v) { +function __emval_take_value(type, argv) { type = requireRegisteredType(type, '_emval_take_value'); - v = type['fromWireType'](v); + var v = type['readValueFromPointer'](argv); return __emval_register(v); } @@ -116,70 +117,51 @@ var __newers = {}; // arity -> function function craftEmvalAllocator(argCount) { /*This function returns a new function that looks like this: - function emval_allocator_3(handle, argTypes, arg0Wired, arg1Wired, arg2Wired) { + function emval_allocator_3(constructor, argTypes, args) { var argType0 = requireRegisteredType(HEAP32[(argTypes >> 2)], "parameter 0"); - var arg0 = argType0.fromWireType(arg0Wired); + var arg0 = argType0.readValueFromPointer(args); var argType1 = requireRegisteredType(HEAP32[(argTypes >> 2) + 1], "parameter 1"); - var arg1 = argType1.fromWireType(arg1Wired); + var arg1 = argType1.readValueFromPointer(args + 8); var argType2 = requireRegisteredType(HEAP32[(argTypes >> 2) + 2], "parameter 2"); - var arg2 = argType2.fromWireType(arg2Wired); - var constructor = _emval_handle_array[handle].value; - var emval = new constructor(arg0, arg1, arg2); - return emval; + var arg2 = argType2.readValueFromPointer(args + 16); + var obj = new constructor(arg0, arg1, arg2); + return __emval_register(obj); } */ - var args1 = ["requireRegisteredType", "HEAP32", "_emval_handle_array", "__emval_register"]; - var args2 = [requireRegisteredType, HEAP32, _emval_handle_array, __emval_register]; - var argsList = ""; - var argsListWired = ""; for(var i = 0; i < argCount; ++i) { argsList += (i!==0?", ":"")+"arg"+i; // 'arg0, arg1, ..., argn' - argsListWired += ", arg"+i+"Wired"; // ', arg0Wired, arg1Wired, ..., argnWired' } - var invokerFnBody = - "return function emval_allocator_"+argCount+"(handle, argTypes " + argsListWired + ") {\n"; + var functionBody = + "return function emval_allocator_"+argCount+"(constructor, argTypes, args) {\n"; for(var i = 0; i < argCount; ++i) { - invokerFnBody += + functionBody += "var argType"+i+" = requireRegisteredType(HEAP32[(argTypes >> 2) + "+i+"], \"parameter "+i+"\");\n" + - "var arg"+i+" = argType"+i+".fromWireType(arg"+i+"Wired);\n"; + "var arg"+i+" = argType"+i+".readValueFromPointer(args);\n" + + "args += argType"+i+".argPackAdvance;\n"; } - invokerFnBody += - "var constructor = _emval_handle_array[handle].value;\n" + + functionBody += "var obj = new constructor("+argsList+");\n" + "return __emval_register(obj);\n" + "}\n"; - args1.push(invokerFnBody); - var invokerFunction = new_(Function, args1).apply(null, args2); - return invokerFunction; + /*jshint evil:true*/ + return (new Function("requireRegisteredType", "HEAP32", "__emval_register", functionBody))( + requireRegisteredType, HEAP32, __emval_register); } -function __emval_new(handle, argCount, argTypes) { - requireHandle(handle); - +function __emval_new(handle, argCount, argTypes, args) { + handle = requireHandle(handle); + var newer = __newers[argCount]; if (!newer) { newer = craftEmvalAllocator(argCount); __newers[argCount] = newer; } - if (argCount === 0) { - return newer(handle, argTypes); - } else if (argCount === 1) { - return newer(handle, argTypes, arguments[3]); - } else if (argCount === 2) { - return newer(handle, argTypes, arguments[3], arguments[4]); - } else if (argCount === 3) { - return newer(handle, argTypes, arguments[3], arguments[4], arguments[5]); - } else if (argCount === 4) { - return newer(handle, argTypes, arguments[3], arguments[4], arguments[5], arguments[6]); - } else { - // This is a slow path! (.apply and .splice are slow), so a few specializations are present above. - return newer.apply(null, arguments.splice(1)); - } + return newer(handle, argTypes, args); } // appease jshint (technically this code uses eval) @@ -196,46 +178,39 @@ function __emval_get_module_property(name) { } function __emval_get_property(handle, key) { - requireHandle(handle); - return __emval_register(_emval_handle_array[handle].value[_emval_handle_array[key].value]); + handle = requireHandle(handle); + key = requireHandle(key); + return __emval_register(handle[key]); } function __emval_set_property(handle, key, value) { - requireHandle(handle); - _emval_handle_array[handle].value[_emval_handle_array[key].value] = _emval_handle_array[value].value; + handle = requireHandle(handle); + key = requireHandle(key); + value = requireHandle(value); + handle[key] = value; } function __emval_as(handle, returnType, destructorsRef) { - requireHandle(handle); + handle = requireHandle(handle); returnType = requireRegisteredType(returnType, 'emval::as'); var destructors = []; var rd = __emval_register(destructors); HEAP32[destructorsRef >> 2] = rd; - return returnType['toWireType'](destructors, _emval_handle_array[handle].value); + return returnType['toWireType'](destructors, handle); } -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'](argWireTypes[i]); - } - return a; -} - -function __emval_call(handle, argCount, argTypes) { - requireHandle(handle); +function __emval_call(handle, argCount, argTypes, argv) { + handle = requireHandle(handle); var types = lookupTypes(argCount, argTypes); var args = new Array(argCount); for (var i = 0; i < argCount; ++i) { - args[i] = types[i]['fromWireType'](arguments[3 + i]); + var type = types[i]; + args[i] = type['readValueFromPointer'](argv); + argv += type.argPackAdvance; } - var fn = _emval_handle_array[handle].value; - var rv = fn.apply(undefined, args); + var rv = handle.apply(undefined, args); return __emval_register(rv); } @@ -255,44 +230,59 @@ function allocateDestructors(destructorsRef) { return destructors; } +// Leave id 0 undefined. It's not a big deal, but might be confusing +// to have null be a valid method caller. +var methodCallers = [undefined]; + +function addMethodCaller(caller) { + var id = methodCallers.length; + methodCallers.push(caller); + return id; +} + function __emval_get_method_caller(argCount, argTypes) { var types = lookupTypes(argCount, argTypes); var retType = types[0]; var signatureName = retType.name + "_$" + types.slice(1).map(function (t) { return t.name; }).join("_") + "$"; - var args1 = ["addFunction", "createNamedFunction", "requireHandle", "getStringOrSymbol", "_emval_handle_array", "retType", "allocateDestructors"]; - var args2 = [Runtime.addFunction, createNamedFunction, requireHandle, getStringOrSymbol, _emval_handle_array, retType, allocateDestructors]; + var params = ["retType"]; + var args = [retType]; var argsList = ""; // 'arg0, arg1, arg2, ... , argN' - var argsListWired = ""; // 'arg0Wired, ..., argNWired' for (var i = 0; i < argCount - 1; ++i) { argsList += (i !== 0 ? ", " : "") + "arg" + i; - argsListWired += ", arg" + i + "Wired"; - args1.push("argType" + i); - args2.push(types[1 + i]); + params.push("argType" + i); + args.push(types[1 + i]); } - var invokerFnBody = - "return addFunction(createNamedFunction('" + signatureName + "', function (handle, name, destructorsRef" + argsListWired + ") {\n" + - " requireHandle(handle);\n" + - " name = getStringOrSymbol(name);\n"; + var functionBody = + "return function (handle, name, destructors, args) {\n"; for (var i = 0; i < argCount - 1; ++i) { - invokerFnBody += " var arg" + i + " = argType" + i + ".fromWireType(arg" + i + "Wired);\n"; + functionBody += + " var arg" + i + " = argType" + i + ".readValueFromPointer(args);\n" + + " args += argType" + i + ".argPackAdvance;\n"; } - invokerFnBody += - " var obj = _emval_handle_array[handle].value;\n" + - " var rv = obj[name](" + argsList + ");\n" + - " return retType.toWireType(allocateDestructors(destructorsRef), rv);\n" + - "}));\n"; - - args1.push(invokerFnBody); - var invokerFunction = new_(Function, args1).apply(null, args2); - return invokerFunction; + functionBody += + " var rv = handle[name](" + argsList + ");\n" + + " return retType.toWireType(destructors, rv);\n" + + "};\n"; + + params.push(functionBody); + var invokerFunction = new_(Function, params).apply(null, args); + return addMethodCaller(createNamedFunction(signatureName, invokerFunction)); +} + +function __emval_call_method(caller, handle, methodName, destructorsRef, args) { + caller = methodCallers[caller]; + handle = requireHandle(handle); + methodName = getStringOrSymbol(methodName); + return caller(handle, methodName, allocateDestructors(destructorsRef), args); } function __emval_has_function(handle, name) { + handle = requireHandle(handle); name = getStringOrSymbol(name); - return _emval_handle_array[handle].value[name] instanceof Function; + return handle[name] instanceof Function; } diff --git a/system/include/emscripten/bind.h b/system/include/emscripten/bind.h index 390533f3..872f279b 100644 --- a/system/include/emscripten/bind.h +++ b/system/include/emscripten/bind.h @@ -17,8 +17,6 @@ namespace emscripten { BY_EMVAL = 2, }; -#define EMSCRIPTEN_ALWAYS_INLINE __attribute__((always_inline)) - namespace internal { typedef long GenericEnumValue; @@ -35,18 +33,21 @@ namespace emscripten { void _embind_register_bool( TYPEID boolType, const char* name, + size_t size, bool trueValue, bool falseValue); void _embind_register_integer( TYPEID integerType, const char* name, + size_t size, long minRange, unsigned long maxRange); void _embind_register_float( TYPEID floatType, - const char* name); + const char* name, + size_t size); void _embind_register_std_string( TYPEID stringType, @@ -163,7 +164,9 @@ namespace emscripten { void _embind_register_enum( TYPEID enumType, - const char* name); + const char* name, + size_t size, + bool isSigned); void _embind_register_enum_value( TYPEID enumType, @@ -1182,7 +1185,9 @@ namespace emscripten { enum_(const char* name) { _embind_register_enum( internal::TypeID<EnumType>::get(), - name); + name, + sizeof(EnumType), + std::is_signed<typename std::underlying_type<EnumType>::type>::value); } enum_& value(const char* name, EnumType value) { diff --git a/system/include/emscripten/val.h b/system/include/emscripten/val.h index 19b1beb1..e217c959 100644 --- a/system/include/emscripten/val.h +++ b/system/include/emscripten/val.h @@ -2,6 +2,7 @@ #include <stdint.h> // uintptr_t #include <emscripten/wire.h> +#include <array> #include <vector> namespace emscripten { @@ -12,12 +13,10 @@ namespace emscripten { typedef struct _EM_VAL* EM_VAL; typedef struct _EM_DESTRUCTORS* EM_DESTRUCTORS; + typedef struct _EM_METHOD_CALLER* EM_METHOD_CALLER; + typedef double EM_GENERIC_WIRE_TYPE; + typedef const void* EM_VAR_ARGS; - // TODO: functions returning this are reinterpret_cast - // into the correct return type. this needs some thought - // for asm.js. - typedef void _POLYMORPHIC_RESULT; - void _emval_incref(EM_VAL value); void _emval_decref(EM_VAL value); @@ -28,37 +27,45 @@ namespace emscripten { EM_VAL _emval_undefined(); EM_VAL _emval_null(); EM_VAL _emval_new_cstring(const char*); - void _emval_take_value(TYPEID type/*, ...*/); + + EM_VAL _emval_take_value(TYPEID type, EM_VAR_ARGS argv); EM_VAL _emval_new( EM_VAL value, unsigned argCount, - internal::TYPEID argTypes[] - /*, ... */); + internal::TYPEID argTypes[], + EM_VAR_ARGS argv); EM_VAL _emval_get_global(const char* name); EM_VAL _emval_get_module_property(const char* name); EM_VAL _emval_get_property(EM_VAL object, EM_VAL key); void _emval_set_property(EM_VAL object, EM_VAL key, EM_VAL value); - _POLYMORPHIC_RESULT _emval_as(EM_VAL value, TYPEID returnType, EM_DESTRUCTORS* runDestructors); + EM_GENERIC_WIRE_TYPE _emval_as(EM_VAL value, TYPEID returnType, EM_DESTRUCTORS* destructors); EM_VAL _emval_call( EM_VAL value, unsigned argCount, - internal::TYPEID argTypes[] - /*, ... */); + internal::TYPEID argTypes[], + EM_VAR_ARGS argv); - // DO NOT call this more than once per signature. It will leak function pointer offsets! - GenericFunction _emval_get_method_caller( + // DO NOT call this more than once per signature. It will + // leak generated function objects! + EM_METHOD_CALLER _emval_get_method_caller( unsigned argCount, // including return value internal::TYPEID argTypes[]); + EM_GENERIC_WIRE_TYPE _emval_call_method( + EM_METHOD_CALLER caller, + EM_VAL handle, + const char* methodName, + EM_DESTRUCTORS* destructors, + EM_VAR_ARGS argv); bool _emval_has_function( EM_VAL value, const char* methodName); } template<const char* address> - struct symbol_registrar { + struct symbol_registrar { symbol_registrar() { internal::_emval_register_symbol(address); } @@ -66,19 +73,21 @@ namespace emscripten { template<typename ReturnType, typename... Args> struct Signature { + /* typedef typename BindingType<ReturnType>::WireType (*MethodCaller)( EM_VAL value, const char* methodName, EM_DESTRUCTORS* destructors, typename BindingType<Args>::WireType...); + */ - static MethodCaller get_method_caller() { - static MethodCaller fp = reinterpret_cast<MethodCaller>(init_method_caller()); - return fp; + static EM_METHOD_CALLER get_method_caller() { + static EM_METHOD_CALLER mc = init_method_caller(); + return mc; } private: - static GenericFunction init_method_caller() { + static EM_METHOD_CALLER init_method_caller() { WithPolicies<>::ArgTypeList<ReturnType, Args...> args; return _emval_get_method_caller(args.count, args.types); } @@ -100,19 +109,119 @@ namespace emscripten { EM_DESTRUCTORS destructors; }; + template<typename WireType> + struct GenericWireTypeConverter { + static WireType from(double wt) { + return static_cast<WireType>(wt); + } + }; + + template<typename Pointee> + struct GenericWireTypeConverter<Pointee*> { + static Pointee* from(double wt) { + return reinterpret_cast<Pointee*>(static_cast<uintptr_t>(wt)); + } + }; + + template<typename T> + T fromGenericWireType(double g) { + typedef typename BindingType<T>::WireType WireType; + WireType wt = GenericWireTypeConverter<WireType>::from(g); + return BindingType<T>::fromWireType(wt); + } + + template<typename... Args> + struct PackSize; + + template<> + struct PackSize<> { + static constexpr size_t value = 0; + }; + + template<typename Arg, typename... Args> + struct PackSize<Arg, Args...> { + static constexpr size_t value = (sizeof(typename BindingType<Arg>::WireType) + 7) / 8 + PackSize<Args...>::value; + }; + + union GenericWireType { + union { + unsigned u; + float f; + const void* p; + } w[2]; + double d; + }; + static_assert(sizeof(GenericWireType) == 8, "GenericWireType must be 8 bytes"); + static_assert(alignof(GenericWireType) == 8, "GenericWireType must be 8-byte-aligned"); + + inline void writeGenericWireType(GenericWireType*& cursor, float wt) { + cursor->w[0].f = wt; + ++cursor; + } + + inline void writeGenericWireType(GenericWireType*& cursor, double wt) { + cursor->d = wt; + ++cursor; + } + + template<typename T> + void writeGenericWireType(GenericWireType*& cursor, T* wt) { + cursor->w[0].p = wt; + ++cursor; + } + + inline void writeGenericWireType(GenericWireType*& cursor, const memory_view& wt) { + cursor[0].w[0].u = static_cast<unsigned>(wt.type); + cursor[0].w[1].u = wt.size; + cursor[1].w[0].p = wt.data; + cursor += 2; + } + + template<typename T> + void writeGenericWireType(GenericWireType*& cursor, T wt) { + cursor->w[0].u = static_cast<unsigned>(wt); + ++cursor; + } + + inline void writeGenericWireTypes(GenericWireType*&) { + } + + template<typename First, typename... Rest> + EMSCRIPTEN_ALWAYS_INLINE void writeGenericWireTypes(GenericWireType*& cursor, First&& first, Rest&&... rest) { + writeGenericWireType(cursor, BindingType<First>::toWireType(std::forward<First>(first))); + writeGenericWireTypes(cursor, std::forward<Rest>(rest)...); + } + + template<typename... Args> + struct WireTypePack { + WireTypePack(Args&&... args) { + GenericWireType* cursor = elements.data(); + writeGenericWireTypes(cursor, std::forward<Args>(args)...); + } + + operator EM_VAR_ARGS() const { + return elements.data(); + } + + private: + std::array<GenericWireType, PackSize<Args...>::value> elements; + }; + template<typename ReturnType, typename... Args> struct MethodCaller { static ReturnType call(EM_VAL handle, const char* methodName, Args&&... args) { auto caller = Signature<ReturnType, Args...>::get_method_caller(); + WireTypePack<Args...> argv(std::forward<Args>(args)...); EM_DESTRUCTORS destructors; - auto wireType = caller( + EM_GENERIC_WIRE_TYPE result = _emval_call_method( + caller, handle, methodName, &destructors, - toWireType(std::forward<Args>(args))...); + argv); DestructorsRunner rd(destructors); - return BindingType<ReturnType>::fromWireType(wireType); + return fromGenericWireType<ReturnType>(result); } }; @@ -121,12 +230,14 @@ namespace emscripten { static void call(EM_VAL handle, const char* methodName, Args&&... args) { auto caller = Signature<void, Args...>::get_method_caller(); + WireTypePack<Args...> argv(std::forward<Args>(args)...); EM_DESTRUCTORS destructors; - caller( + _emval_call_method( + caller, handle, methodName, &destructors, - toWireType(std::forward<Args>(args))...); + argv); DestructorsRunner rd(destructors); // void requires no translation } @@ -185,9 +296,13 @@ namespace emscripten { template<typename T> explicit val(T&& value) { + using namespace internal; + typedef internal::BindingType<T> BT; - auto taker = reinterpret_cast<internal::EM_VAL (*)(internal::TYPEID, typename BT::WireType)>(&internal::_emval_take_value); - handle = taker(internal::TypeID<T>::get(), BT::toWireType(std::forward<T>(value))); + WireTypePack<T> argv(std::forward<T>(value)); + handle = _emval_take_value( + internal::TypeID<T>::get(), + argv); } val() = delete; @@ -235,20 +350,15 @@ namespace emscripten { using namespace internal; WithPolicies<>::ArgTypeList<Args...> argList; + WireTypePack<Args...> argv(std::forward<Args>(args)...); // todo: this is awfully similar to operator(), can we // merge them somehow? - typedef EM_VAL (*TypedNew)( - EM_VAL, - unsigned, - TYPEID argTypes[], - typename BindingType<Args>::WireType...); - TypedNew typedNew = reinterpret_cast<TypedNew>(&_emval_new); return val( - typedNew( + _emval_new( handle, argList.count, argList.types, - toWireType(std::forward<Args>(args))...)); + argv)); } template<typename T> @@ -266,18 +376,13 @@ namespace emscripten { using namespace internal; WithPolicies<>::ArgTypeList<Args...> argList; - typedef EM_VAL (*TypedCall)( - EM_VAL, - unsigned, - TYPEID argTypes[], - typename BindingType<Args>::WireType...); - TypedCall typedCall = reinterpret_cast<TypedCall>(&_emval_call); + WireTypePack<Args...> argv(std::forward<Args>(args)...); return val( - typedCall( + _emval_call( handle, argList.count, argList.types, - toWireType(std::forward<Args>(args))...)); + argv)); } template<typename ReturnValue, typename... Args> @@ -297,16 +402,13 @@ namespace emscripten { typedef BindingType<T> BT; - typedef typename BT::WireType (*TypedAs)( - EM_VAL value, - TYPEID returnType, - EM_DESTRUCTORS* runDestructors); - TypedAs typedAs = reinterpret_cast<TypedAs>(&_emval_as); - EM_DESTRUCTORS destructors; - typename BT::WireType wt = typedAs(handle, TypeID<T>::get(), &destructors); + EM_GENERIC_WIRE_TYPE result = _emval_as( + handle, + TypeID<T>::get(), + &destructors); DestructorsRunner dr(destructors); - return BT::fromWireType(wt); + return fromGenericWireType<T>(result); } private: diff --git a/system/include/emscripten/wire.h b/system/include/emscripten/wire.h index c3ce8dd0..05b3ac33 100644 --- a/system/include/emscripten/wire.h +++ b/system/include/emscripten/wire.h @@ -12,6 +12,8 @@ #include <memory> #include <string> +#define EMSCRIPTEN_ALWAYS_INLINE __attribute__((always_inline)) + namespace emscripten { namespace internal { typedef void (*GenericFunction)(); diff --git a/system/lib/embind/bind.cpp b/system/lib/embind/bind.cpp index 12264dfd..f43d1ea1 100644 --- a/system/lib/embind/bind.cpp +++ b/system/lib/embind/bind.cpp @@ -8,6 +8,7 @@ #include <algorithm>
#include <emscripten/emscripten.h>
#include <climits>
+#include <limits>
using namespace emscripten;
@@ -36,25 +37,39 @@ extern "C" { }
}
+namespace {
+ template<typename T>
+ static void register_integer(const char* name) {
+ using namespace internal;
+ _embind_register_integer(TypeID<T>::get(), name, sizeof(T), std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
+ }
+
+ template<typename T>
+ static void register_float(const char* name) {
+ using namespace internal;
+ _embind_register_float(TypeID<T>::get(), name, sizeof(T));
+ }
+}
+
EMSCRIPTEN_BINDINGS(native_and_builtin_types) {
using namespace emscripten::internal;
_embind_register_void(TypeID<void>::get(), "void");
- _embind_register_bool(TypeID<bool>::get(), "bool", true, false);
-
- _embind_register_integer(TypeID<char>::get(), "char", CHAR_MIN, CHAR_MAX);
- _embind_register_integer(TypeID<signed char>::get(), "signed char", SCHAR_MIN, SCHAR_MAX);
- _embind_register_integer(TypeID<unsigned char>::get(), "unsigned char", 0, UCHAR_MAX);
- _embind_register_integer(TypeID<signed short>::get(), "short", SHRT_MIN, SHRT_MAX);
- _embind_register_integer(TypeID<unsigned short>::get(), "unsigned short", 0, USHRT_MAX);
- _embind_register_integer(TypeID<signed int>::get(), "int", INT_MIN, INT_MAX);
- _embind_register_integer(TypeID<unsigned int>::get(), "unsigned int", 0, UINT_MAX);
- _embind_register_integer(TypeID<signed long>::get(), "long", LONG_MIN, LONG_MAX);
- _embind_register_integer(TypeID<unsigned long>::get(), "unsigned long", 0, ULONG_MAX);
+ _embind_register_bool(TypeID<bool>::get(), "bool", sizeof(bool), true, false);
+
+ register_integer<char>("char");
+ register_integer<signed char>("signed char");
+ register_integer<unsigned char>("unsigned char");
+ register_integer<signed short>("short");
+ register_integer<unsigned short>("unsigned short");
+ register_integer<signed int>("int");
+ register_integer<unsigned int>("unsigned int");
+ register_integer<signed long>("long");
+ register_integer<unsigned long>("unsigned long");
- _embind_register_float(TypeID<float>::get(), "float");
- _embind_register_float(TypeID<double>::get(), "double");
+ register_float<float>("float");
+ register_float<double>("double");
_embind_register_std_string(TypeID<std::string>::get(), "std::string");
_embind_register_std_wstring(TypeID<std::wstring>::get(), sizeof(wchar_t), "std::wstring");
diff --git a/tests/embind/embind.test.js b/tests/embind/embind.test.js index 5ca972be..e2160c33 100644 --- a/tests/embind/embind.test.js +++ b/tests/embind/embind.test.js @@ -1938,10 +1938,84 @@ module({ derived.delete(); // Let the memory leak test superfixture check that no leaks occurred. }); + + BaseFixture.extend("val::as", function() { + test("built-ins", function() { + assert.equal(true, cm.val_as_bool(true)); + assert.equal(false, cm.val_as_bool(false)); + assert.equal(127, cm.val_as_char(127)); + assert.equal(32767, cm.val_as_short(32767)); + assert.equal(65536, cm.val_as_int(65536)); + assert.equal(65536, cm.val_as_long(65536)); + assert.equal(10.5, cm.val_as_float(10.5)); + assert.equal(10.5, cm.val_as_double(10.5)); + + assert.equal("foo", cm.val_as_string("foo")); + assert.equal("foo", cm.val_as_wstring("foo")); + + var obj = {}; + assert.equal(obj, cm.val_as_val(obj)); + + // JS->C++ memory view not implemented + //var ab = cm.val_as_memory_view(new ArrayBuffer(13)); + //assert.equal(13, ab.byteLength); + }); + + test("value types", function() { + var tuple = [1, 2, 3, 4]; + assert.deepEqual(tuple, cm.val_as_value_array(tuple)); + + var struct = {x: 1, y: 2, z: 3, w: 4}; + assert.deepEqual(struct, cm.val_as_value_object(struct)); + }); + + test("enums", function() { + assert.equal(cm.Enum.ONE, cm.val_as_enum(cm.Enum.ONE)); + }); + }); + + BaseFixture.extend("val::new_", function() { + test("variety of types", function() { + function factory() { + this.arguments = Array.prototype.slice.call(arguments, 0); + } + var instance = cm.construct_with_6_arguments(factory); + assert.deepEqual( + [6, -12.5, "a3", {x: 1, y: 2, z: 3, w: 4}, cm.EnumClass.TWO, [-1, -2, -3, -4]], + instance.arguments); + }); + + test("memory view", function() { + function factory(before, view, after) { + this.before = before; + this.view = view; + this.after = after; + } + + var instance = cm.construct_with_memory_view(factory); + assert.equal("before", instance.before); + assert.equal(10, instance.view.byteLength); + assert.equal("after", instance.after); + }); + + test("ints_and_float", function() { + function factory(a, b, c) { + this.a = a; + this.b = b; + this.c = c; + } + + var instance = cm.construct_with_ints_and_float(factory); + assert.equal(65537, instance.a); + assert.equal(4.0, instance.b); + assert.equal(65538, instance.c); + }); + }); }); /* global run_all_tests */ // If running as part of the emscripten test runner suite, and not as part of the IMVU suite, // we launch the test execution from here. IMVU suite uses its own dedicated mechanism instead of this. -if (typeof run_all_tests !== "undefined") +if (typeof run_all_tests !== "undefined") { run_all_tests(); +} diff --git a/tests/embind/embind_test.cpp b/tests/embind/embind_test.cpp index 4efc4bd8..d299660a 100644 --- a/tests/embind/embind_test.cpp +++ b/tests/embind/embind_test.cpp @@ -842,7 +842,7 @@ Enum emval_test_take_and_return_Enum(Enum e) { return e; } -enum class EnumClass { ONE, TWO }; +enum class EnumClass : char { ONE, TWO }; EnumClass emval_test_take_and_return_EnumClass(EnumClass e) { return e; @@ -2304,3 +2304,60 @@ EMSCRIPTEN_BINDINGS(mixins) { .constructor<>() ); } + +template<typename T> +T val_as(const val& v) { + return v.as<T>(); +} + +EMSCRIPTEN_BINDINGS(val_as) { + function("val_as_bool", &val_as<bool>); + function("val_as_char", &val_as<char>); + function("val_as_short", &val_as<short>); + function("val_as_int", &val_as<int>); + function("val_as_long", &val_as<long>); + + function("val_as_float", &val_as<float>); + function("val_as_double", &val_as<double>); + + function("val_as_string", &val_as<std::string>); + function("val_as_wstring", &val_as<std::wstring>); + function("val_as_val", &val_as<val>); + + function("val_as_value_object", &val_as<StructVector>); + function("val_as_value_array", &val_as<TupleVector>); + + function("val_as_enum", &val_as<Enum>); + + // memory_view is always JS -> C++ + //function("val_as_memory_view", &val_as<memory_view>); +} + +val construct_with_6(val factory) { + unsigned char a1 = 6; + double a2 = -12.5; + std::string a3("a3"); + StructVector a4(1, 2, 3, 4); + EnumClass a5 = EnumClass::TWO; + TupleVector a6(-1, -2, -3, -4); + return factory.new_(a1, a2, a3, a4, a5, a6); +} + +val construct_with_memory_view(val factory) { + static const char data[11] = "0123456789"; + return factory.new_( + std::string("before"), + memory_view(10, data), + std::string("after")); +} + +val construct_with_ints_and_float(val factory) { + static const char data[11] = "0123456789"; + return factory.new_(65537, 4.0f, 65538); +} + +EMSCRIPTEN_BINDINGS(val_new_) { + function("construct_with_6_arguments", &construct_with_6); + function("construct_with_memory_view", &construct_with_memory_view); + function("construct_with_ints_and_float", &construct_with_ints_and_float); +} |