Skip to content

Commit 90d8185

Browse files
authored
Remove the GCNNLocals feature (#5080)
Now that the WasmGC spec has settled on a way of validating non-nullable locals, we no longer need this experimental feature that allowed nonstandard uses of non-nullable locals.
1 parent 5bfb192 commit 90d8185

31 files changed

Lines changed: 101 additions & 252 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Current Trunk
2020
- C API: Add BinaryenAddFunctionWithHeapType() which is like BinaryenAddFunction
2121
but takes a heap type. The old function is kept for backwards compatibility
2222
and as a convenience.
23+
- The nonstandard, experimental gc-nn-locals feature has been removed now that
24+
standard non-nullable locals are supported.
2325

2426
v114
2527
----

src/ir/type-updating.cpp

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -307,10 +307,6 @@ bool canHandleAsLocal(Type type) {
307307
}
308308

309309
void handleNonDefaultableLocals(Function* func, Module& wasm) {
310-
if (wasm.features.hasGCNNLocals()) {
311-
// We have nothing to fix up: all locals are allowed.
312-
return;
313-
}
314310
if (!wasm.features.hasReferenceTypes()) {
315311
// No references, so no non-nullable ones at all.
316312
return;
@@ -404,9 +400,6 @@ void handleNonDefaultableLocals(Function* func, Module& wasm) {
404400

405401
Type getValidLocalType(Type type, FeatureSet features) {
406402
assert(type.isConcrete());
407-
if (features.hasGCNNLocals()) {
408-
return type;
409-
}
410403
if (type.isNonNullable()) {
411404
return Type(type.getHeapType(), Nullable);
412405
}
@@ -421,9 +414,6 @@ Type getValidLocalType(Type type, FeatureSet features) {
421414
}
422415

423416
Expression* fixLocalGet(LocalGet* get, Module& wasm) {
424-
if (wasm.features.hasGCNNLocals()) {
425-
return get;
426-
}
427417
if (get->type.isNonNullable()) {
428418
// The get should now return a nullable value, and a ref.as_non_null
429419
// fixes that up.

src/passes/Flatten.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,9 @@
3333
// )
3434
// )
3535
//
36-
// The tuple has a non-nullable type, and so it cannot be set to a local. We
37-
// would need to split up the tuple and reconstruct it later, but that would
38-
// require allowing tuple operations in more nested places than Flat IR allows
39-
// today. For now, error on this; eventually changes in the spec regarding
40-
// null-nullability may make this easier.
36+
// The tuple has a non-nullable type, and so it cannot currently be set to a
37+
// local, but in principle there's no reason it couldn't be. For now, error on
38+
// this.
4139

4240
#include <ir/branch-utils.h>
4341
#include <ir/effects.h>

src/passes/LocalSubtyping.cpp

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -78,27 +78,11 @@ struct LocalSubtyping : public WalkerPass<PostWalker<LocalSubtyping>> {
7878
// Find which vars can be non-nullable.
7979
std::unordered_set<Index> cannotBeNonNullable;
8080

81-
if (getModule()->features.hasGCNNLocals()) {
82-
// If the feature is enabled then the only constraint is being able to
83-
// read the default value - if it is readable, the local cannot become
84-
// non-nullable.
85-
for (auto& [get, sets] : localGraph.getSetses) {
86-
auto index = get->index;
87-
if (func->isVar(index) &&
88-
std::any_of(sets.begin(), sets.end(), [&](LocalSet* set) {
89-
return set == nullptr;
90-
})) {
91-
cannotBeNonNullable.insert(index);
92-
}
93-
}
94-
} else {
95-
// Without GCNNLocals, validation rules follow the spec rules: all gets
96-
// must be dominated structurally by sets, for the local to be non-
97-
// nullable.
98-
LocalStructuralDominance info(func, *getModule());
99-
for (auto index : info.nonDominatingIndices) {
100-
cannotBeNonNullable.insert(index);
101-
}
81+
// All gets must be dominated structurally by sets for the local to be non-
82+
// nullable.
83+
LocalStructuralDominance info(func, *getModule());
84+
for (auto index : info.nonDominatingIndices) {
85+
cannotBeNonNullable.insert(index);
10286
}
10387

10488
auto varBase = func->getVarIndexBase();

src/passes/OptimizeInstructions.cpp

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,17 +1155,9 @@ struct OptimizeInstructions
11551155

11561156
void visitLocalSet(LocalSet* curr) {
11571157
// Interactions between local.set/tee and ref.as_non_null can be optimized
1158-
// in some cases, by removing or moving the ref.as_non_null operation. In
1159-
// all cases, we only do this when we do *not* allow non-nullable locals. If
1160-
// we do allow such locals, then (1) this local might be non-nullable, so we
1161-
// can't remove or move a ref.as_non_null flowing into a local.set/tee, and
1162-
// (2) even if the local were nullable, if we change things we might prevent
1163-
// the LocalSubtyping pass from turning it into a non-nullable local later.
1164-
// Note that we must also check if this local is nullable regardless, as a
1165-
// parameter might be non-nullable even if nullable locals are disallowed
1166-
// (as that just affects vars, and not params).
1158+
// in some cases, by removing or moving the ref.as_non_null operation.
11671159
if (auto* as = curr->value->dynCast<RefAs>()) {
1168-
if (as->op == RefAsNonNull && !getModule()->features.hasGCNNLocals() &&
1160+
if (as->op == RefAsNonNull &&
11691161
getFunction()->getLocalType(curr->index).isNullable()) {
11701162
// (local.tee (ref.as_non_null ..))
11711163
// =>

src/tools/tool-options.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ struct ToolOptions : public Options {
8989
.addFeature(FeatureSet::Multivalue, "multivalue functions")
9090
.addFeature(FeatureSet::GC, "garbage collection")
9191
.addFeature(FeatureSet::Memory64, "memory64")
92-
.addFeature(FeatureSet::GCNNLocals, "GC non-null locals")
9392
.addFeature(FeatureSet::RelaxedSIMD, "relaxed SIMD")
9493
.addFeature(FeatureSet::ExtendedConst, "extended const expressions")
9594
.addFeature(FeatureSet::Strings, "strings")

src/wasm-features.h

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,15 @@ struct FeatureSet {
4040
Multivalue = 1 << 9,
4141
GC = 1 << 10,
4242
Memory64 = 1 << 11,
43-
// TODO: Remove this feature when the wasm spec stabilizes.
44-
GCNNLocals = 1 << 12,
45-
RelaxedSIMD = 1 << 13,
46-
ExtendedConst = 1 << 14,
47-
Strings = 1 << 15,
48-
MultiMemory = 1 << 16,
43+
RelaxedSIMD = 1 << 12,
44+
ExtendedConst = 1 << 13,
45+
Strings = 1 << 14,
46+
MultiMemory = 1 << 15,
4947
MVP = None,
5048
// Keep in sync with llvm default features:
5149
// https://github.com/llvm/llvm-project/blob/c7576cb89d6c95f03968076e902d3adfd1996577/clang/lib/Basic/Targets/WebAssembly.cpp#L150-L153
5250
Default = SignExt | MutableGlobals,
53-
// GCNNLocals are opt-in: merely asking for "All" does not apply them. To
54-
// get all possible values use AllPossible. See setAll() below for more
55-
// details.
56-
All = ((1 << 17) - 1) & ~GCNNLocals,
57-
AllPossible = (1 << 17) - 1,
51+
All = (1 << 16) - 1,
5852
};
5953

6054
static std::string toString(Feature f) {
@@ -83,8 +77,6 @@ struct FeatureSet {
8377
return "gc";
8478
case Memory64:
8579
return "memory64";
86-
case GCNNLocals:
87-
return "gc-nn-locals";
8880
case RelaxedSIMD:
8981
return "relaxed-simd";
9082
case ExtendedConst:
@@ -101,7 +93,7 @@ struct FeatureSet {
10193
std::string toString() const {
10294
std::string ret;
10395
uint32_t x = 1;
104-
while (x & Feature::AllPossible) {
96+
while (x & Feature::All) {
10597
if (features & x) {
10698
if (!ret.empty()) {
10799
ret += ", ";
@@ -133,12 +125,11 @@ struct FeatureSet {
133125
bool hasMultivalue() const { return (features & Multivalue) != 0; }
134126
bool hasGC() const { return (features & GC) != 0; }
135127
bool hasMemory64() const { return (features & Memory64) != 0; }
136-
bool hasGCNNLocals() const { return (features & GCNNLocals) != 0; }
137128
bool hasRelaxedSIMD() const { return (features & RelaxedSIMD) != 0; }
138129
bool hasExtendedConst() const { return (features & ExtendedConst) != 0; }
139130
bool hasStrings() const { return (features & Strings) != 0; }
140131
bool hasMultiMemory() const { return (features & MultiMemory) != 0; }
141-
bool hasAll() const { return (features & AllPossible) != 0; }
132+
bool hasAll() const { return (features & All) != 0; }
142133

143134
void set(FeatureSet f, bool v = true) {
144135
features = v ? (features | f) : (features & ~f);
@@ -155,34 +146,18 @@ struct FeatureSet {
155146
void setMultivalue(bool v = true) { set(Multivalue, v); }
156147
void setGC(bool v = true) { set(GC, v); }
157148
void setMemory64(bool v = true) { set(Memory64, v); }
158-
void setGCNNLocals(bool v = true) { set(GCNNLocals, v); }
159149
void setRelaxedSIMD(bool v = true) { set(RelaxedSIMD, v); }
160150
void setExtendedConst(bool v = true) { set(ExtendedConst, v); }
161151
void setStrings(bool v = true) { set(Strings, v); }
162152
void setMultiMemory(bool v = true) { set(MultiMemory, v); }
163153
void setMVP() { features = MVP; }
164-
void setAll() {
165-
// Do not set GCNNLocals, which forces the user to opt in to that feature
166-
// explicitly. That is, wasm-opt -all will enable GC but *not* enable
167-
// non-nullable locals. To get them, do wasm-opt -all --enable-gc-nn-locals
168-
// FIXME: When the wasm spec stabilizes, this feature will go away, as the
169-
// non-nullable locals experiment will either become the standard,
170-
// or it will go away.
171-
// Leave the old GCNNLocals value unmodified. This makes things like
172-
// --enable-gc-nn-locals -all work (that is, if we enable the feature,
173-
// then -all does not disable it; it simply does not enable it by itself).
174-
auto oldGCNNLocals = hasGCNNLocals();
175-
features = AllPossible;
176-
setGCNNLocals(oldGCNNLocals);
177-
}
154+
void setAll() { features = All; }
178155

179156
void enable(const FeatureSet& other) { features |= other.features; }
180-
void disable(const FeatureSet& other) {
181-
features = features & ~other.features & AllPossible;
182-
}
157+
void disable(const FeatureSet& other) { features &= ~other.features; }
183158

184159
template<typename F> void iterFeatures(F f) const {
185-
for (uint32_t feature = MVP + 1; feature < AllPossible; feature <<= 1) {
160+
for (uint32_t feature = MVP + 1; feature < All; feature <<= 1) {
186161
if (has(feature)) {
187162
f(static_cast<Feature>(feature));
188163
}

src/wasm/wasm-binary.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2627,9 +2627,7 @@ void WasmBinaryReader::readFunctions() {
26272627
}
26282628
}
26292629

2630-
if (!wasm.features.hasGCNNLocals()) {
2631-
TypeUpdating::handleNonDefaultableLocals(func, wasm);
2632-
}
2630+
TypeUpdating::handleNonDefaultableLocals(func, wasm);
26332631

26342632
std::swap(func->epilogLocation, debugLocation);
26352633
currFunction = nullptr;

src/wasm/wasm-validator.cpp

Lines changed: 7 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3195,45 +3195,13 @@ void FunctionValidator::visitFunction(Function* curr) {
31953195

31963196
if (getModule()->features.hasGC()) {
31973197
// If we have non-nullable locals, verify that local.get are valid.
3198-
if (!getModule()->features.hasGCNNLocals()) {
3199-
// Without the special GCNNLocals feature, we implement the spec rules,
3200-
// that is, a set allows gets until the end of the block.
3201-
LocalStructuralDominance info(curr, *getModule());
3202-
for (auto index : info.nonDominatingIndices) {
3203-
auto localType = curr->getLocalType(index);
3204-
for (auto type : localType) {
3205-
shouldBeTrue(!type.isNonNullable(),
3206-
index,
3207-
"non-nullable local's sets must dominate gets");
3208-
}
3209-
}
3210-
} else {
3211-
// With the special GCNNLocals feature, we allow gets anywhere, so long as
3212-
// we can prove they cannot read the null value. (TODO: remove this once
3213-
// the spec is stable).
3214-
//
3215-
// This is slow, so only do it if we find such locals exist at all.
3216-
bool hasNNLocals = false;
3217-
for (const auto& var : curr->vars) {
3218-
if (!var.isDefaultable()) {
3219-
hasNNLocals = true;
3220-
break;
3221-
}
3222-
}
3223-
if (hasNNLocals) {
3224-
LocalGraph graph(curr);
3225-
for (auto& [get, sets] : graph.getSetses) {
3226-
auto index = get->index;
3227-
// It is always ok to read nullable locals, and it is always ok to
3228-
// read params even if they are non-nullable.
3229-
if (curr->getLocalType(index).isDefaultable() ||
3230-
curr->isParam(index)) {
3231-
continue;
3232-
}
3233-
for (auto* set : sets) {
3234-
shouldBeTrue(!!set, index, "non-nullable local must not read null");
3235-
}
3236-
}
3198+
LocalStructuralDominance info(curr, *getModule());
3199+
for (auto index : info.nonDominatingIndices) {
3200+
auto localType = curr->getLocalType(index);
3201+
for (auto type : localType) {
3202+
shouldBeTrue(!type.isNonNullable(),
3203+
index,
3204+
"non-nullable local's sets must dominate gets");
32373205
}
32383206
}
32393207
}

test/binaryen.js/kitchen-sink.js.txt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ Features.ReferenceTypes: 256
2929
Features.Multivalue: 512
3030
Features.GC: 1024
3131
Features.Memory64: 2048
32-
Features.RelaxedSIMD: 8192
33-
Features.ExtendedConst: 16384
34-
Features.Strings: 32768
35-
Features.MultiMemory: 65536
36-
Features.All: 126975
32+
Features.RelaxedSIMD: 4096
33+
Features.ExtendedConst: 8192
34+
Features.Strings: 16384
35+
Features.MultiMemory: 32768
36+
Features.All: 65535
3737
InvalidId: 0
3838
BlockId: 1
3939
IfId: 2

0 commit comments

Comments
 (0)