Skip to content

Commit a150177

Browse files
authored
Type allocations as exact (#7589)
Update RefFunc, StructNew, ArrayNew*, ContNew, and ContBind expression to have exact reference types, as they will under the custom descriptors proposal. Even without that proposal enabled, using exact types in the IR can help better optimize casts and generally allows us to propagate more precise type information. Update the fuzzer to avoid assertion failures and invalid output now that it can observe exact types via allocations. Update test output to contain newly emitted exact types.
1 parent 2bef62a commit a150177

Some content is hidden

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

59 files changed

+463
-417
lines changed

src/literal.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class Literal {
8888
explicit Literal(const std::array<Literal, 4>&);
8989
explicit Literal(const std::array<Literal, 2>&);
9090
explicit Literal(Name func, HeapType type)
91-
: func(func), type(type, NonNullable) {
91+
: func(func), type(type, NonNullable, Exact) {
9292
assert(type.isSignature());
9393
}
9494
explicit Literal(std::shared_ptr<GCData> gcData, HeapType type);

src/passes/FuncCastEmulation.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,7 @@ struct FuncCastEmulation : public Pass {
178178
}
179179
auto* thunk = iter->second;
180180
ref->func = thunk->name;
181-
// TODO: Make this exact.
182-
ref->type = Type(thunk->type, NonNullable);
181+
ref->finalize(thunk->type);
183182
}
184183
}
185184

src/passes/LegalizeJSInterface.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,7 @@ struct LegalizeJSInterface : public Pass {
148148
}
149149

150150
curr->func = iter->second->name;
151-
// TODO: Make this exact.
152-
curr->type = Type(iter->second->type, NonNullable);
151+
curr->finalize(iter->second->type);
153152
}
154153
};
155154

src/passes/TypeSSA.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ struct TypeSSA : public Pass {
437437
auto* curr = newsToModify[i];
438438
auto oldType = curr->type.getHeapType();
439439
auto newType = newTypes[i];
440-
curr->type = Type(newType, NonNullable);
440+
curr->type = Type(newType, NonNullable, Exact);
441441

442442
// If the old type has a nice name, make a nice name for the new one.
443443
if (typeNames.count(oldType)) {

src/tools/fuzzing/fuzzing.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4801,12 +4801,17 @@ Expression* TranslateToFuzzReader::makeRefTest(Type type) {
48014801
// This unreachable avoids a warning on refType being possibly undefined.
48024802
WASM_UNREACHABLE("bad case");
48034803
}
4804+
if (!wasm.features.hasCustomDescriptors()) {
4805+
// Exact cast targets disallowed without custom descriptors.
4806+
castType = castType.with(Inexact);
4807+
}
48044808
return builder.makeRefTest(make(refType), castType);
48054809
}
48064810

48074811
Expression* TranslateToFuzzReader::makeRefCast(Type type) {
48084812
assert(type.isRef());
48094813
assert(wasm.features.hasReferenceTypes() && wasm.features.hasGC());
4814+
assert(type.isInexact() || wasm.features.hasCustomDescriptors());
48104815
// As with RefTest, use possibly related types. Unlike there, we are given the
48114816
// output type, which is the cast type, so just generate the ref's type.
48124817
Type refType;
@@ -4907,6 +4912,10 @@ Expression* TranslateToFuzzReader::makeBrOn(Type type) {
49074912
// nullability, so the combination of the two must be a subtype of
49084913
// targetType.
49094914
castType = getSubType(targetType);
4915+
if (!wasm.features.hasCustomDescriptors()) {
4916+
// Exact cast targets disallowed without custom descriptors.
4917+
castType = castType.with(Inexact);
4918+
}
49104919
// The ref's type must be castable to castType, or we'd not validate. But
49114920
// it can also be a subtype, which will trivially also succeed (so do that
49124921
// more rarely). Pick subtypes rarely, as they make the cast trivial.
@@ -4933,6 +4942,10 @@ Expression* TranslateToFuzzReader::makeBrOn(Type type) {
49334942
refType = getSubType(targetType);
49344943
// See above on BrOnCast, but flipped.
49354944
castType = oneIn(5) ? getSuperType(refType) : getSubType(refType);
4945+
if (!wasm.features.hasCustomDescriptors()) {
4946+
// Exact cast targets disallowed without custom descriptors.
4947+
castType = castType.with(Inexact);
4948+
}
49364949
// There is no nullability to adjust: if targetType is non-nullable then
49374950
// both refType and castType are as well, as subtypes of it. But we can
49384951
// also allow castType to be nullable (it is not sent to the target).
@@ -5546,7 +5559,8 @@ Type TranslateToFuzzReader::getSubType(Type type) {
55465559
heapType = getSubType(heapType);
55475560
}
55485561
auto nullability = getSubType(type.getNullability());
5549-
auto exactness = getSubType(type.getExactness());
5562+
auto exactness =
5563+
heapType.isBasic() ? Inexact : getSubType(type.getExactness());
55505564
auto subType = Type(heapType, nullability, exactness);
55515565
// We don't want to emit lots of uninhabitable types like (ref none), so
55525566
// avoid them with high probability. Specifically, if the original type was

src/wasm-builder.h

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -908,21 +908,21 @@ class Builder {
908908
std::initializer_list<Expression*> args) {
909909
auto* ret = wasm.allocator.alloc<StructNew>();
910910
ret->operands.set(args);
911-
ret->type = Type(type, NonNullable);
911+
ret->type = Type(type, NonNullable, Exact);
912912
ret->finalize();
913913
return ret;
914914
}
915915
StructNew* makeStructNew(HeapType type, ExpressionList&& args) {
916916
auto* ret = wasm.allocator.alloc<StructNew>();
917917
ret->operands = std::move(args);
918-
ret->type = Type(type, NonNullable);
918+
ret->type = Type(type, NonNullable, Exact);
919919
ret->finalize();
920920
return ret;
921921
}
922922
template<typename T> StructNew* makeStructNew(HeapType type, const T& args) {
923923
auto* ret = wasm.allocator.alloc<StructNew>();
924924
ret->operands.set(args);
925-
ret->type = Type(type, NonNullable);
925+
ret->type = Type(type, NonNullable, Exact);
926926
ret->finalize();
927927
return ret;
928928
}
@@ -985,7 +985,7 @@ class Builder {
985985
auto* ret = wasm.allocator.alloc<ArrayNew>();
986986
ret->size = size;
987987
ret->init = init;
988-
ret->type = Type(type, NonNullable);
988+
ret->type = Type(type, NonNullable, Exact);
989989
ret->finalize();
990990
return ret;
991991
}
@@ -997,7 +997,7 @@ class Builder {
997997
ret->segment = seg;
998998
ret->offset = offset;
999999
ret->size = size;
1000-
ret->type = Type(type, NonNullable);
1000+
ret->type = Type(type, NonNullable, Exact);
10011001
ret->finalize();
10021002
return ret;
10031003
}
@@ -1009,15 +1009,15 @@ class Builder {
10091009
ret->segment = seg;
10101010
ret->offset = offset;
10111011
ret->size = size;
1012-
ret->type = Type(type, NonNullable);
1012+
ret->type = Type(type, NonNullable, Exact);
10131013
ret->finalize();
10141014
return ret;
10151015
}
10161016
template<typename T>
10171017
ArrayNewFixed* makeArrayNewFixed(HeapType type, const T& values) {
10181018
auto* ret = wasm.allocator.alloc<ArrayNewFixed>();
10191019
ret->values.set(values);
1020-
ret->type = Type(type, NonNullable);
1020+
ret->type = Type(type, NonNullable, Exact);
10211021
ret->finalize();
10221022
return ret;
10231023
}
@@ -1185,7 +1185,7 @@ class Builder {
11851185
}
11861186
ContNew* makeContNew(HeapType type, Expression* func) {
11871187
auto* ret = wasm.allocator.alloc<ContNew>();
1188-
ret->type = Type(type, NonNullable);
1188+
ret->type = Type(type, NonNullable, Exact);
11891189
ret->func = func;
11901190
ret->finalize();
11911191
return ret;
@@ -1194,7 +1194,7 @@ class Builder {
11941194
ExpressionList&& operands,
11951195
Expression* cont) {
11961196
auto* ret = wasm.allocator.alloc<ContBind>();
1197-
ret->type = Type(targetType, NonNullable);
1197+
ret->type = Type(targetType, NonNullable, Exact);
11981198
ret->operands = std::move(operands);
11991199
ret->cont = cont;
12001200
ret->finalize();

src/wasm/literal.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +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) {
76-
// TODO: Use exact types for gcData.
75+
: gcData(gcData), type(type,
76+
gcData ? NonNullable : Nullable,
77+
gcData && !type.isBasic() ? Exact : Inexact) {
7778
// The type must be a proper type for GC data: either a struct, array, or
7879
// string; or an externalized version of the same; or a null; or an
7980
// internalized string (which appears as an anyref).

src/wasm/wasm-ir-builder.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2120,7 +2120,7 @@ Result<> IRBuilder::makeBrOn(
21202120

21212121
Result<> IRBuilder::makeStructNew(HeapType type) {
21222122
StructNew curr(wasm.allocator);
2123-
curr.type = Type(type, NonNullable);
2123+
curr.type = Type(type, NonNullable, Exact);
21242124
// Differentiate from struct.new_default with a non-empty expression list.
21252125
curr.operands.resize(type.getStruct().fields.size());
21262126
CHECK_ERR(visitStructNew(&curr));
@@ -2181,7 +2181,7 @@ IRBuilder::makeStructCmpxchg(HeapType type, Index field, MemoryOrder order) {
21812181

21822182
Result<> IRBuilder::makeArrayNew(HeapType type) {
21832183
ArrayNew curr;
2184-
curr.type = Type(type, NonNullable);
2184+
curr.type = Type(type, NonNullable, Exact);
21852185
// Differentiate from array.new_default with dummy initializer.
21862186
curr.init = (Expression*)0x01;
21872187
CHECK_ERR(visitArrayNew(&curr));

src/wasm/wasm-validator.cpp

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ struct FunctionValidator : public WalkerPass<PostWalker<FunctionValidator>> {
502502
void visitStructRMW(StructRMW* curr);
503503
void visitStructCmpxchg(StructCmpxchg* curr);
504504
void visitArrayNew(ArrayNew* curr);
505-
template<typename ArrayNew> void visitArrayNew(ArrayNew* curr);
505+
template<typename ArrayNew> void visitArrayNewSegment(ArrayNew* curr);
506506
void visitArrayNewData(ArrayNewData* curr);
507507
void visitArrayNewElem(ArrayNewElem* curr);
508508
void visitArrayNewFixed(ArrayNewFixed* curr);
@@ -2375,6 +2375,8 @@ void FunctionValidator::visitRefFunc(RefFunc* curr) {
23752375
shouldBeTrue(func->type == curr->type.getHeapType(),
23762376
curr,
23772377
"function reference type must match referenced function type");
2378+
shouldBeTrue(
2379+
curr->type.isExact(), curr, "function reference should be exact");
23782380
}
23792381

23802382
void FunctionValidator::visitRefEq(RefEq* curr) {
@@ -3022,6 +3024,7 @@ void FunctionValidator::visitStructNew(StructNew* curr) {
30223024
"struct.new should have a non-nullable reference type")) {
30233025
return;
30243026
}
3027+
shouldBeTrue(curr->type.isExact(), curr, "struct.new should be exact");
30253028
auto heapType = curr->type.getHeapType();
30263029
if (!shouldBeTrue(
30273030
heapType.isStruct(), curr, "struct.new heap type must be struct")) {
@@ -3260,6 +3263,7 @@ void FunctionValidator::visitArrayNew(ArrayNew* curr) {
32603263
"array.new should have a non-nullable reference type")) {
32613264
return;
32623265
}
3266+
shouldBeTrue(curr->type.isExact(), curr, "array.new* should be exact");
32633267
auto heapType = curr->type.getHeapType();
32643268
if (!shouldBeTrue(
32653269
heapType.isArray(), curr, "array.new heap type must be array")) {
@@ -3284,7 +3288,7 @@ void FunctionValidator::visitArrayNew(ArrayNew* curr) {
32843288
}
32853289

32863290
template<typename ArrayNew>
3287-
void FunctionValidator::visitArrayNew(ArrayNew* curr) {
3291+
void FunctionValidator::visitArrayNewSegment(ArrayNew* curr) {
32883292
shouldBeTrue(getModule()->features.hasGC(),
32893293
curr,
32903294
"array.new_{data, elem} requires gc [--enable-gc]");
@@ -3307,6 +3311,8 @@ void FunctionValidator::visitArrayNew(ArrayNew* curr) {
33073311
"array.new_{data, elem} type should be an array reference")) {
33083312
return;
33093313
}
3314+
shouldBeTrue(
3315+
curr->type.isExact(), curr, "array.new_{data, elem} should be exact");
33103316
auto heapType = curr->type.getHeapType();
33113317
if (!shouldBeTrue(
33123318
heapType.isArray(),
@@ -3317,7 +3323,7 @@ void FunctionValidator::visitArrayNew(ArrayNew* curr) {
33173323
}
33183324

33193325
void FunctionValidator::visitArrayNewData(ArrayNewData* curr) {
3320-
visitArrayNew(curr);
3326+
visitArrayNewSegment(curr);
33213327

33223328
shouldBeTrue(
33233329
getModule()->features.hasBulkMemory(),
@@ -3340,7 +3346,7 @@ void FunctionValidator::visitArrayNewData(ArrayNewData* curr) {
33403346
}
33413347

33423348
void FunctionValidator::visitArrayNewElem(ArrayNewElem* curr) {
3343-
visitArrayNew(curr);
3349+
visitArrayNewSegment(curr);
33443350

33453351
if (!shouldBeTrue(getModule()->getElementSegment(curr->segment),
33463352
curr,
@@ -3363,21 +3369,22 @@ void FunctionValidator::visitArrayNewElem(ArrayNewElem* curr) {
33633369
void FunctionValidator::visitArrayNewFixed(ArrayNewFixed* curr) {
33643370
shouldBeTrue(getModule()->features.hasGC(),
33653371
curr,
3366-
"array.init requires gc [--enable-gc]");
3372+
"array.new_fixed requires gc [--enable-gc]");
33673373
if (curr->type == Type::unreachable) {
33683374
return;
33693375
}
3376+
shouldBeTrue(curr->type.isExact(), curr, "array.new_fixed should be exact");
33703377
auto heapType = curr->type.getHeapType();
33713378
if (!shouldBeTrue(
3372-
heapType.isArray(), curr, "array.init heap type must be array")) {
3379+
heapType.isArray(), curr, "array.new_fixed heap type must be array")) {
33733380
return;
33743381
}
33753382
const auto& element = heapType.getArray().element;
33763383
for (auto* value : curr->values) {
33773384
shouldBeSubType(value->type,
33783385
element.type,
33793386
curr,
3380-
"array.init value must have proper type");
3387+
"array.new_fixed value must have proper type");
33813388
}
33823389
}
33833390

@@ -3699,6 +3706,7 @@ void FunctionValidator::visitContNew(ContNew* curr) {
36993706
"cont.new should have a non-nullable reference type")) {
37003707
return;
37013708
}
3709+
shouldBeTrue(curr->type.isExact(), curr, "cont.new should be exact");
37023710

37033711
shouldBeTrue(curr->type.isContinuation() &&
37043712
curr->type.getHeapType().getContinuation().type.isSignature(),
@@ -3735,6 +3743,7 @@ void FunctionValidator::visitContBind(ContBind* curr) {
37353743
"cont.bind should have a non-nullable reference type")) {
37363744
return;
37373745
}
3746+
shouldBeTrue(curr->type.isExact(), curr, "cont.bind should be exact");
37383747
}
37393748

37403749
void FunctionValidator::visitSuspend(Suspend* curr) {

src/wasm/wasm.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,7 @@ void RefFunc::finalize() {
830830
}
831831

832832
void RefFunc::finalize(HeapType heapType) {
833-
type = Type(heapType, NonNullable);
833+
type = Type(heapType, NonNullable, Exact);
834834
}
835835

836836
void RefEq::finalize() {

test/binaryen.js/expressions.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1997,7 +1997,8 @@ console.log("# RefFunc");
19971997
assert(theRefFunc instanceof binaryen.RefFunc);
19981998
assert(theRefFunc instanceof binaryen.Expression);
19991999
assert(theRefFunc.func === func);
2000-
assert(theRefFunc.type === type);
2000+
// TODO: assert that the type is a non-nullable, exact reference to the
2001+
// function type once we can express that in the JS API.
20012002

20022003
var info = binaryen.getExpressionInfo(theRefFunc);
20032004
assert(info.id === theRefFunc.id);
@@ -2007,7 +2008,8 @@ console.log("# RefFunc");
20072008
theRefFunc.func = func = "b";
20082009
assert(theRefFunc.func === func);
20092010
theRefFunc.finalize();
2010-
assert(theRefFunc.type === type);
2011+
// TODO: assert that the type is a non-nullable, exact reference to the
2012+
// function type once we can express that in the JS API.
20112013

20122014
info = binaryen.getExpressionInfo(theRefFunc);
20132015
assert(info.type === theRefFunc.type);
@@ -2223,7 +2225,8 @@ console.log("# StructNew");
22232225
assert(theStructNew instanceof binaryen.Expression);
22242226
assertDeepEqual(theStructNew.operands, operands);
22252227
assertDeepEqual(theStructNew.getOperands(), operands);
2226-
assert(theStructNew.type === type);
2228+
// TODO: assert that the type is a non-nullable, exact reference to the
2229+
// function type once we can express that in the JS API.
22272230

22282231
var info = binaryen.getExpressionInfo(theStructNew);
22292232
assert(info.id === theStructNew.id);
@@ -2405,7 +2408,8 @@ console.log("# ArrayNew");
24052408
assert(theArrayNew instanceof binaryen.Expression);
24062409
assert(theArrayNew.size === size);
24072410
assert(theArrayNew.init === init);
2408-
assert(theArrayNew.type === type);
2411+
// TODO: assert that the type is a non-nullable, exact reference to the
2412+
// function type once we can express that in the JS API.
24092413

24102414
var info = binaryen.getExpressionInfo(theArrayNew);
24112415
assert(info.id === theArrayNew.id);
@@ -2458,7 +2462,8 @@ console.log("# ArrayNewFixed");
24582462
assert(theArrayNewFixed instanceof binaryen.Expression);
24592463
assertDeepEqual(theArrayNewFixed.values, values);
24602464
assertDeepEqual(theArrayNewFixed.getValues(), values);
2461-
assert(theArrayNewFixed.type === type);
2465+
// TODO: assert that the type is a non-nullable, exact reference to the
2466+
// function type once we can express that in the JS API.
24622467

24632468
var info = binaryen.getExpressionInfo(theArrayNewFixed);
24642469
assert(info.id === theArrayNewFixed.id);
@@ -2519,7 +2524,8 @@ console.log("# ArrayNewData");
25192524
assert(theArrayNewData.segment === segment);
25202525
assert(theArrayNewData.offset === offset);
25212526
assert(theArrayNewData.size === size);
2522-
assert(theArrayNewData.type === type);
2527+
// TODO: assert that the type is a non-nullable, exact reference to the
2528+
// function type once we can express that in the JS API.
25232529

25242530
var info = binaryen.getExpressionInfo(theArrayNewData);
25252531
assert(info.id === theArrayNewData.id);
@@ -2576,7 +2582,8 @@ console.log("# ArrayNewElem");
25762582
assert(theArrayNewElem.segment === segment);
25772583
assert(theArrayNewElem.offset === offset);
25782584
assert(theArrayNewElem.size === size);
2579-
assert(theArrayNewElem.type === type);
2585+
// TODO: assert that the type is a non-nullable, exact reference to the
2586+
// function type once we can express that in the JS API.
25802587

25812588
var info = binaryen.getExpressionInfo(theArrayNewElem);
25822589
assert(info.id === theArrayNewElem.id);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2089,7 +2089,7 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7}
20892089
)
20902090
)
20912091
(drop
2092-
(select (result (ref null $0))
2092+
(select (result (ref null (exact $0)))
20932093
(ref.null nofunc)
20942094
(ref.func $foobar)
20952095
(i32.const 1)

0 commit comments

Comments
 (0)