浏览代码

LibWasm: Improve table support

Implements `table.get`, `table.set`, `elem.drop`, `table.size`,
and `table.grow`. Also fixes a few issues when generating ref-related
spectests. Also changes the `TableInstance` type to use
`Vector<Reference>` instead of `Vector<Optional<Reference>>`, because
the ability to be null is already encoded in the `Reference` type.
Diego 1 年之前
父节点
当前提交
d906255cbb

+ 7 - 2
Meta/generate-libwasm-spec-test.py

@@ -62,6 +62,9 @@ def parse_typed_value(ast):
         'i64.const': 'i64',
         'f32.const': 'float',
         'f64.const': 'double',
+        'ref.null': 'null',
+        'ref.extern': 'i32',
+        'ref.func': 'i32',
         'v128.const': 'bigint',
     }
 
@@ -331,9 +334,9 @@ def generate(ast):
                 "function": {
                     "module": module,
                     "name": name,
-                    "args": list(parse_typed_value(x) for x in entry[1][arg + 2:])
+                    "args": [parse_typed_value(entry[2])] if len(entry) == 3 else []
                 },
-                "result": parse_typed_value(entry[2]) if len(entry) == 3 + arg else None
+                "result": None
             })
         elif len(entry) > 1 and entry[0][0] == 'register':
             if len(entry) == 3:
@@ -370,6 +373,8 @@ def genarg(spec):
         x = spec['value']
         if spec['type'] == 'bigint':
             return f"0x{x}n"
+        if spec['type'] == 'null':
+            return 'null'
 
         if spec['type'] in ('i32', 'i64'):
             if x.startswith('0x'):

+ 9 - 1
Tests/LibWasm/test-wasm.cpp

@@ -242,10 +242,18 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyModule::wasm_invoke)
             break;
         }
         case Wasm::ValueType::Kind::FunctionReference:
+            if (argument.is_null()) {
+                arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Null { Wasm::ValueType(Wasm::ValueType::Kind::FunctionReference) } }));
+                break;
+            }
             arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Func { static_cast<u64>(double_value) } }));
             break;
         case Wasm::ValueType::Kind::ExternReference:
-            arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Func { static_cast<u64>(double_value) } }));
+            if (argument.is_null()) {
+                arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Null { Wasm::ValueType(Wasm::ValueType::Kind::ExternReference) } }));
+                break;
+            }
+            arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Extern { static_cast<u64>(double_value) } }));
             break;
         case Wasm::ValueType::Kind::NullFunctionReference:
             arguments.append(Wasm::Value(Wasm::Reference { Wasm::Reference::Null { Wasm::ValueType(Wasm::ValueType::Kind::FunctionReference) } }));

+ 2 - 10
Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.cpp

@@ -34,7 +34,7 @@ Optional<FunctionAddress> Store::allocate(HostFunction&& function)
 Optional<TableAddress> Store::allocate(TableType const& type)
 {
     TableAddress address { m_tables.size() };
-    Vector<Optional<Reference>> elements;
+    Vector<Reference> elements;
     elements.resize(type.limits().min());
     m_tables.empend(TableInstance { type, move(elements) });
     return address;
@@ -252,10 +252,6 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
             auto active_ptr = segment.mode.get_pointer<ElementSection::Active>();
             if (!active_ptr)
                 continue;
-            if (active_ptr->index.value() != 0) {
-                instantiation_result = InstantiationError { "Non-zero table referenced by active element segment" };
-                return IterationDecision::Break;
-            }
             Configuration config { m_store };
             if (m_should_limit_instruction_count)
                 config.enable_instruction_count_limit();
@@ -275,11 +271,7 @@ InstantiationResult AbstractMachine::instantiate(Module const& module, Vector<Ex
                 instantiation_result = InstantiationError { "Element section initialisation returned invalid table initial offset" };
                 return IterationDecision::Break;
             }
-            if (main_module_instance.tables().size() < 1) {
-                instantiation_result = InstantiationError { "Element section initialisation references nonexistent table" };
-                return IterationDecision::Break;
-            }
-            auto table_instance = m_store.get(main_module_instance.tables()[0]);
+            auto table_instance = m_store.get(main_module_instance.tables()[active_ptr->index.value()]);
             if (current_index >= main_module_instance.elements().size()) {
                 instantiation_result = InstantiationError { "Invalid element referenced by active element segment" };
                 return IterationDecision::Break;

+ 11 - 4
Userland/Libraries/LibWasm/AbstractMachine/AbstractMachine.h

@@ -61,6 +61,10 @@ public:
         : m_ref(move(ref))
     {
     }
+    explicit Reference()
+        : m_ref(Reference::Null { ValueType(ValueType::Kind::FunctionReference) })
+    {
+    }
 
     auto& ref() const { return m_ref; }
 
@@ -370,7 +374,7 @@ using FunctionInstance = Variant<WasmFunction, HostFunction>;
 
 class TableInstance {
 public:
-    explicit TableInstance(TableType const& type, Vector<Optional<Reference>> elements)
+    explicit TableInstance(TableType const& type, Vector<Reference> elements)
         : m_elements(move(elements))
         , m_type(type)
     {
@@ -380,15 +384,18 @@ public:
     auto& elements() { return m_elements; }
     auto& type() const { return m_type; }
 
-    bool grow(size_t size_to_grow, Reference const& fill_value)
+    bool grow(u32 size_to_grow, Reference const& fill_value)
     {
         if (size_to_grow == 0)
             return true;
-        auto new_size = m_elements.size() + size_to_grow;
+        size_t new_size = m_elements.size() + size_to_grow;
         if (auto max = m_type.limits().max(); max.has_value()) {
             if (max.value() < new_size)
                 return false;
         }
+        if (new_size >= NumericLimits<u32>::max()) {
+            return false;
+        }
         auto previous_size = m_elements.size();
         if (m_elements.try_resize(new_size).is_error())
             return false;
@@ -398,7 +405,7 @@ public:
     }
 
 private:
-    Vector<Optional<Reference>> m_elements;
+    Vector<Reference> m_elements;
     TableType m_type;
 };
 

+ 51 - 10
Userland/Libraries/LibWasm/AbstractMachine/BytecodeInterpreter.cpp

@@ -665,9 +665,8 @@ void BytecodeInterpreter::interpret(Configuration& configuration, InstructionPoi
         TRAP_IF_NOT(index.value() >= 0);
         TRAP_IF_NOT(static_cast<size_t>(index.value()) < table_instance->elements().size());
         auto element = table_instance->elements()[index.value()];
-        TRAP_IF_NOT(element.has_value());
-        TRAP_IF_NOT(element->ref().has<Reference::Func>());
-        auto address = element->ref().get<Reference::Func>().address;
+        TRAP_IF_NOT(element.ref().has<Reference::Func>());
+        auto address = element.ref().get<Reference::Func>().address;
         dbgln_if(WASM_TRACE_DEBUG, "call_indirect({} -> {})", index.value(), address.value());
         call_address(configuration, address);
         return;
@@ -859,9 +858,55 @@ void BytecodeInterpreter::interpret(Configuration& configuration, InstructionPoi
         *configuration.store().get(data_address) = DataInstance({});
         return;
     }
-    case Instructions::table_get.value():
-    case Instructions::table_set.value():
-        goto unimplemented;
+    case Instructions::elem_drop.value(): {
+        auto elem_index = instruction.arguments().get<ElementIndex>();
+        auto address = configuration.frame().module().elements()[elem_index.value()];
+        auto elem = configuration.store().get(address);
+        *configuration.store().get(address) = ElementInstance(elem->type(), {});
+        return;
+    }
+    case Instructions::table_set.value(): {
+        auto ref = *configuration.stack().pop().get<Value>().to<Reference>();
+        auto index = (size_t)(*configuration.stack().pop().get<Value>().to<i32>());
+        auto table_index = instruction.arguments().get<TableIndex>();
+        auto address = configuration.frame().module().tables()[table_index.value()];
+        auto table = configuration.store().get(address);
+        TRAP_IF_NOT(index < table->elements().size());
+        table->elements()[index] = ref;
+        return;
+    }
+    case Instructions::table_get.value(): {
+        auto index = (size_t)(*configuration.stack().pop().get<Value>().to<i32>());
+        auto table_index = instruction.arguments().get<TableIndex>();
+        auto address = configuration.frame().module().tables()[table_index.value()];
+        auto table = configuration.store().get(address);
+        TRAP_IF_NOT(index < table->elements().size());
+        auto ref = table->elements()[index];
+        configuration.stack().push(Value(ref));
+        return;
+    }
+    case Instructions::table_grow.value(): {
+        auto size = *configuration.stack().pop().get<Value>().to<u32>();
+        auto fill_value = *configuration.stack().pop().get<Value>().to<Reference>();
+        auto table_index = instruction.arguments().get<TableIndex>();
+        auto address = configuration.frame().module().tables()[table_index.value()];
+        auto table = configuration.store().get(address);
+        auto previous_size = table->elements().size();
+        auto did_grow = table->grow(size, fill_value);
+        if (!did_grow) {
+            configuration.stack().push(Value((i32)-1));
+        } else {
+            configuration.stack().push(Value((i32)previous_size));
+        }
+        return;
+    }
+    case Instructions::table_size.value(): {
+        auto table_index = instruction.arguments().get<TableIndex>();
+        auto address = configuration.frame().module().tables()[table_index.value()];
+        auto table = configuration.store().get(address);
+        configuration.stack().push(Value((i32)table->elements().size()));
+        return;
+    }
     case Instructions::ref_null.value(): {
         auto type = instruction.arguments().get<ValueType>();
         configuration.stack().push(Value(Reference(Reference::Null { type })));
@@ -1503,13 +1548,9 @@ void BytecodeInterpreter::interpret(Configuration& configuration, InstructionPoi
     case Instructions::f64x2_convert_low_i32x4_s.value():
     case Instructions::f64x2_convert_low_i32x4_u.value():
     case Instructions::table_init.value():
-    case Instructions::elem_drop.value():
     case Instructions::table_copy.value():
-    case Instructions::table_grow.value():
-    case Instructions::table_size.value():
     case Instructions::table_fill.value():
     default:
-    unimplemented:;
         dbgln_if(WASM_TRACE_DEBUG, "Instruction '{}' not implemented", instruction_name(instruction.opcode()));
         m_trap = Trap { ByteString::formatted("Unimplemented instruction {}", instruction_name(instruction.opcode())) };
         return;

+ 2 - 2
Userland/Libraries/LibWeb/WebAssembly/Table.cpp

@@ -106,10 +106,10 @@ WebIDL::ExceptionOr<JS::Value> Table::get(u32 index) const
         return vm.throw_completion<JS::RangeError>("Table element index out of range"sv);
 
     auto& ref = table->elements()[index];
-    if (!ref.has_value())
+    if (!ref.ref().has<Wasm::Reference::Null>())
         return JS::js_undefined();
 
-    Wasm::Value wasm_value { ref.value() };
+    Wasm::Value wasm_value { ref };
     return Detail::to_js_value(vm, wasm_value);
 }