Skip to content

Commit 203bc12

Browse files
authored
[embind] Change how the number of arguments are verified. (#22591)
This is really two changes, but they both will affect the same code so I did them at once: - The number of arguments is now only verified with ASSERTIONS enabled. This will help code size and the speed of embind invoker functions. - Allow optional arguments to be omitted. Fixes #22389
1 parent 57aeff1 commit 203bc12

File tree

6 files changed

+70
-16
lines changed

6 files changed

+70
-16
lines changed

ChangeLog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ See docs/process.md for more on how version tagging works.
2121
3.1.68 (in development)
2222
-----------------------
2323
- The freetype port was updated from v2.6 to v2.13.3. (#22585)
24+
- The number of arguments passed to Embind function calls is now only verified
25+
with ASSERTIONS enabled. (#22591)
26+
- Optional arguments can now be omitted from Embind function calls. (#22591)
2427

2528
3.1.67 - 09/17/24
2629
-----------------

src/embind/embind.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ var LibraryEmbind = {
5757
// TODO: do we need a deleteObject here? write a test where
5858
// emval is passed into JS via an interface
5959
}`,
60+
$EmValOptionalType__deps: ['$EmValType'],
61+
$EmValOptionalType: '=Object.assign({optional: true}, EmValType);',
6062
$init_embind__deps: [
6163
'$getInheritedInstanceCount', '$getLiveInheritedInstances',
6264
'$flushPendingDeletes', '$setDelayFunction'],
@@ -687,9 +689,9 @@ var LibraryEmbind = {
687689
__embind_register_emval(rawType);
688690
},
689691

690-
_embind_register_optional__deps: ['_embind_register_emval'],
692+
_embind_register_optional__deps: ['$registerType', '$EmValOptionalType'],
691693
_embind_register_optional: (rawOptionalType, rawType) => {
692-
__embind_register_emval(rawOptionalType);
694+
registerType(rawOptionalType, EmValOptionalType);
693695
},
694696

695697
_embind_register_memory_view__deps: ['$readLatin1String', '$registerType'],
@@ -778,6 +780,10 @@ var LibraryEmbind = {
778780
#endif
779781
#if ASYNCIFY
780782
'$Asyncify',
783+
#endif
784+
#if ASSERTIONS
785+
'$getRequiredArgCount',
786+
'$checkArgCount',
781787
#endif
782788
],
783789
$craftInvokerFunction: function(humanName, argTypes, classType, cppInvokerFunc, cppTargetFunc, /** boolean= */ isAsync) {
@@ -821,15 +827,18 @@ var LibraryEmbind = {
821827

822828
var returns = (argTypes[0].name !== "void");
823829

824-
#if DYNAMIC_EXECUTION == 0 && !EMBIND_AOT
825830
var expectedArgCount = argCount - 2;
831+
#if ASSERTIONS
832+
var minArgs = getRequiredArgCount(argTypes);
833+
#endif
834+
#if DYNAMIC_EXECUTION == 0 && !EMBIND_AOT
826835
var argsWired = new Array(expectedArgCount);
827836
var invokerFuncArgs = [];
828837
var destructors = [];
829838
var invokerFn = function(...args) {
830-
if (args.length !== expectedArgCount) {
831-
throwBindingError(`function ${humanName} called with ${args.length} arguments, expected ${expectedArgCount}`);
832-
}
839+
#if ASSERTIONS
840+
checkArgCount(args.length, minArgs, expectedArgCount, humanName, throwBindingError);
841+
#endif
833842
#if EMSCRIPTEN_TRACING
834843
Module.emscripten_trace_enter_context(`embind::${humanName}`);
835844
#endif
@@ -901,6 +910,9 @@ var LibraryEmbind = {
901910
}
902911
}
903912
}
913+
#if ASSERTIONS
914+
closureArgs.push(checkArgCount, minArgs, expectedArgCount);
915+
#endif
904916

905917
#if EMBIND_AOT
906918
var signature = createJsInvokerSignature(argTypes, isClassMethodFunc, returns, isAsync);

src/embind/embind_shared.js

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,29 @@ var LibraryEmbindShared = {
204204
return signature.join('');
205205
},
206206

207-
$createJsInvoker__deps: ['$usesDestructorStack'],
207+
$checkArgCount(numArgs, minArgs, maxArgs, humanName, throwBindingError) {
208+
if (numArgs < minArgs || numArgs > maxArgs) {
209+
var argCountMessage = minArgs == maxArgs ? minArgs : `${minArgs} to ${maxArgs}`;
210+
throwBindingError(`function ${humanName} called with ${numArgs} arguments, expected ${argCountMessage}`);
211+
}
212+
},
213+
214+
$getRequiredArgCount(argTypes) {
215+
var requiredArgCount = argTypes.length - 2;
216+
for (var i = argTypes.length - 1; i >= 2; --i) {
217+
if (!argTypes[i].optional) {
218+
break;
219+
}
220+
requiredArgCount--;
221+
}
222+
return requiredArgCount;
223+
},
224+
225+
$createJsInvoker__deps: ['$usesDestructorStack',
226+
#if ASSERTIONS
227+
'$checkArgCount',
228+
#endif
229+
],
208230
$createJsInvoker(argTypes, isClassMethodFunc, returns, isAsync) {
209231
var needsDestructorStack = usesDestructorStack(argTypes);
210232
var argCount = argTypes.length - 2;
@@ -220,11 +242,11 @@ var LibraryEmbindShared = {
220242
argsList = argsList.join(',')
221243
argsListWired = argsListWired.join(',')
222244

223-
var invokerFnBody = `
224-
return function (${argsList}) {
225-
if (arguments.length !== ${argCount}) {
226-
throwBindingError('function ' + humanName + ' called with ' + arguments.length + ' arguments, expected ${argCount}');
227-
}`;
245+
var invokerFnBody = `return function (${argsList}) {\n`;
246+
247+
#if ASSERTIONS
248+
invokerFnBody += "checkArgCount(arguments.length, minArgs, maxArgs, humanName, throwBindingError);\n";
249+
#endif
228250

229251
#if EMSCRIPTEN_TRACING
230252
invokerFnBody += `Module.emscripten_trace_enter_context('embind::' + humanName );\n`;
@@ -295,6 +317,7 @@ var LibraryEmbindShared = {
295317
invokerFnBody += "}\n";
296318

297319
#if ASSERTIONS
320+
args1.push('checkArgCount', 'minArgs', 'maxArgs');
298321
invokerFnBody = `if (arguments.length !== ${args1.length}){ throw new Error(humanName + "Expected ${args1.length} closure arguments " + arguments.length + " given."); }\n${invokerFnBody}`;
299322
#endif
300323
return [args1, invokerFnBody];

test/code_size/embind_hello_wasm.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"a.html": 552,
33
"a.html.gz": 380,
4-
"a.js": 9910,
5-
"a.js.gz": 4352,
4+
"a.js": 9727,
5+
"a.js.gz": 4295,
66
"a.wasm": 7728,
77
"a.wasm.gz": 3519,
8-
"total": 18190,
9-
"total_gz": 8251
8+
"total": 18007,
9+
"total_gz": 8194
1010
}

test/embind/embind.test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,6 +1294,20 @@ module({
12941294
value = cm.embind_test_optional_small_class_arg(undefined);
12951295
assert.equal(-1, value);
12961296
});
1297+
1298+
test("std::optional args can be omitted", function() {
1299+
if (cm.getCompilerSetting('ASSERTIONS')) {
1300+
// Argument length is only validated with assertions enabled.
1301+
assert.throws(cm.BindingError, function() {
1302+
cm.embind_test_optional_multiple_arg();
1303+
});
1304+
assert.throws(cm.BindingError, function() {
1305+
cm.embind_test_optional_multiple_arg(1, 2, 3, 4);
1306+
});
1307+
}
1308+
cm.embind_test_optional_multiple_arg(1);
1309+
cm.embind_test_optional_multiple_arg(1, 2);
1310+
});
12971311
});
12981312

12991313
BaseFixture.extend("functors", function() {

test/embind/embind_test.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,6 +1377,7 @@ int embind_test_optional_small_class_arg(std::optional<SmallClass> arg) {
13771377
}
13781378
return -1;
13791379
}
1380+
void embind_test_optional_multiple_arg(int arg1, std::optional<int> arg2, std::optional<int> arg3) {}
13801381
#endif
13811382

13821383
val embind_test_getglobal() {
@@ -2412,6 +2413,7 @@ EMSCRIPTEN_BINDINGS(tests) {
24122413
function("embind_test_optional_float_arg", &embind_test_optional_float_arg);
24132414
function("embind_test_optional_string_arg", &embind_test_optional_string_arg);
24142415
function("embind_test_optional_small_class_arg", &embind_test_optional_small_class_arg);
2416+
function("embind_test_optional_multiple_arg", &embind_test_optional_multiple_arg);
24152417
#endif
24162418

24172419
register_map<std::string, int>("StringIntMap");

0 commit comments

Comments
 (0)