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

[wasm-gc] Implement dataref

As per latest wasm-gc spec, the data heap type is a subtype of eq and a
supertype of all array and struct types.
The heap type expected for arrays and structs when interacting with JS
changes from eq to data.

Bug: v8:7748
Change-Id: Idd1670b9e47acc95c098559e674c629ea44ca49d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2649044
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#72399}
parent 5d1b26c8
......@@ -6243,7 +6243,7 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
representation == wasm::HeapType::kFunc) {
return node;
}
if (representation == wasm::HeapType::kEq) {
if (representation == wasm::HeapType::kData) {
// TODO(7748): Update this when JS interop is settled.
return BuildAllocateObjectWrapper(node);
}
......@@ -6387,10 +6387,11 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
case wasm::HeapType::kFunc:
BuildCheckValidRefValue(input, js_context, type);
return input;
case wasm::HeapType::kEq:
case wasm::HeapType::kData:
// TODO(7748): Update this when JS interop has settled.
BuildCheckValidRefValue(input, js_context, type);
return BuildUnpackObjectWrapper(input, kReturnNull);
case wasm::HeapType::kEq:
case wasm::HeapType::kI31:
// If this is reached, then IsJSCompatibleSignature() is too
// permissive.
......
......@@ -185,10 +185,10 @@ V8_INLINE WasmFeature feature_for_heap_type(HeapType heap_type) {
return WasmFeature::kFeature_reftypes;
case HeapType::kEq:
case HeapType::kI31:
case HeapType::kData:
case HeapType::kAny:
return WasmFeature::kFeature_gc;
case HeapType::kBottom:
default:
UNREACHABLE();
}
}
......@@ -214,6 +214,7 @@ HeapType read_heap_type(Decoder* decoder, const byte* pc,
case kEqRefCode:
case kExternRefCode:
case kI31RefCode:
case kDataRefCode:
case kAnyRefCode: {
HeapType result = HeapType::from_code(code);
if (!VALIDATE(enabled.contains(feature_for_heap_type(result)))) {
......@@ -279,10 +280,13 @@ ValueType read_value_type(Decoder* decoder, const byte* pc,
case kEqRefCode:
case kExternRefCode:
case kI31RefCode:
case kDataRefCode:
case kAnyRefCode: {
HeapType heap_type = HeapType::from_code(code);
ValueType result = ValueType::Ref(
heap_type, code == kI31RefCode ? kNonNullable : kNullable);
Nullability nullability = code == kI31RefCode || code == kDataRefCode
? kNonNullable
: kNullable;
ValueType result = ValueType::Ref(heap_type, nullability);
if (!VALIDATE(enabled.contains(feature_for_heap_type(heap_type)))) {
DecodeError<validate>(
decoder, pc,
......
......@@ -57,6 +57,7 @@ class HeapType {
kExtern, // shorthand: e
kEq, // shorthand: q
kI31, // shorthand: j
kData, // shorthand: o
kAny, // shorthand: a
// This value is used to represent failures in the parsing of heap types and
// does not correspond to a wasm heap type.
......@@ -79,6 +80,8 @@ class HeapType {
return HeapType(kI31);
case ValueTypeCode::kAnyRefCode:
return HeapType(kAny);
case ValueTypeCode::kDataRefCode:
return HeapType(kData);
default:
return HeapType(kBottom);
}
......@@ -129,6 +132,8 @@ class HeapType {
return std::string("eq");
case kI31:
return std::string("i31");
case kData:
return std::string("data");
case kAny:
return std::string("any");
default:
......@@ -150,6 +155,8 @@ class HeapType {
return mask | kEqRefCode;
case kI31:
return mask | kI31RefCode;
case kData:
return mask | kDataRefCode;
case kAny:
return mask | kAnyRefCode;
default:
......@@ -381,8 +388,14 @@ class ValueType {
return kOptRefCode;
}
case kRef:
if (heap_representation() == HeapType::kI31) return kI31RefCode;
return kRefCode;
switch (heap_representation()) {
case HeapType::kI31:
return kI31RefCode;
case HeapType::kData:
return kDataRefCode;
default:
return kRefCode;
}
case kStmt:
return kVoidCode;
case kRtt:
......@@ -403,9 +416,11 @@ class ValueType {
// Returns true iff the heap type is needed to encode this type in the wasm
// binary format, taking into account available type shorthands.
constexpr bool encoding_needs_heap_type() const {
return (kind() == kRef && heap_representation() != HeapType::kI31) ||
(kind() == kOptRef && (!heap_type().is_generic() ||
heap_representation() == HeapType::kI31));
return (kind() == kRef && heap_representation() != HeapType::kI31 &&
heap_representation() != HeapType::kData) ||
(kind() == kOptRef && (heap_type().is_index() ||
heap_representation() == HeapType::kI31 ||
heap_representation() == HeapType::kData));
}
static constexpr int kLastUsedBit = 30;
......@@ -425,19 +440,12 @@ class ValueType {
std::ostringstream buf;
switch (kind()) {
case kRef:
if (heap_representation() == HeapType::kI31) {
buf << "i31ref";
} else {
buf << "(ref " << heap_type().name() << ")";
}
break;
case kOptRef:
if (heap_type().is_generic() &&
heap_representation() != HeapType::kI31) {
// We use shorthands to be compatible with the 'reftypes' proposal.
buf << heap_type().name() << "ref";
if (encoding_needs_heap_type()) {
buf << "(ref " << (kind() == kOptRef ? "null " : "")
<< heap_type().name() << ")";
} else {
buf << "(ref null " << heap_type().name() << ")";
buf << heap_type().name() << "ref";
}
break;
case kRttWithDepth:
......@@ -511,12 +519,14 @@ constexpr ValueType kWasmI8 = ValueType::Primitive(ValueType::kI8);
constexpr ValueType kWasmI16 = ValueType::Primitive(ValueType::kI16);
constexpr ValueType kWasmStmt = ValueType::Primitive(ValueType::kStmt);
constexpr ValueType kWasmBottom = ValueType::Primitive(ValueType::kBottom);
// Established wasm shorthands:
// Established reference-type proposal shorthands.
constexpr ValueType kWasmFuncRef = ValueType::Ref(HeapType::kFunc, kNullable);
constexpr ValueType kWasmExternRef =
ValueType::Ref(HeapType::kExtern, kNullable);
constexpr ValueType kWasmEqRef = ValueType::Ref(HeapType::kEq, kNullable);
constexpr ValueType kWasmI31Ref = ValueType::Ref(HeapType::kI31, kNonNullable);
constexpr ValueType kWasmDataRef =
ValueType::Ref(HeapType::kData, kNonNullable);
constexpr ValueType kWasmAnyRef = ValueType::Ref(HeapType::kAny, kNullable);
#define FOREACH_WASMVALUE_CTYPES(V) \
......
......@@ -40,6 +40,7 @@ enum ValueTypeCode : uint8_t {
kI31RefCode = 0x6a,
kRttWithDepthCode = 0x69,
kRttCode = 0x68,
kDataRefCode = 0x67,
};
// Binary encoding of other types.
constexpr uint8_t kWasmFunctionTypeCode = 0x60;
......
......@@ -1368,10 +1368,15 @@ void WebAssemblyGlobal(const v8::FunctionCallbackInfo<v8::Value>& args) {
}
break;
}
case internal::wasm::HeapType::kBottom:
UNREACHABLE();
case i::wasm::HeapType::kEq:
case internal::wasm::HeapType::kI31:
case internal::wasm::HeapType::kData:
default:
// TODO(7748): Implement these.
UNIMPLEMENTED();
break;
}
break;
}
......@@ -1840,6 +1845,10 @@ void WebAssemblyGlobalGetValueCommon(
case i::wasm::HeapType::kAny:
return_value.Set(Utils::ToLocal(receiver->GetRef()));
break;
case internal::wasm::HeapType::kBottom:
UNREACHABLE();
case internal::wasm::HeapType::kI31:
case internal::wasm::HeapType::kData:
case i::wasm::HeapType::kEq:
default:
// TODO(7748): Implement these.
......@@ -1932,7 +1941,10 @@ void WebAssemblyGlobalSetValue(
}
break;
}
case internal::wasm::HeapType::kBottom:
UNREACHABLE();
case internal::wasm::HeapType::kI31:
case internal::wasm::HeapType::kData:
case i::wasm::HeapType::kEq:
default:
// TODO(7748): Implement these.
......
......@@ -452,6 +452,7 @@ void WasmTableObject::Set(Isolate* isolate, Handle<WasmTableObject> table,
SetFunctionTableEntry(isolate, table, entries, entry_index, entry);
return;
case wasm::HeapType::kEq:
case wasm::HeapType::kData:
case wasm::HeapType::kI31:
// TODO(7748): Implement once we have a story for struct/arrays/i31ref in
// JS.
......@@ -499,6 +500,7 @@ Handle<Object> WasmTableObject::Get(Isolate* isolate,
break;
case wasm::HeapType::kEq:
case wasm::HeapType::kI31:
case wasm::HeapType::kData:
case wasm::HeapType::kAny:
// TODO(7748): Implement once we have a story for struct/arrays/i31ref in
// JS.
......@@ -2111,7 +2113,7 @@ bool TypecheckJSObject(Isolate* isolate, const WasmModule* module,
case HeapType::kExtern:
case HeapType::kAny:
return true;
case HeapType::kEq: {
case HeapType::kData: {
// TODO(7748): Change this when we have a decision on the JS API for
// structs/arrays.
Handle<Name> key = isolate->factory()->wasm_wrapped_object_symbol();
......@@ -2119,13 +2121,15 @@ bool TypecheckJSObject(Isolate* isolate, const WasmModule* module,
LookupIterator::OWN_SKIP_INTERCEPTOR);
if (it.state() == LookupIterator::DATA) return true;
*error_message =
"eqref object must be null (if nullable) or wrapped with wasm "
"object wrapper";
"dataref object must be null (if nullable) or wrapped with the "
"wasm object wrapper";
return false;
}
case HeapType::kEq:
case HeapType::kI31:
// TODO(7748): Implement when the JS API for i31ref is decided on.
*error_message = "Assigning JS objects to i31ref not supported yet.";
*error_message =
"Assigning JS objects to eqref/i31ref not supported yet.";
return false;
default:
// Tables defined outside a module can't refer to user-defined types.
......
......@@ -41,7 +41,8 @@ bool IsJSCompatibleSignature(const FunctionSig* sig, const WasmModule* module,
for (auto type : sig->all()) {
// TODO(7748): Allow structs, arrays, rtts and i31s when their
// JS-interaction is decided on.
if (type == kWasmS128 || type.is_reference_to(HeapType::kI31) ||
if (type == kWasmS128 || type.is_reference_to(HeapType::kEq) ||
type.is_reference_to(HeapType::kI31) ||
(type.has_index() && !module->has_signature(type.ref_index())) ||
type.is_rtt()) {
return false;
......
......@@ -326,7 +326,8 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(
case HeapType::kAny:
return super_heap == HeapType::kAny;
case HeapType::kI31:
return super_heap == HeapType::kI31 || super_heap == HeapType::kEq ||
case HeapType::kData:
return super_heap == sub_heap || super_heap == HeapType::kEq ||
super_heap == HeapType::kAny;
case HeapType::kBottom:
UNREACHABLE();
......@@ -342,6 +343,7 @@ V8_NOINLINE V8_EXPORT_PRIVATE bool IsSubtypeOfImpl(
case HeapType::kFunc:
return sub_module->has_signature(sub_index);
case HeapType::kEq:
case HeapType::kData:
return !sub_module->has_signature(sub_index);
case HeapType::kExtern:
case HeapType::kI31:
......
......@@ -45,10 +45,14 @@ V8_NOINLINE bool EquivalentTypes(ValueType type1, ValueType type2,
// - ref(ht1) <: ref/optref(ht2) iff ht1 <: ht2.
// - rtt1 <: rtt2 iff rtt1 ~ rtt2.
// For heap types, the following subtyping rules hold:
// - Each generic heap type is a subtype of itself.
// - All heap types are subtypes of any.
// - The abstract heap types form the following type hierarchy:
// any
// / | \
// eq func extern
// / \
// i31 data
// - All structs and arrays are subtypes of data.
// - All functions are subtypes of func.
// - i31, structs and arrays are subtypes of eq.
// - Struct subtyping: Subtype must have at least as many fields as supertype,
// covariance for immutable fields, equivalence for mutable fields.
// - Array subtyping (mutable only) is the equivalence relation.
......
......@@ -1251,72 +1251,70 @@ WASM_COMPILED_EXEC_TEST(IndirectNullSetManually) {
}
TEST(JsAccess) {
for (ValueType supertype : {kWasmEqRef, kWasmAnyRef}) {
WasmGCTester tester;
const byte type_index = tester.DefineStruct({F(wasm::kWasmI32, true)});
ValueType kRefType = ref(type_index);
ValueType kSupertypeToI[] = {kWasmI32, supertype};
FunctionSig sig_t_v(1, 0, &kRefType);
FunctionSig sig_super_v(1, 0, &supertype);
FunctionSig sig_i_super(1, 1, kSupertypeToI);
tester.DefineExportedFunction(
"disallowed", &sig_t_v,
{WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42),
WASM_RTT_CANON(type_index)),
kExprEnd});
// Same code, different signature.
tester.DefineExportedFunction(
"producer", &sig_super_v,
{WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42),
WASM_RTT_CANON(type_index)),
kExprEnd});
tester.DefineExportedFunction(
"consumer", &sig_i_super,
{WASM_STRUCT_GET(type_index, 0,
WASM_REF_CAST(type_index, WASM_LOCAL_GET(0),
WASM_RTT_CANON(type_index))),
kExprEnd});
tester.CompileModule();
Isolate* isolate = tester.isolate();
TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate));
MaybeHandle<Object> maybe_result =
tester.CallExportedFunction("disallowed", 0, nullptr);
CHECK(maybe_result.is_null());
CHECK(try_catch.HasCaught());
try_catch.Reset();
isolate->clear_pending_exception();
maybe_result = tester.CallExportedFunction("producer", 0, nullptr);
if (maybe_result.is_null()) {
FATAL("Calling 'producer' failed: %s",
*v8::String::Utf8Value(reinterpret_cast<v8::Isolate*>(isolate),
try_catch.Message()->Get()));
}
{
Handle<Object> args[] = {maybe_result.ToHandleChecked()};
maybe_result = tester.CallExportedFunction("consumer", 1, args);
}
if (maybe_result.is_null()) {
FATAL("Calling 'consumer' failed: %s",
*v8::String::Utf8Value(reinterpret_cast<v8::Isolate*>(isolate),
try_catch.Message()->Get()));
}
Handle<Object> result = maybe_result.ToHandleChecked();
CHECK(result->IsSmi());
CHECK_EQ(42, Smi::cast(*result).value());
// Calling {consumer} with any other object (e.g. the Smi we just got as
// {result}) should trap.
{
Handle<Object> args[] = {result};
maybe_result = tester.CallExportedFunction("consumer", 1, args);
}
CHECK(maybe_result.is_null());
CHECK(try_catch.HasCaught());
try_catch.Reset();
isolate->clear_pending_exception();
WasmGCTester tester;
const byte type_index = tester.DefineStruct({F(wasm::kWasmI32, true)});
ValueType kRefType = ref(type_index);
ValueType kSupertypeToI[] = {kWasmI32, kWasmDataRef};
FunctionSig sig_t_v(1, 0, &kRefType);
FunctionSig sig_super_v(1, 0, &kWasmDataRef);
FunctionSig sig_i_super(1, 1, kSupertypeToI);
tester.DefineExportedFunction(
"disallowed", &sig_t_v,
{WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42),
WASM_RTT_CANON(type_index)),
kExprEnd});
// Same code, different signature.
tester.DefineExportedFunction(
"producer", &sig_super_v,
{WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42),
WASM_RTT_CANON(type_index)),
kExprEnd});
tester.DefineExportedFunction(
"consumer", &sig_i_super,
{WASM_STRUCT_GET(type_index, 0,
WASM_REF_CAST(type_index, WASM_LOCAL_GET(0),
WASM_RTT_CANON(type_index))),
kExprEnd});
tester.CompileModule();
Isolate* isolate = tester.isolate();
TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate));
MaybeHandle<Object> maybe_result =
tester.CallExportedFunction("disallowed", 0, nullptr);
CHECK(maybe_result.is_null());
CHECK(try_catch.HasCaught());
try_catch.Reset();
isolate->clear_pending_exception();
maybe_result = tester.CallExportedFunction("producer", 0, nullptr);
if (maybe_result.is_null()) {
FATAL("Calling 'producer' failed: %s",
*v8::String::Utf8Value(reinterpret_cast<v8::Isolate*>(isolate),
try_catch.Message()->Get()));
}
{
Handle<Object> args[] = {maybe_result.ToHandleChecked()};
maybe_result = tester.CallExportedFunction("consumer", 1, args);
}
if (maybe_result.is_null()) {
FATAL("Calling 'consumer' failed: %s",
*v8::String::Utf8Value(reinterpret_cast<v8::Isolate*>(isolate),
try_catch.Message()->Get()));
}
Handle<Object> result = maybe_result.ToHandleChecked();
CHECK(result->IsSmi());
CHECK_EQ(42, Smi::cast(*result).value());
// Calling {consumer} with any other object (e.g. the Smi we just got as
// {result}) should trap.
{
Handle<Object> args[] = {result};
maybe_result = tester.CallExportedFunction("consumer", 1, args);
}
CHECK(maybe_result.is_null());
CHECK(try_catch.HasCaught());
try_catch.Reset();
isolate->clear_pending_exception();
}
} // namespace test_gc
......
......@@ -3026,7 +3026,11 @@ class WasmInterpreterInternals {
encoded_values->set(encoded_index++, *externref);
break;
}
case HeapType::kBottom:
UNREACHABLE();
case HeapType::kEq:
case HeapType::kData:
case HeapType::kI31:
default:
// TODO(7748): Implement these.
UNIMPLEMENTED();
......
......@@ -57,9 +57,9 @@ TEST_F(WasmSubtypingTest, Subtyping) {
ValueType numeric_types[] = {kWasmI32, kWasmI64, kWasmF32, kWasmF64,
kWasmS128};
ValueType ref_types[] = {kWasmExternRef, kWasmFuncRef, kWasmEqRef,
kWasmI31Ref, kWasmAnyRef, optRef(0),
ref(0), optRef(2), ref(2)};
ValueType ref_types[] = {
kWasmExternRef, kWasmFuncRef, kWasmEqRef, kWasmI31Ref, kWasmDataRef,
kWasmAnyRef, optRef(0), ref(0), optRef(2), ref(2)};
// Type judgements across modules should work the same as within one module.
for (WasmModule* module : {module1, module2}) {
......@@ -80,11 +80,16 @@ TEST_F(WasmSubtypingTest, Subtyping) {
}
for (ValueType ref_type : ref_types) {
// Concrete reference types and i31ref are subtypes of eqref,
// Concrete reference types, i31ref and dataref are subtypes of eqref,
// externref/funcref/anyref are not.
CHECK_EQ(IsSubtypeOf(ref_type, kWasmEqRef, module1, module),
ref_type != kWasmFuncRef && ref_type != kWasmExternRef &&
ref_type != kWasmAnyRef);
// Non-nullable struct/array types are subtypes of dataref.
CHECK_EQ(
IsSubtypeOf(ref_type, kWasmDataRef, module1, module),
ref_type == kWasmDataRef ||
(ref_type.kind() == ValueType::kRef && ref_type.has_index()));
// Each reference type is a subtype of itself.
CHECK(IsSubtypeOf(ref_type, ref_type, module1, module));
// Each reference type is a subtype of anyref.
......
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