Skip to content

Commit dea3473

Browse files
committed
[wasm-merge] Handle table initializer referring to imported global
Unlike global initializers and element segments (which under GC may refer to preceding module-defined globals), a table initializer expression may only refer to imported globals. Fusing can turn an imported global that a table initializer refers to into a module-defined one, which would make the initializer invalid. When that happens, inline the referenced global's (immutable, constant) value so the initializer remains valid.
1 parent eacbf23 commit dea3473

3 files changed

Lines changed: 81 additions & 0 deletions

File tree

‎src/tools/wasm-merge.cpp‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
// time, so we do not do it by default.
9898
//
9999

100+
#include "ir/manipulation.h"
100101
#include "ir/module-utils.h"
101102
#include "ir/names.h"
102103
#include "ir/utils.h"
@@ -576,6 +577,47 @@ void fuseImportsAndExports(const PassOptions& options) {
576577
updateNames(merged, kindNameUpdates);
577578
}
578579

580+
// Unlike global initializers and element segments (which under GC may refer to
581+
// preceding module-defined globals), a table initializer expression may only
582+
// refer to imported globals. Fusing can turn an imported global that a table
583+
// initializer refers to into a module-defined one, which would make the
584+
// initializer invalid. When that happens, inline the referenced global's
585+
// (immutable, constant) value so the initializer remains valid. The fused
586+
// global is necessarily immutable, since fusing requires the import and export
587+
// mutabilities to match and a table initializer could only refer to an
588+
// immutable global in the first place.
589+
void fixTableInitializers() {
590+
struct Inliner : public PostWalker<Inliner> {
591+
bool inlined = false;
592+
void visitGlobalGet(GlobalGet* curr) {
593+
auto* global = getModule()->getGlobalOrNull(curr->name);
594+
if (global && !global->imported()) {
595+
assert(global->init && !global->mutable_);
596+
replaceCurrent(ExpressionManipulator::copy(global->init, *getModule()));
597+
inlined = true;
598+
}
599+
}
600+
};
601+
602+
for (auto& table : merged.tables) {
603+
if (!table->init) {
604+
continue;
605+
}
606+
// Inlining a global may expose further module-defined globals (if that
607+
// global's initializer refers to another one), so iterate to a fixed point.
608+
// Globals cannot form cycles, so this terminates. Any global.gets that
609+
// remain refer to imported globals, which are valid in table initializers.
610+
while (true) {
611+
Inliner inliner;
612+
inliner.setModule(&merged);
613+
inliner.walk(table->init);
614+
if (!inliner.inlined) {
615+
break;
616+
}
617+
}
618+
}
619+
}
620+
579621
// Things may have been imported using supertypes, which means they can get
580622
// refined after merging.
581623
void updateTypes(Module& wasm) {
@@ -880,6 +922,10 @@ Input source maps can be specified by adding an -ism option right after the modu
880922
// module.
881923
fuseImportsAndExports(options.passOptions);
882924

925+
// Fusing may have made table initializers refer to module-defined globals,
926+
// which is not allowed; fix that up.
927+
fixTableInitializers();
928+
883929
// Update types after combing and linking everything.
884930
updateTypes(merged);
885931

‎test/lit/merge/table-init.wat‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
2+
3+
;; RUN: wasm-merge %s first %s.second second -all -S -o - | filecheck %s
4+
5+
;; A table initializer expression may only refer to imported globals. When
6+
;; wasm-merge fuses the imported global $g that the table initializer refers to
7+
;; with the module-defined global exported from the second module, the
8+
;; initializer would end up referring to a module-defined global, which is not
9+
;; allowed. Verify that we inline the global's value into the initializer to keep
10+
;; it valid, rather than emitting an invalid module.
11+
12+
(module
13+
;; CHECK: (type $f (func))
14+
(type $f (func))
15+
16+
(global $g (import "second" "g") (ref $f))
17+
18+
;; CHECK: (global $g_1 (ref $f) (ref.func $h))
19+
20+
;; CHECK: (table $t 3 funcref (ref.func $h))
21+
(table $t (export "t") 3 funcref (global.get $g))
22+
)
23+
;; CHECK: (export "t" (table $t))
24+
25+
;; CHECK: (export "g" (global $g_1))
26+
27+
;; CHECK: (func $h (type $f)
28+
;; CHECK-NEXT: )
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
(module
2+
(type $f (func))
3+
4+
(func $h)
5+
6+
(global $g (export "g") (ref $f) (ref.func $h))
7+
)

0 commit comments

Comments
 (0)