Skip to content

Commit 7d117c9

Browse files
committed
embind: Fix method pointers for AOT JS generation.
Previously, AOT JS glue code for method pointers used the address of the method pointer as a unique ID to match JS glue with the binding. However, sometimes static initializers are run in different orders when running the AOT generation code versus when the code is run normally. This caused the method pointer address to be different and the binding didn't match up. Instead of using the function pointer or the method pointer address we now use a generated signature for the invoker to match up the glue code. This works for both types of pointers and also has the advantage that many of the glue code functions are nearly identical and can be reusued (closure arguments make them behave differently). Fixes #20994
1 parent 8d4e172 commit 7d117c9

File tree

5 files changed

+80
-19
lines changed

5 files changed

+80
-19
lines changed

src/embind/embind.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,7 @@ var LibraryEmbind = {
781781
#endif
782782
#if EMBIND_AOT
783783
'$InvokerFunctions',
784+
'$createJsInvokerSignature',
784785
#endif
785786
#if ASYNCIFY
786787
'$Asyncify',
@@ -884,7 +885,7 @@ var LibraryEmbind = {
884885
#else
885886
// Builld the arguments that will be passed into the closure around the invoker
886887
// function.
887-
var closureArgs = [throwBindingError, cppInvokerFunc, cppTargetFunc, runDestructors, argTypes[0], argTypes[1]];
888+
var closureArgs = [humanName, throwBindingError, cppInvokerFunc, cppTargetFunc, runDestructors, argTypes[0], argTypes[1]];
888889
#if EMSCRIPTEN_TRACING
889890
closureArgs.push(Module);
890891
#endif
@@ -903,9 +904,10 @@ var LibraryEmbind = {
903904
}
904905

905906
#if EMBIND_AOT
906-
var invokerFn = InvokerFunctions[cppTargetFunc].apply(null, closureArgs);
907+
var signature = createJsInvokerSignature(argTypes, isClassMethodFunc, returns, isAsync);
908+
var invokerFn = InvokerFunctions[signature].apply(null, closureArgs);
907909
#else
908-
let [args, invokerFnBody] = createJsInvoker(humanName, argTypes, isClassMethodFunc, returns, isAsync);
910+
let [args, invokerFnBody] = createJsInvoker(argTypes, isClassMethodFunc, returns, isAsync);
909911
args.push(invokerFnBody);
910912
var invokerFn = newFunc(Function, args).apply(null, closureArgs);
911913
#endif

src/embind/embind_gen.js

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ var LibraryEmbind = {
4040
this.destructorType = 'none'; // Same as emval.
4141
}
4242
},
43-
$FunctionDefinition__deps: ['$createJsInvoker'],
43+
$FunctionDefinition__deps: ['$createJsInvoker', '$createJsInvokerSignature'],
4444
$FunctionDefinition: class {
4545
constructor(name, returnType, argumentTypes, functionIndex, thisType = null, isAsync = false) {
4646
this.name = name;
@@ -95,7 +95,7 @@ var LibraryEmbind = {
9595
return ret;
9696
}
9797

98-
printJs(out) {
98+
printJs(emitted, out) {
9999
const argTypes = [this.convertToEmbindType(this.returnType)];
100100
if (this.thisType) {
101101
argTypes.push(this.convertToEmbindType(this.thisType));
@@ -105,10 +105,15 @@ var LibraryEmbind = {
105105
for (const argType of this.argumentTypes) {
106106
argTypes.push(this.convertToEmbindType(argType.type));
107107
}
108-
let [args, body] = createJsInvoker(this.name, argTypes, !!this.thisType, this.returnType.name !== 'void', this.isAsync);
108+
const signature = createJsInvokerSignature(argTypes, !!this.thisType, this.returnType.name !== 'void', this.isAsync)
109+
if (emitted[signature]) {
110+
return;
111+
}
112+
emitted[signature] = true;
113+
let [args, body] = createJsInvoker(argTypes, !!this.thisType, this.returnType.name !== 'void', this.isAsync);
109114
out.push(
110115
// The ${""} is hack to workaround the preprocessor replacing "function".
111-
`'${this.functionIndex}': f${""}unction(${args.join(',')}) {\n${body}},`
116+
`'${signature}': f${""}unction(${args.join(',')}) {\n${body}},`
112117
);
113118
}
114119
},
@@ -182,24 +187,24 @@ var LibraryEmbind = {
182187
out.push('};\n');
183188
}
184189

185-
printJs(out) {
190+
printJs(emitted, out) {
186191
out.push(`// class ${this.name}\n`);
187192
if (this.constructors.length) {
188193
out.push(`// constructors\n`);
189194
for (const construct of this.constructors) {
190-
construct.printJs(out);
195+
construct.printJs(emitted, out);
191196
}
192197
}
193198
if (this.staticMethods.length) {
194199
out.push(`// static methods\n`);
195200
for (const method of this.staticMethods) {
196-
method.printJs(out);
201+
method.printJs(emitted, out);
197202
}
198203
}
199204
if (this.methods.length) {
200205
out.push(`// methods\n`);
201206
for (const method of this.methods) {
202-
method.printJs(out);
207+
method.printJs(emitted, out);
203208
}
204209
}
205210
out.push('\n');
@@ -377,11 +382,12 @@ var LibraryEmbind = {
377382

378383
print() {
379384
const out = ['{\n'];
385+
const emitted = {};
380386
for (const def of this.definitions) {
381387
if (!def.printJs) {
382388
continue;
383389
}
384-
def.printJs(out);
390+
def.printJs(emitted, out);
385391
}
386392
out.push('}')
387393
console.log(out.join(''));

src/embind/embind_shared.js

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,33 @@ var LibraryEmbindShared = {
179179
return false;
180180
},
181181

182+
// Many of the JS invoker functions are generic and can be reused for multiple
183+
// function bindings. This function needs to match createJsInvoker and create
184+
// a unique signature for any inputs that will create different invoker
185+
// function outputs.
186+
$createJsInvokerSignature(argTypes, isClassMethodFunc, returns, isAsync) {
187+
const signature = [
188+
isClassMethodFunc ? 't' : 'f',
189+
returns ? 't' : 'f',
190+
isAsync ? 't' : 'f'
191+
];
192+
for (var i = isClassMethodFunc ? 1 : 2; i < argTypes.length; ++i) {
193+
const arg = argTypes[i];
194+
let destructorSig = '';
195+
if (arg.destructorFunction === undefined) {
196+
destructorSig = 'u';
197+
} else if (arg.destructorFunction === null) {
198+
destructorSig = 'n';
199+
} else {
200+
destructorSig = 't';
201+
}
202+
signature.push(destructorSig);
203+
}
204+
return signature.join('');
205+
},
206+
182207
$createJsInvoker__deps: ['$usesDestructorStack'],
183-
$createJsInvoker(humanName, argTypes, isClassMethodFunc, returns, isAsync) {
208+
$createJsInvoker(argTypes, isClassMethodFunc, returns, isAsync) {
184209
var needsDestructorStack = usesDestructorStack(argTypes);
185210
var argCount = argTypes.length;
186211
var argsList = "";
@@ -193,19 +218,19 @@ var LibraryEmbindShared = {
193218
var invokerFnBody = `
194219
return function (${argsList}) {
195220
if (arguments.length !== ${argCount - 2}) {
196-
throwBindingError('function ${humanName} called with ' + arguments.length + ' arguments, expected ${argCount - 2}');
221+
throwBindingError('function ' + humanName + ' called with ' + arguments.length + ' arguments, expected ${argCount - 2}');
197222
}`;
198223

199224
#if EMSCRIPTEN_TRACING
200-
invokerFnBody += `Module.emscripten_trace_enter_context('embind::${humanName}');\n`;
225+
invokerFnBody += `Module.emscripten_trace_enter_context('embind::' + humanName );\n`;
201226
#endif
202227

203228
if (needsDestructorStack) {
204229
invokerFnBody += "var destructors = [];\n";
205230
}
206231

207232
var dtorStack = needsDestructorStack ? "destructors" : "null";
208-
var args1 = ["throwBindingError", "invoker", "fn", "runDestructors", "retType", "classParam"];
233+
var args1 = ["humanName", "throwBindingError", "invoker", "fn", "runDestructors", "retType", "classParam"];
209234

210235
#if EMSCRIPTEN_TRACING
211236
args1.push("Module");
@@ -216,7 +241,7 @@ var LibraryEmbindShared = {
216241
}
217242

218243
for (var i = 0; i < argCount - 2; ++i) {
219-
invokerFnBody += "var arg"+i+"Wired = argType"+i+"['toWireType']("+dtorStack+", arg"+i+"); // "+argTypes[i+2].name+"\n";
244+
invokerFnBody += "var arg"+i+"Wired = argType"+i+"['toWireType']("+dtorStack+", arg"+i+");\n";
220245
args1.push("argType"+i);
221246
}
222247

@@ -240,7 +265,7 @@ var LibraryEmbindShared = {
240265
for (var i = isClassMethodFunc?1:2; i < argTypes.length; ++i) { // Skip return value at index 0 - it's not deleted here. Also skip class type if not a method.
241266
var paramName = (i === 1 ? "thisWired" : ("arg"+(i - 2)+"Wired"));
242267
if (argTypes[i].destructorFunction !== null) {
243-
invokerFnBody += paramName+"_dtor("+paramName+"); // "+argTypes[i].name+"\n";
268+
invokerFnBody += paramName+"_dtor("+paramName+");\n";
244269
args1.push(paramName+"_dtor");
245270
}
246271
}
@@ -269,7 +294,7 @@ var LibraryEmbindShared = {
269294
invokerFnBody += "}\n";
270295

271296
#if ASSERTIONS
272-
invokerFnBody = `if (arguments.length !== ${args1.length}){ throw new Error("${humanName} Expected ${args1.length} closure arguments " + arguments.length + " given."); }\n${invokerFnBody}`;
297+
invokerFnBody = `if (arguments.length !== ${args1.length}){ throw new Error(humanName + "Expected ${args1.length} closure arguments " + arguments.length + " given."); }\n${invokerFnBody}`;
273298
#endif
274299
return [args1, invokerFnBody];
275300
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include <stdio.h>
2+
#include <fstream>
3+
#include <emscripten.h>
4+
#include <emscripten/bind.h>
5+
6+
using namespace emscripten;
7+
8+
class Foo {
9+
public:
10+
void go(const std::string& filename) {
11+
std::ifstream ifs(filename, std::ifstream::binary);
12+
ifs.close();
13+
}
14+
};
15+
16+
int main() {
17+
printf("done\n");
18+
}
19+
20+
EMSCRIPTEN_BINDINGS(xxx) {
21+
class_<Foo>("Foo")
22+
.function("go", &Foo::go);
23+
}

test/test_other.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3129,6 +3129,11 @@ def test_embind_tsgen_exceptions(self):
31293129
'-lembind', '--embind-emit-tsd', 'embind_tsgen.d.ts', '-fwasm-exceptions', '-sASSERTIONS'])
31303130
self.assertFileContents(test_file('other/embind_tsgen.d.ts'), read_file('embind_tsgen.d.ts'))
31313131

3132+
def test_embind_jsgen_static_init(self):
3133+
self.emcc_args += ['-lembind', '-sEMBIND_AOT']
3134+
3135+
self.do_runf('other/embind_jsgen_static_init.cpp', 'done')
3136+
31323137
def test_emconfig(self):
31333138
output = self.run_process([emconfig, 'LLVM_ROOT'], stdout=PIPE).stdout.strip()
31343139
self.assertEqual(output, config.LLVM_ROOT)

0 commit comments

Comments
 (0)