diff options
24 files changed, 183 insertions, 4 deletions
@@ -120,6 +120,12 @@ There are a few differences between Binaryen IR and the WebAssembly language: and so they are observable externally. To support that, Binaryen may use `push` and `pop` as mentioned earlier; another option is to add LLVM-like `extractvalue/composevalue` instructions. + * Reference Types + * The wasm text and binary formats require that a function whose address is + taken by `ref.func` must be either in the table, or declared via an + `(elem declare func $..)`. Binaryen will emit that data when necessary, but + it does not represent it in IR. That is, IR can be worked on without needing + to think about declaring function references. As a result, you might notice that round-trip conversions (wasm => Binaryen IR => wasm) change code a little in some corner cases. diff --git a/src/ir/CMakeLists.txt b/src/ir/CMakeLists.txt index e2bad94bd..40868899d 100644 --- a/src/ir/CMakeLists.txt +++ b/src/ir/CMakeLists.txt @@ -5,6 +5,7 @@ set(ir_SOURCES LocalGraph.cpp ReFinalize.cpp stack-utils.cpp + table-utils.cpp module-splitting.cpp ${ir_HEADERS} ) diff --git a/src/ir/table-utils.cpp b/src/ir/table-utils.cpp new file mode 100644 index 000000000..ef89e50f3 --- /dev/null +++ b/src/ir/table-utils.cpp @@ -0,0 +1,73 @@ +/* + * Copyright 2021 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "table-utils.h" +#include "find_all.h" +#include "module-utils.h" + +namespace wasm { + +namespace TableUtils { + +std::set<Name> getFunctionsNeedingElemDeclare(Module& wasm) { + // Without reference types there are no ref.funcs or elem declare. + if (!wasm.features.hasReferenceTypes()) { + return {}; + } + + // Find all the names in the tables. + + std::unordered_set<Name> tableNames; + for (auto& table : wasm.tables) { + for (auto& segment : table->segments) { + for (auto name : segment.data) { + tableNames.insert(name); + } + } + } + + // Find all the names in ref.funcs. + using Names = std::unordered_set<Name>; + + ModuleUtils::ParallelFunctionAnalysis<Names> analysis( + wasm, [&](Function* func, Names& names) { + if (func->imported()) { + return; + } + for (auto* refFunc : FindAll<RefFunc>(func->body).list) { + names.insert(refFunc->func); + } + }); + + // Find the names that need to be declared. + + std::set<Name> ret; + + for (auto& kv : analysis.map) { + auto& names = kv.second; + for (auto name : names) { + if (!tableNames.count(name)) { + ret.insert(name); + } + } + } + + return ret; +} + +} // namespace TableUtils + +} // namespace wasm diff --git a/src/ir/table-utils.h b/src/ir/table-utils.h index da0bb7241..80ffc0c06 100644 --- a/src/ir/table-utils.h +++ b/src/ir/table-utils.h @@ -94,6 +94,10 @@ inline Index getOrAppend(Table& table, Name name, Module& wasm) { return append(table, name, wasm); } +// Functions that we take a reference to, but are not in a Table, but get an +// "elem declare" mention in the text and binary formats. +std::set<Name> getFunctionsNeedingElemDeclare(Module& wasm); + } // namespace TableUtils } // namespace wasm diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 0a25430cb..993eddb99 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -20,6 +20,7 @@ #include <ir/iteration.h> #include <ir/module-utils.h> +#include <ir/table-utils.h> #include <pass.h> #include <pretty_printing.h> #include <wasm-stack.h> @@ -2832,6 +2833,16 @@ struct PrintSExpression : public UnifiedExpressionVisitor<PrintSExpression> { *curr, [&](Memory* memory) { visitMemory(memory); }); ModuleUtils::iterDefinedTables(*curr, [&](Table* table) { visitTable(table); }); + auto elemDeclareNames = TableUtils::getFunctionsNeedingElemDeclare(*curr); + if (!elemDeclareNames.empty()) { + doIndent(o, indent); + printMedium(o, "(elem"); + o << " declare func"; + for (auto name : elemDeclareNames) { + o << " $" << name; + } + o << ')' << maybeNewLine; + } ModuleUtils::iterDefinedGlobals( *curr, [&](Global* global) { visitGlobal(global); }); ModuleUtils::iterDefinedEvents(*curr, diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index d5579eedf..0993eb124 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -18,6 +18,7 @@ #include <fstream> #include "ir/module-utils.h" +#include "ir/table-utils.h" #include "support/bits.h" #include "support/debug.h" #include "wasm-binary.h" @@ -548,6 +549,10 @@ void WasmBinaryWriter::writeTableElements() { for (auto& table : wasm->tables) { elemCount += table->segments.size(); } + auto needingElemDecl = TableUtils::getFunctionsNeedingElemDeclare(*wasm); + if (!needingElemDecl.empty()) { + elemCount++; + } if (elemCount == 0) { return; } @@ -588,6 +593,16 @@ void WasmBinaryWriter::writeTableElements() { } } } + + if (!needingElemDecl.empty()) { + o << U32LEB(BinaryConsts::IsPassive | BinaryConsts::IsDeclarative); + o << U32LEB(0); // type (indicating funcref) + o << U32LEB(needingElemDecl.size()); + for (auto name : needingElemDecl) { + o << U32LEB(indexes.functionIndexes[name]); + } + } + finishSection(start); } @@ -2677,6 +2692,19 @@ void WasmBinaryBuilder::readTableElements() { bool usesExpressions = (flags & BinaryConsts::UsesExpressions) != 0; if (isPassive) { + bool isDeclarative = (flags & BinaryConsts::IsDeclarative) != 0; + if (isDeclarative) { + // "elem declare" is needed in wasm text and binary, but not in Binaryen + // IR; read and ignore the contents. + auto type = getU32LEB(); + WASM_UNUSED(type); + auto num = getU32LEB(); + for (Index i = 0; i < num; i++) { + getU32LEB(); + } + continue; + } + throwError("Only active elem segments are supported."); } diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index 3baf19063..31ae188d0 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -51,7 +51,7 @@ int unhex(char c) { namespace wasm { static Name STRUCT("struct"), FIELD("field"), ARRAY("array"), I8("i8"), - I16("i16"), RTT("rtt"); + I16("i16"), RTT("rtt"), DECLARE("declare"); static Address getAddress(const Element* s) { return atoll(s->c_str()); } @@ -3257,6 +3257,7 @@ void SExpressionWasmBuilder::parseTable(Element& s, bool preParseImport) { // elem ::= (elem (expr) vec(funcidx)) // | (elem (offset (expr)) func vec(funcidx)) // | (elem (table tableidx) (offset (expr)) func vec(funcidx)) +// | (elem declare func $foo) // // abbreviation: // (offset (expr)) ≡ (expr) @@ -3270,6 +3271,11 @@ void SExpressionWasmBuilder::parseElem(Element& s) { if (!s[i]->isList()) { // optional segment id OR 'declare' OR start of elemList + if (s[i]->str() == DECLARE) { + // "elem declare" is needed in wasm text and binary, but not in Binaryen + // IR; ignore the contents. + return; + } i += 1; } diff --git a/test/passes/Oz_fuzz-exec_all-features.txt b/test/passes/Oz_fuzz-exec_all-features.txt index 0c918e3b4..377d936fd 100644 --- a/test/passes/Oz_fuzz-exec_all-features.txt +++ b/test/passes/Oz_fuzz-exec_all-features.txt @@ -41,6 +41,7 @@ (type $anyref_=>_none (func (param anyref))) (type $int_func (func (result i32))) (import "fuzzing-support" "log-i32" (func $log (param i32))) + (elem declare func $a-void-func) (export "structs" (func $0)) (export "arrays" (func $1)) (export "rtts" (func $2)) diff --git a/test/passes/dae_all-features.txt b/test/passes/dae_all-features.txt index d4769ac16..4ca21576f 100644 --- a/test/passes/dae_all-features.txt +++ b/test/passes/dae_all-features.txt @@ -282,6 +282,7 @@ (module (type $funcref_i32_f64_=>_i64 (func (param funcref i32 f64) (result i64))) (type $f32_=>_funcref (func (param f32) (result funcref))) + (elem declare func $0) (export "export" (func $1)) (func $0 (param $0 funcref) (param $1 i32) (param $2 f64) (result i64) (nop) diff --git a/test/passes/duplicate-function-elimination_all-features.txt b/test/passes/duplicate-function-elimination_all-features.txt index aa234da67..a7a751f76 100644 --- a/test/passes/duplicate-function-elimination_all-features.txt +++ b/test/passes/duplicate-function-elimination_all-features.txt @@ -1,6 +1,7 @@ (module (type $none_=>_i32 (func (result i32))) (type $none_=>_funcref (func (result funcref))) + (elem declare func $0) (func $0 (result i32) (i32.const 0) ) diff --git a/test/passes/fuzz-exec_all-features.txt b/test/passes/fuzz-exec_all-features.txt index 22e342fc1..8785d088a 100644 --- a/test/passes/fuzz-exec_all-features.txt +++ b/test/passes/fuzz-exec_all-features.txt @@ -211,6 +211,7 @@ [fuzz-exec] note result: func => funcref(func) (module (type $none_=>_funcref (func (result funcref))) + (elem declare func $func) (export "func" (func $func)) (func $func (result funcref) (ref.func $func) diff --git a/test/passes/inlining_all-features.txt b/test/passes/inlining_all-features.txt index f255ed8ff..e4a563dec 100644 --- a/test/passes/inlining_all-features.txt +++ b/test/passes/inlining_all-features.txt @@ -1,6 +1,7 @@ (module (type $none_=>_none (func)) (type $none_=>_funcref (func (result funcref))) + (elem declare func $foo) (export "ref_func_test" (func $ref_func_test)) (func $foo (nop) diff --git a/test/passes/instrument-locals_all-features_disable-typed-function-references.txt b/test/passes/instrument-locals_all-features_disable-typed-function-references.txt index 481d7a72b..3d8a0b651 100644 --- a/test/passes/instrument-locals_all-features_disable-typed-function-references.txt +++ b/test/passes/instrument-locals_all-features_disable-typed-function-references.txt @@ -34,6 +34,7 @@ (import "env" "set_dataref" (func $set_dataref (param i32 i32 dataref) (result dataref))) (import "env" "get_v128" (func $get_v128 (param i32 i32 v128) (result v128))) (import "env" "set_v128" (func $set_v128 (param i32 i32 v128) (result v128))) + (elem declare func $test) (event $e (attr 0) (param i32)) (func $test (local $x i32) diff --git a/test/passes/legalize-js-interface_all-features.txt b/test/passes/legalize-js-interface_all-features.txt index 90353c4c9..d05d1bc30 100644 --- a/test/passes/legalize-js-interface_all-features.txt +++ b/test/passes/legalize-js-interface_all-features.txt @@ -11,6 +11,7 @@ (import "env" "imported" (func $legalimport$imported (result i32))) (import "env" "other" (func $legalimport$other (param i32 i32 i32 i32 i32))) (import "env" "ref-func-arg" (func $legalimport$ref-func-arg (result i32))) + (elem declare func $ref-func-arg) (export "func" (func $legalstub$func)) (export "ref-func-test" (func $ref-func-test)) (export "imported" (func $legalstub$imported)) diff --git a/test/passes/precompute_all-features.txt b/test/passes/precompute_all-features.txt index b35bef77d..7ee4db1f2 100644 --- a/test/passes/precompute_all-features.txt +++ b/test/passes/precompute_all-features.txt @@ -8,6 +8,7 @@ (type $none_=>_i32_i64 (func (result i32 i64))) (memory $0 512 512) (data (i32.const 0) "passive") + (elem declare func $dummy) (global $global i32 (i32.const 1)) (global $global-mut (mut i32) (i32.const 2)) (func $x (param $x i32) diff --git a/test/passes/remove-unused-module-elements_all-features.txt b/test/passes/remove-unused-module-elements_all-features.txt index c2f86125d..025079225 100644 --- a/test/passes/remove-unused-module-elements_all-features.txt +++ b/test/passes/remove-unused-module-elements_all-features.txt @@ -316,6 +316,7 @@ ) (module (type $none_=>_none (func)) + (elem declare func $foo) (export "test" (func $test)) (func $foo (nop) diff --git a/test/passes/simplify-globals_all-features_fuzz-exec.txt b/test/passes/simplify-globals_all-features_fuzz-exec.txt index fa236fbc1..141fe9f01 100644 --- a/test/passes/simplify-globals_all-features_fuzz-exec.txt +++ b/test/passes/simplify-globals_all-features_fuzz-exec.txt @@ -3,6 +3,7 @@ (module (type $f32_ref?|i31|_i64_f64_funcref_=>_none (func (param f32 (ref null i31) i64 f64 funcref))) (type $none_=>_funcref (func (result funcref))) + (elem declare func $0) (global $global$0 (mut funcref) (ref.null func)) (export "export" (func $1)) (func $0 (param $0 f32) (param $1 (ref null i31)) (param $2 i64) (param $3 f64) (param $4 funcref) diff --git a/test/reference-types.wast b/test/reference-types.wast index e79508839..28e24b4d7 100644 --- a/test/reference-types.wast +++ b/test/reference-types.wast @@ -17,6 +17,7 @@ (func $foo) (table funcref (elem $take_externref $take_funcref $take_anyref)) + (elem declare func $ref-taken-but-not-in-table) (import "env" "import_func" (func $import_func (param externref) (result funcref))) (import "env" "import_global" (global $import_global externref)) @@ -532,4 +533,13 @@ (return (ref.func $foo)) (return (ref.null func)) ) + + (func $ref-user + (drop + ;; an "elem declare func" must be emitted for this ref.func which is not + ;; in the table + (ref.func $ref-taken-but-not-in-table) + ) + ) + (func $ref-taken-but-not-in-table) ) diff --git a/test/reference-types.wast.from-wast b/test/reference-types.wast.from-wast index 1ccdc4d52..5a8147584 100644 --- a/test/reference-types.wast.from-wast +++ b/test/reference-types.wast.from-wast @@ -3,15 +3,16 @@ (type $sig_anyref (func (param anyref))) (type $sig_funcref (func (param funcref))) (type $none_=>_funcref (func (result funcref))) + (type $none_=>_none (func)) (type $sig_externref (func (param externref))) (type $none_=>_externref (func (result externref))) - (type $none_=>_none (func)) (type $i32_=>_none (func (param i32))) (type $externref_=>_funcref (func (param externref) (result funcref))) (import "env" "import_global" (global $import_global externref)) (import "env" "import_func" (func $import_func (param externref) (result funcref))) (table $0 3 3 funcref) (elem (i32.const 0) $take_externref $take_funcref $take_anyref) + (elem declare func $foo $ref-taken-but-not-in-table) (global $global_externref (mut externref) (ref.null extern)) (global $global_funcref (mut funcref) (ref.null func)) (global $global_funcref_func (mut funcref) (ref.func $foo)) @@ -764,4 +765,12 @@ (ref.null func) ) ) + (func $ref-user + (drop + (ref.func $ref-taken-but-not-in-table) + ) + ) + (func $ref-taken-but-not-in-table + (nop) + ) ) diff --git a/test/reference-types.wast.fromBinary b/test/reference-types.wast.fromBinary index 283a1efe3..f55aae6be 100644 --- a/test/reference-types.wast.fromBinary +++ b/test/reference-types.wast.fromBinary @@ -3,15 +3,16 @@ (type $sig_anyref (func (param anyref))) (type $sig_funcref (func (param funcref))) (type $none_=>_funcref (func (result funcref))) + (type $none_=>_none (func)) (type $sig_externref (func (param externref))) (type $none_=>_externref (func (result externref))) - (type $none_=>_none (func)) (type $i32_=>_none (func (param i32))) (type $externref_=>_funcref (func (param externref) (result funcref))) (import "env" "import_global" (global $import_global externref)) (import "env" "import_func" (func $import_func (param externref) (result funcref))) (table $0 3 3 funcref) (elem (i32.const 0) $take_externref $take_funcref $take_anyref) + (elem declare func $foo $ref-taken-but-not-in-table) (global $global_externref (mut externref) (ref.null extern)) (global $global_funcref (mut funcref) (ref.null func)) (global $global_funcref_func (mut funcref) (ref.func $foo)) @@ -725,5 +726,13 @@ (local.get $local_funcref) ) ) + (func $ref-user + (drop + (ref.func $ref-taken-but-not-in-table) + ) + ) + (func $ref-taken-but-not-in-table + (nop) + ) ) diff --git a/test/reference-types.wast.fromBinary.noDebugInfo b/test/reference-types.wast.fromBinary.noDebugInfo index 9613c300b..970037571 100644 --- a/test/reference-types.wast.fromBinary.noDebugInfo +++ b/test/reference-types.wast.fromBinary.noDebugInfo @@ -3,15 +3,16 @@ (type $anyref_=>_none (func (param anyref))) (type $funcref_=>_none (func (param funcref))) (type $none_=>_funcref (func (result funcref))) + (type $none_=>_none (func)) (type $externref_=>_none (func (param externref))) (type $none_=>_externref (func (result externref))) - (type $none_=>_none (func)) (type $i32_=>_none (func (param i32))) (type $externref_=>_funcref (func (param externref) (result funcref))) (import "env" "import_global" (global $gimport$0 externref)) (import "env" "import_func" (func $fimport$0 (param externref) (result funcref))) (table $0 3 3 funcref) (elem (i32.const 0) $0 $1 $2) + (elem declare func $27 $3) (global $global$0 (mut externref) (ref.null extern)) (global $global$1 (mut funcref) (ref.null func)) (global $global$2 (mut funcref) (ref.func $3)) @@ -725,5 +726,13 @@ (local.get $1) ) ) + (func $26 + (drop + (ref.func $27) + ) + ) + (func $27 + (nop) + ) ) diff --git a/test/typed-function-references.wast.from-wast b/test/typed-function-references.wast.from-wast index f96303aa5..aa7d23477 100644 --- a/test/typed-function-references.wast.from-wast +++ b/test/typed-function-references.wast.from-wast @@ -8,6 +8,7 @@ (type $none_=>_i32_ref?|$mixed_results|_f64 (func (result i32 (ref null $mixed_results) f64))) (type $mixed_results (func (result anyref f32 anyref f32))) (type $f64_=>_ref_null<_->_eqref> (func (param f64) (result (ref null $=>eqref)))) + (elem declare func $call-ref $call-ref-more) (func $call-ref (call_ref (ref.func $call-ref) diff --git a/test/typed-function-references.wast.fromBinary b/test/typed-function-references.wast.fromBinary index b6dfd2a5d..047767ca6 100644 --- a/test/typed-function-references.wast.fromBinary +++ b/test/typed-function-references.wast.fromBinary @@ -8,6 +8,7 @@ (type $=>anyref (func (result anyref))) (type $none_=>_i32_ref?|$mixed_results|_f64 (func (result i32 (ref null $mixed_results) f64))) (type $f64_=>_ref_null<_->_eqref> (func (param f64) (result (ref null $=>eqref)))) + (elem declare func $call-ref $call-ref-more) (func $call-ref (call_ref (ref.func $call-ref) diff --git a/test/typed-function-references.wast.fromBinary.noDebugInfo b/test/typed-function-references.wast.fromBinary.noDebugInfo index 7d09438df..7cecbd5b1 100644 --- a/test/typed-function-references.wast.fromBinary.noDebugInfo +++ b/test/typed-function-references.wast.fromBinary.noDebugInfo @@ -8,6 +8,7 @@ (type $none_=>_anyref (func (result anyref))) (type $none_=>_i32_ref?|none_->_anyref_f32_anyref_f32|_f64 (func (result i32 (ref null $none_=>_anyref_f32_anyref_f32) f64))) (type $f64_=>_ref?|none_->_eqref| (func (param f64) (result (ref null $none_=>_eqref)))) + (elem declare func $0 $2) (func $0 (call_ref (ref.func $0) |