Skip to content

Commit 425d7c8

Browse files
authored
Type ref.null as exact (#7371)
`RefNull` expression now have type `(ref exact null bot)`, allowing them to be used wherever a nullable exact reference is expected. Update the fuzzer and fix a few bugs with exactness propagation this uncovers.
1 parent 83d8edb commit 425d7c8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+336
-237
lines changed

scripts/test/fuzzing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
'remove-unused-types-exact.wast',
109109
'coalesce-locals-exact.wast',
110110
'remove-unused-brs-exact.wast',
111+
'exact.wast',
111112
]
112113

113114

src/ir/manipulation.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,9 @@ template<typename InputType> inline Nop* nop(InputType* target) {
4040
}
4141

4242
template<typename InputType>
43-
inline RefNull* refNull(InputType* target, Type type) {
44-
assert(type.isNullable() && type.getHeapType().isBottom());
43+
inline RefNull* refNull(InputType* target, HeapType type) {
4544
auto* ret = convert<InputType, RefNull>(target);
46-
ret->finalize(type);
45+
ret->finalize(Type(type.getBottom(), Nullable, Exact));
4746
return ret;
4847
}
4948

src/ir/type-updating.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ Type GlobalTypeRewriter::getTempType(Type type) {
342342
if (type.isRef()) {
343343
auto heapType = type.getHeapType();
344344
if (auto it = typeIndices.find(heapType); it != typeIndices.end()) {
345+
// TODO: Handle exactness.
345346
return typeBuilder.getTempRefType(typeBuilder[it->second],
346347
type.getNullability());
347348
}

src/ir/type-updating.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,7 @@ class TypeMapper : public GlobalTypeRewriter {
497497
auto heapType = type.getHeapType();
498498
auto iter = mapping.find(heapType);
499499
if (iter != mapping.end()) {
500+
// TODO: Handle exactness.
500501
return getTempType(Type(iter->second, type.getNullability()));
501502
}
502503
return getTempType(type);

src/literal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ class Literal {
243243
}
244244
}
245245
static Literal makeNull(HeapType type) {
246-
return Literal(Type(type.getBottom(), Nullable));
246+
return Literal(Type(type.getBottom(), Nullable, Exact));
247247
}
248248
static Literal makeFunc(Name func, HeapType type) {
249249
return Literal(func, type);

src/passes/OptimizeInstructions.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2297,11 +2297,6 @@ struct OptimizeInstructions
22972297
// we need to check exactness. We can replace the cast with a drop
22982298
// followed by a direct return of the value, though.
22992299
if (ref->type.isNull()) {
2300-
// TODO: Remove this once we type ref.null as exact.
2301-
if (needsExactCast) {
2302-
return;
2303-
}
2304-
23052300
// We can materialize the resulting null value directly.
23062301
//
23072302
// The type must be nullable for us to do that, which it normally

src/tools/fuzzing.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,10 @@ class TranslateToFuzzReader {
360360
// instruction for EH is supposed to exist only at the beginning of a 'catch'
361361
// block, so it shouldn't be moved around or deleted freely.
362362
bool canBeArbitrarilyReplaced(Expression* curr) {
363+
// TODO: Remove this once we better support exact references.
364+
if (curr->type.isExact()) {
365+
return false;
366+
}
363367
return curr->type.isDefaultable() &&
364368
!EHUtils::containsValidDanglingPop(curr);
365369
}
@@ -521,7 +525,9 @@ class TranslateToFuzzReader {
521525
Type getLoggableType();
522526
bool isLoggableType(Type type);
523527
Nullability getNullability();
528+
Exactness getExactness();
524529
Nullability getSubType(Nullability nullability);
530+
Exactness getSubType(Exactness exactness);
525531
HeapType getSubType(HeapType type);
526532
Type getSubType(Type type);
527533
Nullability getSuperType(Nullability nullability);

src/tools/fuzzing/fuzzing.cpp

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,20 +1566,22 @@ void TranslateToFuzzReader::recombine(Function* func) {
15661566
}
15671567

15681568
std::vector<Type> ret;
1569-
auto heapType = type.getHeapType();
1570-
auto nullability = type.getNullability();
1569+
ret.push_back(type);
15711570

1572-
if (nullability == NonNullable) {
1573-
ret = getRelevantTypes(Type(heapType, Nullable));
1571+
if (type.isNonNullable()) {
1572+
auto nullable = getRelevantTypes(type.with(Nullable));
1573+
ret.insert(ret.end(), nullable.begin(), nullable.end());
1574+
}
1575+
if (type.isExact()) {
1576+
auto inexact = getRelevantTypes(type.with(Inexact));
1577+
ret.insert(ret.end(), inexact.begin(), inexact.end());
1578+
// Do not consider exact references to supertypes.
1579+
return ret;
15741580
}
15751581

1576-
while (1) {
1577-
ret.push_back(Type(heapType, nullability));
1578-
auto super = heapType.getSuperType();
1579-
if (!super) {
1580-
break;
1581-
}
1582-
heapType = *super;
1582+
for (auto heapType = type.getHeapType().getSuperType(); heapType;
1583+
heapType = heapType->getSuperType()) {
1584+
ret.push_back(type.with(*heapType));
15831585
}
15841586

15851587
return ret;
@@ -4902,9 +4904,17 @@ static auto makeArrayBoundsCheck(Expression* ref,
49024904
Function* func,
49034905
Builder& builder,
49044906
Expression* length = nullptr) {
4905-
auto tempRef = builder.addVar(func, ref->type);
4907+
// The reference might be a RefNull, in which case its type is exact. But we
4908+
// want to avoid creating exact-typed locals until we support them more widely
4909+
// in the fuzzer, so adjust the type. TODO: remove this once exact references
4910+
// are better supported.
4911+
Type refType = ref->type;
4912+
if (refType.isExact()) {
4913+
refType = refType.with(Inexact);
4914+
}
4915+
auto tempRef = builder.addVar(func, refType);
49064916
auto tempIndex = builder.addVar(func, index->type);
4907-
auto* teeRef = builder.makeLocalTee(tempRef, ref, ref->type);
4917+
auto* teeRef = builder.makeLocalTee(tempRef, ref, refType);
49084918
auto* teeIndex = builder.makeLocalTee(tempIndex, index, index->type);
49094919
auto* getSize = builder.makeArrayLen(teeRef);
49104920

@@ -4931,7 +4941,7 @@ static auto makeArrayBoundsCheck(Expression* ref,
49314941
// An additional use of the length, if it was provided.
49324942
Expression* getLength = nullptr;
49334943
} result = {builder.makeBinary(LtUInt32, effectiveIndex, getSize),
4934-
builder.makeLocalGet(tempRef, ref->type),
4944+
builder.makeLocalGet(tempRef, refType),
49354945
builder.makeLocalGet(tempIndex, index->type),
49364946
getLength};
49374947
return result;
@@ -5320,13 +5330,37 @@ Nullability TranslateToFuzzReader::getNullability() {
53205330
return Nullable;
53215331
}
53225332

5333+
Exactness TranslateToFuzzReader::getExactness() {
5334+
// Without GC, the only heap types are func and extern, neither of which is
5335+
// exactly inhabitable. To avoid introducing uninhabitable types, only
5336+
// generate exact references when GC is enabled. We don't need custom
5337+
// descriptors to be enabled even though that is the feature that introduces
5338+
// exact references because the binary writer can always generalize the exact
5339+
// reference types away.
5340+
//
5341+
// if (wasm.features.hasGC() && oneIn(8)) {
5342+
// return Exact;
5343+
// }
5344+
//
5345+
// However, we cannot yet handle creating exact references in general, so for
5346+
// now we always generate inexact references when given the choice. TODO.
5347+
return Inexact;
5348+
}
5349+
53235350
Nullability TranslateToFuzzReader::getSubType(Nullability nullability) {
53245351
if (nullability == NonNullable) {
53255352
return NonNullable;
53265353
}
53275354
return getNullability();
53285355
}
53295356

5357+
Exactness TranslateToFuzzReader::getSubType(Exactness exactness) {
5358+
if (exactness == Exact) {
5359+
return Exact;
5360+
}
5361+
return getExactness();
5362+
}
5363+
53305364
HeapType TranslateToFuzzReader::getSubType(HeapType type) {
53315365
if (oneIn(3)) {
53325366
return type;
@@ -5418,9 +5452,18 @@ Type TranslateToFuzzReader::getSubType(Type type) {
54185452
if (!funcContext && heapType.isMaybeShared(HeapType::exn)) {
54195453
return type;
54205454
}
5421-
heapType = getSubType(heapType);
5455+
if (type.isExact()) {
5456+
// The only other possible heap type is bottom, but we don't want to
5457+
// generate too many bottom types.
5458+
if (!heapType.isBottom() && oneIn(20)) {
5459+
heapType = heapType.getBottom();
5460+
}
5461+
} else {
5462+
heapType = getSubType(heapType);
5463+
}
54225464
auto nullability = getSubType(type.getNullability());
5423-
auto subType = Type(heapType, nullability);
5465+
auto exactness = getSubType(type.getExactness());
5466+
auto subType = Type(heapType, nullability, exactness);
54245467
// We don't want to emit lots of uninhabitable types like (ref none), so
54255468
// avoid them with high probability. Specifically, if the original type was
54265469
// inhabitable then return that; avoid adding more uninhabitability.

src/wasm-builder.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -670,11 +670,11 @@ class Builder {
670670
}
671671
RefNull* makeRefNull(HeapType type) {
672672
auto* ret = wasm.allocator.alloc<RefNull>();
673-
ret->finalize(Type(type.getBottom(), Nullable));
673+
ret->finalize(Type(type.getBottom(), Nullable, Exact));
674674
return ret;
675675
}
676676
RefNull* makeRefNull(Type type) {
677-
assert(type.isNullable() && type.isNull());
677+
assert(type.isNullable() && type.isNull() && type.isExact());
678678
auto* ret = wasm.allocator.alloc<RefNull>();
679679
ret->finalize(type);
680680
return ret;
@@ -1270,7 +1270,7 @@ class Builder {
12701270
return makeConst(value);
12711271
}
12721272
if (value.isNull()) {
1273-
return makeRefNull(type);
1273+
return makeRefNull(type.getHeapType());
12741274
}
12751275
if (type.isFunction()) {
12761276
return makeRefFunc(value.getFunc(), type.getHeapType());
@@ -1435,8 +1435,8 @@ class Builder {
14351435
return maybeWrap(makeConstantExpression(Literal::makeZeros(curr->type)));
14361436
}
14371437
if (curr->type.isNullable()) {
1438-
return maybeWrap(ExpressionManipulator::refNull(
1439-
curr, Type(curr->type.getHeapType().getBottom(), Nullable)));
1438+
return maybeWrap(
1439+
ExpressionManipulator::refNull(curr, curr->type.getHeapType()));
14401440
}
14411441
if (curr->type.isRef() &&
14421442
curr->type.getHeapType().isMaybeShared(HeapType::i31)) {

src/wasm/literal.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ Literal::Literal(const uint8_t init[16]) : type(Type::v128) {
7272
}
7373

7474
Literal::Literal(std::shared_ptr<GCData> gcData, HeapType type)
75-
: gcData(gcData), type(type, gcData ? NonNullable : Nullable) {
75+
: gcData(gcData),
76+
type(type, gcData ? NonNullable : Nullable, gcData ? Inexact : Exact) {
77+
// TODO: Use exact types for more than just nulls.
7678
// The type must be a proper type for GC data: either a struct, array, or
7779
// string; or an externalized version of the same; or a null.
7880
assert((isData() && gcData) ||

0 commit comments

Comments
 (0)