[wasm] Unify treatment of expressions in elem. segments

We unify the implementation of element segment expression entries with
other initializer expressions: we represent them with a {WireBytesRef}
and decode them with {InitExprInterface}. Except for reducing code
duplication, this also fixes a bug where {global.get} entries in element
segments could reference invalid globals.

Changes:
- Change {WasmElemSegment::Entry} to a union of a {WireBytesRef}
  initializer expression and a {uint32_t} function index.
- In module-decoder, change parsing of expression entries to use
  {consume_init_expr}. Add type checking to
  {consume_element_func_index}, to complement type checking happening in
  {consume_init_expr}.
- In module-instantiate.cc:
  - Move instantiation of indirect tables before loading of element
    segments. This way, when we call {UpdateDispatchTables} in
    {SetTableEntry}, the indirect table for the current table will also
    be updated.
  - Consolidate table entry instantiation into {SetTableEntry}, which
    handles lazily instantiated functions, or dispatches to
    {WasmTableObject::Set}.
  - Rename {InitializeIndirectFunctionTables} to
    {InitializeNonDefaultableTables}.
  - Change {InitializeNonDefaultableTables} and {LoadElemSegmentImpl}
    to use {EvaluateInitExpression}.
- Add a test to exclude mutable/non-imported globals from the element
  section.
- Update tests as needed.
- Update .js module emission in wasm-fuzzer-common.

Change-Id: I29c541bbca8531e8d0312ed95869c8e78a5a0c57
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3364082
Reviewed-by: Andreas Haas <ahaas@chromium.org>
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78476}
This commit is contained in:
Manos Koukoutos 2022-01-04 10:23:23 +00:00 committed by V8 LUCI CQ
parent 7bf42ad6a4
commit e9440c45fa
9 changed files with 146 additions and 225 deletions

View File

@ -998,20 +998,13 @@ class ModuleDecoderImpl : public Decoder {
consume_count("number of elements", max_table_init_entries());
for (uint32_t j = 0; j < num_elem; j++) {
WasmElemSegment::Entry init =
using Entry = WasmElemSegment::Entry;
Entry entry =
segment.element_type == WasmElemSegment::kExpressionElements
? consume_element_expr()
: WasmElemSegment::Entry(WasmElemSegment::Entry::kRefFuncEntry,
consume_element_func_index());
? Entry(consume_init_expr(module_.get(), segment.type))
: Entry(consume_element_func_index(segment.type));
if (failed()) return;
if (!IsSubtypeOf(TypeOf(init), segment.type, module_.get())) {
errorf(pc_,
"Invalid type in the init expression. The expected type is "
"'%s', but the actual type is '%s'.",
segment.type.name().c_str(), TypeOf(init).name().c_str());
return;
}
segment.entries.push_back(init);
segment.entries.push_back(entry);
}
module_->elem_segments.push_back(std::move(segment));
}
@ -1504,18 +1497,6 @@ class ModuleDecoderImpl : public Decoder {
AccountingAllocator allocator_;
Zone init_expr_zone_{&allocator_, "initializer expression zone"};
ValueType TypeOf(WasmElemSegment::Entry entry) {
switch (entry.kind) {
case WasmElemSegment::Entry::kGlobalGetEntry:
return module_->globals[entry.index].type;
case WasmElemSegment::Entry::kRefFuncEntry:
return ValueType::Ref(module_->functions[entry.index].sig_index,
kNonNullable);
case WasmElemSegment::Entry::kRefNullEntry:
return ValueType::Ref(entry.index, kNullable);
}
}
bool has_seen_unordered_section(SectionCode section_code) {
return seen_unordered_sections_ & (1 << section_code);
}
@ -2046,51 +2027,23 @@ class ModuleDecoderImpl : public Decoder {
}
}
uint32_t consume_element_func_index() {
uint32_t consume_element_func_index(ValueType expected) {
WasmFunction* func = nullptr;
const byte* initial_pc = pc();
uint32_t index =
consume_func_index(module_.get(), &func, "element function index");
if (failed()) return index;
func->declared = true;
DCHECK_NE(func, nullptr);
DCHECK_NOT_NULL(func);
DCHECK_EQ(index, func->func_index);
ValueType entry_type = ValueType::Ref(func->sig_index, kNonNullable);
if (V8_UNLIKELY(!IsSubtypeOf(entry_type, expected, module_.get()))) {
errorf(initial_pc,
"Invalid type in element entry: expected %s, got %s instead.",
expected.name().c_str(), entry_type.name().c_str());
return index;
}
// TODO(manoskouk): Implement this with consume_init_expr(). It will require
// changes in module-instantiate.cc, in {LoadElemSegmentImpl}.
WasmElemSegment::Entry consume_element_expr() {
uint8_t opcode = consume_u8("element opcode");
if (failed()) return {};
switch (opcode) {
case kExprRefNull: {
HeapTypeImmediate<kFullValidation> imm(WasmFeatures::All(), this,
this->pc(), module_.get());
consume_bytes(imm.length, "ref.null immediate");
expect_u8("end opcode", kExprEnd);
return {WasmElemSegment::Entry::kRefNullEntry,
static_cast<uint32_t>(imm.type.representation())};
}
case kExprRefFunc: {
uint32_t index = consume_element_func_index();
if (failed()) return {};
expect_u8("end opcode", kExprEnd);
return {WasmElemSegment::Entry::kRefFuncEntry, index};
}
case kExprGlobalGet: {
uint32_t index = this->consume_u32v("global index");
if (failed()) return {};
if (index >= module_->globals.size()) {
errorf("Out-of-bounds global index %d", index);
return {};
}
expect_u8("end opcode", kExprEnd);
return {WasmElemSegment::Entry::kGlobalGetEntry, index};
}
default:
error("invalid opcode in element");
return {};
}
func->declared = true;
return index;
}
};

View File

@ -455,7 +455,7 @@ class InstanceBuilder {
// and globals.
void ProcessExports(Handle<WasmInstanceObject> instance);
void InitializeIndirectFunctionTables(Handle<WasmInstanceObject> instance);
void InitializeNonDefaultableTables(Handle<WasmInstanceObject> instance);
void LoadTableSegments(Handle<WasmInstanceObject> instance);
@ -668,7 +668,7 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
for (int i = module_->num_imported_tables; i < table_count; i++) {
const WasmTable& table = module_->tables[i];
// Initialize tables with null for now. We will initialize non-defaultable
// tables later, in {InitializeIndirectFunctionTables}.
// tables later, in {InitializeNonDefaultableTables}.
Handle<WasmTableObject> table_obj = WasmTableObject::New(
isolate_, instance, table.type, table.initial_size,
table.has_maximum_size, table.maximum_size, nullptr,
@ -745,11 +745,31 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
InitGlobals(instance);
//--------------------------------------------------------------------------
// Initialize the indirect tables.
// Initialize the indirect function tables and dispatch tables. We do this
// before initializing non-defaultable tables and loading element segments, so
// that indirect function tables in this module are included in the updates
// when we do so.
//--------------------------------------------------------------------------
if (table_count > 0) {
InitializeIndirectFunctionTables(instance);
for (int table_index = 0;
table_index < static_cast<int>(module_->tables.size()); ++table_index) {
const WasmTable& table = module_->tables[table_index];
if (IsSubtypeOf(table.type, kWasmFuncRef, module_)) {
WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize(
instance, table_index, table.initial_size);
if (thrower_->error()) return {};
auto table_object = handle(
WasmTableObject::cast(instance->tables().get(table_index)), isolate_);
WasmTableObject::AddDispatchTable(isolate_, table_object, instance,
table_index);
}
}
//--------------------------------------------------------------------------
// Initialize non-defaultable tables.
//--------------------------------------------------------------------------
if (FLAG_experimental_wasm_typed_funcref) {
InitializeNonDefaultableTables(instance);
}
//--------------------------------------------------------------------------
@ -766,7 +786,7 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
if (thrower_->error()) return {};
//--------------------------------------------------------------------------
// Initialize the indirect function tables.
// Load element segments into tables.
//--------------------------------------------------------------------------
if (table_count > 0) {
LoadTableSegments(instance);
@ -1891,108 +1911,61 @@ void InstanceBuilder::ProcessExports(Handle<WasmInstanceObject> instance) {
}
}
void SetNullTableEntry(Isolate* isolate, Handle<WasmInstanceObject> instance,
Handle<WasmTableObject> table_object,
uint32_t table_index, uint32_t entry_index) {
const WasmModule* module = instance->module();
if (IsSubtypeOf(table_object->type(), kWasmFuncRef, module)) {
instance->GetIndirectFunctionTable(isolate, table_index)
->Clear(entry_index);
}
WasmTableObject::Set(isolate, table_object, entry_index,
isolate->factory()->null_value());
}
void SetFunctionTableEntry(Isolate* isolate,
Handle<WasmInstanceObject> instance,
Handle<WasmTableObject> table_object,
uint32_t table_index, uint32_t entry_index,
uint32_t func_index) {
namespace {
void SetTableEntry(Isolate* isolate, Handle<WasmInstanceObject> instance,
Handle<WasmTableObject> table_object, uint32_t table_index,
uint32_t entry_index, Handle<Object> entry) {
const WasmModule* module = instance->module();
if (IsSubtypeOf(table_object->type(), kWasmFuncRef, module) &&
entry->IsSmi()) {
// We might get a Smi entry for a function table, representing the
// function's index. In this case, we need to initialize the table with a
// placeholder if the function has not been initialized.
const uint32_t func_index = entry->ToSmi().value();
const WasmFunction* function = &module->functions[func_index];
// For externref tables, we have to generate the WasmExternalFunction eagerly.
// Later we cannot know if an entry is a placeholder or not.
if (table_object->type().is_reference_to(HeapType::kExtern)) {
Handle<WasmInternalFunction> wasm_internal_function =
WasmInstanceObject::GetOrCreateWasmInternalFunction(isolate, instance,
func_index);
WasmTableObject::Set(isolate, table_object, entry_index,
wasm_internal_function);
} else {
DCHECK(IsSubtypeOf(table_object->type(), kWasmFuncRef, module));
// Update the local dispatch table first if necessary.
uint32_t sig_id = module->canonicalized_type_ids[function->sig_index];
FunctionTargetAndRef entry(instance, func_index);
instance->GetIndirectFunctionTable(isolate, table_index)
->Set(entry_index, sig_id, entry.call_target(), *entry.ref());
// Update the table object's other dispatch tables.
MaybeHandle<WasmInternalFunction> wasm_internal_function =
WasmInstanceObject::GetWasmInternalFunction(isolate, instance,
func_index);
if (wasm_internal_function.is_null()) {
// No JSFunction entry yet exists for this function. Create a
// {Tuple2} holding the information to lazily allocate one.
// No JSFunction entry yet exists for this function. Create a {Tuple2}
// holding the information to lazily allocate one.
WasmTableObject::SetFunctionTablePlaceholder(
isolate, table_object, entry_index, instance, func_index);
} else {
table_object->entries().set(entry_index,
*wasm_internal_function.ToHandleChecked());
}
// UpdateDispatchTables() updates all other dispatch tables, since
// we have not yet added the dispatch table we are currently building.
WasmTableObject::UpdateDispatchTables(isolate, table_object, entry_index,
function->sig, instance, func_index);
} else {
// In all other cases, simply call {WasmTableObject::Set}.
WasmTableObject::Set(isolate, table_object, entry_index, entry);
}
}
} // namespace
void InstanceBuilder::InitializeIndirectFunctionTables(
void InstanceBuilder::InitializeNonDefaultableTables(
Handle<WasmInstanceObject> instance) {
for (int table_index = 0;
table_index < static_cast<int>(module_->tables.size()); ++table_index) {
const WasmTable& table = module_->tables[table_index];
if (IsSubtypeOf(table.type, kWasmFuncRef, module_)) {
WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize(
instance, table_index, table.initial_size);
}
if (!table.type.is_defaultable()) {
auto table_object = handle(
WasmTableObject::cast(instance->tables().get(table_index)), isolate_);
Handle<Object> value =
EvaluateInitExpression(&init_expr_zone_, table.initial_value,
table.type, isolate_, instance)
table.type, isolate_, instance,
IsSubtypeOf(table_object->type(), kWasmFuncRef,
instance->module())
? InitExprInterface::kLazyFunctions
: InitExprInterface::kStrictFunctions)
.to_ref();
if (value.is_null()) {
for (uint32_t entry_index = 0; entry_index < table.initial_size;
entry_index++) {
SetNullTableEntry(isolate_, instance, table_object, table_index,
entry_index);
}
} else if (value->IsWasmInternalFunction()) {
Handle<Object> external = handle(
Handle<WasmInternalFunction>::cast(value)->external(), isolate_);
// TODO(manoskouk): Support WasmJSFunction/WasmCapiFunction.
if (!WasmExportedFunction::IsWasmExportedFunction(*external)) {
thrower_->TypeError(
"Initializing a table with a Webassembly.Function object is not "
"supported yet");
}
uint32_t function_index =
Handle<WasmExportedFunction>::cast(external)->function_index();
for (uint32_t entry_index = 0; entry_index < table.initial_size;
entry_index++) {
SetFunctionTableEntry(isolate_, instance, table_object, table_index,
entry_index, function_index);
}
} else {
for (uint32_t entry_index = 0; entry_index < table.initial_size;
entry_index++) {
WasmTableObject::Set(isolate_, table_object, entry_index, value);
}
SetTableEntry(isolate_, instance, table_object, table_index,
entry_index, value);
}
}
}
@ -2018,40 +1991,22 @@ bool LoadElemSegmentImpl(Zone* zone, Isolate* isolate,
return false;
}
bool is_function_table =
IsSubtypeOf(table_object->type(), kWasmFuncRef, instance->module());
for (size_t i = 0; i < count; ++i) {
WasmElemSegment::Entry init = elem_segment.entries[src + i];
WasmElemSegment::Entry entry = elem_segment.entries[src + i];
int entry_index = static_cast<int>(dst + i);
switch (init.kind) {
case WasmElemSegment::Entry::kRefNullEntry:
SetNullTableEntry(isolate, instance, table_object, table_index,
entry_index);
break;
case WasmElemSegment::Entry::kRefFuncEntry:
SetFunctionTableEntry(isolate, instance, table_object, table_index,
entry_index, init.index);
break;
case WasmElemSegment::Entry::kGlobalGetEntry: {
Handle<Object> value =
WasmInstanceObject::GetGlobalValue(
instance, instance->module()->globals[init.index])
.to_ref();
if (value.is_null()) {
SetNullTableEntry(isolate, instance, table_object, table_index,
entry_index);
} else if (WasmExportedFunction::IsWasmExportedFunction(*value)) {
uint32_t function_index =
Handle<WasmExportedFunction>::cast(value)->function_index();
SetFunctionTableEntry(isolate, instance, table_object, table_index,
entry_index, function_index);
} else if (WasmJSFunction::IsWasmJSFunction(*value)) {
// TODO(manoskouk): Support WasmJSFunction.
return false;
} else {
WasmTableObject::Set(isolate, table_object, entry_index, value);
}
break;
}
}
elem_segment.element_type == WasmElemSegment::kExpressionElements
? EvaluateInitExpression(
zone, entry.ref, elem_segment.type, isolate, instance,
is_function_table ? InitExprInterface::kLazyFunctions
: InitExprInterface::kStrictFunctions)
.to_ref()
: handle(Smi::FromInt(entry.index), isolate);
SetTableEntry(isolate, instance, table_object, table_index, entry_index,
value);
}
return true;
}
@ -2089,18 +2044,6 @@ void InstanceBuilder::LoadTableSegments(Handle<WasmInstanceObject> instance) {
break;
}
}
int table_count = static_cast<int>(module_->tables.size());
for (int index = 0; index < table_count; ++index) {
if (IsSubtypeOf(module_->tables[index].type, kWasmFuncRef, module_)) {
auto table_object = handle(
WasmTableObject::cast(instance->tables().get(index)), isolate_);
// Add the new dispatch table at the end to avoid redundant lookups.
WasmTableObject::AddDispatchTable(isolate_, table_object, instance,
index);
}
}
}
void InstanceBuilder::InitializeTags(Handle<WasmInstanceObject> instance) {

View File

@ -11,7 +11,6 @@
#include <stdint.h>
#include "include/v8-metrics.h"
#include "include/v8config.h"
namespace v8 {
@ -20,7 +19,6 @@ namespace internal {
class Isolate;
class JSArrayBuffer;
class JSReceiver;
class WasmInitExpr;
class WasmModuleObject;
class WasmInstanceObject;
class Zone;
@ -43,9 +41,6 @@ bool LoadElemSegment(Isolate* isolate, Handle<WasmInstanceObject> instance,
uint32_t table_index, uint32_t segment_index, uint32_t dst,
uint32_t src, uint32_t count) V8_WARN_UNUSED_RESULT;
uint32_t EvalUint32InitExpr(Handle<WasmInstanceObject> instance,
const WasmInitExpr& expr);
} // namespace wasm
} // namespace internal
} // namespace v8

View File

@ -121,13 +121,17 @@ struct WasmElemSegment {
kStatusPassive, // copied explicitly after instantiation.
kStatusDeclarative // purely declarative and never copied.
};
struct Entry {
enum Kind { kGlobalGetEntry, kRefFuncEntry, kRefNullEntry } kind;
uint32_t index;
Entry(Kind kind, uint32_t index) : kind(kind), index(index) {}
Entry() : kind(kRefNullEntry), index(0) {}
};
enum ElementType { kFunctionIndexElements, kExpressionElements };
// An element segment entry. If {element_type == kExpressionElements}, it
// refers to an initializer expression (via a {WireBytesRef}); otherwise, it
// represents a function index.
union Entry {
WireBytesRef ref;
uint32_t index;
explicit Entry(uint32_t index) : index(index) {}
explicit Entry(WireBytesRef ref) : ref(ref) {}
};
// Construct an active segment.
WasmElemSegment(ValueType type, uint32_t table_index, WireBytesRef offset,

View File

@ -331,8 +331,7 @@ uint32_t TestingModuleBuilder::AddPassiveElementSegment(
WasmElemSegment::kFunctionIndexElements);
auto& elem_segment = test_module_->elem_segments.back();
for (uint32_t entry : entries) {
elem_segment.entries.push_back(
WasmElemSegment::Entry(WasmElemSegment::Entry::kRefFuncEntry, entry));
elem_segment.entries.emplace_back(entry);
}
// The vector pointers may have moved, so update the instance object.

View File

@ -298,22 +298,6 @@ std::ostream& operator<<(std::ostream& os, const PrintName& name) {
return os.write(name.name.begin(), name.name.size());
}
std::ostream& operator<<(std::ostream& os, WasmElemSegment::Entry entry) {
os << "WasmInitExpr.";
switch (entry.kind) {
case WasmElemSegment::Entry::kGlobalGetEntry:
os << "GlobalGet(" << entry.index;
break;
case WasmElemSegment::Entry::kRefFuncEntry:
os << "RefFunc(" << entry.index;
break;
case WasmElemSegment::Entry::kRefNullEntry:
os << "RefNull(" << HeapType(entry.index).name().c_str();
break;
}
return os << ")";
}
// An interface for WasmFullDecoder used to decode initializer expressions. As
// opposed to the one in src/wasm/, this emits {WasmInitExpr} as opposed to a
// {WasmValue}.
@ -664,10 +648,19 @@ void GenerateTestCase(Isolate* isolate, ModuleWireBytes wire_bytes,
}
os << "[";
for (uint32_t i = 0; i < elem_segment.entries.size(); i++) {
os << elem_segment.entries[i];
if (elem_segment.element_type == WasmElemSegment::kExpressionElements) {
DecodeAndAppendInitExpr(os, &zone, module, wire_bytes,
elem_segment.entries[i].ref, elem_segment.type);
} else {
os << elem_segment.entries[i].index;
}
if (i < elem_segment.entries.size() - 1) os << ", ";
}
os << "], " << ValueTypeToConstantName(elem_segment.type) << ");\n";
os << "], "
<< (elem_segment.element_type == WasmElemSegment::kExpressionElements
? ValueTypeToConstantName(elem_segment.type)
: "undefined")
<< ");\n";
}
for (const WasmTag& tag : module->tags) {

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --experimental-wasm-typed-funcref
// Flags: --experimental-wasm-gc
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
@ -97,3 +97,22 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
assertEquals(instance.exports.table.get(2)(10), 20);
assertEquals(instance.exports.table.get(3)(10), 11);
})();
// Test that mutable globals cannot be used in element segments, even under
// --experimental-wasm-gc.
(function TestMutableGlobalInElementSegment() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let global = builder.addImportedGlobal("m", "g", kWasmFuncRef, true);
let table = builder.addTable(kWasmFuncRef, 10, 10);
builder.addActiveElementSegment(
table.index, WasmInitExpr.I32Const(0),
[WasmInitExpr.GlobalGet(global.index)], kWasmFuncRef);
builder.addExportOfKind("table", kExternalTable, table.index);
assertThrows(
() => builder.instantiate({m : {g :
new WebAssembly.Global({value: "anyfunc", mutable: true}, null)}}),
WebAssembly.CompileError,
/mutable globals cannot be used in initializer expressions/);
})();

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --expose-wasm --expose-gc
// Flags: --expose-gc
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
@ -900,3 +900,19 @@ function js_div(a, b) { return (a / b) | 0; }
assertEquals(300, main(2));
assertEquals(400, main(3));
})();
(function TestNonImportedGlobalInElementSegment() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let global = builder.addGlobal(kWasmFuncRef, false,
WasmInitExpr.RefNull(kWasmFuncRef));
let table = builder.addTable(kWasmFuncRef, 10, 10);
builder.addActiveElementSegment(
table.index, WasmInitExpr.I32Const(0),
[WasmInitExpr.GlobalGet(global.index)], kWasmFuncRef);
builder.addExportOfKind("table", kExternalTable, table.index);
assertThrows(
() => builder.instantiate(), WebAssembly.CompileError,
/non-imported globals cannot be used in initializer expressions/);
})();

View File

@ -2210,8 +2210,7 @@ TEST_F(WasmModuleVerifyTest, ElementSectionInitFuncRefTableWithExternRefNull) {
EXPECT_FAILURE_WITH_MSG(
data,
"Invalid type in the init expression. The expected "
"type is 'funcref', but the actual type is 'externref'.");
"type error in init. expression[0] (expected funcref, got externref)");
}
TEST_F(WasmModuleVerifyTest, ElementSectionDontInitExternRefImportedTable) {
@ -2265,7 +2264,7 @@ TEST_F(WasmModuleVerifyTest, ElementSectionGlobalGetOutOfBounds) {
kFuncRefCode, // type
ENTRY_COUNT(1), // element count
kExprGlobalGet, 0x00, kExprEnd)}; // init. expression
EXPECT_FAILURE_WITH_MSG(data, "Out-of-bounds global index 0");
EXPECT_FAILURE_WITH_MSG(data, "Invalid global index: 0");
}
TEST_F(WasmModuleVerifyTest, IndirectFunctionNoFunctions) {