Skip to content

Commit 9370310

Browse files
authored
Parsing and emitting of exact types (#7342)
Implement text and binary parsing of exact references types, including parsing of reference type shorthands with the exact prefix. Also implement binary writing of exact types. When writing exact types when custom descriptors are not enabled, generalize the types to their inexact versions. This is very similar to how we generalize function types when GC is not enabled, for instance.
1 parent 6b5327c commit 9370310

File tree

6 files changed

+153
-35
lines changed

6 files changed

+153
-35
lines changed

scripts/test/fuzzing.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,9 @@
100100
'stack_switching_suspend.wast',
101101
'stack_switching_resume.wast',
102102
'stack_switching_resume_throw.wast',
103-
'stack_switching_switch.wast'
103+
'stack_switching_switch.wast',
104+
# TODO: fuzzer support for exact references
105+
'exact-references.wast',
104106
]
105107

106108

src/parser/contexts.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@ struct NullTypeParserCtx {
127127
TypeT makeF64() { return Ok{}; }
128128
TypeT makeV128() { return Ok{}; }
129129

130-
TypeT makeRefType(HeapTypeT, Nullability) { return Ok{}; }
130+
TypeT makeRefType(HeapTypeT, Nullability, Exactness) { return Ok{}; }
131+
132+
HeapTypeT getHeapTypeFromRefType(TypeT) { return Ok{}; }
131133

132134
TupleElemListT makeTupleElemList() { return Ok{}; }
133135
void appendTupleElem(TupleElemListT&, TypeT) {}
@@ -257,10 +259,13 @@ template<typename Ctx> struct TypeParserCtx {
257259
TypeT makeF64() { return Type::f64; }
258260
TypeT makeV128() { return Type::v128; }
259261

260-
TypeT makeRefType(HeapTypeT ht, Nullability nullability) {
261-
return Type(ht, nullability);
262+
TypeT
263+
makeRefType(HeapTypeT ht, Nullability nullability, Exactness exactness) {
264+
return Type(ht, nullability, exactness);
262265
}
263266

267+
HeapTypeT getHeapTypeFromRefType(TypeT t) { return t.getHeapType(); }
268+
264269
std::vector<Type> makeTupleElemList() { return {}; }
265270
void appendTupleElem(std::vector<Type>& elems, Type elem) {
266271
elems.push_back(elem);
@@ -1115,10 +1120,13 @@ struct ParseTypeDefsCtx : TypeParserCtx<ParseTypeDefsCtx> {
11151120
: TypeParserCtx<ParseTypeDefsCtx>(typeIndices), in(in), builder(builder),
11161121
names(builder.size()) {}
11171122

1118-
TypeT makeRefType(HeapTypeT ht, Nullability nullability) {
1119-
return builder.getTempRefType(ht, nullability);
1123+
TypeT
1124+
makeRefType(HeapTypeT ht, Nullability nullability, Exactness exactness) {
1125+
return builder.getTempRefType(ht, nullability, exactness);
11201126
}
11211127

1128+
HeapTypeT getHeapTypeFromRefType(TypeT t) { return t.getHeapType(); }
1129+
11221130
TypeT makeTupleType(const std::vector<Type> types) {
11231131
return builder.getTempTupleType(types);
11241132
}

src/parser/parsers.h

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ using namespace std::string_view_literals;
3030
template<typename Ctx>
3131
Result<typename Ctx::HeapTypeT> absheaptype(Ctx&, Shareability);
3232
template<typename Ctx> Result<typename Ctx::HeapTypeT> heaptype(Ctx&);
33+
template<typename Ctx>
34+
MaybeResult<typename Ctx::TypeT> maybeReftypeAbbrev(Ctx&);
3335
template<typename Ctx> MaybeResult<typename Ctx::RefTypeT> maybeReftype(Ctx&);
3436
template<typename Ctx> Result<typename Ctx::RefTypeT> reftype(Ctx&);
3537
template<typename Ctx> MaybeResult<typename Ctx::TypeT> tupletype(Ctx&);
@@ -456,68 +458,85 @@ template<typename Ctx> Result<typename Ctx::HeapTypeT> heaptype(Ctx& ctx) {
456458
// | 'i31ref' => i31ref
457459
// | 'structref' => structref
458460
// | 'arrayref' => arrayref
459-
// | '(' ref null? t:heaptype ')' => ref null? t
460-
template<typename Ctx> MaybeResult<typename Ctx::TypeT> maybeReftype(Ctx& ctx) {
461+
// | ...
462+
template<typename Ctx>
463+
MaybeResult<typename Ctx::TypeT> maybeReftypeAbbrev(Ctx& ctx) {
461464
if (ctx.in.takeKeyword("funcref"sv)) {
462-
return ctx.makeRefType(ctx.makeFuncType(Unshared), Nullable);
465+
return ctx.makeRefType(ctx.makeFuncType(Unshared), Nullable, Inexact);
463466
}
464467
if (ctx.in.takeKeyword("externref"sv)) {
465-
return ctx.makeRefType(ctx.makeExternType(Unshared), Nullable);
468+
return ctx.makeRefType(ctx.makeExternType(Unshared), Nullable, Inexact);
466469
}
467470
if (ctx.in.takeKeyword("anyref"sv)) {
468-
return ctx.makeRefType(ctx.makeAnyType(Unshared), Nullable);
471+
return ctx.makeRefType(ctx.makeAnyType(Unshared), Nullable, Inexact);
469472
}
470473
if (ctx.in.takeKeyword("eqref"sv)) {
471-
return ctx.makeRefType(ctx.makeEqType(Unshared), Nullable);
474+
return ctx.makeRefType(ctx.makeEqType(Unshared), Nullable, Inexact);
472475
}
473476
if (ctx.in.takeKeyword("i31ref"sv)) {
474-
return ctx.makeRefType(ctx.makeI31Type(Unshared), Nullable);
477+
return ctx.makeRefType(ctx.makeI31Type(Unshared), Nullable, Inexact);
475478
}
476479
if (ctx.in.takeKeyword("structref"sv)) {
477-
return ctx.makeRefType(ctx.makeStructType(Unshared), Nullable);
480+
return ctx.makeRefType(ctx.makeStructType(Unshared), Nullable, Inexact);
478481
}
479482
if (ctx.in.takeKeyword("arrayref"sv)) {
480-
return ctx.makeRefType(ctx.makeArrayType(Unshared), Nullable);
483+
return ctx.makeRefType(ctx.makeArrayType(Unshared), Nullable, Inexact);
481484
}
482485
if (ctx.in.takeKeyword("exnref"sv)) {
483-
return ctx.makeRefType(ctx.makeExnType(Unshared), Nullable);
486+
return ctx.makeRefType(ctx.makeExnType(Unshared), Nullable, Inexact);
484487
}
485488
if (ctx.in.takeKeyword("stringref"sv)) {
486-
return ctx.makeRefType(ctx.makeStringType(Unshared), Nullable);
489+
return ctx.makeRefType(ctx.makeStringType(Unshared), Nullable, Inexact);
487490
}
488491
if (ctx.in.takeKeyword("contref"sv)) {
489-
return ctx.makeRefType(ctx.makeContType(Unshared), Nullable);
492+
return ctx.makeRefType(ctx.makeContType(Unshared), Nullable, Inexact);
490493
}
491494
if (ctx.in.takeKeyword("nullref"sv)) {
492-
return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable);
495+
return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable, Inexact);
493496
}
494497
if (ctx.in.takeKeyword("nullexternref"sv)) {
495-
return ctx.makeRefType(ctx.makeNoextType(Unshared), Nullable);
498+
return ctx.makeRefType(ctx.makeNoextType(Unshared), Nullable, Inexact);
496499
}
497500
if (ctx.in.takeKeyword("nullfuncref"sv)) {
498-
return ctx.makeRefType(ctx.makeNofuncType(Unshared), Nullable);
501+
return ctx.makeRefType(ctx.makeNofuncType(Unshared), Nullable, Inexact);
499502
}
500503
if (ctx.in.takeKeyword("nullexnref"sv)) {
501-
return ctx.makeRefType(ctx.makeNoexnType(Unshared), Nullable);
504+
return ctx.makeRefType(ctx.makeNoexnType(Unshared), Nullable, Inexact);
502505
}
503506
if (ctx.in.takeKeyword("nullcontref"sv)) {
504-
return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable);
507+
return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable, Inexact);
505508
}
509+
return {};
510+
}
506511

507-
if (!ctx.in.takeSExprStart("ref"sv)) {
508-
return {};
512+
// reftype ::= ...
513+
// | '(' 'exact' (ref null ht):shorthand ')' => ref null exact ht
514+
// | '(' ref null? exact? ht:heaptype ')' => ref null? t
515+
template<typename Ctx> MaybeResult<typename Ctx::TypeT> maybeReftype(Ctx& ctx) {
516+
if (ctx.in.takeSExprStart("exact"sv)) {
517+
auto rt = maybeReftypeAbbrev(ctx);
518+
CHECK_ERR(rt);
519+
if (!rt) {
520+
return ctx.in.err("expected reftype shorthand");
521+
}
522+
if (!ctx.in.takeRParen()) {
523+
return ctx.in.err("expected end of reftype");
524+
}
525+
return ctx.makeRefType(ctx.getHeapTypeFromRefType(*rt), Nullable, Exact);
509526
}
510527

511-
auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable;
512-
513-
auto type = heaptype(ctx);
514-
CHECK_ERR(type);
515-
516-
if (!ctx.in.takeRParen()) {
517-
return ctx.in.err("expected end of reftype");
528+
if (ctx.in.takeSExprStart("ref"sv)) {
529+
auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable;
530+
auto exactness = ctx.in.takeKeyword("exact"sv) ? Exact : Inexact;
531+
auto type = heaptype(ctx);
532+
CHECK_ERR(type);
533+
if (!ctx.in.takeRParen()) {
534+
return ctx.in.err("expected end of reftype");
535+
}
536+
return ctx.makeRefType(*type, nullability, exactness);
518537
}
519538

520-
return ctx.makeRefType(*type, nullability);
539+
return maybeReftypeAbbrev(ctx);
521540
}
522541

523542
template<typename Ctx> Result<typename Ctx::TypeT> reftype(Ctx& ctx) {

src/wasm-binary.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ enum EncodedType {
327327
eqref = -0x13, // 0x6d
328328
nonnullable = -0x1c, // 0x64
329329
nullable = -0x1d, // 0x63
330+
exact = -0x1e, // 0x62
330331
contref = -0x18, // 0x68
331332
nullcontref = -0x0b, // 0x75
332333
// exception handling
@@ -1497,6 +1498,7 @@ class WasmBinaryReader {
14971498
Type getType();
14981499
// Get a type given the initial S32LEB has already been read, and is provided.
14991500
Type getType(int code);
1501+
Type getTypeNoExact(int code);
15001502
HeapType getHeapType();
15011503
HeapType getIndexedHeapType();
15021504

src/wasm/wasm-binary.cpp

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,6 +1551,12 @@ void WasmBinaryWriter::writeInlineBuffer(const char* data, size_t size) {
15511551

15521552
void WasmBinaryWriter::writeType(Type type) {
15531553
if (type.isRef()) {
1554+
// Exact references are introduced by the custom descriptors feature, but
1555+
// can be used internally even when it is not enabled. In that case, we have
1556+
// to generalize the types to be inexact before writing them.
1557+
if (!wasm->features.hasCustomDescriptors()) {
1558+
type = Type(type.getHeapType(), type.getNullability(), Inexact);
1559+
}
15541560
// The only reference types allowed without GC are funcref, externref, and
15551561
// exnref. We internally use more refined versions of those types, but we
15561562
// cannot emit those without GC.
@@ -1567,6 +1573,12 @@ void WasmBinaryWriter::writeType(Type type) {
15671573
type = Type(type.getHeapType().getTop(), Nullable);
15681574
}
15691575
}
1576+
// If the type is exact, emit the exact prefix and continue on without
1577+
// considering exactness.
1578+
if (type.isExact()) {
1579+
o << S32LEB(BinaryConsts::EncodedType::exact);
1580+
type = Type(type.getHeapType(), type.getNullability(), Inexact);
1581+
}
15701582
auto heapType = type.getHeapType();
15711583
if (type.isNullable() && heapType.isBasic() && !heapType.isShared()) {
15721584
switch (heapType.getBasic(Unshared)) {
@@ -2155,7 +2167,7 @@ Signature WasmBinaryReader::getBlockType() {
21552167
return Signature(Type::none, getType(code));
21562168
}
21572169

2158-
Type WasmBinaryReader::getType(int code) {
2170+
Type WasmBinaryReader::getTypeNoExact(int code) {
21592171
Type type;
21602172
if (getBasicType(code, type)) {
21612173
return type;
@@ -2171,6 +2183,17 @@ Type WasmBinaryReader::getType(int code) {
21712183
WASM_UNREACHABLE("unexpected type");
21722184
}
21732185

2186+
Type WasmBinaryReader::getType(int code) {
2187+
if (code == BinaryConsts::EncodedType::exact) {
2188+
auto type = getTypeNoExact(getS32LEB());
2189+
if (!type.isRef()) {
2190+
throwError("invalid exact prefix on non-reference type");
2191+
}
2192+
return Type(type.getHeapType(), type.getNullability(), Exact);
2193+
}
2194+
return getTypeNoExact(code);
2195+
}
2196+
21742197
Type WasmBinaryReader::getType() { return getType(getS32LEB()); }
21752198

21762199
HeapType WasmBinaryReader::getHeapType() {
@@ -2331,7 +2354,7 @@ void WasmBinaryReader::readTypes() {
23312354
}
23322355
return builder.getTempHeapType(size_t(htCode));
23332356
};
2334-
auto makeType = [&](int32_t typeCode) {
2357+
auto makeTypeNoExact = [&](int32_t typeCode) {
23352358
Type type;
23362359
if (getBasicType(typeCode, type)) {
23372360
return type;
@@ -2356,6 +2379,17 @@ void WasmBinaryReader::readTypes() {
23562379
}
23572380
WASM_UNREACHABLE("unexpected type");
23582381
};
2382+
auto makeType = [&](int32_t typeCode) {
2383+
if (typeCode == BinaryConsts::EncodedType::exact) {
2384+
auto type = makeTypeNoExact(getS32LEB());
2385+
if (!type.isRef()) {
2386+
throwError("unexpected exact prefix on non-reference type");
2387+
}
2388+
return builder.getTempRefType(
2389+
type.getHeapType(), type.getNullability(), Exact);
2390+
}
2391+
return makeTypeNoExact(typeCode);
2392+
};
23592393
auto readType = [&]() { return makeType(getS32LEB()); };
23602394

23612395
auto readSignatureDef = [&]() {

test/lit/basic/exact-references.wast

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
2+
3+
;; RUN: wasm-opt %s -all -o %t.text.wast -g -S
4+
;; RUN: wasm-as %s -all -g -o %t.wasm
5+
;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast
6+
;; RUN: wasm-as %s -all -o %t.nodebug.wasm
7+
;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast
8+
;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT
9+
;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN
10+
;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG
11+
12+
;; Also check that if we emit a binary without custom descriptors enabled, the
13+
;; types are generalized to be inexact.
14+
15+
;; RUN: wasm-opt %s -all --disable-custom-descriptors -g -o - | wasm-opt -all -S -o - \
16+
;; RUN: | filecheck %s --check-prefix=NO-EXACT
17+
18+
(module
19+
;; CHECK-TEXT: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo))))
20+
;; CHECK-BIN: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo))))
21+
;; NO-EXACT: (type $foo (struct (field anyref) (field (ref any)) (field (ref null $foo)) (field (ref $foo))))
22+
(type $foo (struct (field (exact anyref) (ref exact any) (ref null exact $foo) (ref exact $foo))))
23+
24+
25+
;; CHECK-TEXT: (import "" "g1" (global $g1 (exact anyref)))
26+
;; CHECK-BIN: (import "" "g1" (global $g1 (exact anyref)))
27+
;; NO-EXACT: (import "" "g1" (global $g1 anyref))
28+
(import "" "g1" (global $g1 (exact anyref)))
29+
30+
;; CHECK-TEXT: (import "" "g2" (global $g2 (ref exact any)))
31+
;; CHECK-BIN: (import "" "g2" (global $g2 (ref exact any)))
32+
;; NO-EXACT: (import "" "g2" (global $g2 (ref any)))
33+
(import "" "g2" (global $g2 (ref exact any)))
34+
35+
;; CHECK-TEXT: (import "" "g3" (global $g3 (ref null exact $foo)))
36+
;; CHECK-BIN: (import "" "g3" (global $g3 (ref null exact $foo)))
37+
;; NO-EXACT: (import "" "g3" (global $g3 (ref null $foo)))
38+
(import "" "g3" (global $g3 (ref null exact $foo)))
39+
40+
;; CHECK-TEXT: (import "" "g4" (global $g4 (ref exact $foo)))
41+
;; CHECK-BIN: (import "" "g4" (global $g4 (ref exact $foo)))
42+
;; NO-EXACT: (import "" "g4" (global $g4 (ref $foo)))
43+
(import "" "g4" (global $g4 (ref exact $foo)))
44+
)
45+
;; CHECK-BIN-NODEBUG: (type $0 (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $0)) (field (ref exact $0))))
46+
47+
;; CHECK-BIN-NODEBUG: (import "" "g1" (global $gimport$0 (exact anyref)))
48+
49+
;; CHECK-BIN-NODEBUG: (import "" "g2" (global $gimport$1 (ref exact any)))
50+
51+
;; CHECK-BIN-NODEBUG: (import "" "g3" (global $gimport$2 (ref null exact $0)))
52+
53+
;; CHECK-BIN-NODEBUG: (import "" "g4" (global $gimport$3 (ref exact $0)))

0 commit comments

Comments
 (0)