Skip to content

Commit 8a66d2a

Browse files
authored
GUFA: Represent imported functions as globals (#8025)
The meaning of Global in PossibleContents changes from "an actual wasm Global" to "an immutable global wasm thing, either a Global or a Function." Imported wasm Functions are effectively immutable values in the global scope - not concrete, specifically-known functions - so this is more correct. In particular, two imported Globals (of compatible types) might or might not be the same, and likewise with imported Functions. And that is not the case for defined Functions - they are definitely different. This does not fix the issues related to imported function type handling - #7993 does that - but this refactoring makes it possible to properly optimize imported functions afterward. This does make the PossibleContents object grow from 32 to 40 bytes, which might be why this adds 3-4% overhead to GUFA, but I don't see a good way to avoid that, unfortunately. Keeping our ability to optimize imported functions might be worth that 3-4% (we recently saw a few digits improvement due to properly optimizing imported externs, for example, from #8005).
1 parent 878ddba commit 8a66d2a

File tree

4 files changed

+133
-30
lines changed

4 files changed

+133
-30
lines changed

src/ir/possible-contents.cpp

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,9 @@ void PossibleContents::intersect(const PossibleContents& other) {
208208
// Note the global's information, if we started as a global. In that case, the
209209
// code below will refine our type but we can remain a global, which we will
210210
// accomplish by restoring our global status at the end.
211-
std::optional<Name> globalName;
211+
std::optional<GlobalInfo> global;
212212
if (isGlobal()) {
213-
globalName = getGlobal();
213+
global = getGlobal();
214214
}
215215

216216
if (hasFullCone() && other.hasFullCone()) {
@@ -230,9 +230,9 @@ void PossibleContents::intersect(const PossibleContents& other) {
230230
value = ConeType{newType, std::min(newDepth, otherNewDepth)};
231231
}
232232

233-
if (globalName) {
233+
if (global) {
234234
// Restore the global but keep the new and refined type.
235-
value = GlobalInfo{*globalName, getType()};
235+
value = GlobalInfo{global->name, global->kind, getType()};
236236
}
237237
}
238238

@@ -641,9 +641,17 @@ struct InfoCollector
641641
addRoot(curr);
642642
}
643643
void visitRefFunc(RefFunc* curr) {
644-
addRoot(curr,
645-
PossibleContents::literal(
646-
Literal::makeFunc(curr->func, curr->type.getHeapType())));
644+
if (!getModule()->getFunction(curr->func)->imported()) {
645+
// This is not imported, so we know the exact function literal.
646+
addRoot(curr,
647+
PossibleContents::literal(
648+
Literal::makeFunc(curr->func, curr->type.getHeapType())));
649+
} else {
650+
// This is imported, so it is effectively a global.
651+
addRoot(curr,
652+
PossibleContents::global(
653+
curr->func, ExternalKind::Function, curr->type));
654+
}
647655

648656
// The presence of a RefFunc indicates the function may be called
649657
// indirectly, so add the relevant connections for this particular function.
@@ -2836,7 +2844,8 @@ void Flower::filterGlobalContents(PossibleContents& contents,
28362844
// a cone/exact type *and* that something is equal to a global, in some
28372845
// cases. See https://github.com/WebAssembly/binaryen/pull/5083
28382846
if (contents.isMany() || contents.isConeType()) {
2839-
contents = PossibleContents::global(global->name, global->type);
2847+
contents = PossibleContents::global(
2848+
global->name, ExternalKind::Global, global->type);
28402849

28412850
// TODO: We could do better here, to set global->init->type instead of
28422851
// global->type, or even the contents.getType() - either of those

src/ir/possible-contents.h

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,11 @@ namespace wasm {
4040
//
4141
// * Literal: One possible constant value like an i32 of 42.
4242
//
43-
// * Global: The name of a global whose value is here. We do not know
44-
// the actual value at compile time, but we know it is equal
45-
// to that global. Typically we can only infer this for
46-
// immutable globals.
43+
// * Global: An immutable global value, something who we can identify
44+
// but do not know the actual value of at runtime. This can
45+
// be either a wasm (immutable) Global, or an imported wasm
46+
// Function (which is effectively the same: we can refer to
47+
// it, but do not know what is being imported there).
4748
//
4849
// * ConeType: Any possible value of a particular type, and a possible
4950
// "cone" of a certain depth below it. If the depth is 0
@@ -85,6 +86,7 @@ class PossibleContents {
8586

8687
struct GlobalInfo {
8788
Name name;
89+
ExternalKind kind;
8890
// The type of contents. Note that this may not match the type of the
8991
// global, if we were filtered. For example:
9092
//
@@ -99,7 +101,7 @@ class PossibleContents {
99101
// way. In principle, not having depth info can lead to loss of
100102
// precision.
101103
bool operator==(const GlobalInfo& other) const {
102-
return name == other.name && type == other.type;
104+
return name == other.name && kind == other.kind && type == other.type;
103105
}
104106
};
105107

@@ -144,8 +146,8 @@ class PossibleContents {
144146

145147
static PossibleContents none() { return PossibleContents{None()}; }
146148
static PossibleContents literal(Literal c) { return PossibleContents{c}; }
147-
static PossibleContents global(Name name, Type type) {
148-
return PossibleContents{GlobalInfo{name, type}};
149+
static PossibleContents global(Name name, ExternalKind kind, Type type) {
150+
return PossibleContents{GlobalInfo{name, kind, type}};
149151
}
150152
// Helper for a cone type with depth 0, i.e., an exact type.
151153
static PossibleContents exactType(Type type) {
@@ -216,9 +218,9 @@ class PossibleContents {
216218
return std::get<Literal>(value);
217219
}
218220

219-
Name getGlobal() const {
221+
GlobalInfo getGlobal() const {
220222
assert(isGlobal());
221-
return std::get<GlobalInfo>(value).name;
223+
return std::get<GlobalInfo>(value);
222224
}
223225

224226
bool isNull() const { return isLiteral() && getLiteral().isNull(); }
@@ -316,11 +318,18 @@ class PossibleContents {
316318
if (isLiteral()) {
317319
return builder.makeConstantExpression(getLiteral());
318320
} else {
319-
auto name = getGlobal();
321+
auto info = getGlobal();
320322
// Note that we load the type from the module, rather than use the type
321323
// in the GlobalInfo, as that type may not match the global (see comment
322324
// in the GlobalInfo declaration above).
323-
return builder.makeGlobalGet(name, wasm.getGlobal(name)->type);
325+
if (info.kind == ExternalKind::Global) {
326+
return builder.makeGlobalGet(info.name,
327+
wasm.getGlobal(info.name)->type);
328+
} else {
329+
assert(info.kind == ExternalKind::Function);
330+
return builder.makeRefFunc(
331+
info.name, wasm.getFunction(info.name)->type.getHeapType());
332+
}
324333
}
325334
}
326335

@@ -352,6 +361,7 @@ class PossibleContents {
352361
rehash(ret, getLiteral());
353362
} else if (auto* global = std::get_if<GlobalInfo>(&value)) {
354363
rehash(ret, global->name);
364+
rehash(ret, global->kind);
355365
rehash(ret, global->type);
356366
} else if (auto* coneType = std::get_if<ConeType>(&value)) {
357367
rehash(ret, coneType->type);
@@ -374,7 +384,9 @@ class PossibleContents {
374384
o << " HT: " << h;
375385
}
376386
} else if (isGlobal()) {
377-
o << "GlobalInfo $" << getGlobal() << " T: " << getType();
387+
auto info = getGlobal();
388+
o << "GlobalInfo $" << info.name << " K: " << int(info.kind)
389+
<< " T: " << getType();
378390
} else if (auto* coneType = std::get_if<ConeType>(&value)) {
379391
auto t = coneType->type;
380392
o << "ConeType " << t;

test/gtest/possible-contents.cpp

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,22 @@ class PossibleContentsTest : public testing::Test {
8080
PossibleContents::literal(Literal::makeNull(HeapType::i31));
8181

8282
PossibleContents i32Global1 =
83-
PossibleContents::global("i32Global1", Type::i32);
83+
PossibleContents::global("i32Global1", ExternalKind::Global, Type::i32);
8484
PossibleContents i32Global2 =
85-
PossibleContents::global("i32Global2", Type::i32);
86-
PossibleContents f64Global = PossibleContents::global("f64Global", Type::f64);
87-
PossibleContents anyGlobal = PossibleContents::global("anyGlobal", anyref);
88-
PossibleContents funcGlobal = PossibleContents::global("funcGlobal", funcref);
89-
PossibleContents nonNullFuncGlobal =
90-
PossibleContents::global("funcGlobal", Type(HeapType::func, NonNullable));
85+
PossibleContents::global("i32Global2", ExternalKind::Global, Type::i32);
86+
PossibleContents f64Global =
87+
PossibleContents::global("f64Global", ExternalKind::Global, Type::f64);
88+
PossibleContents anyGlobal =
89+
PossibleContents::global("anyGlobal", ExternalKind::Global, anyref);
90+
PossibleContents funcGlobal =
91+
PossibleContents::global("funcGlobal", ExternalKind::Global, funcref);
92+
PossibleContents nonNullFuncGlobal = PossibleContents::global(
93+
"funcGlobal", ExternalKind::Global, Type(HeapType::func, NonNullable));
94+
95+
PossibleContents importedFunc1 = PossibleContents::global(
96+
"impfunc1", ExternalKind::Function, Type(HeapType::func, NonNullable));
97+
PossibleContents importedFunc2 = PossibleContents::global(
98+
"impfunc2", ExternalKind::Function, Type(HeapType::func, NonNullable));
9199

92100
PossibleContents nonNullFunc = PossibleContents::literal(
93101
Literal::makeFunc("func", Signature(Type::none, Type::none)));
@@ -114,6 +122,8 @@ class PossibleContentsTest : public testing::Test {
114122
PossibleContents coneAnyref = PossibleContents::coneType(anyref);
115123
PossibleContents coneFuncref = PossibleContents::coneType(funcref);
116124
PossibleContents coneFuncref1 = PossibleContents::coneType(funcref, 1);
125+
PossibleContents coneNonNullFuncref =
126+
PossibleContents::coneType(Type(HeapType::func, NonNullable));
117127
};
118128

119129
TEST_F(PossibleContentsTest, TestComparisons) {
@@ -135,6 +145,9 @@ TEST_F(PossibleContentsTest, TestComparisons) {
135145
assertNotEqualSymmetric(i32Global1, exactI32);
136146
assertNotEqualSymmetric(i32Global1, many);
137147

148+
assertEqualSymmetric(importedFunc1, importedFunc1);
149+
assertNotEqualSymmetric(importedFunc1, importedFunc2);
150+
138151
assertEqualSymmetric(exactI32, exactI32);
139152
assertNotEqualSymmetric(exactI32, exactAnyref);
140153
assertNotEqualSymmetric(exactI32, many);
@@ -151,6 +164,23 @@ TEST_F(PossibleContentsTest, TestComparisons) {
151164
assertNotEqualSymmetric(exactNonNullAnyref, exactAnyref);
152165
}
153166

167+
TEST_F(PossibleContentsTest, TestComparisonsGlobals) {
168+
// Check if two PossibleContents::global, one a wasm Global and one a wasm
169+
// Function, and equal in their names and types, are still understood to be
170+
// non-equal: the |kind| field differentiates them.
171+
172+
PossibleContents wasmGlobal = PossibleContents::global(
173+
"foo", ExternalKind::Global, Type(HeapType::func, NonNullable));
174+
PossibleContents wasmFunction = PossibleContents::global(
175+
"foo", ExternalKind::Function, Type(HeapType::func, NonNullable));
176+
177+
assertNotEqualSymmetric(wasmGlobal, wasmFunction);
178+
179+
// But they are equal to themselves, of course.
180+
assertEqualSymmetric(wasmGlobal, wasmGlobal);
181+
assertEqualSymmetric(wasmFunction, wasmFunction);
182+
}
183+
154184
TEST_F(PossibleContentsTest, TestHash) {
155185
// Hashes should be deterministic.
156186
EXPECT_EQ(none.hash(), none.hash());
@@ -257,6 +287,10 @@ TEST_F(PossibleContentsTest, TestCombinations) {
257287

258288
assertCombination(anyGlobal, anyNull, coneAnyref);
259289
assertCombination(anyGlobal, i31Null, coneAnyref);
290+
291+
// Imported functions.
292+
assertCombination(importedFunc1, importedFunc1, importedFunc1);
293+
assertCombination(importedFunc1, importedFunc2, coneNonNullFuncref);
260294
}
261295

262296
static PassOptions options;
@@ -338,6 +372,13 @@ TEST_F(PossibleContentsTest, TestIntersection) {
338372

339373
// Separate hierarchies.
340374
assertLackIntersection(funcGlobal, anyGlobal);
375+
376+
// Imported functions.
377+
assertHaveIntersection(importedFunc1, importedFunc1);
378+
assertHaveIntersection(importedFunc1, exactFuncSignatureType);
379+
assertHaveIntersection(importedFunc1, exactNonNullFuncSignatureType);
380+
assertHaveIntersection(importedFunc1, importedFunc2);
381+
assertHaveIntersection(importedFunc1, funcGlobal);
341382
}
342383

343384
TEST_F(PossibleContentsTest, TestIntersectWithCombinations) {
@@ -484,6 +525,8 @@ TEST_F(PossibleContentsTest, TestIntersectWithCombinations) {
484525
funcGlobal,
485526
nonNullFuncGlobal,
486527
nonNullFunc,
528+
importedFunc1,
529+
importedFunc2,
487530
exactI32,
488531
exactAnyref,
489532
exactFuncref,
@@ -785,9 +828,10 @@ TEST_F(PossibleContentsTest, TestStructCones) {
785828
nonNullFunc, PossibleContents::coneType(signature), nonNullFunc);
786829

787830
// Filter a global to a more specific type.
788-
assertIntersection(funcGlobal,
789-
PossibleContents::coneType(signature),
790-
PossibleContents::global("funcGlobal", signature));
831+
assertIntersection(
832+
funcGlobal,
833+
PossibleContents::coneType(signature),
834+
PossibleContents::global("funcGlobal", ExternalKind::Global, signature));
791835

792836
// Filter a global's nullability only.
793837
auto nonNullFuncRef = Type(HeapType::func, NonNullable);
@@ -814,6 +858,13 @@ TEST_F(PossibleContentsTest, TestStructCones) {
814858
assertIntersection(funcGlobal, none, none);
815859
assertIntersection(PossibleContents::coneType(signature), none, none);
816860

861+
// Imported functions. TODO: These are not yet supported, and assert instead.
862+
// assertIntersection(
863+
// importedFunc1, importedFunc1, importedFunc1);
864+
// assertIntersection(
865+
// importedFunc1, PossibleContents::coneType(nonNullFuncRef),
866+
// importedFunc1);
867+
817868
// Subcontents. This API only supports the case where one of the inputs is a
818869
// full cone type.
819870
// First, compare exact types to such a cone.

test/lit/passes/gufa.wast

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,3 +1154,34 @@
11541154
)
11551155
)
11561156
)
1157+
1158+
;; Imported functions can be inferred.
1159+
(module
1160+
;; CHECK: (type $0 (func))
1161+
1162+
;; CHECK: (type $1 (func (result funcref)))
1163+
1164+
;; CHECK: (import "" "" (func $f (type $0)))
1165+
(import "" "" (func $f))
1166+
1167+
;; CHECK: (elem declare func $f)
1168+
1169+
;; CHECK: (export "test" (func $test))
1170+
1171+
;; CHECK: (func $test (type $1) (result funcref)
1172+
;; CHECK-NEXT: (local $temp funcref)
1173+
;; CHECK-NEXT: (local.set $temp
1174+
;; CHECK-NEXT: (ref.func $f)
1175+
;; CHECK-NEXT: )
1176+
;; CHECK-NEXT: (ref.func $f)
1177+
;; CHECK-NEXT: )
1178+
(func $test (export "test") (result funcref)
1179+
(local $temp funcref)
1180+
(local.set $temp
1181+
(ref.func $f)
1182+
)
1183+
;; This will become a ref.func $f.
1184+
(local.get $temp)
1185+
)
1186+
)
1187+

0 commit comments

Comments
 (0)