diff --git a/src/lib/libembind.js b/src/lib/libembind.js index 62a11711938d9..4c11a83e36791 100644 --- a/src/lib/libembind.js +++ b/src/lib/libembind.js @@ -36,7 +36,13 @@ var LibraryEmbind = { $PureVirtualError: undefined, $GenericWireTypeSize: {{{ 2 * POINTER_SIZE }}}, #if EMBIND_AOT - $InvokerFunctions: '<<< EMBIND_AOT_OUTPUT >>>', + $InvokerFunctions: '<<< EMBIND_AOT_INVOKERS >>>', +#if MODULARIZE == 'instance' + // embindUpdateExports is called in the generated code after embind is + // initialized. It will link the bindings to the ES module exports. + $embindUpdateExports: '<<< EMBIND_AOT_UPDATE_EXPORTS >>>', + $embindUpdateExports__postset: 'addOnInit(embindUpdateExports);', +#endif #endif // If register_type is used, emval will be registered multiple times for // different type id's, but only a single type object is needed on the JS side @@ -2362,4 +2368,8 @@ var LibraryEmbind = { }, }; +#if MODULARIZE == 'instance' +extraLibraryFuncs.push('$embindUpdateExports'); +#endif + addToLibrary(LibraryEmbind); diff --git a/src/lib/libembind_gen.js b/src/lib/libembind_gen.js index 557bf79dc027e..bbc73803b2a64 100644 --- a/src/lib/libembind_gen.js +++ b/src/lib/libembind_gen.js @@ -44,6 +44,7 @@ var LibraryEmbind = { }, $FunctionDefinition__deps: ['$createJsInvoker', '$createJsInvokerSignature', '$emittedFunctions'], $FunctionDefinition: class { + hasPublicSymbol = true; constructor(name, returnType, argumentTypes, functionIndex, thisType = null, isNonnullReturn = false, isAsync = false) { this.name = name; this.returnType = returnType; @@ -157,6 +158,7 @@ var LibraryEmbind = { } }, $ClassDefinition: class { + hasPublicSymbol = true; constructor(typeId, name, base = null) { this.typeId = typeId; this.name = name; @@ -268,6 +270,7 @@ var LibraryEmbind = { } }, $ConstantDefinition: class { + hasPublicSymbol = true; constructor(type, name) { this.type = type; this.name = name; @@ -278,6 +281,7 @@ var LibraryEmbind = { } }, $EnumDefinition: class { + hasPublicSymbol = true; constructor(typeId, name) { this.typeId = typeId; this.name = name; @@ -454,14 +458,27 @@ var LibraryEmbind = { print() { const out = ['{\n']; + const publicSymbols = []; for (const def of this.definitions) { + if (def.hasPublicSymbol) { + publicSymbols.push(def.name); + } if (!def.printJs) { continue; } def.printJs(out); } - out.push('}') - console.log(out.join('')); + out.push('}\n'); + let updateExports = '() => {\n'; + for (const publicSymbol of publicSymbols) { + updateExports += `__exp_${publicSymbol} = Module['${publicSymbol}'];\n` + } + updateExports += '}\n'; + console.log(JSON.stringify({ + 'invokers': out.join(''), + publicSymbols, + updateExports + })); } }, diff --git a/src/settings_internal.js b/src/settings_internal.js index 8b4d82426f026..2d5f6e6912b10 100644 --- a/src/settings_internal.js +++ b/src/settings_internal.js @@ -89,6 +89,9 @@ var USE_ASAN = false; // Whether embind has been enabled. var EMBIND = false; +// Symbols that embind exports. Only available when using EMBIND_AOT. +var EMBIND_EXPORTS = []; + // Whether a TypeScript definition file has been requested. var EMIT_TSD = false; diff --git a/test/modularize_instance_embind.cpp b/test/modularize_instance_embind.cpp new file mode 100644 index 0000000000000..542fca8fc14ec --- /dev/null +++ b/test/modularize_instance_embind.cpp @@ -0,0 +1,26 @@ +#include +#include +#include + +using namespace emscripten; + +void foo() { + printf("foo\n"); +} + +struct Bar { + void print() { + printf("bar\n"); + } +}; + +int main() { + printf("main\n"); +} + +EMSCRIPTEN_BINDINGS(xxx) { + function("foo", &foo); + class_("Bar") + .constructor<>() + .function("print", &Bar::print); +} diff --git a/test/test_other.py b/test/test_other.py index 8f16cef823248..9bf3d4adde5c8 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -477,6 +477,24 @@ def test_modularize_instance(self, args): self.assertContained('main1\nmain2\nfoo\nbar\nbaz\n', self.run_js('runner.mjs')) + def test_modularize_instance_embind(self): + self.run_process([EMCC, test_file('modularize_instance_embind.cpp'), + '-sMODULARIZE=instance', + '-lembind', + '-sEMBIND_AOT', + '-o', 'modularize_instance_embind.mjs']) + + create_file('runner.mjs', ''' + import init, { foo, Bar } from "./modularize_instance_embind.mjs"; + await init(); + foo(); + const bar = new Bar(); + bar.print(); + bar.delete(); + ''') + + self.assertContained('main\nfoo\nbar\n', self.run_js('runner.mjs')) + def test_emcc_out_file(self): # Verify that "-ofile" works in addition to "-o" "file" self.run_process([EMCC, '-c', '-ofoo.o', test_file('hello_world.c')]) @@ -3402,7 +3420,7 @@ def test_embind_tsgen_end_to_end(self, opts, tsc_opts): # Check that TypeScript generation works and that the program is runs as # expected. self.emcc(test_file('other/embind_tsgen.cpp'), - ['-o', 'embind_tsgen.js', '-lembind', '--emit-tsd', 'embind_tsgen.d.ts'] + opts) + ['-o', 'embind_tsgen.js', '-sEMBIND_AOT', '-lembind', '--emit-tsd', 'embind_tsgen.d.ts'] + opts) # Test that the output compiles with a TS file that uses the defintions. shutil.copyfile(test_file('other/embind_tsgen_main.ts'), 'main.ts') diff --git a/tools/link.py b/tools/link.py index 11fed89ae1855..5ac6aa9eb2a55 100644 --- a/tools/link.py +++ b/tools/link.py @@ -2047,9 +2047,13 @@ def phase_emit_tsd(options, wasm_target, js_target, js_syms, metadata, linker_in def phase_embind_aot(wasm_target, js_syms, linker_inputs): out = run_embind_gen(wasm_target, js_syms, {}, linker_inputs) if DEBUG: - write_file(in_temp('embind_aot.js'), out) + write_file(in_temp('embind_aot.json'), out) + out = json.loads(out) src = read_file(final_js) - src = do_replace(src, '<<< EMBIND_AOT_OUTPUT >>>', out) + src = do_replace(src, '<<< EMBIND_AOT_INVOKERS >>>', out['invokers']) + if settings.MODULARIZE == 'instance': + settings.EMBIND_EXPORTS = out['publicSymbols'] + src = do_replace(src, '<<< EMBIND_AOT_UPDATE_EXPORTS >>>', out['updateExports']) write_file(final_js, src) @@ -2488,7 +2492,7 @@ def modularize(): # Export using a UMD style export, or ES6 exports if selected if settings.EXPORT_ES6: if settings.MODULARIZE == 'instance': - exports = settings.EXPORTED_FUNCTIONS + settings.EXPORTED_RUNTIME_METHODS + exports = settings.EXPORTED_FUNCTIONS + settings.EXPORTED_RUNTIME_METHODS + settings.EMBIND_EXPORTS # Declare a top level var for each export so that code in the init function # can assign to it and update the live module bindings. src += 'var ' + ', '.join(['__exp_' + export for export in exports]) + ';\n'