summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md6
-rw-r--r--src/ir/CMakeLists.txt1
-rw-r--r--src/ir/table-utils.cpp73
-rw-r--r--src/ir/table-utils.h4
-rw-r--r--src/passes/Print.cpp11
-rw-r--r--src/wasm/wasm-binary.cpp28
-rw-r--r--src/wasm/wasm-s-parser.cpp8
-rw-r--r--test/passes/Oz_fuzz-exec_all-features.txt1
-rw-r--r--test/passes/dae_all-features.txt1
-rw-r--r--test/passes/duplicate-function-elimination_all-features.txt1
-rw-r--r--test/passes/fuzz-exec_all-features.txt1
-rw-r--r--test/passes/inlining_all-features.txt1
-rw-r--r--test/passes/instrument-locals_all-features_disable-typed-function-references.txt1
-rw-r--r--test/passes/legalize-js-interface_all-features.txt1
-rw-r--r--test/passes/precompute_all-features.txt1
-rw-r--r--test/passes/remove-unused-module-elements_all-features.txt1
-rw-r--r--test/passes/simplify-globals_all-features_fuzz-exec.txt1
-rw-r--r--test/reference-types.wast10
-rw-r--r--test/reference-types.wast.from-wast11
-rw-r--r--test/reference-types.wast.fromBinary11
-rw-r--r--test/reference-types.wast.fromBinary.noDebugInfo11
-rw-r--r--test/typed-function-references.wast.from-wast1
-rw-r--r--test/typed-function-references.wast.fromBinary1
-rw-r--r--test/typed-function-references.wast.fromBinary.noDebugInfo1
24 files changed, 183 insertions, 4 deletions
diff --git a/README.md b/README.md
index 635c2f1a7..50d79343c 100644
--- a/README.md
+++ b/README.md
@@ -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)