Skip to content

Commit 8a3144a

Browse files
Refactor Emval implementation to avoid JS heap allocations. (#21205)
1 parent fdc2ea7 commit 8a3144a

File tree

3 files changed

+54
-39
lines changed

3 files changed

+54
-39
lines changed

src/embind/emval.js

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,39 +12,43 @@
1212
/*jslint sub:true*/ /* The symbols 'fromWireType' and 'toWireType' must be accessed via array notation to be closure-safe since craftInvokerFunction crafts functions as strings that can't be closured. */
1313

1414
// -- jshint doesn't understand library syntax, so we need to mark the symbols exposed here
15-
/*global getStringOrSymbol, emval_handles, Emval, __emval_unregister, count_emval_handles, emval_symbols, __emval_decref*/
15+
/*global getStringOrSymbol, emval_freelist, emval_handles, Emval, __emval_unregister, count_emval_handles, emval_symbols, __emval_decref*/
1616
/*global emval_addMethodCaller, emval_methodCallers, addToLibrary, global, emval_lookupTypes, makeLegalFunctionName*/
1717
/*global emval_get_global*/
1818

19+
// Number of handles reserved for non-use (0) or common values w/o refcount.
20+
{{{
21+
globalThis.EMVAL_RESERVED_HANDLES = 5;
22+
globalThis.EMVAL_LAST_RESERVED_HANDLE = globalThis.EMVAL_RESERVED_HANDLES * 2 - 1;
23+
null;
24+
}}}
1925
var LibraryEmVal = {
20-
$emval_handles__deps: ['$HandleAllocator'],
21-
$emval_handles: "new HandleAllocator();",
26+
// Stack of handles available for reuse.
27+
$emval_freelist: [],
28+
// Array of alternating pairs (value, refcount).
29+
$emval_handles: [],
2230
$emval_symbols: {}, // address -> string
2331

2432
$init_emval__deps: ['$count_emval_handles', '$emval_handles'],
2533
$init_emval__postset: 'init_emval();',
2634
$init_emval: () => {
27-
// reserve some special values. These never get de-allocated.
28-
// The HandleAllocator takes care of reserving zero.
29-
emval_handles.allocated.push(
30-
{value: undefined},
31-
{value: null},
32-
{value: true},
33-
{value: false},
35+
// reserve 0 and some special values. These never get de-allocated.
36+
emval_handles.push(
37+
0, 1,
38+
undefined, 1,
39+
null, 1,
40+
true, 1,
41+
false, 1,
3442
);
35-
Object.assign(emval_handles, /** @lends {emval_handles} */ { reserved: emval_handles.allocated.length }),
43+
#if ASSERTIONS
44+
assert(emval_handles.length === {{{ EMVAL_RESERVED_HANDLES }}} * 2);
45+
#endif
3646
Module['count_emval_handles'] = count_emval_handles;
3747
},
3848

39-
$count_emval_handles__deps: ['$emval_handles'],
49+
$count_emval_handles__deps: ['$emval_freelist', '$emval_handles'],
4050
$count_emval_handles: () => {
41-
var count = 0;
42-
for (var i = emval_handles.reserved; i < emval_handles.allocated.length; ++i) {
43-
if (emval_handles.allocated[i] !== undefined) {
44-
++count;
45-
}
46-
}
47-
return count;
51+
return emval_handles.length / 2 - {{{ EMVAL_RESERVED_HANDLES }}} - emval_freelist.length;
4852
},
4953

5054
_emval_register_symbol__deps: ['$emval_symbols', '$readLatin1String'],
@@ -61,39 +65,50 @@ var LibraryEmVal = {
6165
return symbol;
6266
},
6367

64-
$Emval__deps: ['$emval_handles', '$throwBindingError', '$init_emval'],
68+
$Emval__deps: ['$emval_freelist', '$emval_handles', '$throwBindingError', '$init_emval'],
6569
$Emval: {
6670
toValue: (handle) => {
6771
if (!handle) {
6872
throwBindingError('Cannot use deleted val. handle = ' + handle);
6973
}
70-
return emval_handles.get(handle).value;
74+
#if ASSERTIONS
75+
// handle 2 is supposed to be `undefined`.
76+
assert(handle === 2 || emval_handles[handle] !== undefined && handle % 2 === 0, `invalid handle: ${handle}`);
77+
#endif
78+
return emval_handles[handle];
7179
},
7280

7381
toHandle: (value) => {
7482
switch (value) {
75-
case undefined: return 1;
76-
case null: return 2;
77-
case true: return 3;
78-
case false: return 4;
83+
case undefined: return 2;
84+
case null: return 4;
85+
case true: return 6;
86+
case false: return 8;
7987
default:{
80-
return emval_handles.allocate({refcount: 1, value: value});
88+
const handle = emval_freelist.pop() || emval_handles.length;
89+
emval_handles[handle] = value;
90+
emval_handles[handle + 1] = 1;
91+
return handle;
8192
}
8293
}
8394
}
8495
},
8596

8697
_emval_incref__deps: ['$emval_handles'],
8798
_emval_incref: (handle) => {
88-
if (handle > 4) {
89-
emval_handles.get(handle).refcount += 1;
99+
if (handle > {{{ EMVAL_LAST_RESERVED_HANDLE }}}) {
100+
emval_handles[handle + 1] += 1;
90101
}
91102
},
92103

93-
_emval_decref__deps: ['$emval_handles'],
104+
_emval_decref__deps: ['$emval_freelist', '$emval_handles'],
94105
_emval_decref: (handle) => {
95-
if (handle >= emval_handles.reserved && 0 === --emval_handles.get(handle).refcount) {
96-
emval_handles.free(handle);
106+
if (handle > {{{ EMVAL_LAST_RESERVED_HANDLE }}} && 0 === --emval_handles[handle + 1]) {
107+
#if ASSERTIONS
108+
assert(emval_handles[handle] !== undefined, `Decref for unallocated handle.`);
109+
#endif
110+
emval_handles[handle] = undefined;
111+
emval_freelist.push(handle);
97112
}
98113
},
99114

system/include/emscripten/val.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ extern "C" {
4646
void _emval_register_symbol(const char*);
4747

4848
enum {
49-
_EMVAL_UNDEFINED = 1,
50-
_EMVAL_NULL = 2,
51-
_EMVAL_TRUE = 3,
52-
_EMVAL_FALSE = 4
49+
_EMVAL_UNDEFINED = 2,
50+
_EMVAL_NULL = 4,
51+
_EMVAL_TRUE = 6,
52+
_EMVAL_FALSE = 8
5353
};
5454

5555
typedef struct _EM_DESTRUCTORS* EM_DESTRUCTORS;

test/code_size/embind_val_wasm.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"a.html": 673,
33
"a.html.gz": 431,
4-
"a.js": 7325,
5-
"a.js.gz": 3088,
4+
"a.js": 7086,
5+
"a.js.gz": 3000,
66
"a.wasm": 11433,
77
"a.wasm.gz": 5725,
8-
"total": 19431,
9-
"total_gz": 9244
8+
"total": 19192,
9+
"total_gz": 9156
1010
}

0 commit comments

Comments
 (0)