summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <alonzakai@gmail.com>2013-05-21 12:39:07 -0700
committerAlon Zakai <alonzakai@gmail.com>2013-05-21 12:39:07 -0700
commitabe634001c34f74fabc660d61c37cbe44f44a443 (patch)
tree7005e3c219fe78ab606542c2b661a7c8efcaad5f
parentf46a8ba8cadd98ddfb6e13a6c34aa890f3355bfd (diff)
parent954f70274ed3b85b9d3885c6087df96376ca07d3 (diff)
Merge pull request #1185 from imvu/embind-performance-and-code-size-improvements
Embind performance and code size improvements
-rw-r--r--src/embind/embind.js99
-rw-r--r--src/embind/emval.js177
-rw-r--r--system/include/emscripten/bind.h62
-rw-r--r--system/include/emscripten/val.h116
-rw-r--r--system/include/emscripten/wire.h81
-rw-r--r--system/lib/embind/bind.cpp1
-rw-r--r--tests/embind/build_benchmark2
-rw-r--r--tests/embind/embind.benchmark.js35
-rw-r--r--tests/embind/embind.test.js153
-rw-r--r--tests/embind/embind_benchmark.cpp71
-rw-r--r--tests/embind/embind_test.cpp65
11 files changed, 678 insertions, 184 deletions
diff --git a/src/embind/embind.js b/src/embind/embind.js
index cadee700..91386c69 100644
--- a/src/embind/embind.js
+++ b/src/embind/embind.js
@@ -131,6 +131,7 @@ function extendError(baseErrorType, errorName) {
// 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",
@@ -270,6 +271,10 @@ function __embind_register_void(rawType, name) {
'fromWireType': function() {
return undefined;
},
+ 'toWireType': function(destructors, o) {
+ // TODO: assert if anything else is given?
+ return undefined;
+ },
});
}
@@ -306,7 +311,7 @@ function __embind_register_integer(primitiveType, name, minRange, maxRange) {
'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") {
+ if (typeof value !== "number" && typeof value !== "boolean") {
throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' + this.name);
}
if (value < minRange || value > maxRange) {
@@ -328,8 +333,8 @@ function __embind_register_float(rawType, name) {
'toWireType': function(destructors, value) {
// todo: Here we have an opportunity for -O3 level "unsafe" optimizations: we could
// avoid the following if() and assume value is of proper type.
- if (typeof value !== "number") {
- throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' +this.name);
+ if (typeof value !== "number" && typeof value !== "boolean") {
+ throw new TypeError('Cannot convert "' + _embind_repr(value) + '" to ' + this.name);
}
return value;
},
@@ -449,6 +454,31 @@ function __embind_register_emval(rawType, name) {
});
}
+function __embind_register_memory_view(rawType, name) {
+ var typeMapping = [
+ Int8Array,
+ Uint8Array,
+ Int16Array,
+ Uint16Array,
+ Int32Array,
+ Uint32Array,
+ Float32Array,
+ Float64Array,
+ ];
+
+ 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) {
while (destructors.length) {
var ptr = destructors.pop();
@@ -677,7 +707,7 @@ function __embind_finalize_tuple(rawTupleType) {
},
'toWireType': function(destructors, o) {
if (elementsLength !== o.length) {
- throw new TypeError("Incorrect number of tuple elements");
+ 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) {
@@ -685,7 +715,7 @@ function __embind_finalize_tuple(rawTupleType) {
}
if (destructors !== null) {
destructors.push(rawDestructor, ptr);
- }
+ }
return ptr;
},
destructorFunction: rawDestructor,
@@ -802,7 +832,9 @@ var genericPointerToWireType = function(destructors, handle) {
if (this.isSmartPointer) {
var ptr = this.rawConstructor();
- destructors.push(this.rawDestructor, ptr);
+ if (destructors !== null) {
+ destructors.push(this.rawDestructor, ptr);
+ }
return ptr;
} else {
return 0;
@@ -854,7 +886,9 @@ var genericPointerToWireType = function(destructors, handle) {
clonedHandle.delete();
})
);
- destructors.push(this.rawDestructor, ptr);
+ if (destructors !== null) {
+ destructors.push(this.rawDestructor, ptr);
+ }
}
break;
@@ -1080,9 +1114,13 @@ ClassHandle.prototype.isAliasOf = function(other) {
return leftClass === rightClass && left === right;
};
+function throwInstanceAlreadyDeleted(obj) {
+ throwBindingError(getInstanceTypeName(obj) + ' instance already deleted');
+}
+
ClassHandle.prototype.clone = function() {
if (!this.$$.ptr) {
- throwBindingError(getInstanceTypeName(this) + ' instance already deleted');
+ throwInstanceAlreadyDeleted(this);
}
var clone = Object.create(Object.getPrototypeOf(this), {
@@ -1104,9 +1142,12 @@ function runDestructor(handle) {
}
}
-ClassHandle.prototype['delete'] = function() {
+ClassHandle.prototype['delete'] = function ClassHandle_delete() {
if (!this.$$.ptr) {
- throwBindingError(getInstanceTypeName(this) + ' instance already deleted');
+ throwInstanceAlreadyDeleted(this);
+ }
+ if (this.$$.deleteScheduled) {
+ throwBindingError('Object already scheduled for deletion');
}
this.$$.count.value -= 1;
@@ -1116,6 +1157,44 @@ ClassHandle.prototype['delete'] = function() {
this.$$.smartPtr = undefined;
this.$$.ptr = undefined;
};
+
+var deletionQueue = [];
+
+ClassHandle.prototype['isDeleted'] = function isDeleted() {
+ return !this.$$.ptr;
+};
+
+ClassHandle.prototype['deleteLater'] = function deleteLater() {
+ if (!this.$$.ptr) {
+ throwInstanceAlreadyDeleted(this);
+ }
+ if (this.$$.deleteScheduled) {
+ throwBindingError('Object already scheduled for deletion');
+ }
+ deletionQueue.push(this);
+ if (deletionQueue.length === 1 && delayFunction) {
+ delayFunction(flushPendingDeletes);
+ }
+ this.$$.deleteScheduled = true;
+ return this;
+};
+
+function flushPendingDeletes() {
+ while (deletionQueue.length) {
+ var obj = deletionQueue.pop();
+ obj.$$.deleteScheduled = false;
+ obj['delete']();
+ }
+}
+Module['flushPendingDeletes'] = flushPendingDeletes;
+
+var delayFunction;
+Module['setDelayFunction'] = function setDelayFunction(fn) {
+ delayFunction = fn;
+ if (deletionQueue.length && delayFunction) {
+ delayFunction(flushPendingDeletes);
+ }
+};
function RegisteredClass(
name,
diff --git a/src/embind/emval.js b/src/embind/emval.js
index c02ffa92..77270597 100644
--- a/src/embind/emval.js
+++ b/src/embind/emval.js
@@ -1,8 +1,12 @@
-/*global Module*/
+/*global Module:true, Runtime*/
/*global HEAP32*/
+/*global new_*/
+/*global createNamedFunction*/
/*global readLatin1String, writeStringToMemory*/
/*global requireRegisteredType, throwBindingError*/
+var Module = Module || {};
+
var _emval_handle_array = [{}]; // reserve zero
var _emval_free_list = [];
@@ -69,14 +73,8 @@ function __emval_incref(handle) {
function __emval_decref(handle) {
if (handle && 0 === --_emval_handle_array[handle].refcount) {
- delete _emval_handle_array[handle];
+ _emval_handle_array[handle] = undefined;
_emval_free_list.push(handle);
-
- var actual_length = _emval_handle_array.length;
- while (actual_length > 0 && _emval_handle_array[actual_length - 1] === undefined) {
- --actual_length;
- }
- _emval_handle_array.length = actual_length;
}
}
@@ -108,44 +106,73 @@ function __emval_take_value(type, v) {
var __newers = {}; // arity -> function
-function __emval_new(handle, argCount, argTypes) {
- requireHandle(handle);
- var args = parseParameters(
- argCount,
- argTypes,
- Array.prototype.slice.call(arguments, 3));
+function craftEmvalAllocator(argCount) {
+ /*This function returns a new function that looks like this:
+ function emval_allocator_3(handle, argTypes, arg0Wired, arg1Wired, arg2Wired) {
+ var argType0 = requireRegisteredType(HEAP32[(argTypes >> 2)], "parameter 0");
+ var arg0 = argType0.fromWireType(arg0Wired);
+ var argType1 = requireRegisteredType(HEAP32[(argTypes >> 2) + 1], "parameter 1");
+ var arg1 = argType1.fromWireType(arg1Wired);
+ 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 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'
+ }
- // Alas, we are forced to use operator new until WebKit enables
- // constructing typed arrays without new.
- // In WebKit, Uint8Array(10) throws an error.
- // In every other browser, it's identical to new Uint8Array(10).
+ var invokerFnBody =
+ "return function emval_allocator_"+argCount+"(handle, argTypes " + argsListWired + ") {\n";
+ for(var i = 0; i < argCount; ++i) {
+ invokerFnBody +=
+ "var argType"+i+" = requireRegisteredType(HEAP32[(argTypes >> 2) + "+i+"], \"parameter "+i+"\");\n" +
+ "var arg"+i+" = argType"+i+".fromWireType(arg"+i+"Wired);\n";
+ }
+ invokerFnBody +=
+ "var constructor = _emval_handle_array[handle].value;\n" +
+ "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;
+}
+
+function __emval_new(handle, argCount, argTypes) {
+ requireHandle(handle);
+
var newer = __newers[argCount];
if (!newer) {
- var parameters = new Array(argCount);
- for (var i = 0; i < argCount; ++i) {
- parameters[i] = 'a' + i;
- }
- /*jshint evil:true*/
- newer = __newers[argCount] = new Function(
- ['c'].concat(parameters),
- "return new c(" + parameters.join(',') + ");");
+ newer = craftEmvalAllocator(argCount);
+ __newers[argCount] = newer;
}
-
- var constructor = _emval_handle_array[handle].value;
- var obj = newer.apply(undefined, [constructor].concat(args));
-/*
- // implement what amounts to operator new
- function dummy(){}
- dummy.prototype = constructor.prototype;
- var obj = new constructor;
- var rv = constructor.apply(obj, args);
- if (typeof rv === 'object') {
- obj = rv;
+
+ 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 __emval_register(obj);
}
// appease jshint (technically this code uses eval)
@@ -192,38 +219,62 @@ function parseParameters(argCount, argTypes, argWireTypes) {
function __emval_call(handle, argCount, argTypes) {
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 fn = _emval_handle_array[handle].value;
- var args = parseParameters(
- argCount,
- argTypes,
- Array.prototype.slice.call(arguments, 3));
var rv = fn.apply(undefined, args);
return __emval_register(rv);
}
-function __emval_call_method(handle, name, argCount, argTypes) {
- requireHandle(handle);
- name = getStringOrSymbol(name);
-
- var args = parseParameters(
- argCount,
- argTypes,
- Array.prototype.slice.call(arguments, 4));
- var obj = _emval_handle_array[handle].value;
- var rv = obj[name].apply(obj, args);
- return __emval_register(rv);
+function lookupTypes(argCount, argTypes, argWireTypes) {
+ var a = new Array(argCount);
+ for (var i = 0; i < argCount; ++i) {
+ a[i] = requireRegisteredType(
+ HEAP32[(argTypes >> 2) + i],
+ "parameter " + i);
+ }
+ return a;
}
-function __emval_call_void_method(handle, name, argCount, argTypes) {
- requireHandle(handle);
- name = getStringOrSymbol(name);
+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 = ["Runtime", "createNamedFunction", "requireHandle", "getStringOrSymbol", "_emval_handle_array", "retType"];
+ var args2 = [Runtime, createNamedFunction, requireHandle, getStringOrSymbol, _emval_handle_array, retType];
- var args = parseParameters(
- argCount,
- argTypes,
- Array.prototype.slice.call(arguments, 4));
- var obj = _emval_handle_array[handle].value;
- obj[name].apply(obj, args);
+ 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]);
+ }
+
+ var invokerFnBody =
+ "return Runtime.addFunction(createNamedFunction('" + signatureName + "', function (handle, name" + argsListWired + ") {\n" +
+ "requireHandle(handle);\n" +
+ "name = getStringOrSymbol(name);\n";
+
+ for (var i = 0; i < argCount - 1; ++i) {
+ invokerFnBody += "var arg" + i + " = argType" + i + ".fromWireType(arg" + i + "Wired);\n";
+ }
+ invokerFnBody +=
+ "var obj = _emval_handle_array[handle].value;\n" +
+ "return retType.toWireType(null, obj[name](" + argsList + "));\n" +
+ "}));\n";
+
+ args1.push(invokerFnBody);
+ var invokerFunction = new_(Function, args1).apply(null, args2);
+ return invokerFunction;
}
function __emval_has_function(handle, name) {
diff --git a/system/include/emscripten/bind.h b/system/include/emscripten/bind.h
index 7aa2a55e..cd465e45 100644
--- a/system/include/emscripten/bind.h
+++ b/system/include/emscripten/bind.h
@@ -18,7 +18,6 @@ namespace emscripten {
};
namespace internal {
- typedef void (*GenericFunction)();
typedef long GenericEnumValue;
// Implemented in JavaScript. Don't call these directly.
@@ -60,6 +59,10 @@ namespace emscripten {
TYPEID emvalType,
const char* name);
+ void _embind_register_memory_view(
+ TYPEID memoryViewType,
+ const char* name);
+
void _embind_register_function(
const char* name,
unsigned argCount,
@@ -414,11 +417,10 @@ namespace emscripten {
// TODO: This could do a reinterpret-cast if sizeof(T) === sizeof(void*)
template<typename T>
- inline void* getContext(const T& t) {
+ inline T* getContext(const T& t) {
// not a leak because this is called once per binding
- void* p = malloc(sizeof(T));
- assert(p);
- memcpy(p, &t, sizeof(T));
+ T* p = reinterpret_cast<T*>(malloc(sizeof(T)));
+ new(p) T(t);
return p;
}
@@ -749,38 +751,19 @@ namespace emscripten {
template<typename ReturnType, typename... Args>
ReturnType call(const char* name, Args&&... args) const {
- return Caller<ReturnType, Args...>::call(wrapped, name, std::forward<Args>(args)...);
+ return wrapped.call<ReturnType>(name, std::forward<Args>(args)...);
}
template<typename ReturnType, typename... Args, typename Default>
ReturnType optional_call(const char* name, Default def, Args&&... args) const {
- if (has_function(name)) {
- return Caller<ReturnType, Args...>::call(wrapped, name, std::forward<Args>(args)...);
+ if (wrapped.has_function(name)) {
+ return call<ReturnType>(name, std::forward<Args>(args)...);
} else {
return def();
}
}
private:
- bool has_function(const char* name) const {
- return wrapped.has_function(name);
- }
-
- // this class only exists because you can't partially specialize function templates
- template<typename ReturnType, typename... Args>
- struct Caller {
- static ReturnType call(const val& v, const char* name, Args&&... args) {
- return v.call(name, std::forward<Args>(args)...).template as<ReturnType>();
- }
- };
-
- template<typename... Args>
- struct Caller<void, Args...> {
- static void call(const val& v, const char* name, Args&&... args) {
- v.call_void(name, std::forward<Args>(args)...);
- }
- };
-
val wrapped;
};
@@ -844,23 +827,8 @@ namespace emscripten {
}
};
- template<typename PointerType>
- struct ptr {
- typedef PointerType pointer_type;
- };
-
namespace internal {
template<typename T>
- struct is_ptr {
- enum { value = false };
- };
-
- template<typename T>
- struct is_ptr<ptr<T>> {
- enum { value = true };
- };
-
- template<typename T>
struct SmartPtrIfNeeded {
template<typename U>
SmartPtrIfNeeded(U& cls) {
@@ -881,7 +849,6 @@ namespace emscripten {
public:
class_() = delete;
- template<typename = typename std::enable_if<!internal::is_ptr<ClassType>::value>::type>
explicit class_(const char* name) {
using namespace internal;
@@ -927,16 +894,17 @@ namespace emscripten {
policies...);
}
- template<typename... Args, typename... Policies>
- class_& constructor(ClassType* (*factory)(Args...), Policies...) {
+ template<typename... Args, typename ReturnType, typename... Policies>
+ class_& constructor(ReturnType (*factory)(Args...), Policies...) {
using namespace internal;
- typename WithPolicies<Policies...>::template ArgTypeList<AllowedRawPointer<ClassType>, Args...> args;
+ // TODO: allows all raw pointers... policies need a rethink
+ typename WithPolicies<allow_raw_pointers, Policies...>::template ArgTypeList<ReturnType, Args...> args;
_embind_register_class_constructor(
TypeID<ClassType>::get(),
args.count,
args.types,
- reinterpret_cast<GenericFunction>(&Invoker<ClassType*, Args...>::invoke),
+ reinterpret_cast<GenericFunction>(&Invoker<ReturnType, Args...>::invoke),
reinterpret_cast<GenericFunction>(factory));
return *this;
}
diff --git a/system/include/emscripten/val.h b/system/include/emscripten/val.h
index edd070e3..b712d164 100644
--- a/system/include/emscripten/val.h
+++ b/system/include/emscripten/val.h
@@ -10,7 +10,6 @@ namespace emscripten {
extern "C" {
void _emval_register_symbol(const char*);
- typedef struct _EM_SIG* EM_SIG;
typedef struct _EM_VAL* EM_VAL;
void _emval_incref(EM_VAL value);
@@ -39,34 +38,67 @@ namespace emscripten {
unsigned argCount,
internal::TYPEID argTypes[]
/*, ... */);
- EM_VAL _emval_call_method(
- EM_VAL value,
- const char* methodName,
- unsigned argCount,
- internal::TYPEID argTypes[]
- /*, ... */);
- void _emval_call_void_method(
- EM_VAL value,
- const char* methodName,
- unsigned argCount,
- internal::TYPEID argTypes[]
- /*, ...*/);
+
+ // DO NOT call this more than once per signature. It will leak function pointer offsets!
+ GenericFunction _emval_get_method_caller(
+ unsigned argCount, // including return value
+ internal::TYPEID argTypes[]);
bool _emval_has_function(
EM_VAL value,
const char* methodName);
}
- }
- template<const char* address>
- struct symbol_registrar {
- symbol_registrar() {
- internal::_emval_register_symbol(address);
- }
- };
+ template<const char* address>
+ struct symbol_registrar {
+ symbol_registrar() {
+ internal::_emval_register_symbol(address);
+ }
+ };
+
+ template<typename ReturnType, typename... Args>
+ struct Signature {
+ typedef typename BindingType<ReturnType>::WireType (*MethodCaller)(EM_VAL value, const char* methodName, typename BindingType<Args>::WireType...);
+
+ static MethodCaller get_method_caller() {
+ static MethodCaller fp = reinterpret_cast<MethodCaller>(init_method_caller());
+ return fp;
+ }
+
+ private:
+ static GenericFunction init_method_caller() {
+ WithPolicies<>::ArgTypeList<ReturnType, Args...> args;
+ return _emval_get_method_caller(args.count, args.types);
+ }
+ };
+
+ 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();
+ auto wireType = caller(
+ handle,
+ methodName,
+ toWireType(std::forward<Args>(args))...);
+ WireDeleter<ReturnType> deleter(wireType);
+ return BindingType<ReturnType>::fromWireType(wireType);
+ }
+ };
+
+ template<typename... Args>
+ struct MethodCaller<void, Args...> {
+ static void call(EM_VAL handle, const char* methodName, Args&&... args) {
+ auto caller = Signature<void, Args...>::get_method_caller();
+ return caller(
+ handle,
+ methodName,
+ toWireType(std::forward<Args>(args))...);
+ }
+ };
+ }
#define EMSCRIPTEN_SYMBOL(name) \
static const char name##_symbol[] = #name; \
- static const symbol_registrar<name##_symbol> name##_registrar
+ static const ::emscripten::internal::symbol_registrar<name##_symbol> name##_registrar
class val {
public:
@@ -158,7 +190,7 @@ namespace emscripten {
}
bool hasOwnProperty(const char* key) const {
- return val::global("Object")["prototype"]["hasOwnProperty"].call("call", *this, val(key)).as<bool>();
+ return val::global("Object")["prototype"]["hasOwnProperty"].call<bool>("call", *this, val(key));
}
template<typename... Args>
@@ -211,45 +243,11 @@ namespace emscripten {
toWireType(std::forward<Args>(args))...));
}
- template<typename ...Args>
- val call(const char* name, Args&&... args) const {
+ template<typename ReturnValue, typename... Args>
+ ReturnValue call(const char* name, Args&&... args) const {
using namespace internal;
- WithPolicies<>::ArgTypeList<Args...> argList;
- typedef EM_VAL (*TypedCall)(
- EM_VAL,
- const char* name,
- unsigned,
- TYPEID argTypes[],
- typename BindingType<Args>::WireType...);
- TypedCall typedCall = reinterpret_cast<TypedCall>(&_emval_call_method);
- return val(
- typedCall(
- handle,
- name,
- argList.count,
- argList.types,
- toWireType(std::forward<Args>(args))...));
- }
-
- template<typename ...Args>
- void call_void(const char* name, Args&&... args) const {
- using namespace internal;
-
- WithPolicies<>::ArgTypeList<Args...> argList;
- typedef void (*TypedCall)(
- EM_VAL,
- const char* name,
- unsigned,
- TYPEID argTypes[],
- typename BindingType<Args>::WireType...);
- TypedCall typedCall = reinterpret_cast<TypedCall>(&_emval_call_void_method);
- return typedCall(
- handle,
- name,
- argList.count,
- argList.types,
- toWireType(std::forward<Args>(args))...);
+ return MethodCaller<ReturnValue, Args...>::call(handle, name, std::forward<Args>(args)...);
}
bool has_function(const char* name) const {
diff --git a/system/include/emscripten/wire.h b/system/include/emscripten/wire.h
index 6fb15fc7..a5892216 100644
--- a/system/include/emscripten/wire.h
+++ b/system/include/emscripten/wire.h
@@ -13,6 +13,8 @@
namespace emscripten {
namespace internal {
+ typedef void (*GenericFunction)();
+
typedef const struct _TYPEID* TYPEID;
// This implementation is technically not legal, as it's not
@@ -146,6 +148,7 @@ namespace emscripten {
template<>
struct BindingType<void> {
+ typedef void WireType;
};
template<>
@@ -309,4 +312,82 @@ namespace emscripten {
WireType wt;
};
}
+
+ struct memory_view {
+ enum class Type {
+ Int8Array,
+ Uint8Array,
+ Int16Array,
+ Uint16Array,
+ Int32Array,
+ Uint32Array,
+ Float32Array,
+ Float64Array,
+ };
+
+ memory_view() = delete;
+ explicit memory_view(size_t size, const void* data)
+ : type(Type::Uint8Array)
+ , size(size)
+ , data(data)
+ {}
+ explicit memory_view(Type type, size_t size, const void* data)
+ : type(type)
+ , size(size)
+ , data(data)
+ {}
+
+ const Type type;
+ const size_t size; // in elements, not bytes
+ const void* const data;
+ };
+
+ inline memory_view typed_memory_view(size_t size, const int8_t* data) {
+ return memory_view(memory_view::Type::Int8Array, size, data);
+ }
+
+ inline memory_view typed_memory_view(size_t size, const uint8_t* data) {
+ return memory_view(memory_view::Type::Uint8Array, size, data);
+ }
+
+ inline memory_view typed_memory_view(size_t size, const int16_t* data) {
+ return memory_view(memory_view::Type::Int16Array, size, data);
+ }
+
+ inline memory_view typed_memory_view(size_t size, const uint16_t* data) {
+ return memory_view(memory_view::Type::Uint16Array, size, data);
+ }
+
+ inline memory_view typed_memory_view(size_t size, const int32_t* data) {
+ return memory_view(memory_view::Type::Int32Array, size, data);
+ }
+
+ inline memory_view typed_memory_view(size_t size, const uint32_t* data) {
+ return memory_view(memory_view::Type::Uint32Array, size, data);
+ }
+
+ inline memory_view typed_memory_view(size_t size, const float* data) {
+ return memory_view(memory_view::Type::Float32Array, size, data);
+ }
+
+ inline memory_view typed_memory_view(size_t size, const double* data) {
+ return memory_view(memory_view::Type::Float64Array, size, data);
+ }
+
+ namespace internal {
+ template<>
+ struct BindingType<memory_view> {
+ // This non-word-sized WireType only works because I
+ // happen to know that clang will pass aggregates as
+ // pointers to stack elements and we never support
+ // converting JavaScript typed arrays back into
+ // memory_view. (That is, fromWireType is not implemented
+ // on the C++ side, nor is toWireType implemented in
+ // JavaScript.)
+ typedef memory_view WireType;
+ static WireType toWireType(const memory_view& mv) {
+ return mv;
+ }
+ };
+ }
}
diff --git a/system/lib/embind/bind.cpp b/system/lib/embind/bind.cpp
index ec1648a9..12264dfd 100644
--- a/system/lib/embind/bind.cpp
+++ b/system/lib/embind/bind.cpp
@@ -59,4 +59,5 @@ EMSCRIPTEN_BINDINGS(native_and_builtin_types) {
_embind_register_std_string(TypeID<std::string>::get(), "std::string");
_embind_register_std_wstring(TypeID<std::wstring>::get(), sizeof(wchar_t), "std::wstring");
_embind_register_emval(TypeID<val>::get(), "emscripten::val");
+ _embind_register_memory_view(TypeID<memory_view>::get(), "emscripten::memory_view");
}
diff --git a/tests/embind/build_benchmark b/tests/embind/build_benchmark
index 6faad18b..3d5d816b 100644
--- a/tests/embind/build_benchmark
+++ b/tests/embind/build_benchmark
@@ -1,2 +1,2 @@
#!/bin/bash
-EMCC_LLVM_TARGET=le32-unknown-nacl ~/projects/emscripten/emcc --minify 0 --bind --post-js embind.benchmark.js -O2 --shell-file shell.html -o embind_benchmark.html embind_benchmark.cpp
+EMCC_LLVM_TARGET=le32-unknown-nacl ../../emcc --minify 0 --bind --post-js embind.benchmark.js -O2 --shell-file shell.html -o embind_benchmark.html embind_benchmark.cpp
diff --git a/tests/embind/embind.benchmark.js b/tests/embind/embind.benchmark.js
index 7b20db88..3669bc28 100644
--- a/tests/embind/embind.benchmark.js
+++ b/tests/embind/embind.benchmark.js
@@ -248,3 +248,38 @@ function _call_through_interface1() {
Module.print("C++ -> JS std::wstring through interface " + N + " iters: " + elapsed + " msecs.");
obj.delete();
}
+
+function _call_through_interface2() {
+ var N = 1000000;
+ var total = 0;
+ var obj = Module['Interface'].implement({
+ call_with_typed_array: function(ta) {
+ total += ta.length;
+ },
+ call_with_memory_view: function(ta) {
+ total += ta.length;
+ },
+ });
+
+ var start = _emscripten_get_now();
+ Module['callInterface2'](N, obj);
+ var elapsed = _emscripten_get_now() - start;
+ Module.print("C++ -> JS typed array instantiation " + N + " iters: " + elapsed + " msecs.");
+
+ var start = _emscripten_get_now();
+ Module['callInterface3'](N, obj);
+ var elapsed = _emscripten_get_now() - start;
+ Module.print("C++ -> JS memory_view instantiation" + N + " iters: " + elapsed + " msecs.");
+ obj.delete();
+}
+
+function _returns_val_benchmark() {
+ var N = 1000000;
+ var v = 1;
+ var start = _emscripten_get_now();
+ for(var i = 0; i < N; ++i) {
+ v = Module['returns_val'](v);
+ }
+ var elapsed = _emscripten_get_now() - start;
+ Module.print("returns_val " + N + " iters: " + elapsed + " msecs");
+}
diff --git a/tests/embind/embind.test.js b/tests/embind/embind.test.js
index 52b2cad8..e60e1ab3 100644
--- a/tests/embind/embind.test.js
+++ b/tests/embind/embind.test.js
@@ -5,6 +5,8 @@ module({
var CheckForLeaks = fixture("check for leaks", function() {
this.setUp(function() {
+ cm.setDelayFunction(undefined);
+
if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well!
cm._mallocDebug(2);
assert.equal(0, cm.count_emval_handles());
@@ -12,6 +14,7 @@ module({
}
});
this.tearDown(function() {
+ cm.flushPendingDeletes();
if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well!
cm._mallocAssertAllMemoryFree();
assert.equal(0, cm.count_emval_handles());
@@ -490,6 +493,15 @@ module({
assert.equal(true, cm.emval_test_not(false));
});
+ test("can pass booleans as integers", function() {
+ assert.equal(1, cm.emval_test_as_unsigned(true));
+ assert.equal(0, cm.emval_test_as_unsigned(false));
+ });
+
+ test("can pass booleans as floats", function() {
+ assert.equal(2, cm.const_ref_adder(true, true));
+ });
+
test("convert double to unsigned", function() {
var rv = cm.emval_test_as_unsigned(1.5);
assert.equal('number', typeof rv);
@@ -672,6 +684,15 @@ module({
c.delete();
});
+ test("access multiple smart ptr ctors", function() {
+ var a = new cm.MultipleSmartCtors(10);
+ assert.equal(a.WhichCtorCalled(), 1);
+ var b = new cm.MultipleCtors(20, 20);
+ assert.equal(b.WhichCtorCalled(), 2);
+ a.delete();
+ b.delete();
+ });
+
test("wrong number of constructor arguments throws", function() {
assert.throws(cm.BindingError, function() { new cm.MultipleCtors(); });
assert.throws(cm.BindingError, function() { new cm.MultipleCtors(1,2,3,4); });
@@ -1559,6 +1580,28 @@ module({
impl.delete();
});
+ test("returning null shared pointer from interfaces implemented in JS code does not leak", function() {
+ var impl = cm.AbstractClass.implement({
+ returnsSharedPtr: function() {
+ return null;
+ }
+ });
+ cm.callReturnsSharedPtrMethod(impl);
+ impl.delete();
+ // Let the memory leak test superfixture check that no leaks occurred.
+ });
+
+ test("returning a new shared pointer from interfaces implemented in JS code does not leak", function() {
+ var impl = cm.AbstractClass.implement({
+ returnsSharedPtr: function() {
+ return cm.embind_test_return_smart_derived_ptr();
+ }
+ });
+ cm.callReturnsSharedPtrMethod(impl);
+ impl.delete();
+ // Let the memory leak test superfixture check that no leaks occurred.
+ });
+
test("void methods work", function() {
var saved = {};
var impl = cm.AbstractClass.implement({
@@ -1728,6 +1771,116 @@ module({
e.delete();
f.delete();
});
+
+ BaseFixture.extend("memory view", function() {
+ test("can pass memory view from C++ to JS", function() {
+ var views = [];
+ cm.callWithMemoryView(function(view) {
+ views.push(view);
+ });
+ assert.equal(3, views.length);
+
+ assert.instanceof(views[0], Uint8Array);
+ assert.equal(8, views[0].length);
+ assert.deepEqual([0, 1, 2, 3, 4, 5, 6, 7], [].slice.call(new Uint8Array(views[0])));
+
+ assert.instanceof(views[1], Float32Array);
+ assert.equal(4, views[1].length);
+ assert.deepEqual([1.5, 2.5, 3.5, 4.5], [].slice.call(views[1]));
+
+ assert.instanceof(views[2], Int16Array);
+ assert.equal(4, views[2].length);
+ assert.deepEqual([1000, 100, 10, 1], [].slice.call(views[2]));
+ });
+ });
+
+ BaseFixture.extend("delete pool", function() {
+ test("can delete objects later", function() {
+ var v = new cm.ValHolder({});
+ v.deleteLater();
+ assert.deepEqual({}, v.getVal());
+ cm.flushPendingDeletes();
+ assert.throws(cm.BindingError, function() {
+ v.getVal();
+ });
+ });
+
+ test("calling deleteLater twice is an error", function() {
+ var v = new cm.ValHolder({});
+ v.deleteLater();
+ assert.throws(cm.BindingError, function() {
+ v.deleteLater();
+ });
+ });
+
+ test("deleteLater returns the object", function() {
+ var v = (new cm.ValHolder({})).deleteLater();
+ assert.deepEqual({}, v.getVal());
+ });
+
+ test("deleteLater throws if object is already deleted", function() {
+ var v = new cm.ValHolder({});
+ v.delete();
+ assert.throws(cm.BindingError, function() {
+ v.deleteLater();
+ });
+ });
+
+ test("delete throws if object is already scheduled for deletion", function() {
+ var v = new cm.ValHolder({});
+ v.deleteLater();
+ assert.throws(cm.BindingError, function() {
+ v.delete();
+ });
+ });
+
+ test("deleteLater invokes delay function", function() {
+ var runLater;
+ cm.setDelayFunction(function(fn) {
+ runLater = fn;
+ });
+
+ var v = new cm.ValHolder({});
+ assert.false(runLater);
+ v.deleteLater();
+ assert.true(runLater);
+ assert.false(v.isDeleted());
+ runLater();
+ assert.true(v.isDeleted());
+ });
+
+ test("deleteLater twice invokes delay function once", function() {
+ var count = 0;
+ var runLater;
+ cm.setDelayFunction(function(fn) {
+ ++count;
+ runLater = fn;
+ });
+
+ (new cm.ValHolder({})).deleteLater();
+ (new cm.ValHolder({})).deleteLater();
+ assert.equal(1, count);
+ runLater();
+ (new cm.ValHolder({})).deleteLater();
+ assert.equal(2, count);
+ });
+
+ test('The delay function is immediately invoked if the deletion queue is not empty', function() {
+ (new cm.ValHolder({})).deleteLater();
+ var count = 0;
+ cm.setDelayFunction(function(fn) {
+ ++count;
+ });
+ assert.equal(1, count);
+ });
+
+ // The idea is that an interactive application would
+ // periodically flush the deleteLater queue by calling
+ //
+ // setDelayFunction(function(fn) {
+ // setTimeout(fn, 0);
+ // });
+ });
});
/* global run_all_tests */
diff --git a/tests/embind/embind_benchmark.cpp b/tests/embind/embind_benchmark.cpp
index b6a834c9..5ae9a6be 100644
--- a/tests/embind/embind_benchmark.cpp
+++ b/tests/embind/embind_benchmark.cpp
@@ -50,6 +50,14 @@ extern void pass_gameobject_ptr_benchmark_embind_js();
extern void call_through_interface0();
extern void call_through_interface1();
+extern void call_through_interface2();
+
+extern void returns_val_benchmark();
+}
+
+emscripten::val returns_val(emscripten::val value)
+{
+ return emscripten::val(value.as<unsigned>() + 1);
}
class Vec3
@@ -135,19 +143,45 @@ class Interface
public:
virtual void call0() = 0;
virtual std::wstring call1(const std::wstring& str1, const std::wstring& str2) = 0;
+ virtual void call_with_typed_array(size_t size, const void*) = 0;
+ virtual void call_with_memory_view(size_t size, const void*) = 0;
};
+EMSCRIPTEN_SYMBOL(HEAP8);
+EMSCRIPTEN_SYMBOL(buffer);
+
+EMSCRIPTEN_SYMBOL(call0);
+EMSCRIPTEN_SYMBOL(call1);
+EMSCRIPTEN_SYMBOL(call_with_typed_array);
+EMSCRIPTEN_SYMBOL(call_with_memory_view);
+EMSCRIPTEN_SYMBOL(Uint8Array);
+
class InterfaceWrapper : public emscripten::wrapper<Interface>
{
public:
EMSCRIPTEN_WRAPPER(InterfaceWrapper);
void call0() override {
- return call<void>("call0");
+ return call<void>(call0_symbol);
}
std::wstring call1(const std::wstring& str1, const std::wstring& str2) {
- return call<std::wstring>("call1", str1, str2);
+ return call<std::wstring>(call1_symbol, str1, str2);
+ }
+
+ void call_with_typed_array(size_t size, const void* data) {
+ return call<void>(
+ call_with_typed_array_symbol,
+ emscripten::val::global(Uint8Array_symbol).new_(
+ emscripten::val::module_property(HEAP8_symbol)[buffer_symbol],
+ reinterpret_cast<uintptr_t>(data),
+ size));
+ }
+
+ void call_with_memory_view(size_t size, const void* data) {
+ return call<void>(
+ call_with_memory_view_symbol,
+ emscripten::memory_view(size, data));
}
};
@@ -180,6 +214,33 @@ void callInterface1(unsigned N, Interface& o) {
}
}
+void callInterface2(unsigned N, Interface& o) {
+ int i = 0;
+ for (unsigned i = 0; i < N; i += 8) {
+ o.call_with_typed_array(sizeof(int), &i);
+ o.call_with_typed_array(sizeof(int), &i);
+ o.call_with_typed_array(sizeof(int), &i);
+ o.call_with_typed_array(sizeof(int), &i);
+ o.call_with_typed_array(sizeof(int), &i);
+ o.call_with_typed_array(sizeof(int), &i);
+ o.call_with_typed_array(sizeof(int), &i);
+ o.call_with_typed_array(sizeof(int), &i);
+ }
+}
+
+void callInterface3(unsigned N, Interface& o) {
+ for (unsigned i = 0; i < N; i += 8) {
+ o.call_with_memory_view(sizeof(int), &i);
+ o.call_with_memory_view(sizeof(int), &i);
+ o.call_with_memory_view(sizeof(int), &i);
+ o.call_with_memory_view(sizeof(int), &i);
+ o.call_with_memory_view(sizeof(int), &i);
+ o.call_with_memory_view(sizeof(int), &i);
+ o.call_with_memory_view(sizeof(int), &i);
+ o.call_with_memory_view(sizeof(int), &i);
+ }
+}
+
EMSCRIPTEN_BINDINGS(benchmark)
{
using namespace emscripten;
@@ -225,6 +286,10 @@ EMSCRIPTEN_BINDINGS(benchmark)
function("callInterface0", &callInterface0);
function("callInterface1", &callInterface1);
+ function("callInterface2", &callInterface2);
+ function("callInterface3", &callInterface3);
+
+ function("returns_val", &returns_val);
}
void __attribute__((noinline)) emscripten_get_now_benchmark(int N)
@@ -435,4 +500,6 @@ int main()
emscripten_get_now();
call_through_interface0();
call_through_interface1();
+ call_through_interface2();
+ returns_val_benchmark();
}
diff --git a/tests/embind/embind_test.cpp b/tests/embind/embind_test.cpp
index 23761efc..3561b8a1 100644
--- a/tests/embind/embind_test.cpp
+++ b/tests/embind/embind_test.cpp
@@ -1084,6 +1084,7 @@ public:
return "optional" + s;
}
+ virtual std::shared_ptr<Derived> returnsSharedPtr() = 0;
virtual void differentArguments(int i, double d, unsigned char f, double q, std::string) = 0;
};
@@ -1103,6 +1104,10 @@ public:
}, s);
}
+ std::shared_ptr<Derived> returnsSharedPtr() {
+ return call<std::shared_ptr<Derived> >("returnsSharedPtr");
+ }
+
void differentArguments(int i, double d, unsigned char f, double q, std::string s) {
return call<void>("differentArguments", i, d, f, q, s);
}
@@ -1116,6 +1121,10 @@ class ConcreteClass : public AbstractClass {
void differentArguments(int i, double d, unsigned char f, double q, std::string s) {
}
+
+ std::shared_ptr<Derived> returnsSharedPtr() {
+ return std::shared_ptr<Derived>();
+ }
};
std::shared_ptr<AbstractClass> getAbstractClass() {
@@ -1130,6 +1139,11 @@ std::string callOptionalMethod(AbstractClass& ac, std::string s) {
return ac.optionalMethod(s);
}
+void callReturnsSharedPtrMethod(AbstractClass& ac) {
+ std::shared_ptr<Derived> sp = ac.returnsSharedPtr();
+ // unused: sp
+}
+
void callDifferentArguments(AbstractClass& ac, int i, double d, unsigned char f, double q, std::string s) {
return ac.differentArguments(i, d, f, q, s);
}
@@ -1145,9 +1159,29 @@ EMSCRIPTEN_BINDINGS(interface_tests) {
function("getAbstractClass", &getAbstractClass);
function("callAbstractMethod", &callAbstractMethod);
function("callOptionalMethod", &callOptionalMethod);
+ function("callReturnsSharedPtrMethod", &callReturnsSharedPtrMethod);
function("callDifferentArguments", &callDifferentArguments);
}
+template<typename T, size_t sizeOfArray>
+constexpr size_t getElementCount(T (&)[sizeOfArray]) {
+ return sizeOfArray;
+}
+
+static void callWithMemoryView(val v) {
+ // static so the JS test can read the memory after callTakeMemoryView runs
+ static unsigned char data[] = { 0, 1, 2, 3, 4, 5, 6, 7 };
+ v(memory_view(getElementCount(data), data));
+ static float f[] = { 1.5f, 2.5f, 3.5f, 4.5f };
+ v(typed_memory_view(getElementCount(f), f));
+ static short s[] = { 1000, 100, 10, 1 };
+ v(typed_memory_view(getElementCount(s), s));
+}
+
+EMSCRIPTEN_BINDINGS(memory_view_tests) {
+ function("callWithMemoryView", &callWithMemoryView);
+}
+
class HasExternalConstructor {
public:
HasExternalConstructor(const std::string& str)
@@ -1831,7 +1865,7 @@ int overloaded_function(int i, int j) {
class MultipleCtors {
public:
- int value;
+ int value = 0;
MultipleCtors(int i) {
value = 1;
@@ -1854,6 +1888,25 @@ public:
}
};
+class MultipleSmartCtors {
+public:
+ int value = 0;
+
+ MultipleSmartCtors(int i) {
+ value = 1;
+ assert(i == 10);
+ }
+ MultipleSmartCtors(int i, int j) {
+ value = 2;
+ assert(i == 20);
+ assert(j == 20);
+ }
+
+ int WhichCtorCalled() const {
+ return value;
+ }
+};
+
class MultipleOverloads {
public:
MultipleOverloads() {}
@@ -1960,7 +2013,15 @@ EMSCRIPTEN_BINDINGS(overloads) {
.constructor<int>()
.constructor<int, int>()
.constructor<int, int, int>()
- .function("WhichCtorCalled", &MultipleCtors::WhichCtorCalled);
+ .function("WhichCtorCalled", &MultipleCtors::WhichCtorCalled)
+ ;
+
+ class_<MultipleSmartCtors>("MultipleSmartCtors")
+ .smart_ptr<std::shared_ptr<MultipleSmartCtors>>()
+ .constructor(&std::make_shared<MultipleSmartCtors, int>)
+ .constructor(&std::make_shared<MultipleSmartCtors, int, int>)
+ .function("WhichCtorCalled", &MultipleSmartCtors::WhichCtorCalled)
+ ;
class_<MultipleOverloads>("MultipleOverloads")
.constructor<>()