Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion scripts/test/fuzzing.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@
'stack_switching_suspend.wast',
'stack_switching_resume.wast',
'stack_switching_resume_throw.wast',
'stack_switching_switch.wast'
'stack_switching_switch.wast',
# TODO: fuzzer support for exact references
'exact-references.wast',
]


Expand Down
18 changes: 13 additions & 5 deletions src/parser/contexts.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ struct NullTypeParserCtx {
TypeT makeF64() { return Ok{}; }
TypeT makeV128() { return Ok{}; }

TypeT makeRefType(HeapTypeT, Nullability) { return Ok{}; }
TypeT makeRefType(HeapTypeT, Nullability, Exactness) { return Ok{}; }

HeapTypeT getHeapTypeFromRefType(TypeT) { return Ok{}; }

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

TypeT makeRefType(HeapTypeT ht, Nullability nullability) {
return Type(ht, nullability);
TypeT
makeRefType(HeapTypeT ht, Nullability nullability, Exactness exactness) {
return Type(ht, nullability, exactness);
}

HeapTypeT getHeapTypeFromRefType(TypeT t) { return t.getHeapType(); }

std::vector<Type> makeTupleElemList() { return {}; }
void appendTupleElem(std::vector<Type>& elems, Type elem) {
elems.push_back(elem);
Expand Down Expand Up @@ -1115,10 +1120,13 @@ struct ParseTypeDefsCtx : TypeParserCtx<ParseTypeDefsCtx> {
: TypeParserCtx<ParseTypeDefsCtx>(typeIndices), in(in), builder(builder),
names(builder.size()) {}

TypeT makeRefType(HeapTypeT ht, Nullability nullability) {
return builder.getTempRefType(ht, nullability);
TypeT
makeRefType(HeapTypeT ht, Nullability nullability, Exactness exactness) {
return builder.getTempRefType(ht, nullability, exactness);
}

HeapTypeT getHeapTypeFromRefType(TypeT t) { return t.getHeapType(); }

TypeT makeTupleType(const std::vector<Type> types) {
return builder.getTempTupleType(types);
}
Expand Down
73 changes: 46 additions & 27 deletions src/parser/parsers.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ using namespace std::string_view_literals;
template<typename Ctx>
Result<typename Ctx::HeapTypeT> absheaptype(Ctx&, Shareability);
template<typename Ctx> Result<typename Ctx::HeapTypeT> heaptype(Ctx&);
template<typename Ctx>
MaybeResult<typename Ctx::TypeT> maybeReftypeAbbrev(Ctx&);
template<typename Ctx> MaybeResult<typename Ctx::RefTypeT> maybeReftype(Ctx&);
template<typename Ctx> Result<typename Ctx::RefTypeT> reftype(Ctx&);
template<typename Ctx> MaybeResult<typename Ctx::TypeT> tupletype(Ctx&);
Expand Down Expand Up @@ -456,68 +458,85 @@ template<typename Ctx> Result<typename Ctx::HeapTypeT> heaptype(Ctx& ctx) {
// | 'i31ref' => i31ref
// | 'structref' => structref
// | 'arrayref' => arrayref
// | '(' ref null? t:heaptype ')' => ref null? t
template<typename Ctx> MaybeResult<typename Ctx::TypeT> maybeReftype(Ctx& ctx) {
// | ...
template<typename Ctx>
MaybeResult<typename Ctx::TypeT> maybeReftypeAbbrev(Ctx& ctx) {
if (ctx.in.takeKeyword("funcref"sv)) {
return ctx.makeRefType(ctx.makeFuncType(Unshared), Nullable);
return ctx.makeRefType(ctx.makeFuncType(Unshared), Nullable, Inexact);
}
if (ctx.in.takeKeyword("externref"sv)) {
return ctx.makeRefType(ctx.makeExternType(Unshared), Nullable);
return ctx.makeRefType(ctx.makeExternType(Unshared), Nullable, Inexact);
}
if (ctx.in.takeKeyword("anyref"sv)) {
return ctx.makeRefType(ctx.makeAnyType(Unshared), Nullable);
return ctx.makeRefType(ctx.makeAnyType(Unshared), Nullable, Inexact);
}
if (ctx.in.takeKeyword("eqref"sv)) {
return ctx.makeRefType(ctx.makeEqType(Unshared), Nullable);
return ctx.makeRefType(ctx.makeEqType(Unshared), Nullable, Inexact);
}
if (ctx.in.takeKeyword("i31ref"sv)) {
return ctx.makeRefType(ctx.makeI31Type(Unshared), Nullable);
return ctx.makeRefType(ctx.makeI31Type(Unshared), Nullable, Inexact);
}
if (ctx.in.takeKeyword("structref"sv)) {
return ctx.makeRefType(ctx.makeStructType(Unshared), Nullable);
return ctx.makeRefType(ctx.makeStructType(Unshared), Nullable, Inexact);
}
if (ctx.in.takeKeyword("arrayref"sv)) {
return ctx.makeRefType(ctx.makeArrayType(Unshared), Nullable);
return ctx.makeRefType(ctx.makeArrayType(Unshared), Nullable, Inexact);
}
if (ctx.in.takeKeyword("exnref"sv)) {
return ctx.makeRefType(ctx.makeExnType(Unshared), Nullable);
return ctx.makeRefType(ctx.makeExnType(Unshared), Nullable, Inexact);
}
if (ctx.in.takeKeyword("stringref"sv)) {
return ctx.makeRefType(ctx.makeStringType(Unshared), Nullable);
return ctx.makeRefType(ctx.makeStringType(Unshared), Nullable, Inexact);
}
if (ctx.in.takeKeyword("contref"sv)) {
return ctx.makeRefType(ctx.makeContType(Unshared), Nullable);
return ctx.makeRefType(ctx.makeContType(Unshared), Nullable, Inexact);
}
if (ctx.in.takeKeyword("nullref"sv)) {
return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable);
return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable, Inexact);
}
if (ctx.in.takeKeyword("nullexternref"sv)) {
return ctx.makeRefType(ctx.makeNoextType(Unshared), Nullable);
return ctx.makeRefType(ctx.makeNoextType(Unshared), Nullable, Inexact);
}
if (ctx.in.takeKeyword("nullfuncref"sv)) {
return ctx.makeRefType(ctx.makeNofuncType(Unshared), Nullable);
return ctx.makeRefType(ctx.makeNofuncType(Unshared), Nullable, Inexact);
}
if (ctx.in.takeKeyword("nullexnref"sv)) {
return ctx.makeRefType(ctx.makeNoexnType(Unshared), Nullable);
return ctx.makeRefType(ctx.makeNoexnType(Unshared), Nullable, Inexact);
}
if (ctx.in.takeKeyword("nullcontref"sv)) {
return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable);
return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable, Inexact);
}
return {};
}

if (!ctx.in.takeSExprStart("ref"sv)) {
return {};
// reftype ::= ...
// | '(' 'exact' (ref null ht):shorthand ')' => ref null exact ht
// | '(' ref null? exact? ht:heaptype ')' => ref null? t
template<typename Ctx> MaybeResult<typename Ctx::TypeT> maybeReftype(Ctx& ctx) {
if (ctx.in.takeSExprStart("exact"sv)) {
auto rt = maybeReftypeAbbrev(ctx);
CHECK_ERR(rt);
if (!rt) {
return ctx.in.err("expected reftype shorthand");
}
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of reftype");
}
return ctx.makeRefType(ctx.getHeapTypeFromRefType(*rt), Nullable, Exact);
}

auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable;

auto type = heaptype(ctx);
CHECK_ERR(type);

if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of reftype");
if (ctx.in.takeSExprStart("ref"sv)) {
auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable;
auto exactness = ctx.in.takeKeyword("exact"sv) ? Exact : Inexact;
auto type = heaptype(ctx);
CHECK_ERR(type);
if (!ctx.in.takeRParen()) {
return ctx.in.err("expected end of reftype");
}
return ctx.makeRefType(*type, nullability, exactness);
}

return ctx.makeRefType(*type, nullability);
return maybeReftypeAbbrev(ctx);
}

template<typename Ctx> Result<typename Ctx::TypeT> reftype(Ctx& ctx) {
Expand Down
2 changes: 2 additions & 0 deletions src/wasm-binary.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ enum EncodedType {
eqref = -0x13, // 0x6d
nonnullable = -0x1c, // 0x64
nullable = -0x1d, // 0x63
exact = -0x1e, // 0x62
contref = -0x18, // 0x68
nullcontref = -0x0b, // 0x75
// exception handling
Expand Down Expand Up @@ -1497,6 +1498,7 @@ class WasmBinaryReader {
Type getType();
// Get a type given the initial S32LEB has already been read, and is provided.
Type getType(int code);
Type getTypeNoExact(int code);
HeapType getHeapType();
HeapType getIndexedHeapType();

Expand Down
38 changes: 36 additions & 2 deletions src/wasm/wasm-binary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1551,6 +1551,12 @@ void WasmBinaryWriter::writeInlineBuffer(const char* data, size_t size) {

void WasmBinaryWriter::writeType(Type type) {
if (type.isRef()) {
// Exact references are introduced by the custom descriptors feature, but
// can be used internally even when it is not enabled. In that case, we have
// to generalize the types to be inexact before writing them.
if (!wasm->features.hasCustomDescriptors()) {
type = Type(type.getHeapType(), type.getNullability(), Inexact);
}
// The only reference types allowed without GC are funcref, externref, and
// exnref. We internally use more refined versions of those types, but we
// cannot emit those without GC.
Expand All @@ -1567,6 +1573,12 @@ void WasmBinaryWriter::writeType(Type type) {
type = Type(type.getHeapType().getTop(), Nullable);
}
}
// If the type is exact, emit the exact prefix and continue on without
// considering exactness.
if (type.isExact()) {
o << S32LEB(BinaryConsts::EncodedType::exact);
type = Type(type.getHeapType(), type.getNullability(), Inexact);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where below do we need this line for? (1579 makes sense to me, but 1580 I don't see yet)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(lgtm % this)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it's not necessary in the current code, but if we refactored the code below to check equality with specific reference types, for example, it would be important that we ignore exactness from this point.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, I guess it's safer for the future.

}
auto heapType = type.getHeapType();
if (type.isNullable() && heapType.isBasic() && !heapType.isShared()) {
switch (heapType.getBasic(Unshared)) {
Expand Down Expand Up @@ -2155,7 +2167,7 @@ Signature WasmBinaryReader::getBlockType() {
return Signature(Type::none, getType(code));
}

Type WasmBinaryReader::getType(int code) {
Type WasmBinaryReader::getTypeNoExact(int code) {
Type type;
if (getBasicType(code, type)) {
return type;
Expand All @@ -2171,6 +2183,17 @@ Type WasmBinaryReader::getType(int code) {
WASM_UNREACHABLE("unexpected type");
}

Type WasmBinaryReader::getType(int code) {
if (code == BinaryConsts::EncodedType::exact) {
auto type = getTypeNoExact(getS32LEB());
if (!type.isRef()) {
throwError("invalid exact prefix on non-reference type");
}
return Type(type.getHeapType(), type.getNullability(), Exact);
}
return getTypeNoExact(code);
}

Type WasmBinaryReader::getType() { return getType(getS32LEB()); }

HeapType WasmBinaryReader::getHeapType() {
Expand Down Expand Up @@ -2331,7 +2354,7 @@ void WasmBinaryReader::readTypes() {
}
return builder.getTempHeapType(size_t(htCode));
};
auto makeType = [&](int32_t typeCode) {
auto makeTypeNoExact = [&](int32_t typeCode) {
Type type;
if (getBasicType(typeCode, type)) {
return type;
Expand All @@ -2356,6 +2379,17 @@ void WasmBinaryReader::readTypes() {
}
WASM_UNREACHABLE("unexpected type");
};
auto makeType = [&](int32_t typeCode) {
if (typeCode == BinaryConsts::EncodedType::exact) {
auto type = makeTypeNoExact(getS32LEB());
if (!type.isRef()) {
throwError("unexpected exact prefix on non-reference type");
}
return builder.getTempRefType(
type.getHeapType(), type.getNullability(), Exact);
}
return makeTypeNoExact(typeCode);
};
auto readType = [&]() { return makeType(getS32LEB()); };

auto readSignatureDef = [&]() {
Expand Down
53 changes: 53 additions & 0 deletions test/lit/basic/exact-references.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.

;; RUN: wasm-opt %s -all -o %t.text.wast -g -S
;; RUN: wasm-as %s -all -g -o %t.wasm
;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast
;; RUN: wasm-as %s -all -o %t.nodebug.wasm
;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast
;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK-TEXT
;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN
;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG

;; Also check that if we emit a binary without custom descriptors enabled, the
;; types are generalized to be inexact.

;; RUN: wasm-opt %s -all --disable-custom-descriptors -g -o - | wasm-opt -all -S -o - \
;; RUN: | filecheck %s --check-prefix=NO-EXACT

(module
;; CHECK-TEXT: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo))))
;; CHECK-BIN: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo))))
;; NO-EXACT: (type $foo (struct (field anyref) (field (ref any)) (field (ref null $foo)) (field (ref $foo))))
(type $foo (struct (field (exact anyref) (ref exact any) (ref null exact $foo) (ref exact $foo))))


;; CHECK-TEXT: (import "" "g1" (global $g1 (exact anyref)))
;; CHECK-BIN: (import "" "g1" (global $g1 (exact anyref)))
;; NO-EXACT: (import "" "g1" (global $g1 anyref))
(import "" "g1" (global $g1 (exact anyref)))

;; CHECK-TEXT: (import "" "g2" (global $g2 (ref exact any)))
;; CHECK-BIN: (import "" "g2" (global $g2 (ref exact any)))
;; NO-EXACT: (import "" "g2" (global $g2 (ref any)))
(import "" "g2" (global $g2 (ref exact any)))

;; CHECK-TEXT: (import "" "g3" (global $g3 (ref null exact $foo)))
;; CHECK-BIN: (import "" "g3" (global $g3 (ref null exact $foo)))
;; NO-EXACT: (import "" "g3" (global $g3 (ref null $foo)))
(import "" "g3" (global $g3 (ref null exact $foo)))

;; CHECK-TEXT: (import "" "g4" (global $g4 (ref exact $foo)))
;; CHECK-BIN: (import "" "g4" (global $g4 (ref exact $foo)))
;; NO-EXACT: (import "" "g4" (global $g4 (ref $foo)))
(import "" "g4" (global $g4 (ref exact $foo)))
)
;; CHECK-BIN-NODEBUG: (type $0 (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $0)) (field (ref exact $0))))

;; CHECK-BIN-NODEBUG: (import "" "g1" (global $gimport$0 (exact anyref)))

;; CHECK-BIN-NODEBUG: (import "" "g2" (global $gimport$1 (ref exact any)))

;; CHECK-BIN-NODEBUG: (import "" "g3" (global $gimport$2 (ref null exact $0)))

;; CHECK-BIN-NODEBUG: (import "" "g4" (global $gimport$3 (ref exact $0)))