Commit e3acd9f8 authored by Manos Koukoutos's avatar Manos Koukoutos Committed by Commit Bot

[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: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#73215}
parent 476b527b
...@@ -41,7 +41,10 @@ extern macro Allocate(intptr, constexpr AllocationFlag): HeapObject; ...@@ -41,7 +41,10 @@ extern macro Allocate(intptr, constexpr AllocationFlag): HeapObject;
} }
namespace wasm { 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; extern macro WasmBuiltinsAssembler::LoadInstanceFromFrame(): WasmInstanceObject;
...@@ -177,8 +180,12 @@ builtin WasmTableSet(tableIndex: intptr, index: int32, value: Object): Object { ...@@ -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 // Fall back to the runtime to set funcrefs, since we have to update
// function dispatch tables. // function dispatch tables.
// TODO(7748): Update this if further table types are supported.
const tableType: Smi = table.raw_type; 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); const entriesCount: intptr = Convert<intptr, Smi>(table.current_length);
if (entryIndex >= entriesCount) goto IndexOutOfRange; if (entryIndex >= entriesCount) goto IndexOutOfRange;
......
...@@ -3007,29 +3007,36 @@ Node* WasmGraphBuilder::BuildIndirectCall(uint32_t table_index, ...@@ -3007,29 +3007,36 @@ Node* WasmGraphBuilder::BuildIndirectCall(uint32_t table_index,
key = gasm_->Word32And(key, mask); key = gasm_->Word32And(key, mask);
} }
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 = Node* int32_scaled_key =
Uint32ToUintptr(gasm_->Word32Shl(key, Int32Constant(2))); Uint32ToUintptr(gasm_->Word32Shl(key, Int32Constant(2)));
Node* loaded_sig = Node* loaded_sig =
gasm_->Load(MachineType::Int32(), ift_sig_ids, int32_scaled_key); 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 if (table_type.is_reference_to(wasm::HeapType::kFunc)) {
// $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]; int32_t expected_sig_id = env_->module->canonicalized_type_ids[sig_index];
Node* sig_match = Node* sig_match =
gasm_->Word32Equal(loaded_sig, Int32Constant(expected_sig_id)); gasm_->Word32Equal(loaded_sig, Int32Constant(expected_sig_id));
TrapIfFalse(wasm::kTrapFuncSigMismatch, sig_match, position); TrapIfFalse(wasm::kTrapFuncSigMismatch, sig_match, position);
} else { } else {
// We still have to check that the entry is initialized. // If the table entries are nullable, we still have to check that the
// TODO(9495): Skip this check for non-nullable tables when they are // entry is initialized.
// allowed. Node* function_is_null =
Node* function_is_null = gasm_->Word32Equal(loaded_sig, Int32Constant(-1)); gasm_->Word32Equal(loaded_sig, Int32Constant(-1));
TrapIfTrue(wasm::kTrapNullDereference, function_is_null, position); TrapIfTrue(wasm::kTrapNullDereference, function_is_null, position);
} }
}
Node* key_intptr = Uint32ToUintptr(key); Node* key_intptr = Uint32ToUintptr(key);
......
...@@ -5190,6 +5190,7 @@ class LiftoffCompiler { ...@@ -5190,6 +5190,7 @@ class LiftoffCompiler {
__ Load(LiftoffRegister(scratch), table, index, 0, LoadType::kI32Load, __ Load(LiftoffRegister(scratch), table, index, 0, LoadType::kI32Load,
pinned); pinned);
// TODO(9495): Do not always compare signatures, same as wasm-compiler.cc.
// Compare against expected signature. // Compare against expected signature.
__ LoadConstant(LiftoffRegister(tmp_const), WasmValue(canonical_sig_num)); __ LoadConstant(LiftoffRegister(tmp_const), WasmValue(canonical_sig_num));
......
...@@ -728,6 +728,9 @@ class ModuleDecoderImpl : public Decoder { ...@@ -728,6 +728,9 @@ class ModuleDecoderImpl : public Decoder {
"table elements", "elements", std::numeric_limits<uint32_t>::max(), "table elements", "elements", std::numeric_limits<uint32_t>::max(),
&table->initial_size, &table->has_maximum_size, &table->initial_size, &table->has_maximum_size,
std::numeric_limits<uint32_t>::max(), &table->maximum_size, flags); 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 { ...@@ -1713,7 +1716,7 @@ class ModuleDecoderImpl : public Decoder {
if (V8_UNLIKELY(!enabled_features_.has_reftypes() && if (V8_UNLIKELY(!enabled_features_.has_reftypes() &&
!enabled_features_.has_eh())) { !enabled_features_.has_eh())) {
errorf(pc(), 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", "--experimental-wasm-reftypes or --experimental-wasm-eh",
kExprRefNull); kExprRefNull);
return {}; return {};
...@@ -1729,7 +1732,7 @@ class ModuleDecoderImpl : public Decoder { ...@@ -1729,7 +1732,7 @@ class ModuleDecoderImpl : public Decoder {
case kExprRefFunc: { case kExprRefFunc: {
if (V8_UNLIKELY(!enabled_features_.has_reftypes())) { if (V8_UNLIKELY(!enabled_features_.has_reftypes())) {
errorf(pc(), errorf(pc(),
"invalid opcode 0x%x in global initializer, enable with " "invalid opcode 0x%x in initializer expression, enable with "
"--experimental-wasm-reftypes", "--experimental-wasm-reftypes",
kExprRefFunc); kExprRefFunc);
return {}; return {};
...@@ -1752,7 +1755,7 @@ class ModuleDecoderImpl : public Decoder { ...@@ -1752,7 +1755,7 @@ class ModuleDecoderImpl : public Decoder {
// the type check or stack height check at the end. // the type check or stack height check at the end.
opcode = read_prefixed_opcode<validate>(pc(), &len); opcode = read_prefixed_opcode<validate>(pc(), &len);
if (V8_UNLIKELY(opcode != kExprS128Const)) { 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); opcode);
return {}; return {};
} }
...@@ -1804,7 +1807,8 @@ class ModuleDecoderImpl : public Decoder { ...@@ -1804,7 +1807,8 @@ class ModuleDecoderImpl : public Decoder {
break; break;
} }
default: { default: {
errorf(pc(), "invalid opcode 0x%x in global initializer", opcode); errorf(pc(), "invalid opcode 0x%x in initializer expression",
opcode);
return {}; return {};
} }
} }
...@@ -1813,7 +1817,7 @@ class ModuleDecoderImpl : public Decoder { ...@@ -1813,7 +1817,7 @@ class ModuleDecoderImpl : public Decoder {
case kExprEnd: case kExprEnd:
break; break;
default: { default: {
errorf(pc(), "invalid opcode 0x%x in global initializer", opcode); errorf(pc(), "invalid opcode 0x%x in initializer expression", opcode);
return {}; return {};
} }
} }
...@@ -1821,16 +1825,16 @@ class ModuleDecoderImpl : public Decoder { ...@@ -1821,16 +1825,16 @@ class ModuleDecoderImpl : public Decoder {
} }
if (V8_UNLIKELY(pc() > end())) { if (V8_UNLIKELY(pc() > end())) {
error(end(), "Global initializer extending beyond code end"); error(end(), "Initializer expression extending beyond code end");
return {}; return {};
} }
if (V8_UNLIKELY(opcode != kExprEnd)) { if (V8_UNLIKELY(opcode != kExprEnd)) {
error(pc(), "Global initializer is missing 'end'"); error(pc(), "Initializer expression is missing 'end'");
return {}; return {};
} }
if (V8_UNLIKELY(stack.size() != 1)) { if (V8_UNLIKELY(stack.size() != 1)) {
errorf(pc(), errorf(pc(),
"Found 'end' in global initalizer, but %s expressions were " "Found 'end' in initializer expression, but %s expressions were "
"found on the stack", "found on the stack",
stack.size() > 1 ? "more than one" : "no"); stack.size() > 1 ? "more than one" : "no");
return {}; return {};
......
...@@ -1841,12 +1841,50 @@ void InstanceBuilder::ProcessExports(Handle<WasmInstanceObject> instance) { ...@@ -1841,12 +1841,50 @@ void InstanceBuilder::ProcessExports(Handle<WasmInstanceObject> instance) {
void InstanceBuilder::InitializeIndirectFunctionTables( void InstanceBuilder::InitializeIndirectFunctionTables(
Handle<WasmInstanceObject> instance) { Handle<WasmInstanceObject> instance) {
for (int i = 0; i < static_cast<int>(module_->tables.size()); ++i) { for (int table_index = 0;
const WasmTable& table = module_->tables[i]; table_index < static_cast<int>(module_->tables.size()); ++table_index) {
const WasmTable& table = module_->tables[table_index];
if (IsSubtypeOf(table.type, kWasmFuncRef, module_)) { if (IsSubtypeOf(table.type, kWasmFuncRef, module_)) {
WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize( 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 { ...@@ -530,6 +530,8 @@ class ValueType {
static_assert(sizeof(ValueType) <= kUInt32Size, static_assert(sizeof(ValueType) <= kUInt32Size,
"ValueType is small and can be passed by value"); "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) { inline size_t hash_value(ValueType type) {
return static_cast<size_t>(type.kind()); return static_cast<size_t>(type.kind());
...@@ -560,6 +562,10 @@ constexpr ValueType kWasmDataRef = ...@@ -560,6 +562,10 @@ constexpr ValueType kWasmDataRef =
ValueType::Ref(HeapType::kData, kNonNullable); ValueType::Ref(HeapType::kData, kNonNullable);
constexpr ValueType kWasmAnyRef = ValueType::Ref(HeapType::kAny, kNullable); 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) \ #define FOREACH_WASMVALUE_CTYPES(V) \
V(kI32, int32_t) \ V(kI32, int32_t) \
V(kI64, int64_t) \ V(kI64, int64_t) \
......
...@@ -315,7 +315,7 @@ uint32_t WasmModuleBuilder::AllocateIndirectFunctions(uint32_t count) { ...@@ -315,7 +315,7 @@ uint32_t WasmModuleBuilder::AllocateIndirectFunctions(uint32_t count) {
if (tables_.empty()) { if (tables_.empty()) {
// This cannot use {AddTable} because that would flip the // This cannot use {AddTable} because that would flip the
// {allocating_indirect_functions_allowed_} flag. // {allocating_indirect_functions_allowed_} flag.
tables_.push_back({kWasmFuncRef, new_size, max, true}); tables_.push_back({kWasmFuncRef, new_size, max, true, {}});
} else { } else {
// There can only be the indirect function table so far, otherwise the // There can only be the indirect function table so far, otherwise the
// {allocating_indirect_functions_allowed_} flag would have been false. // {allocating_indirect_functions_allowed_} flag would have been false.
...@@ -347,7 +347,7 @@ uint32_t WasmModuleBuilder::AddTable(ValueType type, uint32_t min_size) { ...@@ -347,7 +347,7 @@ uint32_t WasmModuleBuilder::AddTable(ValueType type, uint32_t min_size) {
#if DEBUG #if DEBUG
allocating_indirect_functions_allowed_ = false; allocating_indirect_functions_allowed_ = false;
#endif #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); return static_cast<uint32_t>(tables_.size() - 1);
} }
...@@ -356,7 +356,16 @@ uint32_t WasmModuleBuilder::AddTable(ValueType type, uint32_t min_size, ...@@ -356,7 +356,16 @@ uint32_t WasmModuleBuilder::AddTable(ValueType type, uint32_t min_size,
#if DEBUG #if DEBUG
allocating_indirect_functions_allowed_ = false; allocating_indirect_functions_allowed_ = false;
#endif #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); return static_cast<uint32_t>(tables_.size() - 1);
} }
...@@ -432,7 +441,7 @@ void WriteValueType(ZoneBuffer* buffer, const ValueType& type) { ...@@ -432,7 +441,7 @@ void WriteValueType(ZoneBuffer* buffer, const ValueType& type) {
} }
} }
void WriteGlobalInitializer(ZoneBuffer* buffer, const WasmInitExpr& init, void WriteInitializerExpression(ZoneBuffer* buffer, const WasmInitExpr& init,
ValueType type) { ValueType type) {
switch (init.kind()) { switch (init.kind()) {
case WasmInitExpr::kI32Const: case WasmInitExpr::kI32Const:
...@@ -512,7 +521,7 @@ void WriteGlobalInitializer(ZoneBuffer* buffer, const WasmInitExpr& init, ...@@ -512,7 +521,7 @@ void WriteGlobalInitializer(ZoneBuffer* buffer, const WasmInitExpr& init,
break; break;
case WasmInitExpr::kRttSub: case WasmInitExpr::kRttSub:
// The operand to rtt.sub must be emitted first. // The operand to rtt.sub must be emitted first.
WriteGlobalInitializer(buffer, *init.operand(), kWasmBottom); WriteInitializerExpression(buffer, *init.operand(), kWasmBottom);
STATIC_ASSERT((kExprRttSub >> 8) == kGCPrefix); STATIC_ASSERT((kExprRttSub >> 8) == kGCPrefix);
buffer->write_u8(kGCPrefix); buffer->write_u8(kGCPrefix);
buffer->write_u8(static_cast<uint8_t>(kExprRttSub)); buffer->write_u8(static_cast<uint8_t>(kExprRttSub));
...@@ -611,6 +620,9 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer* buffer) const { ...@@ -611,6 +620,9 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer* buffer) const {
buffer->write_u8(table.has_maximum ? kWithMaximum : kNoMaximum); buffer->write_u8(table.has_maximum ? kWithMaximum : kNoMaximum);
buffer->write_size(table.min_size); buffer->write_size(table.min_size);
if (table.has_maximum) buffer->write_size(table.max_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); FixupSection(buffer, start);
} }
...@@ -651,7 +663,7 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer* buffer) const { ...@@ -651,7 +663,7 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer* buffer) const {
for (const WasmGlobal& global : globals_) { for (const WasmGlobal& global : globals_) {
WriteValueType(buffer, global.type); WriteValueType(buffer, global.type);
buffer->write_u8(global.mutability ? 1 : 0); buffer->write_u8(global.mutability ? 1 : 0);
WriteGlobalInitializer(buffer, global.init, global.type); WriteInitializerExpression(buffer, global.init, global.type);
buffer->write_u8(kExprEnd); buffer->write_u8(kExprEnd);
} }
FixupSection(buffer, start); FixupSection(buffer, start);
......
...@@ -261,6 +261,8 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject { ...@@ -261,6 +261,8 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject {
void SetMaxTableSize(uint32_t max); 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 AddTable(ValueType type, uint32_t min_size, uint32_t max_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 MarkStartFunction(WasmFunctionBuilder* builder);
void AddExport(Vector<const char> name, ImportExportKindCode kind, void AddExport(Vector<const char> name, ImportExportKindCode kind,
uint32_t index); uint32_t index);
...@@ -340,6 +342,7 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject { ...@@ -340,6 +342,7 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject {
uint32_t min_size; uint32_t min_size;
uint32_t max_size; uint32_t max_size;
bool has_maximum; bool has_maximum;
WasmInitExpr init;
}; };
struct WasmDataSegment { struct WasmDataSegment {
......
...@@ -354,7 +354,7 @@ struct WasmTable { ...@@ -354,7 +354,7 @@ struct WasmTable {
// TODO(9495): Update this function as more table types are supported, or // TODO(9495): Update this function as more table types are supported, or
// remove it completely when all reference types are allowed. // remove it completely when all reference types are allowed.
static bool IsValidTableType(ValueType type, const WasmModule* module) { 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(); HeapType heap_type = type.heap_type();
return heap_type == HeapType::kFunc || heap_type == HeapType::kExtern || return heap_type == HeapType::kFunc || heap_type == HeapType::kExtern ||
(module != nullptr && heap_type.is_index() && (module != nullptr && heap_type.is_index() &&
...@@ -367,6 +367,7 @@ struct WasmTable { ...@@ -367,6 +367,7 @@ struct WasmTable {
bool has_maximum_size = false; // true if there is a maximum size. bool has_maximum_size = false; // true if there is a maximum size.
bool imported = false; // true if imported. bool imported = false; // true if imported.
bool exported = false; // true if exported. bool exported = false; // true if exported.
WasmInitExpr initial_value;
}; };
inline bool is_asmjs_module(const WasmModule* module) { inline bool is_asmjs_module(const WasmModule* module) {
......
...@@ -393,8 +393,7 @@ ACCESSORS(WasmIndirectFunctionTable, refs, FixedArray, kRefsOffset) ...@@ -393,8 +393,7 @@ ACCESSORS(WasmIndirectFunctionTable, refs, FixedArray, kRefsOffset)
#undef PRIMITIVE_ACCESSORS #undef PRIMITIVE_ACCESSORS
wasm::ValueType WasmTableObject::type() { wasm::ValueType WasmTableObject::type() {
// TODO(7748): Support other table types? Wait for spec to clear up. return wasm::ValueType::FromRawBitField(raw_type());
return wasm::ValueType::Ref(raw_type(), wasm::kNullable);
} }
bool WasmMemoryObject::has_maximum_pages() { return maximum_pages() >= 0; } bool WasmMemoryObject::has_maximum_pages() { return maximum_pages() >= 0; }
......
...@@ -291,7 +291,7 @@ Handle<WasmTableObject> WasmTableObject::New( ...@@ -291,7 +291,7 @@ Handle<WasmTableObject> WasmTableObject::New(
table_obj->set_entries(*backing_store); table_obj->set_entries(*backing_store);
table_obj->set_current_length(initial); table_obj->set_current_length(initial);
table_obj->set_maximum_length(*max); 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()); table_obj->set_dispatch_tables(ReadOnlyRoots(isolate).empty_fixed_array());
if (entries != nullptr) { if (entries != nullptr) {
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
load("test/mjsunit/wasm/wasm-module-builder.js"); load("test/mjsunit/wasm/wasm-module-builder.js");
(function Test1() { (function TestTables() {
var exporting_instance = (function () { var exporting_instance = (function () {
var builder = new WasmModuleBuilder(); var builder = new WasmModuleBuilder();
var binary_type = builder.addType(kSig_i_ii); var binary_type = builder.addType(kSig_i_ii);
...@@ -15,8 +15,8 @@ load("test/mjsunit/wasm/wasm-module-builder.js"); ...@@ -15,8 +15,8 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add]) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
.exportFunc(); .exportFunc();
builder.addFunction("id", kSig_i_i) builder.addFunction("succ", kSig_i_i)
.addBody([kExprLocalGet, 0]) .addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Add])
.exportFunc(); .exportFunc();
builder.addTable(wasmOptRefType(binary_type), 1, 100).exportAs("table"); builder.addTable(wasmOptRefType(binary_type), 1, 100).exportAs("table");
...@@ -57,7 +57,7 @@ load("test/mjsunit/wasm/wasm-module-builder.js"); ...@@ -57,7 +57,7 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
builder.addImportedTable("imports", "table", 1, 100, builder.addImportedTable("imports", "table", 1, 100,
wasmOptRefType(binary_type)); wasmOptRefType(binary_type));
var table = builder.addTable(wasmOptRefType(unary_type), 1) var table = builder.addTable(wasmOptRefType(unary_type), 10)
.exportAs("table"); .exportAs("table");
builder.addTable(kWasmFuncRef, 1).exportAs("generic_table"); builder.addTable(kWasmFuncRef, 1).exportAs("generic_table");
...@@ -69,6 +69,14 @@ load("test/mjsunit/wasm/wasm-module-builder.js"); ...@@ -69,6 +69,14 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
kExprCallRef]) kExprCallRef])
.exportFunc(); .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. // Instantiate with a table of the correct type.
return builder.instantiate( return builder.instantiate(
{imports: {table: exporting_instance.exports.table}}); {imports: {table: exporting_instance.exports.table}});
...@@ -79,13 +87,50 @@ load("test/mjsunit/wasm/wasm-module-builder.js"); ...@@ -79,13 +87,50 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
// The correct function reference is preserved when setting it to and getting // The correct function reference is preserved when setting it to and getting
// it back from a table. // 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. // Setting from JS API respects types.
instance.exports.generic_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.id); instance.exports.table.set(0, exporting_instance.exports.succ);
assertThrows( assertThrows(
() => instance.exports.table.set(0, exporting_instance.exports.addition), () => instance.exports.table.set(0, exporting_instance.exports.addition),
TypeError, 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; ...@@ -147,7 +147,6 @@ const dummy_func = exports.set_table_func1;
kExprTableSet, table_index, // -- kExprTableSet, table_index, // --
kExprI32Const, index, // entry index kExprI32Const, index, // entry index
kExprCallIndirect, sig_index, table_index // -- kExprCallIndirect, sig_index, table_index // --
]) ])
.exportFunc(); .exportFunc();
......
...@@ -961,12 +961,14 @@ class WasmGlobalBuilder { ...@@ -961,12 +961,14 @@ class WasmGlobalBuilder {
} }
class WasmTableBuilder { class WasmTableBuilder {
constructor(module, type, initial_size, max_size) { constructor(module, type, initial_size, max_size, init_func_index) {
this.module = module; this.module = module;
this.type = type; this.type = type;
this.initial_size = initial_size; this.initial_size = initial_size;
this.has_max = max_size != undefined; this.has_max = max_size != undefined;
this.max_size = max_size; this.max_size = max_size;
this.init_func_index = init_func_index;
this.has_init = init_func_index != undefined;
} }
exportAs(name) { exportAs(name) {
...@@ -1094,12 +1096,14 @@ class WasmModuleBuilder { ...@@ -1094,12 +1096,14 @@ class WasmModuleBuilder {
return glob; 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 || if (type == kWasmI32 || type == kWasmI64 || type == kWasmF32 ||
type == kWasmF64 || type == kWasmS128 || type == kWasmStmt) { type == kWasmF64 || type == kWasmS128 || type == kWasmStmt) {
throw new Error('Tables must be of a reference type'); 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; table.index = this.tables.length + this.num_imported_tables;
this.tables.push(table); this.tables.push(table);
return table; return table;
...@@ -1367,6 +1371,11 @@ class WasmModuleBuilder { ...@@ -1367,6 +1371,11 @@ class WasmModuleBuilder {
section.emit_u8(table.has_max); section.emit_u8(table.has_max);
section.emit_u32v(table.initial_size); section.emit_u32v(table.initial_size);
if (table.has_max) section.emit_u32v(table.max_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) { ...@@ -495,7 +495,7 @@ TEST_F(WasmModuleVerifyTest, GlobalInitializer) {
1) // mutable 1) // mutable
}; };
EXPECT_FAILURE_WITH_MSG(no_initializer_no_end, EXPECT_FAILURE_WITH_MSG(no_initializer_no_end,
"Global initializer is missing 'end'"); "Initializer expression is missing 'end'");
static const byte no_initializer[] = { static const byte no_initializer[] = {
SECTION(Global, //-- SECTION(Global, //--
...@@ -505,7 +505,7 @@ TEST_F(WasmModuleVerifyTest, GlobalInitializer) { ...@@ -505,7 +505,7 @@ TEST_F(WasmModuleVerifyTest, GlobalInitializer) {
kExprEnd) // -- kExprEnd) // --
}; };
EXPECT_FAILURE_WITH_MSG(no_initializer, 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"); "expressions were found on the stack");
static const byte too_many_initializers_no_end[] = { static const byte too_many_initializers_no_end[] = {
...@@ -517,7 +517,7 @@ TEST_F(WasmModuleVerifyTest, GlobalInitializer) { ...@@ -517,7 +517,7 @@ TEST_F(WasmModuleVerifyTest, GlobalInitializer) {
WASM_I32V_1(43)) // another value is too much WASM_I32V_1(43)) // another value is too much
}; };
EXPECT_FAILURE_WITH_MSG(too_many_initializers_no_end, 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[] = { static const byte too_many_initializers[] = {
SECTION(Global, // -- SECTION(Global, // --
...@@ -528,8 +528,8 @@ TEST_F(WasmModuleVerifyTest, GlobalInitializer) { ...@@ -528,8 +528,8 @@ TEST_F(WasmModuleVerifyTest, GlobalInitializer) {
WASM_I32V_1(43), // another value is too much WASM_I32V_1(43), // another value is too much
kExprEnd)}; kExprEnd)};
EXPECT_FAILURE_WITH_MSG(too_many_initializers, EXPECT_FAILURE_WITH_MSG(too_many_initializers,
"Found 'end' in global initalizer, but more than one " "Found 'end' in initializer expression, but more than"
"expressions were found on the stack"); " one expressions were found on the stack");
static const byte missing_end_opcode[] = { static const byte missing_end_opcode[] = {
SECTION(Global, // -- SECTION(Global, // --
...@@ -539,7 +539,7 @@ TEST_F(WasmModuleVerifyTest, GlobalInitializer) { ...@@ -539,7 +539,7 @@ TEST_F(WasmModuleVerifyTest, GlobalInitializer) {
WASM_I32V_1(42)) // init value WASM_I32V_1(42)) // init value
}; };
EXPECT_FAILURE_WITH_MSG(missing_end_opcode, 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[] = { static const byte referencing_out_of_bounds_global[] = {
SECTION(Global, ENTRY_COUNT(1), // -- SECTION(Global, ENTRY_COUNT(1), // --
...@@ -1971,6 +1971,24 @@ TEST_F(WasmModuleVerifyTest, TypedFunctionTable) { ...@@ -1971,6 +1971,24 @@ TEST_F(WasmModuleVerifyTest, TypedFunctionTable) {
EXPECT_EQ(ValueType::Ref(0, kNullable), result.value()->tables[0].type); 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) { TEST_F(WasmModuleVerifyTest, IllegalTableTypes) {
WASM_FEATURE_SCOPE(reftypes); WASM_FEATURE_SCOPE(reftypes);
WASM_FEATURE_SCOPE(typed_funcref); WASM_FEATURE_SCOPE(typed_funcref);
...@@ -1999,13 +2017,47 @@ TEST_F(WasmModuleVerifyTest, IllegalTableTypes) { ...@@ -1999,13 +2017,47 @@ TEST_F(WasmModuleVerifyTest, IllegalTableTypes) {
auto result = DecodeModule(data.data(), data.data() + data.size()); auto result = DecodeModule(data.data(), data.data() + data.size());
EXPECT_NOT_OK( EXPECT_NOT_OK(result,
result, "Currently, only externref and function references are "
"Currently, only externref and function references are allowed " "allowed as table types");
"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) { TEST_F(WasmModuleVerifyTest, TieringCompilationHints) {
WASM_FEATURE_SCOPE(compilation_hints); WASM_FEATURE_SCOPE(compilation_hints);
static const byte data[] = { static const byte data[] = {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment