From 278aba7ce0f4685d80d2a6e956677083da318bc1 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 20 Dec 2019 19:25:53 -0800 Subject: [PATCH 01/16] Refactor module element related functions (NFC) This does something similar to #2489 for more functions, removing boilerplate code for each module element using template functions. --- src/wasm.h | 6 +- src/wasm/wasm.cpp | 148 +++++++++++++++++++++------------------------- 2 files changed, 72 insertions(+), 82 deletions(-) diff --git a/src/wasm.h b/src/wasm.h index 4c8c4d44495..48adf103b06 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -1389,10 +1389,14 @@ class Module { Export* addExport(Export* curr); Function* addFunction(Function* curr); - Function* addFunction(std::unique_ptr curr); Global* addGlobal(Global* curr); Event* addEvent(Event* curr); + Export* addExport(std::unique_ptr curr); + Function* addFunction(std::unique_ptr curr); + Global* addGlobal(std::unique_ptr curr); + Event* addEvent(std::unique_ptr curr); + void addStart(const Name& s); void removeExport(Name name); diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 783d51e0fb4..cc344bcd61d 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -975,134 +975,120 @@ void Function::clearDebugInfo() { epilogLocation.clear(); } -Export* Module::getExport(Name name) { - auto iter = exportsMap.find(name); - if (iter == exportsMap.end()) { - Fatal() << "Module::getExport: " << name << " does not exist"; +template +typename Map::mapped_type& +getModuleElement(Map& m, Name name, std::string funcName) { + auto iter = m.find(name); + if (iter == m.end()) { + Fatal() << "Module::" << funcName << ": " << name << " does not exist"; } return iter->second; } +Export* Module::getExport(Name name) { + return getModuleElement(exportsMap, name, "getExport"); +} + Function* Module::getFunction(Name name) { - auto iter = functionsMap.find(name); - if (iter == functionsMap.end()) { - Fatal() << "Module::getFunction: " << name << " does not exist"; - } - return iter->second; + return getModuleElement(functionsMap, name, "getFunction"); } Global* Module::getGlobal(Name name) { - auto iter = globalsMap.find(name); - if (iter == globalsMap.end()) { - assert(false); - Fatal() << "Module::getGlobal: " << name << " does not exist"; - } - return iter->second; + return getModuleElement(globalsMap, name, "getGlobal"); } Event* Module::getEvent(Name name) { - auto iter = eventsMap.find(name); - if (iter == eventsMap.end()) { - Fatal() << "Module::getEvent: " << name << " does not exist"; - } - return iter->second; + return getModuleElement(eventsMap, name, "getEvent"); } -Export* Module::getExportOrNull(Name name) { - auto iter = exportsMap.find(name); - if (iter == exportsMap.end()) { +template +typename Map::mapped_type getModuleElementOrNull(Map& m, Name name) { + auto iter = m.find(name); + if (iter == m.end()) { return nullptr; } return iter->second; } +Export* Module::getExportOrNull(Name name) { + return getModuleElementOrNull(exportsMap, name); +} + Function* Module::getFunctionOrNull(Name name) { - auto iter = functionsMap.find(name); - if (iter == functionsMap.end()) { - return nullptr; - } - return iter->second; + return getModuleElementOrNull(functionsMap, name); } Global* Module::getGlobalOrNull(Name name) { - auto iter = globalsMap.find(name); - if (iter == globalsMap.end()) { - return nullptr; - } - return iter->second; + return getModuleElementOrNull(globalsMap, name); } Event* Module::getEventOrNull(Name name) { - auto iter = eventsMap.find(name); - if (iter == eventsMap.end()) { - return nullptr; - } - return iter->second; -} - -Export* Module::addExport(Export* curr) { - if (!curr->name.is()) { - Fatal() << "Module::addExport: empty name"; - } - if (getExportOrNull(curr->name)) { - Fatal() << "Module::addExport: " << curr->name << " already exists"; - } - exports.push_back(std::unique_ptr(curr)); - exportsMap[curr->name] = curr; - return curr; + return getModuleElementOrNull(eventsMap, name); } // TODO(@warchant): refactor all usages to use variant with unique_ptr -Function* Module::addFunction(Function* curr) { +template +Elem* addModuleElement(Vector& v, Map& m, Elem* curr, std::string funcName) { if (!curr->name.is()) { - Fatal() << "Module::addFunction: empty name"; + Fatal() << "Module::" << funcName << ": empty name"; } - if (getFunctionOrNull(curr->name)) { - Fatal() << "Module::addFunction: " << curr->name << " already exists"; + if (getModuleElementOrNull(m, curr->name)) { + Fatal() << "Module::" << funcName << ": " << curr->name + << " already exists"; } - functions.push_back(std::unique_ptr(curr)); - functionsMap[curr->name] = curr; + v.push_back(std::unique_ptr(curr)); + m[curr->name] = curr; return curr; } -Function* Module::addFunction(std::unique_ptr curr) { +template +Elem* addModuleElement(Vector& v, + Map& m, + std::unique_ptr curr, + std::string funcName) { if (!curr->name.is()) { - Fatal() << "Module::addFunction: empty name"; + Fatal() << "Module::" << funcName << ": empty name"; } - if (getFunctionOrNull(curr->name)) { - Fatal() << "Module::addFunction: " << curr->name << " already exists"; + if (getModuleElementOrNull(m, curr->name)) { + Fatal() << "Module::" << funcName << ": " << curr->name + << " already exists"; } - auto* ret = functionsMap[curr->name] = curr.get(); - functions.push_back(std::move(curr)); + auto* ret = m[curr->name] = curr.get(); + v.push_back(std::move(curr)); return ret; } -Global* Module::addGlobal(Global* curr) { - if (!curr->name.is()) { - Fatal() << "Module::addGlobal: empty name"; - } - if (getGlobalOrNull(curr->name)) { - Fatal() << "Module::addGlobal: " << curr->name << " already exists"; - } +Export* Module::addExport(Export* curr) { + return addModuleElement(exports, exportsMap, curr, "addExport"); +} - globals.emplace_back(curr); +Function* Module::addFunction(Function* curr) { + return addModuleElement(functions, functionsMap, curr, "addFunction"); +} - globalsMap[curr->name] = curr; - return curr; +Global* Module::addGlobal(Global* curr) { + return addModuleElement(globals, globalsMap, curr, "addGlobal"); } Event* Module::addEvent(Event* curr) { - if (!curr->name.is()) { - Fatal() << "Module::addEvent: empty name"; - } - if (getEventOrNull(curr->name)) { - Fatal() << "Module::addEvent: " << curr->name << " already exists"; - } + return addModuleElement(events, eventsMap, curr, "addEvent"); +} - events.emplace_back(curr); +Export* Module::addExport(std::unique_ptr curr) { + return addModuleElement(exports, exportsMap, std::move(curr), "addExport"); +} - eventsMap[curr->name] = curr; - return curr; +Function* Module::addFunction(std::unique_ptr curr) { + return addModuleElement( + functions, functionsMap, std::move(curr), "addFunction"); +} + +Global* Module::addGlobal(std::unique_ptr curr) { + return addModuleElement(globals, globalsMap, std::move(curr), "addGlobal"); +} + +Event* Module::addEvent(std::unique_ptr curr) { + return addModuleElement(events, eventsMap, std::move(curr), "addEvent"); } void Module::addStart(const Name& s) { start = s; } From e85bc4bc50b81f88899702c1f7fa65c533a2d39c Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 1 Nov 2019 21:38:58 -0700 Subject: [PATCH 02/16] Add support for reference types proposal --- CHANGELOG.md | 3 + check.py | 17 +- scripts/fuzz_opt.py | 6 +- scripts/gen-s-parser.py | 7 + src/asmjs/asm_v_wasm.cpp | 13 +- src/binaryen-c.cpp | 94 +- src/binaryen-c.h | 19 +- src/gen-s-parser.inc | 64 +- src/ir/ExpressionAnalyzer.cpp | 3 + src/ir/ExpressionManipulator.cpp | 15 +- src/ir/ReFinalize.cpp | 27 +- src/ir/abstract.h | 14 +- src/ir/block-utils.h | 3 +- src/ir/effects.h | 3 + src/ir/flat.h | 9 +- src/ir/literal-utils.h | 4 + src/ir/manipulation.h | 16 +- src/ir/utils.h | 6 + src/js/binaryen.js-post.js | 53 +- src/literal.h | 30 +- src/parsing.h | 6 +- src/passes/ConstHoisting.cpp | 9 +- src/passes/DeadCodeElimination.cpp | 6 + src/passes/DuplicateFunctionElimination.cpp | 1 + src/passes/Flatten.cpp | 36 +- src/passes/FuncCastEmulation.cpp | 16 +- src/passes/Inlining.cpp | 17 +- src/passes/InstrumentLocals.cpp | 32 + src/passes/LegalizeJSInterface.cpp | 32 +- src/passes/LocalCSE.cpp | 7 +- src/passes/MergeLocals.cpp | 15 +- src/passes/OptimizeInstructions.cpp | 6 +- src/passes/Precompute.cpp | 17 +- src/passes/Print.cpp | 40 +- src/passes/RemoveUnusedModuleElements.cpp | 6 + src/passes/SimplifyGlobals.cpp | 18 +- src/passes/SimplifyLocals.cpp | 6 +- src/passes/opt-utils.h | 13 +- src/shell-interface.h | 8 +- src/support/name.h | 2 +- src/support/small_vector.h | 10 +- src/tools/execution-results.h | 14 +- src/tools/fuzzing.h | 237 ++- src/tools/spec-wrapper.h | 11 +- src/tools/wasm-reduce.cpp | 14 + src/tools/wasm-shell.cpp | 4 +- src/wasm-binary.h | 28 +- src/wasm-builder.h | 73 +- src/wasm-interpreter.h | 45 +- src/wasm-s-parser.h | 3 + src/wasm-stack.h | 30 + src/wasm-traversal.h | 57 +- src/wasm-type.h | 16 +- src/wasm.h | 55 + src/wasm/literal.cpp | 84 +- src/wasm/wasm-binary.cpp | 82 +- src/wasm/wasm-s-parser.cpp | 43 +- src/wasm/wasm-stack.cpp | 71 +- src/wasm/wasm-type.cpp | 59 +- src/wasm/wasm-validator.cpp | 291 +-- src/wasm/wasm.cpp | 85 +- src/wasm2js.h | 12 + test/anyref.wast | 18 - test/binaryen.js/exception-handling.js | 3 +- test/binaryen.js/exception-handling.js.txt | 8 +- test/binaryen.js/kitchen-sink.js | 7 + test/binaryen.js/kitchen-sink.js.txt | 332 +-- test/binaryen.js/push-pop.js | 4 + test/binaryen.js/push-pop.js.txt | 12 +- test/example/c-api-kitchen-sink.c | 27 +- test/example/c-api-kitchen-sink.txt | 1744 ++++++++-------- test/example/c-api-kitchen-sink.txt.txt | 32 + test/exception-handling.wast | 16 + test/exception-handling.wast.from-wast | 19 + test/exception-handling.wast.fromBinary | 19 + ...ption-handling.wast.fromBinary.noDebugInfo | 19 + ...cate-function-elimination_all-features.txt | 10 + ...ate-function-elimination_all-features.wast | 12 + .../{flatten.txt => flatten_all-features.txt} | 48 + ...flatten.wast => flatten_all-features.wast} | 20 + ...txt => flatten_local-cse_all-features.txt} | 29 + ...st => flatten_local-cse_all-features.wast} | 12 + test/passes/inlining_all-features.txt | 16 + test/passes/inlining_all-features.wast | 10 + .../passes/instrument-locals_all-features.txt | 174 +- .../instrument-locals_all-features.wast | 38 +- ...=> legalize-js-interface_all-features.txt} | 39 +- ...> legalize-js-interface_all-features.wast} | 13 + ...cals.txt => merge-locals_all-features.txt} | 12 + ...ls.wast => merge-locals_all-features.wast} | 12 + ...=> optimize-instructions_all-features.txt} | 6 + ...> optimize-instructions_all-features.wast} | 10 + test/passes/precompute_all-features.txt | 4 + test/passes/precompute_all-features.wast | 4 + ...ve-unused-module-elements_all-features.txt | 12 + ...e-unused-module-elements_all-features.wast | 9 + ...als-optimizing_enable-mutable-globals.wast | 1 - ....txt => simplify-globals_all-features.txt} | 19 + ...ast => simplify-globals_all-features.wast} | 14 +- test/passes/simplify-locals_all-features.txt | 13 + test/passes/simplify-locals_all-features.wast | 16 + .../passes/translate-to-fuzz_all-features.txt | 1788 +++++++++++------ ...late-to-fuzz_no-fuzz-nans_all-features.txt | 848 +++++++- test/push_pop.wast | 16 + test/push_pop.wast.from-wast | 37 + test/push_pop.wast.fromBinary | 25 + test/push_pop.wast.fromBinary.noDebugInfo | 25 + test/reference-types.wast | 363 ++++ test/reference-types.wast.from-wast | 506 +++++ test/reference-types.wast.fromBinary | 492 +++++ ...eference-types.wast.fromBinary.noDebugInfo | 492 +++++ test/spec/events.wast | 5 - test/spec/old_select.wast | 87 +- test/spec/ref_func.wast | 44 + test/spec/ref_is_null.wast | 15 + test/spec/ref_null.wast | 13 + test/unit/test_features.py | 15 +- 117 files changed, 7320 insertions(+), 2287 deletions(-) delete mode 100644 test/anyref.wast create mode 100644 test/passes/duplicate-function-elimination_all-features.txt create mode 100644 test/passes/duplicate-function-elimination_all-features.wast rename test/passes/{flatten.txt => flatten_all-features.txt} (97%) rename test/passes/{flatten.wast => flatten_all-features.wast} (96%) rename test/passes/{flatten_local-cse.txt => flatten_local-cse_all-features.txt} (96%) rename test/passes/{flatten_local-cse.wast => flatten_local-cse_all-features.wast} (94%) create mode 100644 test/passes/inlining_all-features.txt create mode 100644 test/passes/inlining_all-features.wast rename test/passes/{legalize-js-interface.txt => legalize-js-interface_all-features.txt} (69%) rename test/passes/{legalize-js-interface.wast => legalize-js-interface_all-features.wast} (55%) rename test/passes/{merge-locals.txt => merge-locals_all-features.txt} (97%) rename test/passes/{merge-locals.wast => merge-locals_all-features.wast} (97%) rename test/passes/{optimize-instructions_enable-threads.txt => optimize-instructions_all-features.txt} (99%) rename test/passes/{optimize-instructions_enable-threads.wast => optimize-instructions_all-features.wast} (99%) rename test/passes/{simplify-globals_enable-mutable-globals.txt => simplify-globals_all-features.txt} (92%) rename test/passes/{simplify-globals_enable-mutable-globals.wast => simplify-globals_all-features.wast} (92%) create mode 100644 test/reference-types.wast create mode 100644 test/reference-types.wast.from-wast create mode 100644 test/reference-types.wast.fromBinary create mode 100644 test/reference-types.wast.fromBinary.noDebugInfo create mode 100644 test/spec/ref_func.wast create mode 100644 test/spec/ref_is_null.wast create mode 100644 test/spec/ref_null.wast diff --git a/CHANGELOG.md b/CHANGELOG.md index 74fe3191e9b..2dce3bec352 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 377d41918fd..f4d6bed7e64 100755 --- a/check.py +++ b/check.py @@ -153,8 +153,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 + # FIXME Remove this condition after nullref is implemented in V8 + if 'reference-types.wast' not 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) @@ -271,9 +273,9 @@ def run_wasm_reduce_tests(): before = os.stat('a.wasm').st_size support.run_command(shared.WASM_REDUCE + ['a.wasm', '--command=%s b.wasm --fuzz-exec -all' % shared.WASM_OPT[0], '-t', 'b.wasm', '-w', 'c.wasm']) after = os.stat('c.wasm').st_size - # 0.65 is a custom threshold to check if we have shrunk the output - # sufficiently - assert after < 0.7 * before, [before, after] + # This number is a custom threshold to check if we have shrunk the + # output sufficiently + assert after < 0.75 * before, [before, after] def run_spec_tests(): @@ -323,7 +325,10 @@ def check_expected(actual, expected): # some wast files cannot be split: # * comments.wast: contains characters that are not valid utf-8, # so our string splitting code fails there - if os.path.basename(wast) not in ['comments.wast']: + + # FIXME Remove reference type tests from this list after nullref is + # implemented in V8 + if os.path.basename(wast) not in ['comments.wast', 'ref_null.wast', 'ref_is_null.wast', 'ref_func.wast', 'old_select.wast']: split_num = 0 actual = '' for module, asserts in support.split_wast(wast): diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index a0b763aabac..b77d8e9b4d1 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', '--disable-exception-handling']]) # 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 79b1a60f8ad..4a900ac0e51 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)"), @@ -469,6 +471,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 3720ca07902..5959db43e7c 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: diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 82cbc4c1f01..3607141c214 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -64,15 +64,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.getFunc(); + break; + default: WASM_UNREACHABLE("unexpected type"); } return ret; @@ -90,10 +90,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"); @@ -209,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"); } } @@ -265,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); } @@ -397,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) { @@ -1330,17 +1335,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; @@ -502,6 +527,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; @@ -569,6 +611,21 @@ class Builder { return ret; } + Expression* makeConstExpression(Literal value) { + switch (value.type) { + case nullref: + return makeRefNull(); + case funcref: + if (value.getFunc()[0] != 0) { + return makeRefFunc(value.getFunc()); + } + 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 @@ -663,6 +720,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 @@ -728,16 +792,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 bda58de9d97..d898fbd5ad3 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; @@ -1095,7 +1095,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"); @@ -1123,6 +1123,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"); } @@ -1217,10 +1237,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"); @@ -1272,10 +1289,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"); } } @@ -1464,7 +1478,7 @@ template class ModuleInstanceBase { for (size_t i = 0; i < function->getNumLocals(); i++) { if (i < arguments.size()) { assert(i < params.size()); - if (params[i] != arguments[i].type) { + if (!isLeftSubTypeOfRight(arguments[i].type, params[i])) { std::cerr << "Function `" << function->name << "` expects type " << params[i] << " for parameter " << i << ", got " << arguments[i].type << "." << std::endl; @@ -1473,7 +1487,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)); } } } @@ -1580,7 +1594,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(); } @@ -2067,7 +2082,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->sig.results != ret.type) { + if (!isLeftSubTypeOfRight(ret.type, function->sig.results)) { std::cerr << "calling " << function->name << " resulted in " << ret << " but the function type is " << function->sig.results << '\n'; diff --git a/src/wasm-s-parser.h b/src/wasm-s-parser.h index d7324d7564e..8cdcb88f416 100644 --- a/src/wasm-s-parser.h +++ b/src/wasm-s-parser.h @@ -225,6 +225,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 fbd28b0d5d3..c54b4b2ac0e 100644 --- a/src/wasm-stack.h +++ b/src/wasm-stack.h @@ -128,6 +128,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); @@ -207,6 +210,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); @@ -698,6 +704,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 9c6e78360dc..999c89ec7d7 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(); } @@ -167,6 +170,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: @@ -241,6 +250,9 @@ struct OverriddenVisitor { UNIMPLEMENTED(Drop); UNIMPLEMENTED(Return); UNIMPLEMENTED(Host); + UNIMPLEMENTED(RefNull); + UNIMPLEMENTED(RefIsNull); + UNIMPLEMENTED(RefFunc); UNIMPLEMENTED(Try); UNIMPLEMENTED(Throw); UNIMPLEMENTED(Rethrow); @@ -337,6 +349,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: @@ -476,6 +494,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); } @@ -778,6 +805,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()); } @@ -1036,6 +1072,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); @@ -1099,7 +1148,7 @@ struct ControlFlowWalker : public PostWalker { Expression* findBreakTarget(Name name) { assert(!controlFlowStack.empty()); Index i = controlFlowStack.size() - 1; - while (1) { + while (true) { auto* curr = controlFlowStack[i]; if (Block* block = curr->template dynCast()) { if (name == block->name) { @@ -1111,7 +1160,7 @@ struct ControlFlowWalker : public PostWalker { } } else { // an if, ignorable - assert(curr->template is()); + assert(curr->template is() || curr->template is()); } if (i == 0) { return nullptr; @@ -1169,7 +1218,7 @@ struct ExpressionStackWalker : public PostWalker { Expression* findBreakTarget(Name name) { assert(!expressionStack.empty()); Index i = expressionStack.size() - 1; - while (1) { + while (true) { auto* curr = expressionStack[i]; if (Block* block = curr->template dynCast()) { if (name == block->name) { @@ -1179,8 +1228,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 c4d719a35d5..d95c31a5d29 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 48adf103b06..539bfcdb341 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -531,6 +531,9 @@ class Expression { MemoryFillId, PushId, PopId, + RefNullId, + RefIsNullId, + RefFuncId, TryId, ThrowId, RethrowId, @@ -565,10 +568,35 @@ class Expression { assert(int(_id) == int(T::SpecificId)); return (const T*)this; } + + bool isConstExpression() const { + switch (_id) { + case ConstId: + case RefNullId: + case RefFuncId: + return true; + default: + return false; + } + } + + bool canInitializeGlobal() const { + switch (_id) { + case ConstId: + case GlobalGetId: + case RefNullId: + case RefFuncId: + return true; + default: + return false; + } + } }; const char* getExpressionName(Expression* curr); +Literal getLiteralFromConstExpression(Expression* curr); + typedef ArenaVector ExpressionList; template class SpecificExpression : public Expression { @@ -1008,6 +1036,7 @@ class Select : public SpecificExpression { Expression* condition; void finalize(); + void finalize(Type type_); }; class Drop : public SpecificExpression { @@ -1070,6 +1099,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 82a150257c1..2f788f45345 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -137,15 +137,27 @@ 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 && + strcmp(func, other.func) == 0) { + return true; + } + return false; + } if (type != other.type) { return false; } @@ -273,9 +285,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.getFunc() << ")"; + 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 +498,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 +514,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 +530,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 +629,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 +645,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 +732,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"); @@ -966,11 +964,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"); @@ -986,11 +980,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 fa90532d994..4fa3bcd1481 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -262,7 +262,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(), @@ -463,7 +463,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(), @@ -1059,8 +1059,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: @@ -1258,8 +1262,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; @@ -1802,11 +1806,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); + } } } @@ -1869,8 +1878,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( @@ -2117,7 +2126,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()); @@ -2137,6 +2147,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; @@ -2510,7 +2529,7 @@ void WasmBinaryBuilder::visitCall(Call* curr) { curr->operands[num - i - 1] = popNonVoidExpression(); } curr->type = sig.results; - 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(); } @@ -4326,12 +4345,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) { @@ -4383,6 +4414,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"); + Index index = getU32LEB(); + if (index >= functionImports.size() + functionSignatures.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 1319d80fc3e..d9d51696d8c 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -850,16 +850,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) { @@ -936,10 +942,16 @@ Expression* SExpressionWasmBuilder::makeUnary(Element& s, UnaryOp op) { Expression* SExpressionWasmBuilder::makeSelect(Element& s) { auto ret = allocator.alloc