[wasm-gc] Implement non-nullable function tables

This adds the possibility to define non-nullable function tables of heap
types kFunc and user-defined functions. When such table is defined, it
is obligatory to provide an initializer expression after its limits.
Currently, this can only be a function reference.

Changes:
- Change WasmTableObject::raw_type to encode the whole entry type.
- Restructure call_indirect to load the signature only if needed, and
  do null checks only if needed.
- Add the requirement to provide an initializer expression for
  non-nullable tables in module-decoder.
- Rename "global initializer" -> "initializer expression" everywhere.
- Add table initialization in module-instantiate.
- Edit both the C++ and JS WasmModuleBuilder.
- Add and slightly improve tests.
- Format wasm-module-builder.js.

Bug: v8:9495
Change-Id: I7453ee7d567afd5b5fe48a4f1653513787cfe99a
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2732673
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#73215}
This commit is contained in:
Manos Koukoutos 2021-03-05 10:23:27 +00:00 committed by Commit Bot
parent 476b527bb7
commit e3acd9f8fe
15 changed files with 250 additions and 67 deletions

View File

@ -41,7 +41,10 @@ extern macro Allocate(intptr, constexpr AllocationFlag): HeapObject;
}
namespace wasm {
const kFuncTableType: constexpr int31 generates 'wasm::HeapType::kFunc';
const kExternTableType: constexpr int31
generates 'wasm::kWasmExternRef.raw_bit_field()';
const kExternNonNullTableType: constexpr int31
generates 'wasm::kWasmExternNonNullableRef.raw_bit_field()';
extern macro WasmBuiltinsAssembler::LoadInstanceFromFrame(): WasmInstanceObject;
@ -177,8 +180,12 @@ builtin WasmTableSet(tableIndex: intptr, index: int32, value: Object): Object {
// Fall back to the runtime to set funcrefs, since we have to update
// function dispatch tables.
// TODO(7748): Update this if further table types are supported.
const tableType: Smi = table.raw_type;
if (tableType == SmiConstant(kFuncTableType)) goto CallRuntime;
if (tableType != SmiConstant(kExternTableType) &&
tableType != SmiConstant(kExternNonNullTableType)) {
goto CallRuntime;
}
const entriesCount: intptr = Convert<intptr, Smi>(table.current_length);
if (entryIndex >= entriesCount) goto IndexOutOfRange;

View File

@ -3007,28 +3007,35 @@ Node* WasmGraphBuilder::BuildIndirectCall(uint32_t table_index,
key = gasm_->Word32And(key, mask);
}
Node* int32_scaled_key =
Uint32ToUintptr(gasm_->Word32Shl(key, Int32Constant(2)));
const wasm::ValueType table_type = env_->module->tables[table_index].type;
// Check that the table entry is not null and that the type of the function is
// a subtype of the function type declared at the call site. In the absence of
// function subtyping, the latter can only happen if the table type is (ref
// null? func). Also, subtyping reduces to normalized signature equality
// checking.
// TODO(7748): Expand this with function subtyping once we have that.
const bool needs_signature_check =
table_type.is_reference_to(wasm::HeapType::kFunc) ||
table_type.is_nullable();
if (needs_signature_check) {
Node* int32_scaled_key =
Uint32ToUintptr(gasm_->Word32Shl(key, Int32Constant(2)));
Node* loaded_sig =
gasm_->Load(MachineType::Int32(), ift_sig_ids, int32_scaled_key);
// Check that the dynamic type of the function is a subtype of its static
// (table) type. Currently, the only subtyping between function types is
// $t <: funcref for all $t: function_type.
// TODO(7748): Expand this with function subtyping.
const bool needs_typechecking =
env_->module->tables[table_index].type == wasm::kWasmFuncRef;
if (needs_typechecking) {
int32_t expected_sig_id = env_->module->canonicalized_type_ids[sig_index];
Node* sig_match =
gasm_->Word32Equal(loaded_sig, Int32Constant(expected_sig_id));
TrapIfFalse(wasm::kTrapFuncSigMismatch, sig_match, position);
} else {
// We still have to check that the entry is initialized.
// TODO(9495): Skip this check for non-nullable tables when they are
// allowed.
Node* function_is_null = gasm_->Word32Equal(loaded_sig, Int32Constant(-1));
TrapIfTrue(wasm::kTrapNullDereference, function_is_null, position);
Node* loaded_sig =
gasm_->Load(MachineType::Int32(), ift_sig_ids, int32_scaled_key);
if (table_type.is_reference_to(wasm::HeapType::kFunc)) {
int32_t expected_sig_id = env_->module->canonicalized_type_ids[sig_index];
Node* sig_match =
gasm_->Word32Equal(loaded_sig, Int32Constant(expected_sig_id));
TrapIfFalse(wasm::kTrapFuncSigMismatch, sig_match, position);
} else {
// If the table entries are nullable, we still have to check that the
// entry is initialized.
Node* function_is_null =
gasm_->Word32Equal(loaded_sig, Int32Constant(-1));
TrapIfTrue(wasm::kTrapNullDereference, function_is_null, position);
}
}
Node* key_intptr = Uint32ToUintptr(key);

View File

@ -5190,6 +5190,7 @@ class LiftoffCompiler {
__ Load(LiftoffRegister(scratch), table, index, 0, LoadType::kI32Load,
pinned);
// TODO(9495): Do not always compare signatures, same as wasm-compiler.cc.
// Compare against expected signature.
__ LoadConstant(LiftoffRegister(tmp_const), WasmValue(canonical_sig_num));

View File

@ -728,6 +728,9 @@ class ModuleDecoderImpl : public Decoder {
"table elements", "elements", std::numeric_limits<uint32_t>::max(),
&table->initial_size, &table->has_maximum_size,
std::numeric_limits<uint32_t>::max(), &table->maximum_size, flags);
if (!table_type.is_defaultable()) {
table->initial_value = consume_init_expr(module_.get(), table_type, 0);
}
}
}
@ -1713,7 +1716,7 @@ class ModuleDecoderImpl : public Decoder {
if (V8_UNLIKELY(!enabled_features_.has_reftypes() &&
!enabled_features_.has_eh())) {
errorf(pc(),
"invalid opcode 0x%x in global initializer, enable with "
"invalid opcode 0x%x in initializer expression, enable with "
"--experimental-wasm-reftypes or --experimental-wasm-eh",
kExprRefNull);
return {};
@ -1729,7 +1732,7 @@ class ModuleDecoderImpl : public Decoder {
case kExprRefFunc: {
if (V8_UNLIKELY(!enabled_features_.has_reftypes())) {
errorf(pc(),
"invalid opcode 0x%x in global initializer, enable with "
"invalid opcode 0x%x in initializer expression, enable with "
"--experimental-wasm-reftypes",
kExprRefFunc);
return {};
@ -1752,7 +1755,7 @@ class ModuleDecoderImpl : public Decoder {
// the type check or stack height check at the end.
opcode = read_prefixed_opcode<validate>(pc(), &len);
if (V8_UNLIKELY(opcode != kExprS128Const)) {
errorf(pc(), "invalid SIMD opcode 0x%x in global initializer",
errorf(pc(), "invalid SIMD opcode 0x%x in initializer expression",
opcode);
return {};
}
@ -1804,7 +1807,8 @@ class ModuleDecoderImpl : public Decoder {
break;
}
default: {
errorf(pc(), "invalid opcode 0x%x in global initializer", opcode);
errorf(pc(), "invalid opcode 0x%x in initializer expression",
opcode);
return {};
}
}
@ -1813,7 +1817,7 @@ class ModuleDecoderImpl : public Decoder {
case kExprEnd:
break;
default: {
errorf(pc(), "invalid opcode 0x%x in global initializer", opcode);
errorf(pc(), "invalid opcode 0x%x in initializer expression", opcode);
return {};
}
}
@ -1821,16 +1825,16 @@ class ModuleDecoderImpl : public Decoder {
}
if (V8_UNLIKELY(pc() > end())) {
error(end(), "Global initializer extending beyond code end");
error(end(), "Initializer expression extending beyond code end");
return {};
}
if (V8_UNLIKELY(opcode != kExprEnd)) {
error(pc(), "Global initializer is missing 'end'");
error(pc(), "Initializer expression is missing 'end'");
return {};
}
if (V8_UNLIKELY(stack.size() != 1)) {
errorf(pc(),
"Found 'end' in global initalizer, but %s expressions were "
"Found 'end' in initializer expression, but %s expressions were "
"found on the stack",
stack.size() > 1 ? "more than one" : "no");
return {};

View File

@ -1841,12 +1841,50 @@ void InstanceBuilder::ProcessExports(Handle<WasmInstanceObject> instance) {
void InstanceBuilder::InitializeIndirectFunctionTables(
Handle<WasmInstanceObject> instance) {
for (int i = 0; i < static_cast<int>(module_->tables.size()); ++i) {
const WasmTable& table = module_->tables[i];
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, i, table.initial_size);
instance, table_index, table.initial_size);
}
if (!table.type.is_defaultable()) {
// Function constant is currently the only viable initializer.
DCHECK(table.initial_value.kind() == WasmInitExpr::kRefFuncConst);
uint32_t func_index = table.initial_value.immediate().index;
uint32_t sig_id =
module_->canonicalized_type_ids[module_->functions[func_index]
.sig_index];
MaybeHandle<WasmExternalFunction> wasm_external_function =
WasmInstanceObject::GetWasmExternalFunction(isolate_, instance,
func_index);
auto table_object = handle(
WasmTableObject::cast(instance->tables().get(table_index)), isolate_);
for (uint32_t entry_index = 0; entry_index < table.initial_size;
entry_index++) {
// Update the local dispatch table first.
IndirectFunctionTableEntry(instance, table_index, entry_index)
.Set(sig_id, instance, func_index);
// Update the table object's other dispatch tables.
if (wasm_external_function.is_null()) {
// 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_external_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,
module_->functions[func_index].sig, instance, func_index);
}
}
}
}

View File

@ -530,6 +530,8 @@ class ValueType {
static_assert(sizeof(ValueType) <= kUInt32Size,
"ValueType is small and can be passed by value");
static_assert(ValueType::kLastUsedBit < 8 * sizeof(ValueType) - kSmiTagSize,
"ValueType has space to be encoded in a Smi");
inline size_t hash_value(ValueType type) {
return static_cast<size_t>(type.kind());
@ -560,6 +562,10 @@ constexpr ValueType kWasmDataRef =
ValueType::Ref(HeapType::kData, kNonNullable);
constexpr ValueType kWasmAnyRef = ValueType::Ref(HeapType::kAny, kNullable);
// This is used in wasm.tq.
constexpr ValueType kWasmExternNonNullableRef =
ValueType::Ref(HeapType::kExtern, kNonNullable);
#define FOREACH_WASMVALUE_CTYPES(V) \
V(kI32, int32_t) \
V(kI64, int64_t) \

View File

@ -315,7 +315,7 @@ uint32_t WasmModuleBuilder::AllocateIndirectFunctions(uint32_t count) {
if (tables_.empty()) {
// This cannot use {AddTable} because that would flip the
// {allocating_indirect_functions_allowed_} flag.
tables_.push_back({kWasmFuncRef, new_size, max, true});
tables_.push_back({kWasmFuncRef, new_size, max, true, {}});
} else {
// There can only be the indirect function table so far, otherwise the
// {allocating_indirect_functions_allowed_} flag would have been false.
@ -347,7 +347,7 @@ uint32_t WasmModuleBuilder::AddTable(ValueType type, uint32_t min_size) {
#if DEBUG
allocating_indirect_functions_allowed_ = false;
#endif
tables_.push_back({type, min_size, 0, false});
tables_.push_back({type, min_size, 0, false, {}});
return static_cast<uint32_t>(tables_.size() - 1);
}
@ -356,7 +356,16 @@ uint32_t WasmModuleBuilder::AddTable(ValueType type, uint32_t min_size,
#if DEBUG
allocating_indirect_functions_allowed_ = false;
#endif
tables_.push_back({type, min_size, max_size, true});
tables_.push_back({type, min_size, max_size, true, {}});
return static_cast<uint32_t>(tables_.size() - 1);
}
uint32_t WasmModuleBuilder::AddTable(ValueType type, uint32_t min_size,
uint32_t max_size, WasmInitExpr init) {
#if DEBUG
allocating_indirect_functions_allowed_ = false;
#endif
tables_.push_back({type, min_size, max_size, true, std::move(init)});
return static_cast<uint32_t>(tables_.size() - 1);
}
@ -432,8 +441,8 @@ void WriteValueType(ZoneBuffer* buffer, const ValueType& type) {
}
}
void WriteGlobalInitializer(ZoneBuffer* buffer, const WasmInitExpr& init,
ValueType type) {
void WriteInitializerExpression(ZoneBuffer* buffer, const WasmInitExpr& init,
ValueType type) {
switch (init.kind()) {
case WasmInitExpr::kI32Const:
buffer->write_u8(kExprI32Const);
@ -512,7 +521,7 @@ void WriteGlobalInitializer(ZoneBuffer* buffer, const WasmInitExpr& init,
break;
case WasmInitExpr::kRttSub:
// The operand to rtt.sub must be emitted first.
WriteGlobalInitializer(buffer, *init.operand(), kWasmBottom);
WriteInitializerExpression(buffer, *init.operand(), kWasmBottom);
STATIC_ASSERT((kExprRttSub >> 8) == kGCPrefix);
buffer->write_u8(kGCPrefix);
buffer->write_u8(static_cast<uint8_t>(kExprRttSub));
@ -611,6 +620,9 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer* buffer) const {
buffer->write_u8(table.has_maximum ? kWithMaximum : kNoMaximum);
buffer->write_size(table.min_size);
if (table.has_maximum) buffer->write_size(table.max_size);
if (table.init.kind() != WasmInitExpr::kNone) {
WriteInitializerExpression(buffer, table.init, table.type);
}
}
FixupSection(buffer, start);
}
@ -651,7 +663,7 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer* buffer) const {
for (const WasmGlobal& global : globals_) {
WriteValueType(buffer, global.type);
buffer->write_u8(global.mutability ? 1 : 0);
WriteGlobalInitializer(buffer, global.init, global.type);
WriteInitializerExpression(buffer, global.init, global.type);
buffer->write_u8(kExprEnd);
}
FixupSection(buffer, start);

View File

@ -261,6 +261,8 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject {
void SetMaxTableSize(uint32_t max);
uint32_t AddTable(ValueType type, uint32_t min_size);
uint32_t AddTable(ValueType type, uint32_t min_size, uint32_t max_size);
uint32_t AddTable(ValueType type, uint32_t min_size, uint32_t max_size,
WasmInitExpr init);
void MarkStartFunction(WasmFunctionBuilder* builder);
void AddExport(Vector<const char> name, ImportExportKindCode kind,
uint32_t index);
@ -340,6 +342,7 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject {
uint32_t min_size;
uint32_t max_size;
bool has_maximum;
WasmInitExpr init;
};
struct WasmDataSegment {

View File

@ -354,7 +354,7 @@ struct WasmTable {
// TODO(9495): Update this function as more table types are supported, or
// remove it completely when all reference types are allowed.
static bool IsValidTableType(ValueType type, const WasmModule* module) {
if (!type.is_nullable()) return false;
if (!type.is_object_reference()) return false;
HeapType heap_type = type.heap_type();
return heap_type == HeapType::kFunc || heap_type == HeapType::kExtern ||
(module != nullptr && heap_type.is_index() &&
@ -367,6 +367,7 @@ struct WasmTable {
bool has_maximum_size = false; // true if there is a maximum size.
bool imported = false; // true if imported.
bool exported = false; // true if exported.
WasmInitExpr initial_value;
};
inline bool is_asmjs_module(const WasmModule* module) {

View File

@ -393,8 +393,7 @@ ACCESSORS(WasmIndirectFunctionTable, refs, FixedArray, kRefsOffset)
#undef PRIMITIVE_ACCESSORS
wasm::ValueType WasmTableObject::type() {
// TODO(7748): Support other table types? Wait for spec to clear up.
return wasm::ValueType::Ref(raw_type(), wasm::kNullable);
return wasm::ValueType::FromRawBitField(raw_type());
}
bool WasmMemoryObject::has_maximum_pages() { return maximum_pages() >= 0; }

View File

@ -291,7 +291,7 @@ Handle<WasmTableObject> WasmTableObject::New(
table_obj->set_entries(*backing_store);
table_obj->set_current_length(initial);
table_obj->set_maximum_length(*max);
table_obj->set_raw_type(static_cast<int>(type.heap_representation()));
table_obj->set_raw_type(static_cast<int>(type.raw_bit_field()));
table_obj->set_dispatch_tables(ReadOnlyRoots(isolate).empty_fixed_array());
if (entries != nullptr) {

View File

@ -6,7 +6,7 @@
load("test/mjsunit/wasm/wasm-module-builder.js");
(function Test1() {
(function TestTables() {
var exporting_instance = (function () {
var builder = new WasmModuleBuilder();
var binary_type = builder.addType(kSig_i_ii);
@ -15,8 +15,8 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc();
builder.addFunction("id", kSig_i_i)
.addBody([kExprLocalGet, 0])
builder.addFunction("succ", kSig_i_i)
.addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Add])
.exportFunc();
builder.addTable(wasmOptRefType(binary_type), 1, 100).exportAs("table");
@ -57,7 +57,7 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
builder.addImportedTable("imports", "table", 1, 100,
wasmOptRefType(binary_type));
var table = builder.addTable(wasmOptRefType(unary_type), 1)
var table = builder.addTable(wasmOptRefType(unary_type), 10)
.exportAs("table");
builder.addTable(kWasmFuncRef, 1).exportAs("generic_table");
@ -69,6 +69,14 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
kExprCallRef])
.exportFunc();
// Same, but with table[1] and call_indirect
builder.addFunction("table_indirect_test",
makeSig([wasmRefType(unary_type)], [kWasmI32]))
.addBody([kExprI32Const, 1, kExprLocalGet, 0, kExprTableSet, table.index,
kExprI32Const, 42, kExprI32Const, 0,
kExprCallIndirect, unary_type, table.index])
.exportFunc();
// Instantiate with a table of the correct type.
return builder.instantiate(
{imports: {table: exporting_instance.exports.table}});
@ -79,13 +87,50 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
// The correct function reference is preserved when setting it to and getting
// it back from a table.
assertEquals(42, instance.exports.table_test(exporting_instance.exports.id));
assertEquals(43,
instance.exports.table_test(exporting_instance.exports.succ));
// Same for call indirect (the indirect call tables are also set correctly).
assertEquals(43, instance.exports.table_indirect_test(
exporting_instance.exports.succ));
// Setting from JS API respects types.
instance.exports.generic_table.set(0, exporting_instance.exports.id);
instance.exports.table.set(0, exporting_instance.exports.id);
instance.exports.generic_table.set(0, exporting_instance.exports.succ);
instance.exports.table.set(0, exporting_instance.exports.succ);
assertThrows(
() => instance.exports.table.set(0, exporting_instance.exports.addition),
TypeError,
/Argument 1 must be null or a WebAssembly function of type compatible to 'this'/);
/Argument 1 must be null or a WebAssembly function of type compatible to/);
})();
(function TestNonNullableTables() {
var builder = new WasmModuleBuilder();
var binary_type = builder.addType(kSig_i_ii);
var addition = builder.addFunction("addition", kSig_i_ii)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add]);
var subtraction = builder.addFunction("subtraction", kSig_i_ii)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Sub])
.exportFunc();
var table = builder.addTable(wasmRefType(binary_type), 3, 3, addition.index);
builder.addFunction("init", kSig_v_v)
.addBody([kExprI32Const, 1, kExprRefFunc, subtraction.index,
kExprTableSet, table.index])
.exportFunc();
// (index, arg1, arg2) -> table[index](arg1, arg2)
builder.addFunction("table_test", kSig_i_iii)
.addBody([kExprLocalGet, 1, kExprLocalGet, 2, kExprLocalGet, 0,
kExprCallIndirect, binary_type, table.index])
.exportFunc();
var instance = builder.instantiate({});
assertTrue(!!instance);
instance.exports.init();
assertEquals(44, instance.exports.table_test(0, 33, 11));
assertEquals(22, instance.exports.table_test(1, 33, 11));
})();

View File

@ -147,7 +147,6 @@ const dummy_func = exports.set_table_func1;
kExprTableSet, table_index, // --
kExprI32Const, index, // entry index
kExprCallIndirect, sig_index, table_index // --
])
.exportFunc();

View File

@ -961,12 +961,14 @@ class WasmGlobalBuilder {
}
class WasmTableBuilder {
constructor(module, type, initial_size, max_size) {
constructor(module, type, initial_size, max_size, init_func_index) {
this.module = module;
this.type = type;
this.initial_size = initial_size;
this.has_max = max_size != undefined;
this.max_size = max_size;
this.init_func_index = init_func_index;
this.has_init = init_func_index != undefined;
}
exportAs(name) {
@ -1094,12 +1096,14 @@ class WasmModuleBuilder {
return glob;
}
addTable(type, initial_size, max_size = undefined) {
addTable(type, initial_size, max_size = undefined,
init_func_index = undefined) {
if (type == kWasmI32 || type == kWasmI64 || type == kWasmF32 ||
type == kWasmF64 || type == kWasmS128 || type == kWasmStmt) {
throw new Error('Tables must be of a reference type');
}
let table = new WasmTableBuilder(this, type, initial_size, max_size);
let table = new WasmTableBuilder(this, type, initial_size, max_size,
init_func_index);
table.index = this.tables.length + this.num_imported_tables;
this.tables.push(table);
return table;
@ -1367,6 +1371,11 @@ class WasmModuleBuilder {
section.emit_u8(table.has_max);
section.emit_u32v(table.initial_size);
if (table.has_max) section.emit_u32v(table.max_size);
if (table.has_init) {
section.emit_u8(kExprRefFunc);
section.emit_u32v(table.init_func_index);
section.emit_u8(kExprEnd);
}
}
});
}

View File

@ -495,7 +495,7 @@ TEST_F(WasmModuleVerifyTest, GlobalInitializer) {
1) // mutable
};
EXPECT_FAILURE_WITH_MSG(no_initializer_no_end,
"Global initializer is missing 'end'");
"Initializer expression is missing 'end'");
static const byte no_initializer[] = {
SECTION(Global, //--
@ -505,7 +505,7 @@ TEST_F(WasmModuleVerifyTest, GlobalInitializer) {
kExprEnd) // --
};
EXPECT_FAILURE_WITH_MSG(no_initializer,
"Found 'end' in global initalizer, but no "
"Found 'end' in initializer expression, but no "
"expressions were found on the stack");
static const byte too_many_initializers_no_end[] = {
@ -517,7 +517,7 @@ TEST_F(WasmModuleVerifyTest, GlobalInitializer) {
WASM_I32V_1(43)) // another value is too much
};
EXPECT_FAILURE_WITH_MSG(too_many_initializers_no_end,
"Global initializer is missing 'end'");
"Initializer expression is missing 'end'");
static const byte too_many_initializers[] = {
SECTION(Global, // --
@ -528,8 +528,8 @@ TEST_F(WasmModuleVerifyTest, GlobalInitializer) {
WASM_I32V_1(43), // another value is too much
kExprEnd)};
EXPECT_FAILURE_WITH_MSG(too_many_initializers,
"Found 'end' in global initalizer, but more than one "
"expressions were found on the stack");
"Found 'end' in initializer expression, but more than"
" one expressions were found on the stack");
static const byte missing_end_opcode[] = {
SECTION(Global, // --
@ -539,7 +539,7 @@ TEST_F(WasmModuleVerifyTest, GlobalInitializer) {
WASM_I32V_1(42)) // init value
};
EXPECT_FAILURE_WITH_MSG(missing_end_opcode,
"Global initializer is missing 'end'");
"Initializer expression is missing 'end'");
static const byte referencing_out_of_bounds_global[] = {
SECTION(Global, ENTRY_COUNT(1), // --
@ -1971,6 +1971,24 @@ TEST_F(WasmModuleVerifyTest, TypedFunctionTable) {
EXPECT_EQ(ValueType::Ref(0, kNullable), result.value()->tables[0].type);
}
TEST_F(WasmModuleVerifyTest, NullableTableIllegalInitializer) {
WASM_FEATURE_SCOPE(reftypes);
WASM_FEATURE_SCOPE(typed_funcref);
static const byte data[] = {
SECTION(Type, ENTRY_COUNT(1), SIG_ENTRY_v_v), // type section
ONE_EMPTY_FUNCTION(0), // function section
SECTION(Table, // table section
ENTRY_COUNT(1), // 1 table
kOptRefCode, 0, // table 0: type
0, 10, // table 0: limits
kExprRefFunc, 0, kExprEnd)}; // table 0: initializer
EXPECT_FAILURE_WITH_MSG(
data,
"section was shorter than expected size (8 bytes expected, 5 decoded)");
}
TEST_F(WasmModuleVerifyTest, IllegalTableTypes) {
WASM_FEATURE_SCOPE(reftypes);
WASM_FEATURE_SCOPE(typed_funcref);
@ -1999,13 +2017,47 @@ TEST_F(WasmModuleVerifyTest, IllegalTableTypes) {
auto result = DecodeModule(data.data(), data.data() + data.size());
EXPECT_NOT_OK(
result,
"Currently, only externref and function references are allowed "
"as table types");
EXPECT_NOT_OK(result,
"Currently, only externref and function references are "
"allowed as table types");
}
}
TEST_F(WasmModuleVerifyTest, NonNullableTable) {
WASM_FEATURE_SCOPE(reftypes);
WASM_FEATURE_SCOPE(typed_funcref);
static const byte data[] = {
SECTION(Type, ENTRY_COUNT(1), SIG_ENTRY_v_v), // type section
ONE_EMPTY_FUNCTION(0), // function section
SECTION(Table, // table section
ENTRY_COUNT(1), // 1 table
kRefCode, 0, // table 0: type
0, 10, // table 0: limits
kExprRefFunc, 0, kExprEnd), // table 0: init. expression
SECTION(Code, ENTRY_COUNT(1), NOP_BODY)};
ModuleResult result = DecodeModule(data, data + sizeof(data));
EXPECT_OK(result);
EXPECT_EQ(ValueType::Ref(0, kNonNullable), result.value()->tables[0].type);
}
TEST_F(WasmModuleVerifyTest, NonNullableTableNoInitializer) {
WASM_FEATURE_SCOPE(reftypes);
WASM_FEATURE_SCOPE(typed_funcref);
static const byte data[] = {
SECTION(Type, ENTRY_COUNT(1), SIG_ENTRY_v_x(kI32Code)),
SECTION(Table, // table section
ENTRY_COUNT(2), // 2 tables
kRefCode, 0, // table 0: type
0, 10, // table 0: limits
kRefCode, 0, // table 1: type
5, 6)}; // table 1: limits
EXPECT_FAILURE_WITH_MSG(data,
"invalid opcode 0x6b in initializer expression");
}
TEST_F(WasmModuleVerifyTest, TieringCompilationHints) {
WASM_FEATURE_SCOPE(compilation_hints);
static const byte data[] = {