Skip to content

Commit 2af9a81

Browse files
committed
[embind] Drop the try/finally around stack-framed invokers
Restore the frame on the normal return path only, matching the rest of emscripten: a JS caller that catches an exception and carries on owns the stackSave/stackRestore (#27748). The throwing-conversion section of the test goes with it, and hello_embind shrinks by 23 bytes.
1 parent a001454 commit 2af9a81

4 files changed

Lines changed: 29 additions & 53 deletions

File tree

‎src/lib/libembind.js‎

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -745,30 +745,27 @@ var LibraryEmbind = {
745745
sp = stackSave();
746746
}
747747
var thisWired;
748-
var rv;
749-
// The frame must be released on every completion, including a throwing
750-
// argument conversion or callee: a skipped stackRestore permanently
751-
// leaks wasm stack.
752-
try {
753-
invokerFuncArgs.length = isClassMethodFunc ? 2 : 1;
754-
invokerFuncArgs[0] = cppTargetFunc;
755-
if (isClassMethodFunc) {
756-
thisWired = argTypes[1].toWireType(destructors, this);
757-
invokerFuncArgs[1] = thisWired;
758-
}
759-
for (var i = 0; i < expectedArgCount; ++i) {
760-
var argType = argTypes[i + 2];
761-
// Stack-allocating types take the stack path only under a frame; a
762-
// null destructors argument is that contract.
763-
argsWired[i] = argType.toWireType(useStackFrame && argType.argStackAlloc ? null : destructors, args[i]);
764-
invokerFuncArgs.push(argsWired[i]);
765-
}
748+
invokerFuncArgs.length = isClassMethodFunc ? 2 : 1;
749+
invokerFuncArgs[0] = cppTargetFunc;
750+
if (isClassMethodFunc) {
751+
thisWired = argTypes[1].toWireType(destructors, this);
752+
invokerFuncArgs[1] = thisWired;
753+
}
754+
for (var i = 0; i < expectedArgCount; ++i) {
755+
var argType = argTypes[i + 2];
756+
// Stack-allocating types take the stack path only under a frame; a
757+
// null destructors argument is that contract.
758+
argsWired[i] = argType.toWireType(useStackFrame && argType.argStackAlloc ? null : destructors, args[i]);
759+
invokerFuncArgs.push(argsWired[i]);
760+
}
766761

767-
rv = cppInvokerFunc(...invokerFuncArgs);
768-
} finally {
769-
if (useStackFrame) {
770-
stackRestore(sp);
771-
}
762+
var rv = cppInvokerFunc(...invokerFuncArgs);
763+
if (useStackFrame) {
764+
// The callee has consumed the stack-allocated argument temporaries.
765+
// As everywhere else in emscripten, the frame is not restored on an
766+
// exception path; a JS caller that catches and continues must
767+
// stackSave/stackRestore around the call (see the exceptions docs).
768+
stackRestore(sp);
772769
}
773770

774771
function onDone(rv) {

‎src/lib/libembind_shared.js‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,7 @@ var LibraryEmbindShared = {
277277
invokerFnBody += 'var destructors = [];\n';
278278
}
279279
if (useStackFrame) {
280-
// The frame must be released on every completion, including a throwing
281-
// argument conversion or callee: a skipped stackRestore permanently
282-
// leaks wasm stack. `var` declarations hoist out of the try block.
283-
invokerFnBody += 'var sp = stackSave();\ntry {\n';
280+
invokerFnBody += 'var sp = stackSave();\n';
284281
}
285282

286283
var dtorStack = needsDestructorStack ? 'destructors' : 'null';
@@ -306,8 +303,10 @@ var LibraryEmbindShared = {
306303
invokerFnBody += (returns || isAsync ? 'var rv = ' : '') + `invoker(${argsListWired});\n`;
307304
if (useStackFrame) {
308305
// The callee has consumed the stack-allocated argument temporaries;
309-
// release the frame before any post-call work.
310-
invokerFnBody += '} finally {\nstackRestore(sp);\n}\n';
306+
// release the frame before any post-call work. Not restored on an
307+
// exception path, matching the rest of emscripten (see the exceptions
308+
// docs on stackSave/stackRestore at JS catch sites).
309+
invokerFnBody += 'stackRestore(sp);\n';
311310
}
312311

313312
var returnVal = returns ? 'rv' : '';
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"a.html": 540,
33
"a.html.gz": 367,
4-
"a.js": 7487,
5-
"a.js.gz": 3437,
4+
"a.js": 7464,
5+
"a.js.gz": 3418,
66
"a.wasm": 7201,
77
"a.wasm.gz": 3303,
8-
"total": 15228,
9-
"total_gz": 7107
8+
"total": 15205,
9+
"total_gz": 7088
1010
}

‎test/embind/test_embind_trivial_value_stack.cpp‎

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -163,26 +163,6 @@ int main() {
163163
printf("trivial allocs: %d frees: %d\n", allocCount, freeCount);
164164
assert(allocCount == 0 && freeCount == 0);
165165

166-
// A conversion that throws mid-call must unwind the stack frame; the
167-
// stackSave comparison below catches any leaked frame.
168-
EM_ASM({
169-
var trap = ({ index: 1, world: 2, get generation() { throw new Error('boom'); } });
170-
var sp = stackSave();
171-
for (var i = 0; i < 1000; i++) {
172-
try {
173-
Module['sumVec'](trap, [1, 2, 3]);
174-
// No .message on this string, so the catch below rethrows it.
175-
throw 'expected sumVec to throw';
176-
} catch (e) {
177-
if (e.message !== 'boom') throw e;
178-
}
179-
}
180-
if (stackSave() !== sp) throw 'stack leaked across throwing conversions';
181-
if (Module['sumId']({ index: 1, world: 2, generation: 3 }) !== 6) throw 'sumId broken after throws';
182-
});
183-
printf("throwing allocs: %d frees: %d\n", allocCount, freeCount);
184-
assert(allocCount == 0 && freeCount == 0);
185-
186166
// The non-trivial type stays on the heap path, balances its allocations,
187167
// and runs its destructor.
188168
EM_ASM({

0 commit comments

Comments
 (0)