[WASM-References] Add support for Table.size, grow and fill instructions
authorjustin_michaud@apple.com <justin_michaud@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 19 Jun 2019 01:18:43 +0000 (01:18 +0000)
committerjustin_michaud@apple.com <justin_michaud@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 19 Jun 2019 01:18:43 +0000 (01:18 +0000)
https://bugs.webkit.org/show_bug.cgi?id=198761

Reviewed by Yusuke Suzuki.

JSTests:

* wasm/Builder_WebAssemblyBinary.js:
(const.putOp):
* wasm/references/table_misc.js: Added.
(TableSize.End.End.WebAssembly):
(GetLocal.0.GetLocal.1.TableGrow.End.End.WebAssembly):
* wasm/wasm.json:

Source/JavaScriptCore:

Add support for Table.size, grow and fill instructions. This also required
adding support for two-byte opcodes to the ops generator.

* wasm/WasmAirIRGenerator.cpp:
(JSC::Wasm::AirIRGenerator::gAnyref):
(JSC::Wasm::AirIRGenerator::tmpForType):
(JSC::Wasm::AirIRGenerator::addTableSize):
(JSC::Wasm::AirIRGenerator::addTableGrow):
(JSC::Wasm::AirIRGenerator::addTableFill):
* wasm/WasmB3IRGenerator.cpp:
(JSC::Wasm::B3IRGenerator::addTableSize):
(JSC::Wasm::B3IRGenerator::addTableGrow):
(JSC::Wasm::B3IRGenerator::addTableFill):
* wasm/WasmExceptionType.h:
* wasm/WasmFormat.h:
(JSC::Wasm::TableInformation::wasmType const):
* wasm/WasmFunctionParser.h:
(JSC::Wasm::FunctionParser<Context>::parseExpression):
(JSC::Wasm::FunctionParser<Context>::parseUnreachableExpression):
* wasm/WasmInstance.cpp:
(JSC::Wasm::doWasmTableGrow):
(JSC::Wasm::doWasmTableFill):
* wasm/WasmInstance.h:
* wasm/WasmTable.cpp:
(JSC::Wasm::Table::grow):
* wasm/WasmValidate.cpp:
(JSC::Wasm::Validate::addTableSize):
(JSC::Wasm::Validate::addTableGrow):
(JSC::Wasm::Validate::addTableFill):
* wasm/generateWasmOpsHeader.py:
(opcodeMacroizer):
(ExtTableOpType):
* wasm/wasm.json:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@246577 268f45cc-cd09-0410-ab3c-d52691b4dbfc

14 files changed:
JSTests/ChangeLog
JSTests/wasm/Builder_WebAssemblyBinary.js
JSTests/wasm/references/table_misc.js [new file with mode: 0644]
JSTests/wasm/wasm.json
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/wasm/WasmAirIRGenerator.cpp
Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp
Source/JavaScriptCore/wasm/WasmExceptionType.h
Source/JavaScriptCore/wasm/WasmFunctionParser.h
Source/JavaScriptCore/wasm/WasmInstance.cpp
Source/JavaScriptCore/wasm/WasmInstance.h
Source/JavaScriptCore/wasm/WasmValidate.cpp
Source/JavaScriptCore/wasm/generateWasmOpsHeader.py
Source/JavaScriptCore/wasm/wasm.json

index b4ee273..f9e51b6 100644 (file)
@@ -1,5 +1,19 @@
 2019-06-18  Justin Michaud  <justin_michaud@apple.com>
 
+        [WASM-References] Add support for Table.size, grow and fill instructions
+        https://bugs.webkit.org/show_bug.cgi?id=198761
+
+        Reviewed by Yusuke Suzuki.
+
+        * wasm/Builder_WebAssemblyBinary.js:
+        (const.putOp):
+        * wasm/references/table_misc.js: Added.
+        (TableSize.End.End.WebAssembly):
+        (GetLocal.0.GetLocal.1.TableGrow.End.End.WebAssembly):
+        * wasm/wasm.json:
+
+2019-06-18  Justin Michaud  <justin_michaud@apple.com>
+
         [WASM-References] Add support for multiple tables
         https://bugs.webkit.org/show_bug.cgi?id=198760
 
index ca8fd8f..4f3a23f 100644 (file)
@@ -60,6 +60,8 @@ const putGlobalType = (bin, global) => {
 
 const putOp = (bin, op) => {
     put(bin, "uint8", op.value);
+    if (WASM.description.opcode[op.name].extendedOp)
+        put(bin, "uint8", WASM.description.opcode[op.name].extendedOp);
     if (op.arguments.length !== 0)
         throw new Error(`Unimplemented: arguments`); // FIXME https://bugs.webkit.org/show_bug.cgi?id=162706
 
diff --git a/JSTests/wasm/references/table_misc.js b/JSTests/wasm/references/table_misc.js
new file mode 100644 (file)
index 0000000..d2095d5
--- /dev/null
@@ -0,0 +1,225 @@
+import * as assert from '../assert.js';
+import Builder from '../Builder.js';
+
+{
+    const $1 = new WebAssembly.Instance(new WebAssembly.Module((new Builder())
+          .Type().End()
+          .Function().End()
+          .Table()
+                .Table({initial: 0, maximum: 0, element: "anyref"})
+                .Table({initial: 20, maximum: 30, element: "anyref"})
+          .End()
+          .Export()
+              .Function("tbl_size")
+              .Table("tbl", 1)
+          .End()
+          .Code()
+            .Function("tbl_size", { params: [], ret: "i32" })
+              .TableSize(1)
+            .End()
+          .End().WebAssembly().get()));
+    fullGC()
+
+    assert.eq($1.exports.tbl_size(), 20)
+    assert.eq($1.exports.tbl.grow(5), 20)
+    assert.eq($1.exports.tbl_size(), 25)
+    assert.eq($1.exports.tbl.get(0), null)
+    assert.eq($1.exports.tbl.get(24), null)
+}
+
+assert.throws(() => new WebAssembly.Module((new Builder())
+      .Type().End()
+      .Function().End()
+      .Export()
+          .Function("tbl_size")
+      .End()
+      .Code()
+        .Function("tbl_size", { params: [], ret: "i32" })
+          .TableSize(0)
+        .End()
+      .End().WebAssembly().get()), Error, "WebAssembly.Module doesn't validate: table index 0 is invalid, limit is 0, in function at index 0 (evaluating 'new WebAssembly.Module')")
+
+{
+    const $1 = new WebAssembly.Instance(new WebAssembly.Module((new Builder())
+          .Type().End()
+          .Function().End()
+          .Table()
+                .Table({initial: 0, maximum: 0, element: "anyref"})
+                .Table({initial: 20, maximum: 30, element: "anyref"})
+          .End()
+          .Export()
+              .Function("tbl_size")
+              .Function("tbl_grow")
+              .Table("tbl", 1)
+          .End()
+          .Code()
+            .Function("tbl_size", { params: [], ret: "i32" })
+              .TableSize(1)
+            .End()
+            .Function("tbl_grow", { params: ["anyref", "i32"], ret: "i32" })
+                .GetLocal(0)
+                .GetLocal(1)
+                .TableGrow(1)
+            .End()
+          .End().WebAssembly().get()));
+    fullGC()
+
+    assert.eq($1.exports.tbl_size(), 20)
+    assert.eq($1.exports.tbl_grow("hi", 5), 20)
+    assert.eq($1.exports.tbl_size(), 25)
+    assert.eq($1.exports.tbl.get(0), null)
+    assert.eq($1.exports.tbl.get(24), "hi")
+
+    assert.eq($1.exports.tbl_grow(null, 5), 25)
+    assert.eq($1.exports.tbl.get(24), "hi")
+    assert.eq($1.exports.tbl.get(25), null)
+    assert.eq($1.exports.tbl_size(), 30)
+    assert.eq($1.exports.tbl_grow(null, 0), -1)
+    assert.eq($1.exports.tbl_grow(null, 5), -1)
+    assert.eq($1.exports.tbl_grow(null, 0), -1)
+}
+
+assert.throws(() => new WebAssembly.Module((new Builder())
+      .Type().End()
+      .Function().End()
+      .Code()
+        .Function("tbl_grow", { params: ["anyref", "i32"], ret: "i32" })
+            .GetLocal(0)
+            .GetLocal(1)
+            .TableGrow(0)
+        .End()
+      .End().WebAssembly().get()), Error, "WebAssembly.Module doesn't validate: table index 0 is invalid, limit is 0, in function at index 0 (evaluating 'new WebAssembly.Module')")
+
+assert.throws(() => new WebAssembly.Module((new Builder())
+      .Type().End()
+      .Function().End()
+      .Table()
+            .Table({initial: 20, maximum: 30, element: "anyref"})
+      .End()
+      .Code()
+        .Function("tbl_grow", { params: ["anyref", "i32"], ret: "i32" })
+            .GetLocal(0)
+            .TableGrow(0)
+        .End()
+      .End().WebAssembly().get()), Error, "WebAssembly.Module doesn't parse at byte 6: can't pop empty stack in table.grow, in function at index 0 (evaluating 'new WebAssembly.Module')")
+
+assert.throws(() => new WebAssembly.Module((new Builder())
+      .Type().End()
+      .Function().End()
+      .Table()
+            .Table({initial: 20, maximum: 30, element: "anyref"})
+      .End()
+      .Code()
+        .Function("tbl_grow", { params: ["i32", "i32"], ret: "i32" })
+            .GetLocal(0)
+            .GetLocal(1)
+            .TableGrow(0)
+        .End()
+      .End().WebAssembly().get()), Error, "WebAssembly.Module doesn't validate: table.grow expects fill value of type Anyref got I32, in function at index 0 (evaluating 'new WebAssembly.Module')")
+
+{
+    const $1 = new WebAssembly.Instance(new WebAssembly.Module((new Builder())
+          .Type().End()
+          .Function().End()
+          .Table()
+                .Table({initial: 20, maximum: 30, element: "anyref"})
+          .End()
+          .Export()
+              .Function("tbl_size")
+              .Function("tbl_fill")
+              .Table("tbl", 0)
+          .End()
+          .Code()
+            .Function("tbl_size", { params: [], ret: "i32" })
+              .TableSize(0)
+            .End()
+            .Function("tbl_fill", { params: ["i32", "anyref", "i32"], ret: "void" })
+                .GetLocal(0)
+                .GetLocal(1)
+                .GetLocal(2)
+                .TableFill(0)
+            .End()
+          .End().WebAssembly().get()));
+    fullGC()
+
+    assert.eq($1.exports.tbl_size(), 20)
+    $1.exports.tbl_fill(1,"hi",3)
+    assert.eq($1.exports.tbl.get(0), null)
+    assert.eq($1.exports.tbl.get(1), "hi")
+    assert.eq($1.exports.tbl.get(2), "hi")
+    assert.eq($1.exports.tbl.get(3), "hi")
+    assert.eq($1.exports.tbl.get(4), null)
+
+    $1.exports.tbl_fill(0,null,1)
+    assert.eq($1.exports.tbl.get(0), null)
+    $1.exports.tbl_fill(0,null,0)
+
+    $1.exports.tbl_fill(19,"test",1)
+    assert.eq($1.exports.tbl.get(19), "test")
+    assert.eq($1.exports.tbl.get(18), null)
+
+    assert.throws(() => $1.exports.tbl_fill(20,null,0), Error, "Out of bounds table access (evaluating 'func(...args)')")
+    assert.throws(() => $1.exports.tbl_fill(20,null,1), Error, "Out of bounds table access (evaluating 'func(...args)')")
+    assert.throws(() => $1.exports.tbl_fill(19,null,2), Error, "Out of bounds table access (evaluating 'func(...args)')")
+    assert.throws(() => $1.exports.tbl_fill(4294967295,null,1), Error, "Out of bounds table access (evaluating 'func(...args)')")
+}
+
+assert.throws(() => new WebAssembly.Module((new Builder())
+      .Type().End()
+      .Function().End()
+      .Code()
+        .Function("tbl_grow", { params: ["anyref", "i32"], ret: "void" })
+            .GetLocal(1)
+            .GetLocal(0)
+            .GetLocal(1)
+            .TableFill(0)
+        .End()
+      .End().WebAssembly().get()), Error, "WebAssembly.Module doesn't validate: table index 0 is invalid, limit is 0, in function at index 0 (evaluating 'new WebAssembly.Module')")
+
+assert.throws(() => new WebAssembly.Module((new Builder())
+      .Type().End()
+      .Function().End()
+      .Table()
+            .Table({initial: 20, maximum: 30, element: "anyref"})
+      .End()
+      .Code()
+        .Function("tbl_grow", { params: ["anyref", "i32"], ret: "i32" })
+            .GetLocal(0)
+            .TableFill(0)
+        .End()
+      .End().WebAssembly().get()), Error, "WebAssembly.Module doesn't parse at byte 6: can't pop empty stack in table.fill, in function at index 0 (evaluating 'new WebAssembly.Module')")
+
+{
+    const $1 = new WebAssembly.Instance(new WebAssembly.Module((new Builder())
+          .Type().End()
+          .Function().End()
+          .Table()
+                .Table({initial: 0, maximum: 0, element: "anyfunc"})
+                .Table({initial: 20, maximum: 30, element: "anyfunc"})
+          .End()
+          .Export()
+              .Function("tbl_size")
+              .Function("tbl_grow")
+              .Table("tbl", 1)
+          .End()
+          .Code()
+            .Function("tbl_size", { params: [], ret: "i32" })
+              .TableSize(1)
+            .End()
+            .Function("tbl_grow", { params: ["i32"], ret: "i32" })
+                .I32Const(0)
+                .TableGet(1)
+                .GetLocal(0)
+                .TableGrow(1)
+            .End()
+          .End().WebAssembly().get()));
+    fullGC()
+
+    $1.exports.tbl.set(0, $1.exports.tbl_size);
+    assert.eq($1.exports.tbl_size(), 20)
+    assert.eq($1.exports.tbl_grow(5), 20)
+    assert.eq($1.exports.tbl_size(), 25)
+    assert.eq($1.exports.tbl.get(0), $1.exports.tbl_size)
+    assert.eq($1.exports.tbl.get(1), null)
+    assert.eq($1.exports.tbl.get(24), $1.exports.tbl_size)
+}
index 45308fb..4a46904 100644 (file)
@@ -69,6 +69,9 @@
         "set_global":          { "category": "special",    "value":  36, "return": [],           "parameter": ["any"],                  "immediate": [{"name": "global_index",   "type": "varuint32"}],                                            "description": "write a global variable" },
         "table.get":           { "category": "special",    "value":  37, "return": ["anyref"],   "parameter": ["i32"],                  "immediate": [{"name": "table_index",    "type": "varuint32"}],                                            "description": "get a table value" },
         "table.set":           { "category": "special",    "value":  38, "return": [],           "parameter": ["i32", "anyref"],        "immediate": [{"name": "table_index",    "type": "varuint32"}],                                            "description": "set a table value" },
+        "table.size":          { "category": "exttable",   "value":  252, "return": ["i32"],     "parameter": [],                       "immediate": [{"name": "global_index",   "type": "varuint32"}],                                            "description": "get the size of a table", "extendedOp": 15 },
+        "table.grow":          { "category": "exttable",   "value":  252, "return": ["i32"],     "parameter": ["anyref", "i32"],        "immediate": [{"name": "global_index",   "type": "varuint32"}],                                            "description": "grow a table by the given delta and return the previous size, or -1 if enough space cannot be allocated", "extendedOp": 16 },
+        "table.fill":          { "category": "exttable",   "value":  252, "return": ["i32"],     "parameter": ["i32", "anyref", "i32"], "immediate": [{"name": "global_index",   "type": "varuint32"}],                                            "description": "fill entries [i,i+n) with the given value", "extendedOp": 17 },
         "call":                { "category": "call",       "value":  16, "return": ["call"],     "parameter": ["call"],                 "immediate": [{"name": "function_index", "type": "varuint32"}],                                            "description": "call a function by its index" },
         "call_indirect":       { "category": "call",       "value":  17, "return": ["call"],     "parameter": ["call"],                 "immediate": [{"name": "type_index",     "type": "varuint32"}, {"name": "table_index","type": "varuint32"}],"description": "call a function indirect with an expected signature" },
         "i32.load8_s":         { "category": "memory",     "value":  44, "return": ["i32"],      "parameter": ["addr"],                 "immediate": [{"name": "flags",          "type": "varuint32"}, {"name": "offset",   "type": "varuint32"}], "description": "load from memory" },
index 6f4dcb2..0272dfd 100644 (file)
@@ -1,3 +1,44 @@
+2019-06-18  Justin Michaud  <justin_michaud@apple.com>
+
+        [WASM-References] Add support for Table.size, grow and fill instructions
+        https://bugs.webkit.org/show_bug.cgi?id=198761
+
+        Reviewed by Yusuke Suzuki.
+
+        Add support for Table.size, grow and fill instructions. This also required
+        adding support for two-byte opcodes to the ops generator.
+
+        * wasm/WasmAirIRGenerator.cpp:
+        (JSC::Wasm::AirIRGenerator::gAnyref):
+        (JSC::Wasm::AirIRGenerator::tmpForType):
+        (JSC::Wasm::AirIRGenerator::addTableSize):
+        (JSC::Wasm::AirIRGenerator::addTableGrow):
+        (JSC::Wasm::AirIRGenerator::addTableFill):
+        * wasm/WasmB3IRGenerator.cpp:
+        (JSC::Wasm::B3IRGenerator::addTableSize):
+        (JSC::Wasm::B3IRGenerator::addTableGrow):
+        (JSC::Wasm::B3IRGenerator::addTableFill):
+        * wasm/WasmExceptionType.h:
+        * wasm/WasmFormat.h:
+        (JSC::Wasm::TableInformation::wasmType const):
+        * wasm/WasmFunctionParser.h:
+        (JSC::Wasm::FunctionParser<Context>::parseExpression):
+        (JSC::Wasm::FunctionParser<Context>::parseUnreachableExpression):
+        * wasm/WasmInstance.cpp:
+        (JSC::Wasm::doWasmTableGrow):
+        (JSC::Wasm::doWasmTableFill):
+        * wasm/WasmInstance.h:
+        * wasm/WasmTable.cpp:
+        (JSC::Wasm::Table::grow):
+        * wasm/WasmValidate.cpp:
+        (JSC::Wasm::Validate::addTableSize):
+        (JSC::Wasm::Validate::addTableGrow):
+        (JSC::Wasm::Validate::addTableFill):
+        * wasm/generateWasmOpsHeader.py:
+        (opcodeMacroizer):
+        (ExtTableOpType):
+        * wasm/wasm.json:
+
 2019-06-18  Keith Miller  <keith_miller@apple.com>
 
         Unreviewed, fix signature of currentWeakRefVersion to return an uintptr_t.
index eff7e32..47b4ec6 100644 (file)
@@ -239,6 +239,9 @@ public:
     // Tables
     PartialResult WARN_UNUSED_RETURN addTableGet(unsigned, ExpressionType& index, ExpressionType& result);
     PartialResult WARN_UNUSED_RETURN addTableSet(unsigned, ExpressionType& index, ExpressionType& value);
+    PartialResult WARN_UNUSED_RETURN addTableSize(unsigned, ExpressionType& result);
+    PartialResult WARN_UNUSED_RETURN addTableGrow(unsigned, ExpressionType& fill, ExpressionType& delta, ExpressionType& result);
+    PartialResult WARN_UNUSED_RETURN addTableFill(unsigned, ExpressionType& offset, ExpressionType& fill, ExpressionType& count);
 
     // Locals
     PartialResult WARN_UNUSED_RETURN getLocal(uint32_t index, ExpressionType& result);
@@ -361,6 +364,8 @@ private:
 
     TypedTmp g32() { return { newTmp(B3::GP), Type::I32 }; }
     TypedTmp g64() { return { newTmp(B3::GP), Type::I64 }; }
+    TypedTmp gAnyref() { return { newTmp(B3::GP), Type::Anyref }; }
+    TypedTmp gAnyfunc() { return { newTmp(B3::GP), Type::Anyfunc }; }
     TypedTmp f32() { return { newTmp(B3::FP), Type::F32 }; }
     TypedTmp f64() { return { newTmp(B3::FP), Type::F64 }; }
 
@@ -370,9 +375,11 @@ private:
         case Type::I32:
             return g32();
         case Type::I64:
-        case Type::Anyref:
-        case Type::Anyfunc:
             return g64();
+        case Type::Anyfunc:
+            return gAnyfunc();
+        case Type::Anyref:
+            return gAnyref();
         case Type::F32:
             return f32();
         case Type::F64:
@@ -508,6 +515,8 @@ private:
                 resultType = B3::Int32;
                 break;
             case Type::I64:
+            case Type::Anyref:
+            case Type::Anyfunc:
                 resultType = B3::Int64;
                 break;
             case Type::F32:
@@ -975,7 +984,7 @@ auto AirIRGenerator::addTableGet(unsigned tableIndex, ExpressionType& index, Exp
     // FIXME: Emit this inline <https://bugs.webkit.org/show_bug.cgi?id=198506>.
     ASSERT(index.tmp());
     ASSERT(index.type() == Type::I32);
-    result = tmpForType(Type::Anyref);
+    result = tmpForType(m_info.tables[tableIndex].wasmType());
 
     emitCCall(&getWasmTableElement, result, instanceValue(), addConstant(Type::I32, tableIndex), index);
     emitCheck([&] {
@@ -1006,6 +1015,54 @@ auto AirIRGenerator::addTableSet(unsigned tableIndex, ExpressionType& index, Exp
     return { };
 }
 
+auto AirIRGenerator::addTableSize(unsigned tableIndex, ExpressionType& result) -> PartialResult
+{
+    // FIXME: Emit this inline <https://bugs.webkit.org/show_bug.cgi?id=198506>.
+    result = tmpForType(Type::I32);
+
+    int32_t (*doSize)(Instance*, unsigned) = [] (Instance* instance, unsigned tableIndex) -> int32_t {
+        return instance->table(tableIndex)->length();
+    };
+
+    emitCCall(doSize, result, instanceValue(), addConstant(Type::I32, tableIndex));
+
+    return { };
+}
+
+auto AirIRGenerator::addTableGrow(unsigned tableIndex, ExpressionType& fill, ExpressionType& delta, ExpressionType& result) -> PartialResult
+{
+    ASSERT(fill.tmp());
+    ASSERT(isSubtype(fill.type(), m_info.tables[tableIndex].wasmType()));
+    ASSERT(delta.tmp());
+    ASSERT(delta.type() == Type::I32);
+    result = tmpForType(Type::I32);
+
+    emitCCall(&doWasmTableGrow, result, instanceValue(), addConstant(Type::I32, tableIndex), fill, delta);
+
+    return { };
+}
+
+auto AirIRGenerator::addTableFill(unsigned tableIndex, ExpressionType& offset, ExpressionType& fill, ExpressionType& count) -> PartialResult
+{
+    ASSERT(fill.tmp());
+    ASSERT(isSubtype(fill.type(), m_info.tables[tableIndex].wasmType()));
+    ASSERT(offset.tmp());
+    ASSERT(offset.type() == Type::I32);
+    ASSERT(count.tmp());
+    ASSERT(count.type() == Type::I32);
+
+    auto result = tmpForType(Type::I32);
+    emitCCall(&doWasmTableFill, result, instanceValue(), addConstant(Type::I32, tableIndex), offset, fill, count);
+
+    emitCheck([&] {
+        return Inst(BranchTest32, nullptr, Arg::resCond(MacroAssembler::Zero), result, result);
+    }, [=] (CCallHelpers& jit, const B3::StackmapGenerationParams&) {
+        this->emitThrowException(jit, ExceptionType::OutOfBoundsTableAccess);
+    });
+
+    return { };
+}
+
 auto AirIRGenerator::getLocal(uint32_t index, ExpressionType& result) -> PartialResult
 {
     ASSERT(m_locals[index].tmp());
index 1eb2465..99967fd 100644 (file)
@@ -192,7 +192,9 @@ public:
     // Tables
     PartialResult WARN_UNUSED_RETURN addTableGet(unsigned, ExpressionType& index, ExpressionType& result);
     PartialResult WARN_UNUSED_RETURN addTableSet(unsigned, ExpressionType& index, ExpressionType& value);
-
+    PartialResult WARN_UNUSED_RETURN addTableSize(unsigned, ExpressionType& result);
+    PartialResult WARN_UNUSED_RETURN addTableGrow(unsigned, ExpressionType& fill, ExpressionType& delta, ExpressionType& result);
+    PartialResult WARN_UNUSED_RETURN addTableFill(unsigned, ExpressionType& offset, ExpressionType& fill, ExpressionType& count);
     // Locals
     PartialResult WARN_UNUSED_RETURN getLocal(uint32_t index, ExpressionType& result);
     PartialResult WARN_UNUSED_RETURN setLocal(uint32_t index, ExpressionType value);
@@ -617,6 +619,47 @@ auto B3IRGenerator::addRefFunc(uint32_t index, ExpressionType& result) -> Partia
     return { };
 }
 
+auto B3IRGenerator::addTableSize(unsigned tableIndex, ExpressionType& result) -> PartialResult
+{
+    // FIXME: Emit this inline <https://bugs.webkit.org/show_bug.cgi?id=198506>.
+    uint32_t (*doSize)(Instance*, unsigned) = [] (Instance* instance, unsigned tableIndex) -> uint32_t {
+        return instance->table(tableIndex)->length();
+    };
+
+    result = m_currentBlock->appendNew<CCallValue>(m_proc, toB3Type(I32), origin(),
+        m_currentBlock->appendNew<ConstPtrValue>(m_proc, origin(), tagCFunctionPtr<void*>(doSize, B3CCallPtrTag)),
+        instanceValue(), m_currentBlock->appendNew<Const32Value>(m_proc, origin(), tableIndex));
+
+    return { };
+}
+
+auto B3IRGenerator::addTableGrow(unsigned tableIndex, ExpressionType& fill, ExpressionType& delta, ExpressionType& result) -> PartialResult
+{
+    result = m_currentBlock->appendNew<CCallValue>(m_proc, toB3Type(I32), origin(),
+        m_currentBlock->appendNew<ConstPtrValue>(m_proc, origin(), tagCFunctionPtr<void*>(&doWasmTableGrow, B3CCallPtrTag)),
+        instanceValue(), m_currentBlock->appendNew<Const32Value>(m_proc, origin(), tableIndex), fill, delta);
+
+    return { };
+}
+
+auto B3IRGenerator::addTableFill(unsigned tableIndex, ExpressionType& offset, ExpressionType& fill, ExpressionType& count) -> PartialResult
+{
+    auto result = m_currentBlock->appendNew<CCallValue>(m_proc, toB3Type(I32), origin(),
+        m_currentBlock->appendNew<ConstPtrValue>(m_proc, origin(), tagCFunctionPtr<void*>(&doWasmTableFill, B3CCallPtrTag)),
+        instanceValue(), m_currentBlock->appendNew<Const32Value>(m_proc, origin(), tableIndex), offset, fill, count);
+
+    {
+        CheckValue* check = m_currentBlock->appendNew<CheckValue>(m_proc, Check, origin(),
+            m_currentBlock->appendNew<Value>(m_proc, Equal, origin(), result, m_currentBlock->appendNew<Const32Value>(m_proc, origin(), 0)));
+
+        check->setGenerator([=] (CCallHelpers& jit, const B3::StackmapGenerationParams&) {
+            this->emitExceptionCheck(jit, ExceptionType::OutOfBoundsTableAccess);
+        });
+    }
+
+    return { };
+}
+
 auto B3IRGenerator::getLocal(uint32_t index, ExpressionType& result) -> PartialResult
 {
     ASSERT(m_locals[index]);
index 932861c..54dbe26 100644 (file)
@@ -33,7 +33,7 @@ namespace Wasm {
 
 #define FOR_EACH_EXCEPTION(macro) \
     macro(OutOfBoundsMemoryAccess,  "Out of bounds memory access") \
-    macro(OutOfBoundsTableAccess,  "Out of bounds table access") \
+    macro(OutOfBoundsTableAccess, "Out of bounds table access") \
     macro(OutOfBoundsCallIndirect, "Out of bounds call_indirect") \
     macro(NullTableEntry,  "call_indirect to a null table entry") \
     macro(BadSignature, "call_indirect to a signature that does not match") \
index 7250542..1a7658c 100644 (file)
@@ -303,6 +303,43 @@ auto FunctionParser<Context>::parseExpression() -> PartialResult
         return { };
     }
 
+    case ExtTable: {
+        WASM_PARSER_FAIL_IF(!Options::useWebAssemblyReferences(), "references are not enabled");
+        uint8_t extOp;
+        WASM_PARSER_FAIL_IF(!parseUInt8(extOp), "can't parse table extended opcode");
+        unsigned tableIndex;
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(tableIndex), "can't parse table index");
+
+        switch (static_cast<ExtTableOpType>(extOp)) {
+        case ExtTableOpType::TableSize: {
+            ExpressionType result;
+            WASM_TRY_ADD_TO_CONTEXT(addTableSize(tableIndex, result));
+            m_expressionStack.append(result);
+            break;
+        }
+        case ExtTableOpType::TableGrow: {
+            ExpressionType fill, delta, result;
+            WASM_TRY_POP_EXPRESSION_STACK_INTO(delta, "table.grow");
+            WASM_TRY_POP_EXPRESSION_STACK_INTO(fill, "table.grow");
+            WASM_TRY_ADD_TO_CONTEXT(addTableGrow(tableIndex, fill, delta, result));
+            m_expressionStack.append(result);
+            break;
+        }
+        case ExtTableOpType::TableFill: {
+            ExpressionType offset, fill, count;
+            WASM_TRY_POP_EXPRESSION_STACK_INTO(count, "table.fill");
+            WASM_TRY_POP_EXPRESSION_STACK_INTO(fill, "table.fill");
+            WASM_TRY_POP_EXPRESSION_STACK_INTO(offset, "table.fill");
+            WASM_TRY_ADD_TO_CONTEXT(addTableFill(tableIndex, offset, fill, count));
+            break;
+        }
+        default:
+            WASM_PARSER_FAIL_IF(true, "invalid extended table op ", extOp);
+            break;
+        }
+        return { };
+    }
+
     case RefNull: {
         WASM_PARSER_FAIL_IF(!Options::useWebAssemblyReferences(), "references are not enabled");
         m_expressionStack.append(m_context.addConstant(Anyfunc, JSValue::encode(jsNull())));
@@ -688,6 +725,7 @@ auto FunctionParser<Context>::parseUnreachableExpression() -> PartialResult
         return { };
     }
 
+    case ExtTable:
     case TableGet:
     case TableSet: {
         unsigned tableIndex;
index c41549b..ec15d86 100644 (file)
@@ -159,6 +159,40 @@ bool setWasmTableElement(Instance* instance, unsigned tableIndex, int32_t signed
     return true;
 }
 
+int32_t doWasmTableGrow(Instance* instance, unsigned tableIndex, EncodedJSValue fill, int32_t delta)
+{
+    ASSERT(tableIndex < instance->module().moduleInformation().tableCount());
+    auto oldSize = instance->table(tableIndex)->length();
+    if (delta < 0)
+        return oldSize;
+    auto newSize = instance->table(tableIndex)->grow(delta);
+    if (!newSize || *newSize == oldSize)
+        return -1;
+
+    for (unsigned i = oldSize; i < instance->table(tableIndex)->length(); ++i)
+        setWasmTableElement(instance, tableIndex, i, fill);
+
+    return oldSize;
+}
+
+bool doWasmTableFill(Instance* instance, unsigned tableIndex, int32_t unsafeOffset, EncodedJSValue fill, int32_t unsafeCount)
+{
+    ASSERT(tableIndex < instance->module().moduleInformation().tableCount());
+    if (unsafeOffset < 0 || unsafeCount < 0)
+        return false;
+
+    unsigned offset = unsafeOffset;
+    unsigned count = unsafeCount;
+
+    if (offset >= instance->table(tableIndex)->length() || offset + count > instance->table(tableIndex)->length())
+        return false;
+
+    for (unsigned j = 0; j < count; ++j)
+        setWasmTableElement(instance, tableIndex, offset + j, fill);
+
+    return true;
+}
+
 EncodedJSValue doWasmRefFunc(Instance* instance, uint32_t index)
 {
     JSValue value = instance->getFunctionWrapper(index);
index 012cd45..f4f3508 100644 (file)
@@ -44,6 +44,8 @@ class Instance;
 EncodedJSValue getWasmTableElement(Instance*, unsigned, int32_t);
 bool setWasmTableElement(Instance*, unsigned, int32_t, EncodedJSValue encValue);
 EncodedJSValue doWasmRefFunc(Instance*, uint32_t);
+int32_t doWasmTableGrow(Instance*, unsigned, EncodedJSValue fill, int32_t delta);
+bool doWasmTableFill(Instance*, unsigned, int32_t offset, EncodedJSValue fill, int32_t count);
 
 class Instance : public ThreadSafeRefCounted<Instance>, public CanMakeWeakPtr<Instance> {
 public:
index bd21d80..6a0bd9f 100644 (file)
@@ -108,7 +108,9 @@ public:
     // Tables
     Result WARN_UNUSED_RETURN addTableGet(unsigned, ExpressionType& index, ExpressionType& result);
     Result WARN_UNUSED_RETURN addTableSet(unsigned, ExpressionType& index, ExpressionType& value);
-
+    Result WARN_UNUSED_RETURN addTableSize(unsigned, ExpressionType& result);
+    Result WARN_UNUSED_RETURN addTableGrow(unsigned, ExpressionType& fill, ExpressionType& delta, ExpressionType& result);
+    Result WARN_UNUSED_RETURN addTableFill(unsigned, ExpressionType& offset, ExpressionType& fill, ExpressionType& count);
     // Locals
     Result WARN_UNUSED_RETURN getLocal(uint32_t index, ExpressionType& result);
     Result WARN_UNUSED_RETURN setLocal(uint32_t index, ExpressionType value);
@@ -198,6 +200,34 @@ auto Validate::addTableSet(unsigned tableIndex, ExpressionType& index, Expressio
     return { };
 }
 
+auto Validate::addTableSize(unsigned tableIndex, ExpressionType& result) -> Result
+{
+    result = Type::I32;
+    WASM_VALIDATOR_FAIL_IF(tableIndex >= m_module.tableCount(), "table index ", tableIndex, " is invalid, limit is ", m_module.tableCount());
+
+    return { };
+}
+
+auto Validate::addTableGrow(unsigned tableIndex, ExpressionType& fill, ExpressionType& delta, ExpressionType& result) -> Result
+{
+    result = Type::I32;
+    WASM_VALIDATOR_FAIL_IF(tableIndex >= m_module.tableCount(), "table index ", tableIndex, " is invalid, limit is ", m_module.tableCount());
+    WASM_VALIDATOR_FAIL_IF(!isSubtype(fill, m_module.tables[tableIndex].wasmType()), "table.grow expects fill value of type ", m_module.tables[tableIndex].wasmType(), " got ", fill);
+    WASM_VALIDATOR_FAIL_IF(Type::I32 != delta, "table.grow expects an i32 delta value, got ", delta);
+
+    return { };
+}
+
+auto Validate::addTableFill(unsigned tableIndex, ExpressionType& offset, ExpressionType& fill, ExpressionType& count) -> Result
+{
+    WASM_VALIDATOR_FAIL_IF(tableIndex >= m_module.tableCount(), "table index ", tableIndex, " is invalid, limit is ", m_module.tableCount());
+    WASM_VALIDATOR_FAIL_IF(!isSubtype(fill, m_module.tables[tableIndex].wasmType()), "table.fill expects fill value of type ", m_module.tables[tableIndex].wasmType(), " got ", fill);
+    WASM_VALIDATOR_FAIL_IF(Type::I32 != offset, "table.fill expects an i32 offset value, got ", offset);
+    WASM_VALIDATOR_FAIL_IF(Type::I32 != count, "table.fill expects an i32 count value, got ", count);
+
+    return { };
+}
+
 auto Validate::addRefIsNull(ExpressionType& value, ExpressionType& result) -> Result
 {
     result = Type::I32;
index 4e9a665..77564c3 100755 (executable)
@@ -55,17 +55,17 @@ type_definitions.extend([t for t in typeMacroizer()])
 type_definitions = "".join(type_definitions)
 
 
-def opcodeMacroizer(filter):
+def opcodeMacroizer(filter, opcodeField="value"):
     inc = 0
     for op in wasm.opcodeIterator(filter):
         b3op = "Oops"
         if isSimple(op["opcode"]):
             b3op = op["opcode"]["b3op"]
-        yield cppMacro(op["name"], op["opcode"]["value"], b3op, inc)
+        yield cppMacro(op["name"], op["opcode"][opcodeField], b3op, inc)
         inc += 1
 
 defines = ["#define FOR_EACH_WASM_SPECIAL_OP(macro)"]
-defines.extend([op for op in opcodeMacroizer(lambda op: not (isUnary(op) or isBinary(op) or op["category"] == "control" or op["category"] == "memory"))])
+defines.extend([op for op in opcodeMacroizer(lambda op: not (isUnary(op) or isBinary(op) or op["category"] == "control" or op["category"] == "memory" or op["category"] == "exttable"))])
 defines.append("\n\n#define FOR_EACH_WASM_CONTROL_FLOW_OP(macro)")
 defines.extend([op for op in opcodeMacroizer(lambda op: op["category"] == "control")])
 defines.append("\n\n#define FOR_EACH_WASM_SIMPLE_UNARY_OP(macro)")
@@ -80,6 +80,8 @@ defines.append("\n\n#define FOR_EACH_WASM_MEMORY_LOAD_OP(macro)")
 defines.extend([op for op in opcodeMacroizer(lambda op: (op["category"] == "memory" and len(op["return"]) == 1))])
 defines.append("\n\n#define FOR_EACH_WASM_MEMORY_STORE_OP(macro)")
 defines.extend([op for op in opcodeMacroizer(lambda op: (op["category"] == "memory" and len(op["return"]) == 0))])
+defines.append("\n\n#define FOR_EACH_WASM_EXT_TABLE_OP(macro)")
+defines.extend([op for op in opcodeMacroizer(lambda op: (op["category"] == "exttable"), "extendedOp")])
 defines.append("\n\n")
 
 defines = "".join(defines)
@@ -202,7 +204,8 @@ inline Type linearizedToType(int i)
     FOR_EACH_WASM_UNARY_OP(macro) \\
     FOR_EACH_WASM_BINARY_OP(macro) \\
     FOR_EACH_WASM_MEMORY_LOAD_OP(macro) \\
-    FOR_EACH_WASM_MEMORY_STORE_OP(macro)
+    FOR_EACH_WASM_MEMORY_STORE_OP(macro) \\
+    macro(ExtTable, 0xFC, Oops, 0)
 
 #define CREATE_ENUM_VALUE(name, id, b3op, inc) name = id,
 
@@ -234,6 +237,10 @@ enum class StoreOpType : uint8_t {
     FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_ENUM_VALUE)
 };
 
+enum class ExtTableOpType : uint8_t {
+    FOR_EACH_WASM_EXT_TABLE_OP(CREATE_ENUM_VALUE)
+};
+
 #undef CREATE_ENUM_VALUE
 
 inline bool isControlOp(OpType op)
index 96e8a1d..0d280ec 100644 (file)
@@ -69,6 +69,9 @@
         "set_global":          { "category": "special",    "value":  36, "return": [],           "parameter": ["any"],                  "immediate": [{"name": "global_index",   "type": "varuint32"}],                                            "description": "write a global variable" },
         "table.get":           { "category": "special",    "value":  37, "return": ["anyref"],   "parameter": ["i32"],                  "immediate": [{"name": "table_index",    "type": "varuint32"}],                                            "description": "get a table value" },
         "table.set":           { "category": "special",    "value":  38, "return": [],           "parameter": ["i32", "anyref"],        "immediate": [{"name": "table_index",    "type": "varuint32"}],                                            "description": "set a table value" },
+        "table.size":          { "category": "exttable",   "value":  252, "return": ["i32"],     "parameter": [],                       "immediate": [{"name": "table_index",    "type": "varuint32"}],                                            "description": "get the size of a table", "extendedOp": 15 },
+        "table.grow":          { "category": "exttable",   "value":  252, "return": ["i32"],     "parameter": ["anyref", "i32"],        "immediate": [{"name": "table_index",    "type": "varuint32"}],                                            "description": "grow a table by the given delta and return the previous size, or -1 if enough space cannot be allocated", "extendedOp": 16 },
+        "table.fill":          { "category": "exttable",   "value":  252, "return": ["i32"],     "parameter": ["i32", "anyref", "i32"], "immediate": [{"name": "table_index",    "type": "varuint32"}],                                            "description": "fill entries [i,i+n) with the given value", "extendedOp": 17 },
         "call":                { "category": "call",       "value":  16, "return": ["call"],     "parameter": ["call"],                 "immediate": [{"name": "function_index", "type": "varuint32"}],                                            "description": "call a function by its index" },
         "call_indirect":       { "category": "call",       "value":  17, "return": ["call"],     "parameter": ["call"],                 "immediate": [{"name": "type_index",     "type": "varuint32"}, {"name": "table_index","type": "varuint32"}],"description": "call a function indirect with an expected signature" },
         "i32.load8_s":         { "category": "memory",     "value":  44, "return": ["i32"],      "parameter": ["addr"],                 "immediate": [{"name": "flags",          "type": "varuint32"}, {"name": "offset",   "type": "varuint32"}], "description": "load from memory" },