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

[wasm-gc] Preparation for typed function tables

Changes:
- Rename IsSignatureEqual -> MatchesSignature for consistency
- Add WasmInstanceObject field to WasmTableObject.
- Improve some error messages related to tables in
  function-body-decoder-impl.h.
- Introduce WasmTable::IsValidTableType. Use it wherever appropriate.
- Overload equality operators in HeapType to work with
  HeapType::Representation.
- Rename DynamicTypeCheckRef -> TypecheckJSObject.
- Handle WasmCapiFunctions in TypecheckJSObject.
- Use TypecheckJSObject in WasmTableObject::IsValidElement.
- A few more minor improvements.

Bug: v8:9495
Change-Id: I2867dd3486d7c31717ac26b87a50e15cf2b898be
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2416491
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70001}
parent 0832a109
......@@ -7003,7 +7003,7 @@ std::pair<WasmImportCallKind, Handle<JSReceiver>> ResolveWasmImportCall(
}
if (WasmCapiFunction::IsWasmCapiFunction(*callable)) {
auto capi_function = Handle<WasmCapiFunction>::cast(callable);
if (!capi_function->IsSignatureEqual(expected_sig)) {
if (!capi_function->MatchesSignature(expected_sig)) {
return std::make_pair(WasmImportCallKind::kLinkError, callable);
}
return std::make_pair(WasmImportCallKind::kWasmToCapi, callable);
......
......@@ -101,8 +101,8 @@ RUNTIME_FUNCTION(Runtime_WasmIsValidRefValue) {
wasm::ValueType type = wasm::ValueType::FromRawBitField(raw_type);
const char* error_message;
bool result = internal::wasm::DynamicTypeCheckRef(
isolate, instance->module(), value, type, &error_message);
bool result = internal::wasm::TypecheckJSObject(isolate, instance->module(),
value, type, &error_message);
return Smi::FromInt(result);
}
......
......@@ -1810,7 +1810,8 @@ auto Table::make(Store* store_abs, const TableType* type, const Ref* ref)
i::Handle<i::FixedArray> backing_store;
i::Handle<i::WasmTableObject> table_obj = i::WasmTableObject::New(
isolate, i_type, minimum, has_maximum, maximum, &backing_store);
isolate, i::Handle<i::WasmInstanceObject>(), i_type, minimum, has_maximum,
maximum, &backing_store);
if (ref) {
i::Handle<i::JSReceiver> init = impl(ref)->v8_object();
......
......@@ -487,7 +487,7 @@ struct MemoryIndexImmediate {
template <Decoder::ValidateFlag validate>
struct TableIndexImmediate {
uint32_t index = 0;
unsigned length = 1;
uint32_t length = 1;
inline TableIndexImmediate() = default;
inline TableIndexImmediate(Decoder* decoder, const byte* pc) {
index = decoder->read_u32v<validate>(pc, &length, "table index");
......@@ -1238,12 +1238,13 @@ class WasmDecoder : public Decoder {
inline bool Validate(const byte* pc, CallIndirectImmediate<validate>& imm) {
if (!VALIDATE(imm.table_index < module_->tables.size())) {
error("function table has to exist to execute call_indirect");
error("call_indirect: table index immediate out of bounds");
return false;
}
if (!VALIDATE(IsSubtypeOf(module_->tables[imm.table_index].type,
kWasmFuncRef, module_))) {
error("table of call_indirect must be of a function type");
ValueType table_type = module_->tables[imm.table_index].type;
if (!VALIDATE(IsSubtypeOf(table_type, kWasmFuncRef, module_))) {
errorf(pc, "call_indirect: immediate table #%u is not of a function type",
imm.table_index);
return false;
}
if (!Complete(imm)) {
......@@ -1252,12 +1253,11 @@ class WasmDecoder : public Decoder {
}
// Check that the dynamic signature for this call is a subtype of the static
// type of the table the function is defined in.
if (!VALIDATE(IsSubtypeOf(ValueType::Ref(imm.sig_index, kNonNullable),
module_->tables[imm.table_index].type,
module_))) {
ValueType immediate_type = ValueType::Ref(imm.sig_index, kNonNullable);
if (!VALIDATE(IsSubtypeOf(immediate_type, table_type, module_))) {
errorf(pc,
"call_indirect: Signature of function %u is not a subtype of "
"table %u",
"call_indirect: Immediate signature #%u is not a subtype of "
"immediate table #%u",
imm.sig_index, imm.table_index);
}
return true;
......
......@@ -636,7 +636,14 @@ class ModuleDecoderImpl : public Decoder {
module_->tables.emplace_back();
WasmTable* table = &module_->tables.back();
table->imported = true;
const byte* type_position = pc();
ValueType type = consume_reference_type();
if (!WasmTable::IsValidTableType(type, module_.get())) {
error(type_position,
"Currently, only nullable exnref, externref, and "
"function references are allowed as table types");
break;
}
table->type = type;
uint8_t flags = validate_table_flags("element count");
consume_resizable_limits(
......@@ -728,12 +735,14 @@ class ModuleDecoderImpl : public Decoder {
module_->tables.emplace_back();
WasmTable* table = &module_->tables.back();
const byte* type_position = pc();
table->type = consume_reference_type();
if (!table->type.is_nullable()) {
// TODO(7748): Implement other table types.
ValueType table_type = consume_reference_type();
if (!WasmTable::IsValidTableType(table_type, module_.get())) {
error(type_position,
"Currently, only nullable references are allowed as table types");
"Currently, only nullable exnref, externref, and "
"function references are allowed as table types");
continue;
}
table->type = table_type;
uint8_t flags = validate_table_flags("table elements");
consume_resizable_limits(
"table elements", "elements", std::numeric_limits<uint32_t>::max(),
......@@ -1907,7 +1916,10 @@ class ModuleDecoderImpl : public Decoder {
}
// Reads a reference type for tables and element segment headers.
// Note that, unless extensions are enabled, only funcref is allowed.
// Unless extensions are enabled, only funcref is allowed.
// TODO(manoskouk): Replace this with consume_value_type (and checks against
// the returned type at callsites as needed) once the
// 'reftypes' proposal is standardized.
ValueType consume_reference_type() {
if (!enabled_features_.has_reftypes()) {
uint8_t ref_type = consume_u8("reference type");
......
......@@ -560,8 +560,8 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
for (int i = module_->num_imported_tables; i < table_count; i++) {
const WasmTable& table = module_->tables[i];
Handle<WasmTableObject> table_obj = WasmTableObject::New(
isolate_, table.type, table.initial_size, table.has_maximum_size,
table.maximum_size, nullptr);
isolate_, instance, table.type, table.initial_size,
table.has_maximum_size, table.maximum_size, nullptr);
tables->set(i, *table_obj);
}
instance->set_tables(*tables);
......@@ -1360,8 +1360,8 @@ bool InstanceBuilder::ProcessImportedGlobal(Handle<WasmInstanceObject> instance,
if (global.type.is_reference_type()) {
const char* error_message;
if (!wasm::DynamicTypeCheckRef(isolate_, module_, value, global.type,
&error_message)) {
if (!wasm::TypecheckJSObject(isolate_, module_, value, global.type,
&error_message)) {
ReportLinkError(error_message, global_index, module_name, import_name);
return false;
}
......@@ -1514,7 +1514,7 @@ int InstanceBuilder::ProcessImports(Handle<WasmInstanceObject> instance) {
}
Handle<WasmExceptionObject> imported_exception =
Handle<WasmExceptionObject>::cast(value);
if (!imported_exception->IsSignatureEqual(
if (!imported_exception->MatchesSignature(
module_->exceptions[import.index].sig)) {
ReportLinkError("imported exception does not match the expected type",
index, module_name, import_name);
......
......@@ -90,6 +90,14 @@ class HeapType {
return representation_ != other.representation_;
}
constexpr bool operator==(Representation other) const {
return representation_ == other;
}
constexpr bool operator!=(Representation other) const {
return representation_ != other;
}
constexpr Representation representation() const { return representation_; }
constexpr uint32_t ref_index() const {
CONSTEXPR_DCHECK(is_index());
......
......@@ -1108,9 +1108,10 @@ void WebAssemblyTable(const v8::FunctionCallbackInfo<v8::Value>& args) {
}
i::Handle<i::FixedArray> fixed_array;
i::Handle<i::JSObject> table_obj = i::WasmTableObject::New(
i_isolate, type, static_cast<uint32_t>(initial), has_maximum,
static_cast<uint32_t>(maximum), &fixed_array);
i::Handle<i::JSObject> table_obj =
i::WasmTableObject::New(i_isolate, i::Handle<i::WasmInstanceObject>(),
type, static_cast<uint32_t>(initial), has_maximum,
static_cast<uint32_t>(maximum), &fixed_array);
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
return_value.Set(Utils::ToLocal(table_obj));
}
......
......@@ -324,9 +324,7 @@ Handle<JSObject> GetTypeForTable(Isolate* isolate, ValueType type,
// place and then use that constant everywhere.
element = factory->InternalizeUtf8String("anyfunc");
} else {
DCHECK(WasmFeatures::FromFlags().has_reftypes() &&
type.is_reference_to(HeapType::kExtern));
element = factory->InternalizeUtf8String("externref");
element = factory->InternalizeUtf8String(VectorOf(type.name()));
}
Handle<JSFunction> object_function = isolate->object_function();
......
......@@ -100,17 +100,6 @@ struct WasmDataSegment {
bool active = true; // true if copied automatically during instantiation.
};
// Static representation of a wasm indirect call table.
struct WasmTable {
MOVE_ONLY_WITH_DEFAULT_CONSTRUCTORS(WasmTable);
ValueType type = kWasmStmt; // table type.
uint32_t initial_size = 0; // initial table size.
uint32_t maximum_size = 0; // maximum table size.
bool has_maximum_size = false; // true if there is a maximum size.
bool imported = false; // true if imported.
bool exported = false; // true if exported.
};
// Static representation of wasm element segment (table initializer).
struct WasmElemSegment {
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(WasmElemSegment);
......@@ -268,6 +257,8 @@ struct V8_EXPORT_PRIVATE WasmDebugSymbols {
WireBytesRef external_url;
};
struct WasmTable;
// Static representation of a module.
struct V8_EXPORT_PRIVATE WasmModule {
std::unique_ptr<Zone> signature_zone;
......@@ -358,6 +349,30 @@ struct V8_EXPORT_PRIVATE WasmModule {
DISALLOW_COPY_AND_ASSIGN(WasmModule);
};
// Static representation of a wasm indirect call table.
struct WasmTable {
MOVE_ONLY_WITH_DEFAULT_CONSTRUCTORS(WasmTable);
// 'module' can be nullptr
// 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;
HeapType heap_type = type.heap_type();
return heap_type == HeapType::kFunc || heap_type == HeapType::kExtern ||
heap_type == HeapType::kExn ||
(module != nullptr && heap_type.is_index() &&
module->has_signature(heap_type.ref_index()));
}
ValueType type = kWasmStmt; // table type.
uint32_t initial_size = 0; // initial table size.
uint32_t maximum_size = 0; // maximum table size.
bool has_maximum_size = false; // true if there is a maximum size.
bool imported = false; // true if imported.
bool exported = false; // true if exported.
};
inline bool is_asmjs_module(const WasmModule* module) {
return module->origin != kWasmOrigin;
}
......
......@@ -109,6 +109,7 @@ bool WasmModuleObject::is_asm_js() {
}
// WasmTableObject
ACCESSORS(WasmTableObject, instance, HeapObject, kInstanceOffset)
ACCESSORS(WasmTableObject, entries, FixedArray, kEntriesOffset)
SMI_ACCESSORS(WasmTableObject, current_length, kCurrentLengthOffset)
ACCESSORS(WasmTableObject, maximum_length, Object, kMaximumLengthOffset)
......
......@@ -257,13 +257,16 @@ Vector<const uint8_t> WasmModuleObject::GetRawFunctionName(
return Vector<const uint8_t>::cast(name);
}
Handle<WasmTableObject> WasmTableObject::New(Isolate* isolate,
wasm::ValueType type,
uint32_t initial, bool has_maximum,
uint32_t maximum,
Handle<FixedArray>* entries) {
Handle<WasmTableObject> WasmTableObject::New(
Isolate* isolate, Handle<WasmInstanceObject> instance, wasm::ValueType type,
uint32_t initial, bool has_maximum, uint32_t maximum,
Handle<FixedArray>* entries) {
// TODO(7748): Make this work with other types when spec clears up.
CHECK(type.is_nullable());
{
const WasmModule* module =
instance.is_null() ? nullptr : instance->module();
CHECK(wasm::WasmTable::IsValidTableType(type, module));
}
Handle<FixedArray> backing_store = isolate->factory()->NewFixedArray(initial);
Object null = ReadOnlyRoots(isolate).null_value();
......@@ -284,6 +287,7 @@ Handle<WasmTableObject> WasmTableObject::New(Isolate* isolate,
isolate->factory()->NewJSObject(table_ctor));
DisallowHeapAllocation no_gc;
if (!instance.is_null()) table_obj->set_instance(*instance);
table_obj->set_entries(*backing_store);
table_obj->set_current_length(initial);
table_obj->set_maximum_length(*max);
......@@ -388,18 +392,14 @@ bool WasmTableObject::IsInBounds(Isolate* isolate,
bool WasmTableObject::IsValidElement(Isolate* isolate,
Handle<WasmTableObject> table,
Handle<Object> entry) {
// Anyref and exnref tables take everything.
if (table->type().is_reference_to(wasm::HeapType::kExtern) ||
table->type().is_reference_to(wasm::HeapType::kExn)) {
return true;
const char* error_message;
const WasmModule* module =
!table->instance().IsUndefined()
? WasmInstanceObject::cast(table->instance()).module()
: nullptr;
return wasm::TypecheckJSObject(isolate, module, entry, table->type(),
&error_message);
}
// FuncRef tables can store {null}, {WasmExportedFunction}, {WasmJSFunction},
// or {WasmCapiFunction} objects.
if (entry->IsNull(isolate)) return true;
return WasmExportedFunction::IsWasmExportedFunction(*entry) ||
WasmJSFunction::IsWasmJSFunction(*entry) ||
WasmCapiFunction::IsWasmCapiFunction(*entry);
}
void WasmTableObject::Set(Isolate* isolate, Handle<WasmTableObject> table,
uint32_t index, Handle<Object> entry) {
......@@ -1616,7 +1616,7 @@ Handle<WasmExceptionObject> WasmExceptionObject::New(
return exception;
}
bool WasmExceptionObject::IsSignatureEqual(const wasm::FunctionSig* sig) {
bool WasmExceptionObject::MatchesSignature(const wasm::FunctionSig* sig) {
DCHECK_EQ(0, sig->return_count());
DCHECK_LE(sig->parameter_count(), std::numeric_limits<int>::max());
int sig_size = static_cast<int>(sig->parameter_count());
......@@ -1629,7 +1629,7 @@ bool WasmExceptionObject::IsSignatureEqual(const wasm::FunctionSig* sig) {
return true;
}
bool WasmCapiFunction::IsSignatureEqual(const wasm::FunctionSig* sig) const {
bool WasmCapiFunction::MatchesSignature(const wasm::FunctionSig* sig) const {
// TODO(jkummerow): Unify with "SignatureHelper" in c-api.cc.
int param_count = static_cast<int>(sig->parameter_count());
int result_count = static_cast<int>(sig->return_count());
......@@ -2012,9 +2012,9 @@ Handle<AsmWasmData> AsmWasmData::New(
namespace wasm {
bool DynamicTypeCheckRef(Isolate* isolate, const WasmModule* module,
Handle<Object> value, ValueType expected,
const char** error_message) {
bool TypecheckJSObject(Isolate* isolate, const WasmModule* module,
Handle<Object> value, ValueType expected,
const char** error_message) {
DCHECK(expected.is_reference_type());
switch (expected.kind()) {
case ValueType::kOptRef:
......@@ -2023,10 +2023,11 @@ bool DynamicTypeCheckRef(Isolate* isolate, const WasmModule* module,
case ValueType::kRef:
switch (expected.heap_representation()) {
case HeapType::kFunc: {
if (!WasmExternalFunction::IsWasmExternalFunction(*value)) {
if (!(WasmExternalFunction::IsWasmExternalFunction(*value) ||
WasmCapiFunction::IsWasmCapiFunction(*value))) {
*error_message =
"function-typed object must be null (if nullable) or an "
"exported function";
"function-typed object must be null (if nullable) or a Wasm "
"function object";
return false;
}
return true;
......@@ -2085,9 +2086,24 @@ bool DynamicTypeCheckRef(Isolate* isolate, const WasmModule* module,
return true;
}
if (WasmCapiFunction::IsWasmCapiFunction(*value)) {
if (!WasmCapiFunction::cast(*value).MatchesSignature(
module->signature(expected.ref_index()))) {
// Since a WasmCapiFunction cannot refer to indexed types
// (definable in a module), we don't need to invoke
// IsEquivalentType();
*error_message =
"assigned WasmCapiFunction has to be a subtype of the "
"expected type";
return false;
}
return true;
}
*error_message =
"function-typed object must be null (if nullable) or an "
"exported function";
"function-typed object must be null (if nullable) or a Wasm "
"function object";
return false;
}
// TODO(7748): Implement when the JS API for structs/arrays is decided
......
......@@ -191,6 +191,11 @@ class V8_EXPORT_PRIVATE WasmTableObject : public JSObject {
public:
DECL_CAST(WasmTableObject)
// The instance in which this WasmTableObject is defined.
// This field is undefined if the global is defined outside any Wasm module,
// i.e., through the JS API (WebAssembly.Table).
// Because it might be undefined, we declare it as a HeapObject.
DECL_ACCESSORS(instance, HeapObject)
// The entries array is at least as big as {current_length()}, but might be
// bigger to make future growth more efficient.
DECL_ACCESSORS(entries, FixedArray)
......@@ -212,9 +217,10 @@ class V8_EXPORT_PRIVATE WasmTableObject : public JSObject {
static int Grow(Isolate* isolate, Handle<WasmTableObject> table,
uint32_t count, Handle<Object> init_value);
static Handle<WasmTableObject> New(Isolate* isolate, wasm::ValueType type,
uint32_t initial, bool has_maximum,
uint32_t maximum,
static Handle<WasmTableObject> New(Isolate* isolate,
Handle<WasmInstanceObject> instance,
wasm::ValueType type, uint32_t initial,
bool has_maximum, uint32_t maximum,
Handle<FixedArray>* entries);
static void AddDispatchTable(Isolate* isolate, Handle<WasmTableObject> table,
......@@ -605,7 +611,7 @@ class WasmExceptionObject : public JSObject {
// Checks whether the given {sig} has the same parameter types as the
// serialized signature stored within this exception object.
bool IsSignatureEqual(const wasm::FunctionSig* sig);
bool MatchesSignature(const wasm::FunctionSig* sig);
static Handle<WasmExceptionObject> New(Isolate* isolate,
const wasm::FunctionSig* sig,
......@@ -689,7 +695,7 @@ class WasmCapiFunction : public JSFunction {
PodArray<wasm::ValueType> GetSerializedSignature() const;
// Checks whether the given {sig} has the same parameter types as the
// serialized signature stored within this C-API function object.
bool IsSignatureEqual(const wasm::FunctionSig* sig) const;
bool MatchesSignature(const wasm::FunctionSig* sig) const;
DECL_CAST(WasmCapiFunction)
OBJECT_CONSTRUCTORS(WasmCapiFunction, JSFunction);
......@@ -957,9 +963,9 @@ Handle<Map> AllocateSubRtt(Isolate* isolate,
Handle<WasmInstanceObject> instance, uint32_t type,
Handle<Map> parent);
bool DynamicTypeCheckRef(Isolate* isolate, const WasmModule* module,
Handle<Object> value, ValueType expected,
const char** error_message);
bool TypecheckJSObject(Isolate* isolate, const WasmModule* module,
Handle<Object> value, ValueType expected,
const char** error_message);
} // namespace wasm
} // namespace internal
......
......@@ -63,6 +63,7 @@ extern class WasmModuleObject extends JSObject {
}
extern class WasmTableObject extends JSObject {
instance: WasmInstanceObject|Undefined;
entries: FixedArray;
current_length: Smi;
maximum_length: Smi|HeapNumber|Undefined;
......
......@@ -171,7 +171,7 @@ Handle<JSFunction> TestingModuleBuilder::WrapCode(uint32_t index) {
void TestingModuleBuilder::AddIndirectFunctionTable(
const uint16_t* function_indexes, uint32_t table_size) {
auto instance = instance_object();
Handle<WasmInstanceObject> instance = instance_object();
uint32_t table_index = static_cast<uint32_t>(test_module_->tables.size());
test_module_->tables.emplace_back();
WasmTable& table = test_module_->tables.back();
......@@ -197,7 +197,7 @@ void TestingModuleBuilder::AddIndirectFunctionTable(
WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize(
instance_object(), table_index, table_size);
Handle<WasmTableObject> table_obj =
WasmTableObject::New(isolate_, table.type, table.initial_size,
WasmTableObject::New(isolate_, instance, table.type, table.initial_size,
table.has_maximum_size, table.maximum_size, nullptr);
WasmTableObject::AddDispatchTable(isolate_, table_obj, instance_object_,
......
......@@ -61,7 +61,7 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
false);
builder.instantiate({imports: { global: 42 }})},
WebAssembly.LinkError,
/function-typed object must be null \(if nullable\) or an exported function/
/function-typed object must be null \(if nullable\) or a Wasm function object/
);
// Mistyped function import.
......
......@@ -99,7 +99,8 @@ let kWasmI64 = 0x7e;
let kWasmF32 = 0x7d;
let kWasmF64 = 0x7c;
let kWasmS128 = 0x7b;
let kWasmAnyFunc = 0x70;
let kWasmFuncRef = 0x70;
let kWasmAnyFunc = kWasmFuncRef; // Alias named as in the JS API spec
let kWasmExternRef = 0x6f;
function wasmOptRefType(index) { return {opcode: 0x6c, index: index}; }
function wasmRefType(index) { return {opcode: 0x6b, index: index}; }
......
......@@ -111,7 +111,7 @@ class TestModuleBuilder {
byte AddTable(ValueType type, uint32_t initial_size, bool has_maximum_size,
uint32_t maximum_size) {
CHECK(type == kWasmExternRef || type == kWasmFuncRef);
CHECK(WasmTable::IsValidTableType(type, &mod));
mod.tables.emplace_back();
WasmTable& table = mod.tables.back();
table.type = type;
......
......@@ -144,7 +144,9 @@ struct CheckLEB1 : std::integral_constant<size_t, num> {
do { \
ModuleResult result = DecodeModule(data, data + sizeof(data)); \
EXPECT_FALSE(result.ok()); \
EXPECT_THAT(result.error().message(), HasSubstr(msg)); \
if (!result.ok()) { \
EXPECT_THAT(result.error().message(), HasSubstr(msg)); \
} \
} while (false)
#define EXPECT_OFF_END_FAILURE(data, min) \
......
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