[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:
parent
476b527bb7
commit
e3acd9f8fe
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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 {};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) \
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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; }
|
||||
|
@ -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) {
|
||||
|
@ -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));
|
||||
})();
|
||||
|
@ -147,7 +147,6 @@ const dummy_func = exports.set_table_func1;
|
||||
kExprTableSet, table_index, // --
|
||||
kExprI32Const, index, // entry index
|
||||
kExprCallIndirect, sig_index, table_index // --
|
||||
|
||||
])
|
||||
.exportFunc();
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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[] = {
|
||||
|
Loading…
Reference in New Issue
Block a user