diff --git a/CHANGELOG.md b/CHANGELOG.md index fc339a5cc6c..78411c28bd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ full changeset diff at the end of each section. Current Trunk ------------- +- Reference type support is added. Supported instructions are `ref.null`, + `ref.is_null`, `ref.func`, and typed `select`. Table instructions are not + supported yet. - `local.tee`'s C/Binaryen.js API now takes an additional type parameter for its local type, like `local.get`. This is required to handle subtypes. - Added load_splat SIMD instructions diff --git a/check.py b/check.py index f0bb04f1965..de3b966b464 100755 --- a/check.py +++ b/check.py @@ -152,8 +152,10 @@ def check(): shared.fail_if_not_identical_to_file(actual, f) - shared.binary_format_check(t, wasm_as_args=['-g']) # test with debuginfo - shared.binary_format_check(t, wasm_as_args=[], binary_suffix='.fromBinary.noDebugInfo') # test without debuginfo + # HACK Remove this condition after nullref is implemented in V8 + if 'reference_types.wast' in t: + shared.binary_format_check(t, wasm_as_args=['-g']) # test with debuginfo + shared.binary_format_check(t, wasm_as_args=[], binary_suffix='.fromBinary.noDebugInfo') # test without debuginfo shared.minify_check(t) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index a0b763aabac..97ad7f005c1 100644 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -228,7 +228,7 @@ def compare_vs(self, before, after): break def can_run_on_feature_opts(self, feature_opts): - return all([x in feature_opts for x in ['--disable-simd']]) + return all([x in feature_opts for x in ['--disable-simd', '--disable-reference-types']]) # Fuzz the interpreter with --fuzz-exec. This tests everything in a single command (no @@ -294,7 +294,7 @@ def run(self, wasm): return out def can_run_on_feature_opts(self, feature_opts): - return all([x in feature_opts for x in ['--disable-exception-handling', '--disable-simd', '--disable-threads', '--disable-bulk-memory', '--disable-nontrapping-float-to-int', '--disable-tail-call', '--disable-sign-ext']]) + return all([x in feature_opts for x in ['--disable-exception-handling', '--disable-simd', '--disable-threads', '--disable-bulk-memory', '--disable-nontrapping-float-to-int', '--disable-tail-call', '--disable-sign-ext', '--disable-reference-types']]) class Asyncify(TestCaseHandler): @@ -339,7 +339,7 @@ def do_asyncify(wasm): compare(before, after_asyncify, 'Asyncify (before/after_asyncify)') def can_run_on_feature_opts(self, feature_opts): - return all([x in feature_opts for x in ['--disable-exception-handling', '--disable-simd', '--disable-tail-call']]) + return all([x in feature_opts for x in ['--disable-exception-handling', '--disable-simd', '--disable-tail-call', '--disable-reference-types']]) # The global list of all test case handlers diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index c7f0a51c848..8174d6f7e96 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -49,7 +49,9 @@ ("f32.pop", "makePop(f32)"), ("f64.pop", "makePop(f64)"), ("v128.pop", "makePop(v128)"), + ("funcref.pop", "makePop(funcref)"), ("anyref.pop", "makePop(anyref)"), + ("nullref.pop", "makePop(nullref)"), ("exnref.pop", "makePop(exnref)"), ("i32.load", "makeLoad(s, i32, /*isAtomic=*/false)"), ("i64.load", "makeLoad(s, i64, /*isAtomic=*/false)"), @@ -467,6 +469,11 @@ ("i32x4.widen_low_i16x8_u", "makeUnary(s, UnaryOp::WidenLowUVecI16x8ToVecI32x4)"), ("i32x4.widen_high_i16x8_u", "makeUnary(s, UnaryOp::WidenHighUVecI16x8ToVecI32x4)"), ("v8x16.swizzle", "makeBinary(s, BinaryOp::SwizzleVec8x16)"), + # reference types instructions + # TODO Add table instructions + ("ref.null", "makeRefNull(s)"), + ("ref.is_null", "makeRefIsNull(s)"), + ("ref.func", "makeRefFunc(s)"), # exception handling instructions ("try", "makeTry(s)"), ("throw", "makeThrow(s)"), diff --git a/src/asmjs/asm_v_wasm.cpp b/src/asmjs/asm_v_wasm.cpp index 2fba0520a88..3d818093753 100644 --- a/src/asmjs/asm_v_wasm.cpp +++ b/src/asmjs/asm_v_wasm.cpp @@ -53,10 +53,11 @@ AsmType wasmToAsmType(Type type) { return ASM_INT64; case v128: assert(false && "v128 not implemented yet"); + case funcref: case anyref: - assert(false && "anyref is not supported by asm2wasm"); + case nullref: case exnref: - assert(false && "exnref is not supported by asm2wasm"); + assert(false && "reference types are not supported by asm2wasm"); case none: return ASM_NONE; case unreachable: @@ -77,10 +78,14 @@ char getSig(Type type) { return 'd'; case v128: return 'V'; + case funcref: + return 'F'; case anyref: - return 'a'; + return 'A'; + case nullref: + return 'N'; case exnref: - return 'e'; + return 'E'; case none: return 'v'; case unreachable: @@ -109,9 +114,13 @@ Type sigToType(char sig) { return f64; case 'V': return v128; - case 'a': + case 'F': + return funcref; + case 'A': return anyref; - case 'e': + case 'N': + return nullref; + case 'E': return exnref; case 'v': return none; diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index bb81ac5faa0..4f5b2929cef 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -46,9 +46,6 @@ using namespace wasm; // Literal utilities -static_assert(sizeof(BinaryenLiteral) == sizeof(Literal), - "Binaryen C API literal must match wasm.h"); - BinaryenLiteral toBinaryenLiteral(Literal x) { BinaryenLiteral ret; ret.type = x.type; @@ -65,15 +62,15 @@ BinaryenLiteral toBinaryenLiteral(Literal x) { case Type::f64: ret.i64 = x.reinterpreti64(); break; - case Type::v128: { + case Type::v128: memcpy(&ret.v128, x.getv128Ptr(), 16); break; - } - - case Type::anyref: // there's no anyref literals - case Type::exnref: // there's no exnref literals - case Type::none: - case Type::unreachable: + case Type::nullref: + break; + case Type::funcref: + ret.func = x.func.c_str(); + break; + default: WASM_UNREACHABLE("unexpected type"); } return ret; @@ -91,10 +88,7 @@ Literal fromBinaryenLiteral(BinaryenLiteral x) { return Literal(x.i64).castToF64(); case Type::v128: return Literal(x.v128); - case Type::anyref: // there's no anyref literals - case Type::exnref: // there's no exnref literals - case Type::none: - case Type::unreachable: + default: WASM_UNREACHABLE("unexpected type"); } WASM_UNREACHABLE("invalid type"); @@ -212,10 +206,7 @@ void printArg(std::ostream& setup, std::ostream& out, BinaryenLiteral arg) { out << "BinaryenLiteralVec128(" << array << ")"; break; } - case Type::anyref: // there's no anyref literals - case Type::exnref: // there's no exnref literals - case Type::none: - case Type::unreachable: + default: WASM_UNREACHABLE("unexpected type"); } } @@ -268,7 +259,9 @@ BinaryenType BinaryenTypeInt64(void) { return i64; } BinaryenType BinaryenTypeFloat32(void) { return f32; } BinaryenType BinaryenTypeFloat64(void) { return f64; } BinaryenType BinaryenTypeVec128(void) { return v128; } +BinaryenType BinaryenTypeFuncref(void) { return funcref; } BinaryenType BinaryenTypeAnyref(void) { return anyref; } +BinaryenType BinaryenTypeNullref(void) { return nullref; } BinaryenType BinaryenTypeExnref(void) { return exnref; } BinaryenType BinaryenTypeUnreachable(void) { return unreachable; } BinaryenType BinaryenTypeAuto(void) { return uint32_t(-1); } @@ -400,6 +393,15 @@ BinaryenExpressionId BinaryenMemoryCopyId(void) { BinaryenExpressionId BinaryenMemoryFillId(void) { return Expression::Id::MemoryFillId; } +BinaryenExpressionId BinaryenRefNullId(void) { + return Expression::Id::RefNullId; +} +BinaryenExpressionId BinaryenRefIsNullId(void) { + return Expression::Id::RefIsNullId; +} +BinaryenExpressionId BinaryenRefFuncId(void) { + return Expression::Id::RefFuncId; +} BinaryenExpressionId BinaryenTryId(void) { return Expression::Id::TryId; } BinaryenExpressionId BinaryenThrowId(void) { return Expression::Id::ThrowId; } BinaryenExpressionId BinaryenRethrowId(void) { @@ -1392,17 +1394,22 @@ BinaryenExpressionRef BinaryenBinary(BinaryenModuleRef module, BinaryenExpressionRef BinaryenSelect(BinaryenModuleRef module, BinaryenExpressionRef condition, BinaryenExpressionRef ifTrue, - BinaryenExpressionRef ifFalse) { + BinaryenExpressionRef ifFalse, + BinaryenType type) { auto* ret = ((Module*)module)->allocator.alloc(); + ret->condition = condition; + ret->ifTrue = ifTrue; + ret->ifFalse = ifFalse; + ret->finalize(type); + return ret; + } Return* makeReturn(Expression* value = nullptr) { auto* ret = allocator.alloc(); ret->value = value; @@ -511,6 +536,23 @@ class Builder { ret->finalize(); return ret; } + RefNull* makeRefNull() { + auto* ret = allocator.alloc(); + ret->finalize(); + return ret; + } + RefIsNull* makeRefIsNull(Expression* anyref) { + auto* ret = allocator.alloc(); + ret->anyref = anyref; + ret->finalize(); + return ret; + } + RefFunc* makeRefFunc(Name func) { + auto* ret = allocator.alloc(); + ret->func = func; + ret->finalize(); + return ret; + } Try* makeTry(Expression* body, Expression* catchBody) { auto* ret = allocator.alloc(); ret->body = body; @@ -578,6 +620,21 @@ class Builder { return ret; } + Expression* makeConstExpression(Literal value) { + switch (value.type) { + case nullref: + return makeRefNull(); + case funcref: + if (value.func != "") { + return makeRefFunc(value.func); + } + return makeRefNull(); + default: + assert(value.type.isNumber()); + return makeConst(value); + } + } + // Additional utility functions for building on top of nodes // Convenient to have these on Builder, as it has allocation built in @@ -670,6 +727,13 @@ class Builder { return block; } + Block* makeSequence(Expression* left, Expression* right, Type type) { + auto* block = makeBlock(left); + block->list.push_back(right); + block->finalize(type); + return block; + } + // Grab a slice out of a block, replacing it with nops, and returning // either another block with the contents (if more than 1) or a single // expression @@ -735,16 +799,15 @@ class Builder { value = Literal(bytes.data()); break; } + case funcref: case anyref: - // TODO Implement and return nullref - assert(false && "anyref not implemented yet"); + case nullref: case exnref: - // TODO Implement and return nullref - assert(false && "exnref not implemented yet"); + return ExpressionManipulator::refNull(curr); case none: return ExpressionManipulator::nop(curr); case unreachable: - return ExpressionManipulator::convert(curr); + return ExpressionManipulator::unreachable(curr); } return makeConst(value); } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 1a90fdf715f..6cef9b4a581 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -143,13 +143,13 @@ class ExpressionRunner : public OverriddenVisitor { if (!ret.breaking() && (curr->type.isConcrete() || ret.value.type.isConcrete())) { #if 1 // def WASM_INTERPRETER_DEBUG - if (ret.value.type != curr->type) { + if (!isLeftSubTypeOfRight(ret.value.type, curr->type)) { std::cerr << "expected " << curr->type << ", seeing " << ret.value.type << " from\n" << curr << '\n'; } #endif - assert(ret.value.type == curr->type); + assert(isLeftSubTypeOfRight(ret.value.type, curr->type)); } depth--; return ret; @@ -1091,7 +1091,7 @@ class ExpressionRunner : public OverriddenVisitor { return Literal(uint64_t(val)); } } - Flow visitAtomicFence(AtomicFence*) { + Flow visitAtomicFence(AtomicFence* curr) { // Wasm currently supports only sequentially consistent atomics, in which // case atomic_fence can be lowered to nothing. NOTE_ENTER("AtomicFence"); @@ -1119,6 +1119,26 @@ class ExpressionRunner : public OverriddenVisitor { Flow visitSIMDLoadExtend(SIMDLoad*) { WASM_UNREACHABLE("unimp"); } Flow visitPush(Push*) { WASM_UNREACHABLE("unimp"); } Flow visitPop(Pop*) { WASM_UNREACHABLE("unimp"); } + Flow visitRefNull(RefNull* curr) { + NOTE_ENTER("RefNull"); + return Literal::makeNullref(); + } + Flow visitRefIsNull(RefIsNull* curr) { + NOTE_ENTER("RefIsNull"); + Flow flow = visit(curr->anyref); + if (flow.breaking()) { + return flow; + } + Literal value = flow.value; + NOTE_EVAL1(value); + return Literal(value.type == nullref); + } + Flow visitRefFunc(RefFunc* curr) { + NOTE_ENTER("RefFunc"); + NOTE_NAME(curr->func); + return Literal::makeFuncref(curr->func); + } + // TODO Implement EH instructions Flow visitTry(Try*) { WASM_UNREACHABLE("unimp"); } Flow visitThrow(Throw*) { WASM_UNREACHABLE("unimp"); } Flow visitRethrow(Rethrow*) { WASM_UNREACHABLE("unimp"); } @@ -1213,10 +1233,7 @@ template class ModuleInstanceBase { return Literal(load64u(addr)).castToF64(); case v128: return Literal(load128(addr).data()); - case anyref: // anyref cannot be loaded from memory - case exnref: // exnref cannot be loaded from memory - case none: - case unreachable: + default: WASM_UNREACHABLE("unexpected type"); } WASM_UNREACHABLE("invalid type"); @@ -1268,10 +1285,7 @@ template class ModuleInstanceBase { case v128: store128(addr, value.getv128()); break; - case anyref: // anyref cannot be stored from memory - case exnref: // exnref cannot be stored in memory - case none: - case unreachable: + default: WASM_UNREACHABLE("unexpected type"); } } @@ -1459,7 +1473,7 @@ template class ModuleInstanceBase { for (size_t i = 0; i < function->getNumLocals(); i++) { if (i < arguments.size()) { assert(function->isParam(i)); - if (function->params[i] != arguments[i].type) { + if (!isLeftSubTypeOfRight(arguments[i].type, function->params[i])) { std::cerr << "Function `" << function->name << "` expects type " << function->params[i] << " for parameter " << i << ", got " << arguments[i].type << "." << std::endl; @@ -1468,7 +1482,7 @@ template class ModuleInstanceBase { locals[i] = arguments[i]; } else { assert(function->isVar(i)); - locals[i].type = function->getLocalType(i); + locals[i] = Literal::makeZero(function->getLocalType(i)); } } } @@ -1575,7 +1589,8 @@ template class ModuleInstanceBase { } NOTE_EVAL1(index); NOTE_EVAL1(flow.value); - assert(curr->isTee() ? flow.value.type == curr->type : true); + assert(curr->isTee() ? isLeftSubTypeOfRight(flow.value.type, curr->type) + : true); scope.locals[index] = flow.value; return curr->isTee() ? flow : Flow(); } @@ -2053,7 +2068,7 @@ template class ModuleInstanceBase { // cannot still be breaking, it means we missed our stop assert(!flow.breaking() || flow.breakTo == RETURN_FLOW); Literal ret = flow.value; - if (function->result != ret.type) { + if (!isLeftSubTypeOfRight(ret.type, function->result)) { std::cerr << "calling " << function->name << " resulted in " << ret << " but the function type is " << function->result << '\n'; WASM_UNREACHABLE("unexpect result type"); diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h index 924c1d9681d..6c3ec215327 100644 --- a/src/wasm-s-parser.h +++ b/src/wasm-s-parser.h @@ -223,6 +223,9 @@ class SExpressionWasmBuilder { Expression* makeBreak(Element& s); Expression* makeBreakTable(Element& s); Expression* makeReturn(Element& s); + Expression* makeRefNull(Element& s); + Expression* makeRefIsNull(Element& s); + Expression* makeRefFunc(Element& s); Expression* makeTry(Element& s); Expression* makeCatch(Element& s, Type type); Expression* makeThrow(Element& s); diff --git a/src/wasm-stack.h b/src/wasm-stack.h index 7564fbcdb5d..1e5268284d4 100644 --- a/src/wasm-stack.h +++ b/src/wasm-stack.h @@ -120,6 +120,9 @@ class BinaryInstWriter : public OverriddenVisitor { void visitSelect(Select* curr); void visitReturn(Return* curr); void visitHost(Host* curr); + void visitRefNull(RefNull* curr); + void visitRefIsNull(RefIsNull* curr); + void visitRefFunc(RefFunc* curr); void visitTry(Try* curr); void visitThrow(Throw* curr); void visitRethrow(Rethrow* curr); @@ -197,6 +200,9 @@ class BinaryenIRWriter : public OverriddenVisitor> { void visitSelect(Select* curr); void visitReturn(Return* curr); void visitHost(Host* curr); + void visitRefNull(RefNull* curr); + void visitRefIsNull(RefIsNull* curr); + void visitRefFunc(RefFunc* curr); void visitTry(Try* curr); void visitThrow(Throw* curr); void visitRethrow(Rethrow* curr); @@ -688,6 +694,30 @@ void BinaryenIRWriter::visitHost(Host* curr) { emit(curr); } +template +void BinaryenIRWriter::visitRefNull(RefNull* curr) { + emit(curr); +} + +template +void BinaryenIRWriter::visitRefIsNull(RefIsNull* curr) { + visit(curr->anyref); + if (curr->type == unreachable) { + emitUnreachable(); + return; + } + emit(curr); +} + +template +void BinaryenIRWriter::visitRefFunc(RefFunc* curr) { + if (curr->type == unreachable) { + emitUnreachable(); + return; + } + emit(curr); +} + template void BinaryenIRWriter::visitTry(Try* curr) { emit(curr); visitPossibleBlockContents(curr->body); diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h index 85b7ca41587..a417929da00 100644 --- a/src/wasm-traversal.h +++ b/src/wasm-traversal.h @@ -72,6 +72,9 @@ template struct Visitor { ReturnType visitDrop(Drop* curr) { return ReturnType(); } ReturnType visitReturn(Return* curr) { return ReturnType(); } ReturnType visitHost(Host* curr) { return ReturnType(); } + ReturnType visitRefNull(RefNull* curr) { return ReturnType(); } + ReturnType visitRefIsNull(RefIsNull* curr) { return ReturnType(); } + ReturnType visitRefFunc(RefFunc* curr) { return ReturnType(); } ReturnType visitTry(Try* curr) { return ReturnType(); } ReturnType visitThrow(Throw* curr) { return ReturnType(); } ReturnType visitRethrow(Rethrow* curr) { return ReturnType(); } @@ -168,6 +171,12 @@ template struct Visitor { DELEGATE(Return); case Expression::Id::HostId: DELEGATE(Host); + case Expression::Id::RefNullId: + DELEGATE(RefNull); + case Expression::Id::RefIsNullId: + DELEGATE(RefIsNull); + case Expression::Id::RefFuncId: + DELEGATE(RefFunc); case Expression::Id::TryId: DELEGATE(Try); case Expression::Id::ThrowId: @@ -242,6 +251,9 @@ struct OverriddenVisitor { UNIMPLEMENTED(Drop); UNIMPLEMENTED(Return); UNIMPLEMENTED(Host); + UNIMPLEMENTED(RefNull); + UNIMPLEMENTED(RefIsNull); + UNIMPLEMENTED(RefFunc); UNIMPLEMENTED(Try); UNIMPLEMENTED(Throw); UNIMPLEMENTED(Rethrow); @@ -339,6 +351,12 @@ struct OverriddenVisitor { DELEGATE(Return); case Expression::Id::HostId: DELEGATE(Host); + case Expression::Id::RefNullId: + DELEGATE(RefNull); + case Expression::Id::RefIsNullId: + DELEGATE(RefIsNull); + case Expression::Id::RefFuncId: + DELEGATE(RefFunc); case Expression::Id::TryId: DELEGATE(Try); case Expression::Id::ThrowId: @@ -478,6 +496,15 @@ struct UnifiedExpressionVisitor : public Visitor { ReturnType visitHost(Host* curr) { return static_cast(this)->visitExpression(curr); } + ReturnType visitRefNull(RefNull* curr) { + return static_cast(this)->visitExpression(curr); + } + ReturnType visitRefIsNull(RefIsNull* curr) { + return static_cast(this)->visitExpression(curr); + } + ReturnType visitRefFunc(RefFunc* curr) { + return static_cast(this)->visitExpression(curr); + } ReturnType visitTry(Try* curr) { return static_cast(this)->visitExpression(curr); } @@ -783,6 +810,15 @@ struct Walker : public VisitorType { static void doVisitHost(SubType* self, Expression** currp) { self->visitHost((*currp)->cast()); } + static void doVisitRefNull(SubType* self, Expression** currp) { + self->visitRefNull((*currp)->cast()); + } + static void doVisitRefIsNull(SubType* self, Expression** currp) { + self->visitRefIsNull((*currp)->cast()); + } + static void doVisitRefFunc(SubType* self, Expression** currp) { + self->visitRefFunc((*currp)->cast()); + } static void doVisitTry(SubType* self, Expression** currp) { self->visitTry((*currp)->cast()); } @@ -1041,6 +1077,19 @@ struct PostWalker : public Walker { } break; } + case Expression::Id::RefNullId: { + self->pushTask(SubType::doVisitRefNull, currp); + break; + } + case Expression::Id::RefIsNullId: { + self->pushTask(SubType::doVisitRefIsNull, currp); + self->pushTask(SubType::scan, &curr->cast()->anyref); + break; + } + case Expression::Id::RefFuncId: { + self->pushTask(SubType::doVisitRefFunc, currp); + break; + } case Expression::Id::TryId: { self->pushTask(SubType::doVisitTry, currp); self->pushTask(SubType::scan, &curr->cast()->catchBody); @@ -1184,8 +1233,6 @@ struct ExpressionStackWalker : public PostWalker { if (name == loop->name) { return curr; } - } else { - WASM_UNREACHABLE("unexpected expression type"); } if (i == 0) { return nullptr; diff --git a/src/wasm-type.h b/src/wasm-type.h index ddfb7c9a157..358e14cb25e 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -36,7 +36,9 @@ class Type { f32, f64, v128, + funcref, anyref, + nullref, exnref, _last_value_type, }; @@ -64,7 +66,8 @@ class Type { bool isInteger() const { return id == i32 || id == i64; } bool isFloat() const { return id == f32 || id == f64; } bool isVector() const { return id == v128; }; - bool isRef() const { return id == anyref || id == exnref; } + bool isNumber() const { return id >= i32 && id <= v128; } + bool isRef() const { return id >= funcref && id <= exnref; } // (In)equality must be defined for both Type and ValueType because it is // otherwise ambiguous whether to convert both this and other to int or @@ -119,14 +122,25 @@ constexpr Type i64 = Type::i64; constexpr Type f32 = Type::f32; constexpr Type f64 = Type::f64; constexpr Type v128 = Type::v128; +constexpr Type funcref = Type::funcref; constexpr Type anyref = Type::anyref; +constexpr Type nullref = Type::nullref; constexpr Type exnref = Type::exnref; constexpr Type unreachable = Type::unreachable; unsigned getTypeSize(Type type); FeatureSet getFeatures(Type type); Type getType(unsigned size, bool float_); +bool isLeftSubTypeOfRight(Type left, Type right); Type reinterpretType(Type type); +Type getLeastUpperBound(Type a, Type b); +template Type mergeTypes(const T& types) { + Type type = unreachable; + for (auto other : types) { + type = getLeastUpperBound(type, other); + } + return type; +} } // namespace wasm diff --git a/src/wasm.h b/src/wasm.h index b65ada95315..61ce839df32 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -529,6 +529,9 @@ class Expression { MemoryFillId, PushId, PopId, + RefNullId, + RefIsNullId, + RefFuncId, TryId, ThrowId, RethrowId, @@ -563,6 +566,19 @@ class Expression { assert(int(_id) == int(T::SpecificId)); return (const T*)this; } + + // Can this be used in globals' init expressions? + bool isConstExpression() const { + switch (_id) { + case ConstId: + case GlobalGetId: + case RefNullId: + case RefFuncId: + return true; + default: + return false; + } + } }; const char* getExpressionName(Expression* curr); @@ -1022,6 +1038,7 @@ class Select : public SpecificExpression { Expression* condition; void finalize(); + void finalize(Type type_); }; class Drop : public SpecificExpression { @@ -1084,6 +1101,32 @@ class Pop : public SpecificExpression { Pop(MixedArena& allocator) {} }; +class RefNull : public SpecificExpression { +public: + RefNull() = default; + RefNull(MixedArena& allocator) {} + + void finalize(); +}; + +class RefIsNull : public SpecificExpression { +public: + RefIsNull(MixedArena& allocator) {} + + Expression* anyref; + + void finalize(); +}; + +class RefFunc : public SpecificExpression { +public: + RefFunc(MixedArena& allocator) {} + + Name func; + + void finalize(); +}; + class Try : public SpecificExpression { public: Try(MixedArena& allocator) {} diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 4183d8a2360..1c15811ae10 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -137,15 +137,26 @@ void Literal::getBits(uint8_t (&buf)[16]) const { case Type::v128: memcpy(buf, &v128, sizeof(v128)); break; - case Type::anyref: // anyref type is opaque - case Type::exnref: // exnref type is opaque - case Type::none: - case Type::unreachable: + case Type::funcref: + case Type::anyref: + case Type::nullref: + case Type::exnref: + break; + default: WASM_UNREACHABLE("invalid type"); } } bool Literal::operator==(const Literal& other) const { + if (type.isRef() && other.type.isRef()) { + if (type == nullref && other.type == nullref) { + return true; + } + if (type == funcref && other.type == funcref && func == other.func) { + return true; + } + return false; + } if (type != other.type) { return false; } @@ -273,9 +284,19 @@ std::ostream& operator<<(std::ostream& o, Literal literal) { o << "i32x4 "; literal.printVec128(o, literal.getv128()); break; - case Type::anyref: // anyref type is opaque - case Type::exnref: // exnref type is opaque - case Type::unreachable: + case Type::funcref: + o << "funcref(" << literal.func << ")"; + break; + case Type::anyref: + o << "anyref"; + break; + case Type::nullref: + o << "nullref"; + break; + case Type::exnref: + o << "exnref"; + break; + default: WASM_UNREACHABLE("invalid type"); } restoreNormalColor(o); @@ -476,11 +497,7 @@ Literal Literal::eqz() const { return eq(Literal(float(0))); case Type::f64: return eq(Literal(double(0))); - case Type::v128: - case Type::anyref: - case Type::exnref: - case Type::none: - case Type::unreachable: + default: WASM_UNREACHABLE("unexpected type"); } WASM_UNREACHABLE("invalid type"); @@ -496,11 +513,7 @@ Literal Literal::neg() const { return Literal(i32 ^ 0x80000000).castToF32(); case Type::f64: return Literal(int64_t(i64 ^ 0x8000000000000000ULL)).castToF64(); - case Type::v128: - case Type::anyref: - case Type::exnref: - case Type::none: - case Type::unreachable: + default: WASM_UNREACHABLE("unexpected type"); } WASM_UNREACHABLE("invalid type"); @@ -516,11 +529,7 @@ Literal Literal::abs() const { return Literal(i32 & 0x7fffffff).castToF32(); case Type::f64: return Literal(int64_t(i64 & 0x7fffffffffffffffULL)).castToF64(); - case Type::v128: - case Type::anyref: - case Type::exnref: - case Type::none: - case Type::unreachable: + default: WASM_UNREACHABLE("unexpected type"); } WASM_UNREACHABLE("unexpected type"); @@ -619,11 +628,7 @@ Literal Literal::add(const Literal& other) const { return Literal(getf32() + other.getf32()); case Type::f64: return Literal(getf64() + other.getf64()); - case Type::v128: - case Type::anyref: - case Type::exnref: - case Type::none: - case Type::unreachable: + default: WASM_UNREACHABLE("unexpected type"); } WASM_UNREACHABLE("unexpected type"); @@ -639,11 +644,7 @@ Literal Literal::sub(const Literal& other) const { return Literal(getf32() - other.getf32()); case Type::f64: return Literal(getf64() - other.getf64()); - case Type::v128: - case Type::anyref: - case Type::exnref: - case Type::none: - case Type::unreachable: + default: WASM_UNREACHABLE("unexpected type"); } WASM_UNREACHABLE("unexpected type"); @@ -730,11 +731,7 @@ Literal Literal::mul(const Literal& other) const { return Literal(getf32() * other.getf32()); case Type::f64: return Literal(getf64() * other.getf64()); - case Type::v128: - case Type::anyref: - case Type::exnref: - case Type::none: - case Type::unreachable: + default: WASM_UNREACHABLE("unexpected type"); } WASM_UNREACHABLE("unexpected type"); @@ -962,11 +959,7 @@ Literal Literal::eq(const Literal& other) const { return Literal(getf32() == other.getf32()); case Type::f64: return Literal(getf64() == other.getf64()); - case Type::v128: - case Type::anyref: - case Type::exnref: - case Type::none: - case Type::unreachable: + default: WASM_UNREACHABLE("unexpected type"); } WASM_UNREACHABLE("unexpected type"); @@ -982,11 +975,7 @@ Literal Literal::ne(const Literal& other) const { return Literal(getf32() != other.getf32()); case Type::f64: return Literal(getf64() != other.getf64()); - case Type::v128: - case Type::anyref: - case Type::exnref: - case Type::none: - case Type::unreachable: + default: WASM_UNREACHABLE("unexpected type"); } WASM_UNREACHABLE("unexpected type"); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index f9952cc473e..20dc53d428c 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -279,7 +279,7 @@ void WasmBinaryWriter::writeImports() { BYN_TRACE("write one table\n"); writeImportHeader(&wasm->table); o << U32LEB(int32_t(ExternalKind::Table)); - o << S32LEB(BinaryConsts::EncodedType::AnyFunc); + o << S32LEB(BinaryConsts::EncodedType::funcref); writeResizableLimits(wasm->table.initial, wasm->table.max, wasm->table.hasMax(), @@ -470,7 +470,7 @@ void WasmBinaryWriter::writeFunctionTableDeclaration() { BYN_TRACE("== writeFunctionTableDeclaration\n"); auto start = startSection(BinaryConsts::Section::Table); o << U32LEB(1); // Declare 1 table. - o << S32LEB(BinaryConsts::EncodedType::AnyFunc); + o << S32LEB(BinaryConsts::EncodedType::funcref); writeResizableLimits(wasm->table.initial, wasm->table.max, wasm->table.hasMax(), @@ -1019,8 +1019,12 @@ Type WasmBinaryBuilder::getType() { return f64; case BinaryConsts::EncodedType::v128: return v128; + case BinaryConsts::EncodedType::funcref: + return funcref; case BinaryConsts::EncodedType::anyref: return anyref; + case BinaryConsts::EncodedType::nullref: + return nullref; case BinaryConsts::EncodedType::exnref: return exnref; default: @@ -1226,8 +1230,8 @@ void WasmBinaryBuilder::readImports() { wasm.table.name = Name(std::string("timport$") + std::to_string(i)); auto elementType = getS32LEB(); WASM_UNUSED(elementType); - if (elementType != BinaryConsts::EncodedType::AnyFunc) { - throwError("Imported table type is not AnyFunc"); + if (elementType != BinaryConsts::EncodedType::funcref) { + throwError("Imported table type is not funcref"); } wasm.table.exists = true; bool is_shared; @@ -1777,11 +1781,16 @@ void WasmBinaryBuilder::processFunctions() { wasm.addExport(curr); } - for (auto& iter : functionCalls) { + for (auto& iter : functionRefs) { size_t index = iter.first; - auto& calls = iter.second; - for (auto* call : calls) { - call->target = getFunctionName(index); + auto& refs = iter.second; + for (auto* ref : refs) { + if (auto* call = ref->dynCast()) { + call->target = getFunctionName(index); + } + if (auto* refFunc = ref->dynCast()) { + refFunc->func = getFunctionName(index); + } } } @@ -1844,8 +1853,8 @@ void WasmBinaryBuilder::readFunctionTableDeclaration() { } wasm.table.exists = true; auto elemType = getS32LEB(); - if (elemType != BinaryConsts::EncodedType::AnyFunc) { - throwError("ElementType must be AnyFunc in MVP"); + if (elemType != BinaryConsts::EncodedType::funcref) { + throwError("ElementType must be funcref in MVP"); } bool is_shared; getResizableLimits( @@ -2092,7 +2101,8 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) { visitGlobalSet((curr = allocator.alloc())->cast()); break; case BinaryConsts::Select: - visitSelect((curr = allocator.alloc()); + case BinaryConsts::SelectWithType: + visitSelect((curr = allocator.alloc(), code); break; case BinaryConsts::Return: visitReturn((curr = allocator.alloc())->cast()); @@ -2112,6 +2122,15 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) { case BinaryConsts::Catch: curr = nullptr; break; + case BinaryConsts::RefNull: + visitRefNull((curr = allocator.alloc())->cast()); + break; + case BinaryConsts::RefIsNull: + visitRefIsNull((curr = allocator.alloc())->cast()); + break; + case BinaryConsts::RefFunc: + visitRefFunc((curr = allocator.alloc())->cast()); + break; case BinaryConsts::Try: visitTry((curr = allocator.alloc())->cast()); break; @@ -2481,7 +2500,7 @@ void WasmBinaryBuilder::visitCall(Call* curr) { curr->operands[num - i - 1] = popNonVoidExpression(); } curr->type = type->result; - functionCalls[index].push_back(curr); // we don't know function names yet + functionRefs[index].push_back(curr); // we don't know function names yet curr->finalize(); } @@ -4291,12 +4310,24 @@ bool WasmBinaryBuilder::maybeVisitSIMDLoad(Expression*& out, uint32_t code) { return true; } -void WasmBinaryBuilder::visitSelect(Select* curr) { - BYN_TRACE("zz node: Select\n"); +void WasmBinaryBuilder::visitSelect(Select* curr, uint8_t code) { + BYN_TRACE("zz node: Select, code " << int32_t(code) << std::endl); + if (code == BinaryConsts::SelectWithType) { + size_t numTypes = getU32LEB(); + std::vector types; + for (size_t i = 0; i < numTypes; i++) { + types.push_back(getType()); + } + curr->type = Type(types); + } curr->condition = popNonVoidExpression(); curr->ifFalse = popNonVoidExpression(); curr->ifTrue = popNonVoidExpression(); - curr->finalize(); + if (code == BinaryConsts::SelectWithType) { + curr->finalize(curr->type); + } else { + curr->finalize(); + } } void WasmBinaryBuilder::visitReturn(Return* curr) { @@ -4348,6 +4379,27 @@ void WasmBinaryBuilder::visitDrop(Drop* curr) { curr->finalize(); } +void WasmBinaryBuilder::visitRefNull(RefNull* curr) { + BYN_TRACE("zz node: RefNull\n"); + curr->finalize(); +} + +void WasmBinaryBuilder::visitRefIsNull(RefIsNull* curr) { + BYN_TRACE("zz node: RefIsNull\n"); + curr->anyref = popNonVoidExpression(); + curr->finalize(); +} + +void WasmBinaryBuilder::visitRefFunc(RefFunc* curr) { + BYN_TRACE("zz node: RefFunc\n"); + auto index = getU32LEB(); + if (index >= functionImports.size() + functionTypes.size()) { + throwError("ref.func: invalid call index"); + } + functionRefs[index].push_back(curr); // we don't know function names yet + curr->finalize(); +} + void WasmBinaryBuilder::visitTry(Try* curr) { BYN_TRACE("zz node: Try\n"); // For simplicity of implementation, like if scopes, we create a hidden block diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index b820c696da0..c9d10ac16a2 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -893,16 +893,22 @@ Type SExpressionWasmBuilder::stringToType(const char* str, return v128; } } + if (strncmp(str, "funcref", 7) == 0 && (prefix || str[7] == 0)) { + return funcref; + } if (strncmp(str, "anyref", 6) == 0 && (prefix || str[6] == 0)) { return anyref; } + if (strncmp(str, "nullref", 7) == 0 && (prefix || str[7] == 0)) { + return nullref; + } if (strncmp(str, "exnref", 6) == 0 && (prefix || str[6] == 0)) { return exnref; } if (allowError) { return none; } - throw ParseException("invalid wasm type"); + throw ParseException(std::string("invalid wasm type: ") + str); } Type SExpressionWasmBuilder::stringToLaneType(const char* str) { @@ -979,10 +985,16 @@ Expression* SExpressionWasmBuilder::makeUnary(Element& s, UnaryOp op) { Expression* SExpressionWasmBuilder::makeSelect(Element& s) { auto ret = allocator.alloc