Skip to content

Commit 1967514

Browse files
authored
Export mutable wasm globals as JS objects. NFC (#25530)
For immutable wasm globals we currently export them as simple JS numbers at build time. This is because data symbol addresses are exported as immutable globals. However, its also useful to export real wasm globals. For now we treat mutable globals and immutable global differently here but we should look into perhaps changing that in the future.
1 parent 5da7b63 commit 1967514

File tree

11 files changed

+140
-44
lines changed

11 files changed

+140
-44
lines changed

ChangeLog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ See docs/process.md for more on how version tagging works.
2020

2121
4.0.17 (in development)
2222
-----------------------
23+
- Mutable Wasm globals can now be exported from native code. Currently these
24+
cannot be declared in C/C++ but can be defined and exported in assembly code.
25+
This currently only works for mutable globals since immutables are already
26+
(and continue to be) exported as plain JS numbers. (#25530)
2327
- Minimum Firefox version was bumped up to Firefox 68 ESR, since older Firefox
2428
versions are not able to run the parallel browser harness: (#25493)
2529
- Firefox: v65 -> v68

src/lib/libdylink.js

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -230,24 +230,33 @@ var LibraryDylink = {
230230
}
231231
#endif
232232

233-
GOT[symName] ||= new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}', 'mutable': true});
234-
if (replace || GOT[symName].value == 0) {
233+
234+
var existingEntry = GOT[symName] && GOT[symName].value != 0;
235+
if (replace || !existingEntry) {
235236
#if DYLINK_DEBUG == 2
236237
dbg(`updateGOT: before: ${symName} : ${GOT[symName].value}`);
237238
#endif
239+
var newValue;
238240
if (typeof value == 'function') {
239-
GOT[symName].value = {{{ to64('addFunction(value)') }}};
241+
newValue = {{{ to64('addFunction(value)') }}};
240242
#if DYLINK_DEBUG == 2
241243
dbg(`updateGOT: FUNC: ${symName} : ${GOT[symName].value}`);
242244
#endif
243245
} else if (typeof value == {{{ POINTER_JS_TYPE }}}) {
244-
GOT[symName].value = value;
246+
newValue = value;
245247
} else {
246-
err(`unhandled export type for '${symName}': ${typeof value}`);
248+
// The GOT can only contain addresses (i.e data addresses or function
249+
// addresses so we currently ignore other types export here.
250+
#if DYLINK_DEBUG
251+
dbg(`updateGOT: ignoring ${symName} due to its type: ${typeof value}`);
252+
#endif
253+
continue;
247254
}
248255
#if DYLINK_DEBUG == 2
249-
dbg(`updateGOT: after: ${symName} : ${GOT[symName].value} (${value})`);
256+
dbg(`updateGOT: after: ${symName} : ${newValue} (${value})`);
250257
#endif
258+
GOT[symName] ||= new WebAssembly.Global({'value': '{{{ POINTER_WASM_TYPE }}}', 'mutable': true});
259+
GOT[symName].value = newValue;
251260
}
252261
#if DYLINK_DEBUG
253262
else if (GOT[symName].value != value) {
@@ -260,29 +269,43 @@ var LibraryDylink = {
260269
#endif
261270
},
262271

272+
$isImmutableGlobal__internal: true,
273+
$isImmutableGlobal: (val) => {
274+
if (val instanceof WebAssembly.Global) {
275+
try {
276+
val.value = val.value;
277+
} catch {
278+
return true;
279+
}
280+
}
281+
return false;
282+
},
283+
263284
// Applies relocations to exported things.
264285
$relocateExports__internal: true,
265-
$relocateExports__deps: ['$updateGOT'],
286+
$relocateExports__deps: ['$updateGOT', '$isImmutableGlobal'],
266287
$relocateExports__docs: '/** @param {boolean=} replace */',
267288
$relocateExports: (exports, memoryBase, replace) => {
268-
var relocated = {};
269-
270-
for (var e in exports) {
271-
var value = exports[e];
289+
function relocateExport(name, value) {
272290
#if SPLIT_MODULE
273291
// Do not modify exports synthesized by wasm-split
274-
if (e.startsWith('%')) {
275-
relocated[e] = value
276-
continue;
292+
if (name.startsWith('%')) {
293+
return value;
277294
}
278295
#endif
279-
// Detect wasm global exports. These represent data addresses
296+
// Detect immuable wasm global exports. These represent data addresses
280297
// which are relative to `memoryBase`
281-
if (value instanceof WebAssembly.Global) {
282-
value = value.value;
283-
value += {{{ to64('memoryBase') }}};
298+
if (isImmutableGlobal(value)) {
299+
return value.value + {{{ to64('memoryBase') }}};
284300
}
285-
relocated[e] = value;
301+
302+
// Return unmodified value (no relocation required).
303+
return value;
304+
}
305+
306+
var relocated = {};
307+
for (var e in exports) {
308+
relocated[e] = relocateExport(e, exports[e])
286309
}
287310
updateGOT(relocated, replace);
288311
return relocated;

src/settings_internal.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
// underscore.
1717
var WASM_EXPORTS = [];
1818

19-
// Similar to above but only includes the global/data symbols.
20-
var WASM_GLOBAL_EXPORTS = [];
19+
// Similar to above but only includes the data symbols (address exports).
20+
var DATA_EXPORTS = [];
2121

2222
// An array of all symbols exported from all the side modules specified on the
2323
// command line.

test/codesize/test_codesize_hello_dylink.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
2-
"a.out.js": 26912,
3-
"a.out.js.gz": 11470,
2+
"a.out.js": 26919,
3+
"a.out.js.gz": 11469,
44
"a.out.nodebug.wasm": 18567,
55
"a.out.nodebug.wasm.gz": 9199,
6-
"total": 45479,
7-
"total_gz": 20669,
6+
"total": 45486,
7+
"total_gz": 20668,
88
"sent": [
99
"__heap_base",
1010
"__indirect_function_table",

test/codesize/test_codesize_hello_dylink_all.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"a.out.js": 245822,
2+
"a.out.js": 245829,
33
"a.out.nodebug.wasm": 597746,
4-
"total": 843568,
4+
"total": 843575,
55
"sent": [
66
"IMG_Init",
77
"IMG_Load",

test/core/test_wasm_global.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include <stdio.h>
2+
#include <emscripten/em_asm.h>
3+
#include <emscripten.h>
4+
5+
__asm__(
6+
".section .data.my_global,\"\",@\n"
7+
".globl my_global\n"
8+
".globaltype my_global, i32\n"
9+
"my_global:\n"
10+
);
11+
12+
int get_global() {
13+
int val;
14+
// Without volatile here this test fails in O1 and above.
15+
__asm__ volatile ("global.get my_global\n"
16+
"local.set %0\n" : "=r" (val));
17+
return val;
18+
}
19+
20+
void set_global(int val) {
21+
__asm__("local.get %0\n"
22+
"global.set my_global\n" : : "r" (val));
23+
}
24+
25+
int main() {
26+
printf("in main: %d\n", get_global());
27+
set_global(42);
28+
printf("new value: %d\n", get_global());
29+
EM_ASM({
30+
// With the ESM integration, the Wasm global be exported as a regular
31+
// number. Otherwise it will be a WebAssembly.Global object.
32+
#ifdef ESM_INTEGRATION
33+
assert(typeof _my_global == 'number', typeof _my_global);
34+
out('from js:', _my_global);
35+
_my_global += 1
36+
#else
37+
assert(typeof _my_global == 'object', typeof _my_global);
38+
out('from js:', _my_global.value);
39+
_my_global.value += 1
40+
#endif
41+
});
42+
printf("done: %d\n", get_global());
43+
}

test/core/test_wasm_global.out

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
in main: 0
2+
new value: 42
3+
from js: 42
4+
done: 43

test/test_core.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9677,6 +9677,19 @@ def test_externref_emjs(self, dynlink):
96779677
self.set_setting('MAIN_MODULE', 2)
96789678
self.do_core_test('test_externref_emjs.c')
96799679

9680+
@parameterized({
9681+
'': [False],
9682+
'dylink': [True],
9683+
})
9684+
@no_esm_integration('https://github.com/emscripten-core/emscripten/issues/25543')
9685+
def test_wasm_global(self, dynlink):
9686+
if dynlink:
9687+
self.check_dylink()
9688+
self.set_setting('MAIN_MODULE', 2)
9689+
if self.get_setting('WASM_ESM_INTEGRATION'):
9690+
self.cflags.append('-DESM_INTEGRATION')
9691+
self.do_core_test('test_wasm_global.c', cflags=['-sEXPORTED_FUNCTIONS=_main,_my_global'])
9692+
96809693
def test_syscall_intercept(self):
96819694
self.do_core_test('test_syscall_intercept.c')
96829695

tools/building.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,7 @@ def metadce(js_file, wasm_file, debug_info, last):
810810
exports = settings.WASM_EXPORTS
811811
else:
812812
# Ignore exported wasm globals. Those get inlined directly into the JS code.
813-
exports = sorted(set(settings.WASM_EXPORTS) - set(settings.WASM_GLOBAL_EXPORTS))
813+
exports = sorted(set(settings.WASM_EXPORTS) - set(settings.DATA_EXPORTS))
814814

815815
extra_info = '{ "exports": [' + ','.join(f'["{asmjs_mangle(x)}", "{x}"]' for x in exports) + ']}'
816816

tools/emscripten.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def update_settings_glue(wasm_file, metadata, base_metadata):
111111
settings.WASM_EXPORTS = base_metadata.all_exports
112112
else:
113113
settings.WASM_EXPORTS = metadata.all_exports
114-
settings.WASM_GLOBAL_EXPORTS = list(metadata.global_exports.keys())
114+
settings.DATA_EXPORTS = list(metadata.data_exports.keys())
115115
settings.HAVE_EM_ASM = bool(settings.MAIN_MODULE or len(metadata.em_asm_consts) != 0)
116116

117117
# start with the MVP features, and add any detected features.
@@ -269,9 +269,9 @@ def trim_asm_const_body(body):
269269
return body
270270

271271

272-
def create_global_exports(global_exports):
272+
def create_data_exports(data_exports):
273273
lines = []
274-
for k, v in global_exports.items():
274+
for k, v in data_exports.items():
275275
if shared.is_internal_global(k):
276276
continue
277277

@@ -408,11 +408,11 @@ def emscript(in_wasm, out_wasm, outfile_js, js_syms, finalize=True, base_metadat
408408
other_exports = base_metadata.other_exports
409409
# We want the real values from the final metadata but we only want to
410410
# include names from the base_metadata. See phase_link() in link.py.
411-
global_exports = {k: v for k, v in metadata.global_exports.items() if k in base_metadata.global_exports}
411+
data_exports = {k: v for k, v in metadata.data_exports.items() if k in base_metadata.data_exports}
412412
else:
413413
function_exports = metadata.function_exports
414414
other_exports = metadata.other_exports
415-
global_exports = metadata.global_exports
415+
data_exports = metadata.data_exports
416416

417417
if settings.ASYNCIFY == 1:
418418
function_exports['asyncify_start_unwind'] = webassembly.FuncType([webassembly.Type.I32], [])
@@ -421,7 +421,7 @@ def emscript(in_wasm, out_wasm, outfile_js, js_syms, finalize=True, base_metadat
421421
function_exports['asyncify_stop_rewind'] = webassembly.FuncType([], [])
422422

423423
parts = [pre]
424-
parts += create_module(metadata, function_exports, global_exports, other_exports,
424+
parts += create_module(metadata, function_exports, data_exports, other_exports,
425425
forwarded_json['librarySymbols'], forwarded_json['nativeAliases'])
426426
parts.append(post)
427427
settings.ALIASES = list(forwarded_json['nativeAliases'].keys())
@@ -1008,10 +1008,10 @@ def create_receiving(function_exports, other_exports, library_symbols, aliases):
10081008
return '\n'.join(receiving)
10091009

10101010

1011-
def create_module(metadata, function_exports, global_exports, other_exports, library_symbols, aliases):
1011+
def create_module(metadata, function_exports, data_exports, other_exports, library_symbols, aliases):
10121012
module = []
10131013
module.append(create_receiving(function_exports, other_exports, library_symbols, aliases))
1014-
module.append(create_global_exports(global_exports))
1014+
module.append(create_data_exports(data_exports))
10151015

10161016
sending = create_sending(metadata, library_symbols)
10171017
if settings.WASM_ESM_INTEGRATION:

0 commit comments

Comments
 (0)