diff options
-rw-r--r-- | demos/scons-embind/SConstruct | 23 | ||||
-rw-r--r-- | demos/scons-embind/bar.cpp | 2 | ||||
-rw-r--r-- | demos/scons-embind/foo.cpp | 11 | ||||
-rw-r--r-- | demos/scons-embind/test.js | 2 | ||||
-rw-r--r-- | scons-tools/closure.py | 28 | ||||
-rwxr-xr-x | scons-tools/emscripten.py | 359 | ||||
-rwxr-xr-x | scons-tools/llvm.py | 33 | ||||
-rw-r--r-- | src/analyzer.js | 5 | ||||
-rw-r--r-- | src/embind/embind.js | 99 | ||||
-rw-r--r-- | src/embind/emval.js | 177 | ||||
-rw-r--r-- | src/library.js | 20 | ||||
-rw-r--r-- | src/parseTools.js | 15 | ||||
-rw-r--r-- | system/include/emscripten/bind.h | 62 | ||||
-rw-r--r-- | system/include/emscripten/val.h | 116 | ||||
-rw-r--r-- | system/include/emscripten/wire.h | 81 | ||||
-rw-r--r-- | system/lib/embind/bind.cpp | 1 | ||||
-rw-r--r-- | tests/embind/build_benchmark | 2 | ||||
-rw-r--r-- | tests/embind/embind.benchmark.js | 35 | ||||
-rw-r--r-- | tests/embind/embind.test.js | 153 | ||||
-rw-r--r-- | tests/embind/embind_benchmark.cpp | 71 | ||||
-rw-r--r-- | tests/embind/embind_test.cpp | 65 | ||||
-rwxr-xr-x | tests/runner.py | 72 | ||||
-rw-r--r-- | tests/websockets.c | 2 | ||||
-rw-r--r-- | tools/cache.py | 5 |
24 files changed, 1238 insertions, 201 deletions
diff --git a/demos/scons-embind/SConstruct b/demos/scons-embind/SConstruct new file mode 100644 index 00000000..8afc3e27 --- /dev/null +++ b/demos/scons-embind/SConstruct @@ -0,0 +1,23 @@ +env = Environment( + toolpath=['../../scons-tools'], + tools=['cc', 'c++', 'ar', 'emscripten', 'llvm', 'closure'], + LLVM_ROOT='/opt/local/bin', + CLANG='clang-mp-3.2', + CLANGXX='clang++-mp-3.2', + LLVM_LINK='llvm-link-mp-3.2', + LLVM_OPT='opt-mp-3.2', + LLVM_DIS='llvm-dis-mp-3.2', + EMSCRIPTEN_VERSION_FILE=File('build/version_file'), + EMSCRIPTEN_SETTINGS={ + 'ASM_JS': 0, + }) +env['BUILDERS']['WrapInModule'] = Builder( + action='cp $SOURCE $TARGET', +) + +env.Append() +a1 = env.Object('build/foo.bc', 'foo.cpp') +a2 = env.Object('build/bar.bc', 'bar.cpp') +total = env.LLVMLink('build/thelibrary.bc', [a1, a2]) + +env.emscripten('build/thelibrary.js', total) diff --git a/demos/scons-embind/bar.cpp b/demos/scons-embind/bar.cpp new file mode 100644 index 00000000..a3908014 --- /dev/null +++ b/demos/scons-embind/bar.cpp @@ -0,0 +1,2 @@ +void foo() { +} diff --git a/demos/scons-embind/foo.cpp b/demos/scons-embind/foo.cpp new file mode 100644 index 00000000..61be501a --- /dev/null +++ b/demos/scons-embind/foo.cpp @@ -0,0 +1,11 @@ +#include <stdio.h> +#include <emscripten/bind.h> + +void print_some_stuff(int a, float b, const std::string& s) { + printf("print_some_stuff: %d, %f, %s\n", a, b, s.c_str()); +} + +EMSCRIPTEN_BINDINGS(foo) { + emscripten::function("print_some_stuff", &print_some_stuff); +} + diff --git a/demos/scons-embind/test.js b/demos/scons-embind/test.js new file mode 100644 index 00000000..a6252fd8 --- /dev/null +++ b/demos/scons-embind/test.js @@ -0,0 +1,2 @@ +var thelibrary = require('./build/thelibrary.js'); +thelibrary.Module.print_some_stuff(1, 2, 'hello world'); diff --git a/scons-tools/closure.py b/scons-tools/closure.py new file mode 100644 index 00000000..8f53e507 --- /dev/null +++ b/scons-tools/closure.py @@ -0,0 +1,28 @@ +import os.path +from SCons.Builder import Builder + +def generate(env): + def depend_on_closure_compiler(target, source, env): + env.Depends(target, env['CLOSURE_COMPILER']) + return target, source + + ClosureCompiler = Builder( + action='$JAVA $JAVAFLAGS -jar $CLOSURE_COMPILER $CLOSURE_FLAGS --js_output_file $TARGET $SOURCES', + emitter=depend_on_closure_compiler + ) + + closure = os.path.join( + os.path.dirname(__file__), + '..', + 'third_party', + 'closure-compiler', + 'compiler.jar') + closure = env.File(closure) + + env['JAVA'] = 'java' + env['CLOSURE_COMPILER'] = closure + env.Append( + BUILDERS={'ClosureCompiler':ClosureCompiler}) + +def exists(_env): + return True diff --git a/scons-tools/emscripten.py b/scons-tools/emscripten.py new file mode 100755 index 00000000..473c51ad --- /dev/null +++ b/scons-tools/emscripten.py @@ -0,0 +1,359 @@ +import hashlib
+import json
+import sys
+import os
+from SCons.Defaults import Delete
+from SCons.Builder import Builder
+from SCons.Scanner import Scanner
+
+def exists(env):
+ return True
+
+def _expand_settings_flags(settings, env):
+ return [
+ ('-s%s=%s' % (KEY, json.dumps(VALUE).replace('"', '\\"')))
+ for KEY, VALUE in settings.items() ]
+
+emscripten_version_files = {}
+
+def build_version_file(env):
+ if not env.subst('$EMSCRIPTEN_VERSION_FILE'):
+ raise AssertionError('Must set EMSCRIPTEN_VERSION_FILE in environment')
+ if not env.subst('$EMSCRIPTEN_TEMP_DIR'):
+ raise AssertionError('Must set EMSCRIPTEN_TEMP_DIR in environment')
+
+ EMSCRIPTEN_DEPENDENCIES = [
+ env.Glob('${EMSCRIPTEN_HOME}/src/*.js'),
+ env.Glob('${EMSCRIPTEN_HOME}/tools/*.py'),
+ '${EMSCRIPTEN_HOME}/emscripten.py',
+ ]
+ if env.subst('$EMSCRIPTEN_SHELL'):
+ EMSCRIPTEN_DEPENDENCIES.append('$EMSCRIPTEN_SHELL')
+
+ def touch_file(target, source, env):
+ m = hashlib.md5()
+ for s in source:
+ m.update(file(s.abspath, 'rb').read())
+ for t in target:
+ file(t.abspath, 'wb').write(m.hexdigest())
+
+ [emscripten_version_file] = env.Command(
+ '$EMSCRIPTEN_VERSION_FILE',
+ EMSCRIPTEN_DEPENDENCIES,
+ touch_file)
+
+ env.AddPostAction(
+ emscripten_version_file,
+ Delete(env.Dir('$EMSCRIPTEN_TEMP_DIR').abspath))
+
+ return emscripten_version_file
+
+def get_emscripten_version_file(env):
+ EMSCRIPTEN_HOME = env.Dir('$EMSCRIPTEN_HOME').abspath
+ try:
+ version_file = emscripten_version_files[EMSCRIPTEN_HOME]
+ except KeyError:
+ version_file = build_version_file(env)
+ emscripten_version_files[EMSCRIPTEN_HOME] = version_file
+ return version_file
+
+def depend_on_emscripten(node, env, path):
+ return [get_emscripten_version_file(env)]
+
+EmscriptenScanner = Scanner(
+ name='emscripten',
+ function=depend_on_emscripten)
+
+def setExtension(filename, extension):
+ return os.path.splitext(filename)[0] + '.' + extension
+
+def emscripten(env, target_js, source_bc):
+ env = env.Clone()
+ def buildName(extension):
+ return setExtension(target_js, extension)
+
+ # for debugging and reading generated code.
+ # not in critical path, uses spare cores.
+ env.LLVMDis(buildName('ll'), source_bc)
+
+ [opt_ll] = env.LLVMOpt(
+ buildName('opt.ll'),
+ source_bc,
+ LLVM_OPT_FLAGS=['-S'])
+
+ [raw_emscripten_js] = env.Emscripten(
+ buildName('raw.js'),
+ [opt_ll])
+
+ [optimized_js] = env.JSOptimizer(
+ buildName('opt.js'),
+ raw_emscripten_js)
+
+ prejs = [
+ env['EMSCRIPTEN_PREJS'],
+ '${EMSCRIPTEN_HOME}/src/embind/emval.js',
+ '${EMSCRIPTEN_HOME}/src/embind/embind.js' ]
+
+ [concatenated_js] = env.Concatenate(
+ buildName('concat.js'),
+ [ prejs,
+ optimized_js,
+ env['EMSCRIPTEN_POSTJS'] ])
+
+ DISABLE_EMSCRIPTEN_WARNINGS = [
+ '--jscomp_error', 'ambiguousFunctionDecl',
+ '--jscomp_error', 'checkDebuggerStatement',
+ '--jscomp_off', 'checkTypes',
+ '--jscomp_off', 'checkVars',
+ '--jscomp_error', 'deprecated',
+ '--jscomp_off', 'duplicate',
+ #'--jscomp_error', 'es5strict',
+ '--jscomp_off', 'missingProperties', # TODO: fix emscripten and turn this one on
+ '--jscomp_error', 'undefinedNames',
+ '--jscomp_off', 'undefinedVars', # TODO: fix emscripten and turn this one on
+ '--jscomp_off', 'uselessCode',
+ '--jscomp_off', 'globalThis',
+ ]
+
+ [iter_global_emscripten_js] = env.Concatenate(
+ buildName('iter.js'),
+ [ prejs,
+ raw_emscripten_js,
+ env['EMSCRIPTEN_POSTJS'] ])
+
+ [global_cc_emscripten_js] = env.ClosureCompiler(
+ buildName('global.closure.js'),
+ concatenated_js,
+ CLOSURE_FLAGS=['--language_in', 'ECMASCRIPT5']+DISABLE_EMSCRIPTEN_WARNINGS+['--formatting', 'PRETTY_PRINT', '--compilation_level', 'SIMPLE_OPTIMIZATIONS'])
+
+ #env.Append(
+ # NODEJSFLAGS=['--max-stack-size=1000000000'],
+ # UGLIFYJSFLAGS=['--stats', '-c', 'warnings=false', '-b'])
+ #env.UglifyJS(
+ # buildName('global.uglify.js'),
+ # concatenated_js)
+
+ [closure_js] = env.ClosureCompiler(
+ buildName('closure.js'),
+ concatenated_js,
+ CLOSURE_FLAGS=['--language_in', 'ECMASCRIPT5']+DISABLE_EMSCRIPTEN_WARNINGS+['--formatting', 'PRETTY_PRINT', '--compilation_level', 'ADVANCED_OPTIMIZATIONS'])
+
+ [global_emscripten_min_js] = env.JSOptimizer(
+ buildName('global.min.js'),
+ closure_js,
+ JS_OPTIMIZER_PASSES=['simplifyExpressionsPost', 'compress', 'last'])
+
+ [emscripten_iteration_js] = env.WrapInModule(
+ buildName('iteration.js'),
+ iter_global_emscripten_js)
+
+ [emscripten_js] = env.WrapInModule(
+ buildName('debug.js'),
+ global_cc_emscripten_js)
+
+ [emscripten_min_js] = env.WrapInModule(
+ buildName('min.js'),
+ global_emscripten_min_js)
+
+ env.InstallAs(buildName('js'), emscripten_js)
+
+ return [emscripten_iteration_js, emscripten_js, emscripten_min_js]
+
+LIBC_SOURCES = [
+ 'system/lib/dlmalloc.c',
+ 'system/lib/libc/musl/src/string/wmemset.c',
+ 'system/lib/libc/musl/src/string/wmemcpy.c',
+]
+
+LIBCXX_SOURCES = [os.path.join('system/lib/libcxx', x) for x in [
+ 'algorithm.cpp',
+ 'bind.cpp',
+ #'chrono.cpp',
+ #'condition_variable.cpp',
+ #'debug.cpp',
+ #'exception.cpp',
+ 'future.cpp',
+ 'hash.cpp',
+ #'ios.cpp',
+ #'iostream.cpp',
+ 'memory.cpp',
+ 'mutex.cpp',
+ 'new.cpp',
+ 'random.cpp',
+ 'regex.cpp',
+ 'stdexcept.cpp',
+ 'string.cpp',
+ 'strstream.cpp',
+ 'system_error.cpp',
+ #'thread.cpp',
+ 'typeinfo.cpp',
+ 'utility.cpp',
+ 'valarray.cpp',
+]]
+
+LIBCXXABI_SOURCES = [os.path.join('system/lib/libcxxabi/src', x) for x in [
+ 'private_typeinfo.cpp'
+]]
+
+# MAJOR HACK ALERT
+# ugh, SCons imports tool .py files multiple times, meaning that global variables aren't really global
+# store our "globals" "privately" on the SCons object :(
+import SCons
+
+def build_libembind(env):
+ emscripten_temp_dir = env.Dir('$EMSCRIPTEN_TEMP_DIR').abspath
+ try:
+ libembind_cache = SCons.__emscripten_libembind_cache
+ except AttributeError:
+ libembind_cache = {}
+ SCons.__emscripten_libembind_cache = libembind_cache
+ try:
+ return libembind_cache[emscripten_temp_dir]
+ except KeyError:
+ pass
+
+ libembind = env.Object(
+ '$EMSCRIPTEN_TEMP_DIR/internal_libs/bind',
+ '$EMSCRIPTEN_HOME/system/lib/embind/bind.cpp')
+ env.Depends(libembind, get_emscripten_version_file(env))
+ libembind_cache[emscripten_temp_dir] = libembind
+ return libembind
+
+def build_libcxx(env):
+ emscripten_temp_dir = env.Dir('$EMSCRIPTEN_TEMP_DIR').abspath
+ try:
+ libcxx_cache = SCons.__emscripten_libcxx_cache
+ except AttributeError:
+ libcxx_cache = {}
+ SCons.__emscripten_libcxx_cache = libcxx_cache
+ try:
+ return libcxx_cache[emscripten_temp_dir]
+ except KeyError:
+ pass
+
+ env = env.Clone()
+ env['CXXFLAGS'] = filter(lambda e: e not in ('-Werror', '-Wall'), env['CXXFLAGS'])
+ env['CCFLAGS'] = filter(lambda e: e not in ('-Werror', '-Wall'), env['CCFLAGS'])
+
+ objs = [
+ env.Object(
+ '${EMSCRIPTEN_TEMP_DIR}/libcxx_objects/' + os.path.splitext(o)[0] + '.bc',
+ '${EMSCRIPTEN_HOME}/' + o)
+ for o in LIBC_SOURCES + LIBCXXABI_SOURCES + LIBCXX_SOURCES]
+ env.Depends(objs, get_emscripten_version_file(env))
+
+ libcxx = env.Library('${EMSCRIPTEN_TEMP_DIR}/internal_libs/libcxx', objs)
+ libcxx_cache[emscripten_temp_dir] = libcxx
+ return libcxx
+
+def generate(env):
+ env.SetDefault(
+ PYTHON=sys.executable,
+ NODEJS='node',
+ JS_ENGINE='$NODEJS',
+ EMSCRIPTEN_FLAGS=['-v', '-j', '--suppressUsageWarning'],
+ EMSCRIPTEN_TEMP_DIR=env.Dir('#/emscripten.tmp'),
+ _expand_settings_flags=_expand_settings_flags,
+ EMSCRIPTEN_PREJS=[],
+ EMSCRIPTEN_POSTJS=[],
+ EMSCRIPTEN_SETTINGS={},
+ _EMSCRIPTEN_SETTINGS_FLAGS='${_expand_settings_flags(EMSCRIPTEN_SETTINGS, __env__)}',
+ JS_OPTIMIZER_PASSES=[],
+ LLVM_OPT_PASSES=['-std-compile-opts', '-std-link-opts'],
+
+ EMSCRIPTEN_HOME=env.Dir(os.path.join(os.path.dirname(__file__), '..')),
+ )
+
+ env.Replace(
+ CC='${LLVM_ROOT}/${CLANG}',
+ CXX='${LLVM_ROOT}/${CLANGXX}',
+ AR='${LLVM_ROOT}/${LLVM_LINK}',
+ ARCOM='$AR -o $TARGET $SOURCES',
+ OBJSUFFIX='.bc',
+ LIBPREFIX='',
+ LIBSUFFIX='.bc',
+ RANLIBCOM='',
+ CCFLAGS=[
+ '-U__STRICT_ANSI__',
+ '-target', 'le32-unknown-nacl',
+ '-nostdinc',
+ '-Wno-#warnings',
+ '-Wno-error=unused-variable',
+ '-Werror',
+ '-Os',
+ '-fno-threadsafe-statics',
+ '-fvisibility=hidden',
+ '-fvisibility-inlines-hidden',
+ '-Xclang', '-nostdinc++',
+ '-Xclang', '-nobuiltininc',
+ '-Xclang', '-nostdsysteminc',
+ '-Xclang', '-isystem$EMSCRIPTEN_HOME/system/include',
+ '-Xclang', '-isystem$EMSCRIPTEN_HOME/system/include/libc',
+ '-Xclang', '-isystem$EMSCRIPTEN_HOME/system/include/libcxx',
+ '-Xclang', '-isystem$EMSCRIPTEN_HOME/system/include/bsd',
+ '-emit-llvm'],
+ CXXFLAGS=['-std=c++11', '-fno-exceptions'],
+ )
+ env.Append(CPPDEFINES=[
+ 'EMSCRIPTEN',
+ '__EMSCRIPTEN__',
+ '__STDC__',
+ '__IEEE_LITTLE_ENDIAN',
+ ])
+
+ env['BUILDERS']['Emscripten'] = Builder(
+ action='$PYTHON ${EMSCRIPTEN_HOME}/emscripten.py $EMSCRIPTEN_FLAGS $_EMSCRIPTEN_SETTINGS_FLAGS --temp-dir=$EMSCRIPTEN_TEMP_DIR --compiler $JS_ENGINE --relooper=third-party/relooper.js $SOURCE > $TARGET',
+ target_scanner=EmscriptenScanner)
+
+ env['BUILDERS']['JSOptimizer'] = Builder(
+ action='$JS_ENGINE ${EMSCRIPTEN_HOME}/tools/js-optimizer.js $SOURCE $JS_OPTIMIZER_PASSES > $TARGET',
+ target_scanner=EmscriptenScanner)
+
+ def depend_on_embedder(target, source, env):
+ env.Depends(target, env['JS_EMBEDDER'])
+ return target, source
+
+ def embed_files_in_js(target, source, env, for_signature):
+ return '$PYTHON $JS_EMBEDDER $SOURCE.srcpath > $TARGET'
+
+ def get_files_in_tree(node, env, path):
+ tree_paths = []
+ for root, dirs, files in os.walk(str(node)):
+ tree_paths += [os.path.join(root, f) for f in files]
+ return [env.File(p) for p in tree_paths]
+
+ env.SetDefault(
+ JS_EMBEDDER=env.File('#/bin/embed_files_in_js.py'))
+
+ FileTreeScanner = Scanner(
+ function=get_files_in_tree,
+ name='FileTreeScanner',
+ recursive=False)
+
+ env['BUILDERS']['EmbedFilesInJS'] = Builder(
+ generator=embed_files_in_js,
+ emitter=depend_on_embedder,
+ source_scanner=FileTreeScanner)
+
+ env.AddMethod(emscripten)
+
+ def ConcatenateAction(target, source, env):
+ [target] = target
+ total = ''.join(file(str(s), 'rb').read() for s in source)
+ file(str(target), 'wb').write(total)
+ env['BUILDERS']['Concatenate'] = Builder(action=ConcatenateAction)
+
+ libembind = build_libembind(env)
+ libcxx = build_libcxx(env)
+
+ # should embind be its own tool?
+ env.Append(
+ CPPPATH=[
+ '${EMSCRIPTEN_HOME}/system/include' ],
+ LIBPATH=['$EMSCRIPTEN_TEMP_DIR/internal_libs'],
+ LIBS=[
+ libembind,
+ libcxx,
+ ],
+ )
+
diff --git a/scons-tools/llvm.py b/scons-tools/llvm.py new file mode 100755 index 00000000..f272bd16 --- /dev/null +++ b/scons-tools/llvm.py @@ -0,0 +1,33 @@ +from SCons.Scanner.Prog import scan +from SCons.Builder import Builder + +def exists(env): + return True + +def add_libraries(target, source, env): + unique = [] + lib_nodes = set() + for x in scan(None, env, tuple(map(env.Dir, env['LIBPATH']))): + if x in lib_nodes: + continue + lib_nodes.add(x) + unique.append(x) + return (target, source + unique) + +def generate(env): + env.SetDefault( + CLANG='clang', + CLANGXX='clang++', + LLVM_DIS='llvm-dis', + LLVM_OPT='opt', + LLVM_LINK='llvm-link') + + env['BUILDERS']['LLVMDis'] = Builder( + action='${LLVM_ROOT}/$LLVM_DIS -o=$TARGET $SOURCE') + + env['BUILDERS']['LLVMOpt'] = Builder( + action='${LLVM_ROOT}/$LLVM_OPT $LLVM_OPT_FLAGS $LLVM_OPT_PASSES -o=$TARGET $SOURCE') + + env['BUILDERS']['LLVMLink'] = Builder( + action='${LLVM_ROOT}/$LLVM_LINK -o=$TARGET $SOURCES', + emitter=add_libraries) diff --git a/src/analyzer.js b/src/analyzer.js index a131406c..2cc46ab6 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -411,8 +411,9 @@ function analyzer(data, sidePass) { // legalize parameters legalizeFunctionParameters(value.params); // legalize return value, if any - if (value.assignTo && isIllegalType(item.type)) { - bits = getBits(value.type); + var returnType = getReturnType(item.type); + if (value.assignTo && isIllegalType(returnType)) { + bits = getBits(returnType); var elements = getLegalVars(item.assignTo, bits); // legalize return value value.assignTo = elements[0].ident; 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/src/library.js b/src/library.js index 344ec8d6..caf1dd0b 100644 --- a/src/library.js +++ b/src/library.js @@ -3576,14 +3576,14 @@ LibraryManager.library = { return -1; }, fscanf__deps: ['$FS', '__setErrNo', '$ERRNO_CODES', - '_scanString', 'getc', 'ungetc'], + '_scanString', 'fgetc', 'fseek', 'ftell'], fscanf: function(stream, format, varargs) { // int fscanf(FILE *restrict stream, const char *restrict format, ... ); // http://pubs.opengroup.org/onlinepubs/000095399/functions/scanf.html if (FS.streams[stream]) { - var stack = []; - var get = function() { var ret = _fgetc(stream); stack.push(ret); return ret }; - var unget = function(c) { return _ungetc(stack.pop(), stream) }; + var i = _ftell(stream), SEEK_SET = 0; + var get = function () { i++; return _fgetc(stream); }; + var unget = function () { _fseek(stream, --i, SEEK_SET); }; return __scanString(format, get, unget, varargs); } else { return -1; @@ -6794,19 +6794,17 @@ LibraryManager.library = { 26: 'Text file busy', 18: 'Invalid cross-device link' }, + __errno_state: 0, + __setErrNo__deps: ['__errno_state'], + __setErrNo__postset: '___errno_state = Runtime.staticAlloc(4);', __setErrNo: function(value) { // For convenient setting and returning of errno. - if (!___setErrNo.ret) ___setErrNo.ret = allocate([0], 'i32', ALLOC_NORMAL); - {{{ makeSetValue('___setErrNo.ret', '0', 'value', 'i32') }}} + {{{ makeSetValue('___errno_state', '0', 'value', 'i32') }}} return value; }, __errno_location__deps: ['__setErrNo'], __errno_location: function() { - if (!___setErrNo.ret) { - ___setErrNo.ret = allocate([0], 'i32', ALLOC_NORMAL); - {{{ makeSetValue('___setErrNo.ret', '0', '0', 'i32') }}} - } - return ___setErrNo.ret; + return ___errno_state; }, __errno: '__errno_location', diff --git a/src/parseTools.js b/src/parseTools.js index 1a58b4e7..f30883b5 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -286,11 +286,22 @@ function isVarArgsFunctionType(type) { return type.substr(-varArgsSuffix.length) == varArgsSuffix; } +function getNumVars(type) { // how many variables are needed to represent this type + if (type in Runtime.FLOAT_TYPES) return 1; + return Math.max(getNumIntChunks(type), 1); +} + function countNormalArgs(type, out) { out = out || {}; if (!isFunctionType(type, out)) return -1; - if (isVarArgsFunctionType(type)) out.numArgs--; - return out.numArgs; + var ret = 0; + if (out.segments) { + for (var i = 0; i < out.segments.length; i++) { + ret += getNumVars(out.segments[i][0].text); + } + } + if (isVarArgsFunctionType(type)) ret--; + return ret; } function addIdent(token) { 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<>() diff --git a/tests/runner.py b/tests/runner.py index cff43210..37fb2bbb 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -1179,6 +1179,53 @@ m_divisor is 1091269979 ''' self.do_run(src, 'Succeeded!') + def test_i64_varargs(self): + if Settings.USE_TYPED_ARRAYS != 2: return self.skip('full i64 stuff only in ta2') + + src = r''' + #include <stdio.h> + #include <stdint.h> + #include <stdarg.h> + + int64_t ccv_cache_generate_signature(char *msg, int len, int64_t sig_start, ...) { + if (sig_start < 10123) + printf("%s\n", msg+len); + va_list v; + va_start(v, sig_start); + if (sig_start > 1413) + printf("%d\n", va_arg(v, int)); + else + printf("nada\n"); + va_end(v); + return len*sig_start*(msg[0]+1); + } + + int main(int argc, char **argv) + { + for (int i = 0; i < argc; i++) { + int64_t x; + if (i % 123123 == 0) + x = ccv_cache_generate_signature(argv[i], i+2, (int64_t)argc*argc, 54.111); + else + x = ccv_cache_generate_signature(argv[i], i+2, (int64_t)argc*argc, 13); + printf("%lld\n", x); + } + }; + ''' + self.do_run(src, '''in/this.program +nada +1536 +a +nada +5760 +fl +nada +6592 +sdfasdfasdf +nada +7840 +''', 'waka fleefl asdfasdfasdfasdf'.split(' ')) + def test_i32_mul_precise(self): if self.emcc_args == None: return self.skip('needs ta2') @@ -6316,6 +6363,31 @@ def process(filename): self.emcc_args += ['--embed-file', 'eol.txt'] self.do_run(src, 'SUCCESS\n') + def test_fscanf(self): + if self.emcc_args is None: return self.skip('requires emcc') + open(os.path.join(self.get_dir(), 'three_numbers.txt'), 'w').write('''-1 0.1 -.1''') + src = r''' + #include <stdio.h> + #include <assert.h> + #include <float.h> + int main() + { + float x = FLT_MAX, y = FLT_MAX, z = FLT_MAX; + + FILE* fp = fopen("three_numbers.txt", "r"); + if (fp) { + int match = fscanf(fp, " %f %f %f ", &x, &y, &z); + printf("match = %d\n", match); + printf("x = %0.1f, y = %0.1f, z = %0.1f\n", x, y, z); + } else { + printf("failed to open three_numbers.txt\n"); + } + return 0; + } + ''' + self.emcc_args += ['--embed-file', 'three_numbers.txt'] + self.do_run(src, 'match = 3\nx = -1.0, y = 0.1, z = -0.1\n') + def test_folders(self): add_pre_run = ''' def process(filename): diff --git a/tests/websockets.c b/tests/websockets.c index 8e719baa..8882f5ba 100644 --- a/tests/websockets.c +++ b/tests/websockets.c @@ -56,7 +56,7 @@ unsigned int get_all_buf(int sock, char* output, unsigned int maxsize) } if(n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { - fprintf(stderr, "error in get_all_buf!"); + fprintf(stderr, "error in get_all_buf! %d", errno); exit(EXIT_FAILURE); } return offset; diff --git a/tools/cache.py b/tools/cache.py index 13a53fe9..c316a1fd 100644 --- a/tools/cache.py +++ b/tools/cache.py @@ -49,7 +49,10 @@ class JCache: def ensure(self): self.cache.ensure() if not os.path.exists(self.dirname): - os.makedirs(self.dirname) + try: + os.makedirs(self.dirname) + except (IOError, OSError): + pass def get_shortkey(self, keys): if type(keys) not in [list, tuple]: |