Commit db245ed8 authored by Igor Sheludko's avatar Igor Sheludko Committed by V8 LUCI CQ

[wasm-gc] Support WasmObject field loading in runtime

The new functionality is hidden behind the --wasm-gc-js-interop flag.

Bug: v8:11804
Change-Id: I9dd779efe3dbf3c773948b6fd8872e3aea8cd7a6
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2912784
Commit-Queue: Igor Sheludko <ishell@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#74790}
parent f72ec739
......@@ -6270,6 +6270,7 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
// through JavaScript, where they show up as opaque boxes. This will disappear
// once we have a proper WasmGC <-> JS interaction story.
Node* BuildAllocateObjectWrapper(Node* input) {
if (FLAG_wasm_gc_js_interop) return input;
return gasm_->CallBuiltin(
Builtins::kWasmAllocateObjectWrapper, Operator::kEliminatable, input,
LOAD_INSTANCE_FIELD(NativeContext, MachineType::TaggedPointer()));
......@@ -6280,6 +6281,7 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
// {wasm_wrapped_object_symbol} in {input}, or {input} itself if the property
// is not found.
Node* BuildUnpackObjectWrapper(Node* input) {
if (FLAG_wasm_gc_js_interop) return input;
Node* obj = gasm_->CallBuiltin(
Builtins::kWasmGetOwnProperty, Operator::kEliminatable, input,
LOAD_ROOT(wasm_wrapped_object_symbol, wasm_wrapped_object_symbol),
......
......@@ -969,6 +969,8 @@ FOREACH_WASM_FEATURE_FLAG(DECL_WASM_FLAG)
DEFINE_IMPLICATION(experimental_wasm_gc, experimental_wasm_typed_funcref)
DEFINE_IMPLICATION(experimental_wasm_typed_funcref, experimental_wasm_reftypes)
DEFINE_BOOL(wasm_gc_js_interop, false, "experimental WasmGC-JS interop")
DEFINE_BOOL(wasm_staging, false, "enable staged wasm features")
#define WASM_STAGING_IMPLICATION(feat, desc, val) \
......
......@@ -863,15 +863,12 @@ Handle<Object> LoadIC::ComputeHandler(LookupIterator* lookup) {
}
Handle<Map> map = lookup_start_object_map();
Handle<JSObject> holder;
bool holder_is_lookup_start_object;
if (lookup->state() != LookupIterator::JSPROXY) {
holder = lookup->GetHolder<JSObject>();
holder_is_lookup_start_object = lookup_start_object.is_identical_to(holder);
}
bool holder_is_lookup_start_object =
lookup_start_object.is_identical_to(lookup->GetHolder<JSReceiver>());
switch (lookup->state()) {
case LookupIterator::INTERCEPTOR: {
Handle<JSObject> holder = lookup->GetHolder<JSObject>();
Handle<Smi> smi_handler = LoadHandler::LoadInterceptor(isolate());
if (holder->GetNamedInterceptor().non_masking()) {
......@@ -896,6 +893,7 @@ Handle<Object> LoadIC::ComputeHandler(LookupIterator* lookup) {
}
case LookupIterator::ACCESSOR: {
Handle<JSObject> holder = lookup->GetHolder<JSObject>();
// Use simple field loads for some well-known callback properties.
// The method will only return true for absolute truths based on the
// lookup start object maps.
......@@ -1024,10 +1022,11 @@ Handle<Object> LoadIC::ComputeHandler(LookupIterator* lookup) {
}
case LookupIterator::DATA: {
Handle<JSReceiver> holder = lookup->GetHolder<JSReceiver>();
DCHECK_EQ(kData, lookup->property_details().kind());
Handle<Smi> smi_handler;
if (lookup->is_dictionary_holder()) {
if (holder->IsJSGlobalObject()) {
if (holder->IsJSGlobalObject(isolate())) {
// TODO(verwaest): Also supporting the global object as receiver is a
// workaround for code that leaks the global object.
TRACE_HANDLER_STATS(isolate(), LoadIC_LoadGlobalDH);
......@@ -1045,8 +1044,16 @@ Handle<Object> LoadIC::ComputeHandler(LookupIterator* lookup) {
return LoadHandler::LoadSlow(isolate());
} else {
DCHECK_EQ(kField, lookup->property_details().location());
FieldIndex field = lookup->GetFieldIndex();
smi_handler = LoadHandler::LoadField(isolate(), field);
#if V8_ENABLE_WEBASSEMBLY
if (V8_UNLIKELY(holder->IsWasmObject(isolate()))) {
smi_handler = LoadHandler::LoadSlow(isolate());
} else // NOLINT(readability/braces)
#endif // V8_ENABLE_WEBASSEMBLY
{
DCHECK(holder->IsJSObject(isolate()));
FieldIndex field = lookup->GetFieldIndex();
smi_handler = LoadHandler::LoadField(isolate(), field);
}
TRACE_HANDLER_STATS(isolate(), LoadIC_LoadFieldDH);
if (holder_is_lookup_start_object) return smi_handler;
TRACE_HANDLER_STATS(isolate(), LoadIC_LoadFieldFromPrototypeDH);
......@@ -1085,14 +1092,12 @@ Handle<Object> LoadIC::ComputeHandler(LookupIterator* lookup) {
case LookupIterator::INTEGER_INDEXED_EXOTIC:
TRACE_HANDLER_STATS(isolate(), LoadIC_LoadIntegerIndexedExoticDH);
return LoadHandler::LoadNonExistent(isolate());
case LookupIterator::JSPROXY: {
Handle<JSProxy> holder_proxy = lookup->GetHolder<JSProxy>();
bool holder_proxy_is_lookup_start_object =
lookup->lookup_start_object().is_identical_to(holder_proxy);
Handle<Smi> smi_handler = LoadHandler::LoadProxy(isolate());
if (holder_proxy_is_lookup_start_object) {
return smi_handler;
}
if (holder_is_lookup_start_object) return smi_handler;
Handle<JSProxy> holder_proxy = lookup->GetHolder<JSProxy>();
return LoadHandler::LoadFromPrototype(isolate(), map, holder_proxy,
smi_handler);
}
......
......@@ -19,6 +19,10 @@
#include "src/objects/ordered-hash-table.h"
#include "src/objects/struct-inl.h"
#if V8_ENABLE_WEBASSEMBLY
#include "src/wasm/wasm-objects-inl.h"
#endif // V8_ENABLE_WEBASSEMBLY
namespace v8 {
namespace internal {
......@@ -852,6 +856,17 @@ Handle<Object> LookupIterator::FetchValue(
AllocationPolicy allocation_policy) const {
Object result;
if (IsElement(*holder_)) {
#if V8_ENABLE_WEBASSEMBLY
if (V8_UNLIKELY(holder_->IsWasmObject(isolate_))) {
if (holder_->IsWasmStruct()) {
// WasmStructs don't have elements.
return isolate_->factory()->undefined_value();
}
Handle<WasmArray> holder = GetHolder<WasmArray>();
return WasmArray::GetElement(isolate_, holder, number_.as_uint32());
}
#endif // V8_ENABLE_WEBASSEMBLY
DCHECK(holder_->IsJSObject(isolate_));
Handle<JSObject> holder = GetHolder<JSObject>();
ElementsAccessor* accessor = holder->GetElementsAccessor(isolate_);
return accessor->Get(holder, number_);
......@@ -869,6 +884,27 @@ Handle<Object> LookupIterator::FetchValue(
}
} else if (property_details_.location() == kField) {
DCHECK_EQ(kData, property_details_.kind());
#if V8_ENABLE_WEBASSEMBLY
if (V8_UNLIKELY(holder_->IsWasmObject(isolate_))) {
if (allocation_policy == AllocationPolicy::kAllocationDisallowed) {
// TODO(ishell): consider taking field type into account and relaxing
// this a bit.
return isolate_->factory()->undefined_value();
}
if (holder_->IsWasmArray(isolate_)) {
// WasmArrays don't have other named properties besides "length".
DCHECK_EQ(*name_, ReadOnlyRoots(isolate_).length_string());
Handle<WasmArray> holder = GetHolder<WasmArray>();
uint32_t length = holder->length();
return isolate_->factory()->NewNumberFromUint(length);
}
Handle<WasmStruct> holder = GetHolder<WasmStruct>();
return WasmStruct::GetField(isolate_, holder,
property_details_.field_index());
}
#endif // V8_ENABLE_WEBASSEMBLY
DCHECK(holder_->IsJSObject(isolate_));
Handle<JSObject> holder = GetHolder<JSObject>();
FieldIndex field_index =
FieldIndex::ForDescriptor(holder->map(isolate_), descriptor_number());
......
......@@ -416,6 +416,47 @@ ACCESSORS(AsmWasmData, managed_native_module, Managed<wasm::NativeModule>,
ACCESSORS(AsmWasmData, export_wrappers, FixedArray, kExportWrappersOffset)
ACCESSORS(AsmWasmData, uses_bitset, HeapNumber, kUsesBitsetOffset)
// static
Handle<Object> WasmObject::ReadValueAt(Isolate* isolate, Handle<HeapObject> obj,
wasm::ValueType type, uint32_t offset) {
Address field_address = obj->GetFieldAddress(offset);
switch (type.kind()) {
case wasm::kI8: {
int8_t value = base::Memory<int8_t>(field_address);
return handle(Smi::FromInt(value), isolate);
}
case wasm::kI16: {
int16_t value = base::Memory<int16_t>(field_address);
return handle(Smi::FromInt(value), isolate);
}
case wasm::kI32: {
int32_t value = base::Memory<int32_t>(field_address);
return isolate->factory()->NewNumberFromInt(value);
}
case wasm::kI64:
case wasm::kF32:
case wasm::kF64:
case wasm::kS128:
// TODO(ishell): implement
UNREACHABLE();
case wasm::kRef:
case wasm::kOptRef: {
ObjectSlot slot(field_address);
return handle(slot.load(isolate), isolate);
}
case wasm::kRtt:
case wasm::kRttWithDepth:
// Rtt values are not supposed to be made available to JavaScript side.
UNREACHABLE();
case wasm::kVoid:
case wasm::kBottom:
UNREACHABLE();
}
}
wasm::StructType* WasmStruct::type(Map map) {
WasmTypeInfo type_info = map.wasm_type_info();
return reinterpret_cast<wasm::StructType*>(type_info.foreign_address());
......@@ -456,6 +497,16 @@ ObjectSlot WasmStruct::RawField(int raw_offset) {
return ObjectSlot(RawFieldAddress(raw_offset));
}
// static
Handle<Object> WasmStruct::GetField(Isolate* isolate, Handle<WasmStruct> obj,
uint32_t field_index) {
wasm::StructType* type = obj->type();
CHECK_LT(field_index, type->field_count());
wasm::ValueType field_type = type->field(field_index);
int offset = WasmStruct::kHeaderSize + type->field_offset(field_index);
return ReadValueAt(isolate, obj, field_type, offset);
}
wasm::ArrayType* WasmArray::type(Map map) {
DCHECK_EQ(WASM_ARRAY_TYPE, map.instance_type());
WasmTypeInfo type_info = map.wasm_type_info();
......@@ -484,6 +535,18 @@ int WasmArray::GcSafeSizeFor(Map map, int length) {
return kHeaderSize + RoundUp(element_size * length, kTaggedSize);
}
// static
Handle<Object> WasmArray::GetElement(Isolate* isolate, Handle<WasmArray> array,
uint32_t index) {
if (index >= array->length()) {
return isolate->factory()->undefined_value();
}
wasm::ValueType element_type = array->type()->element_type();
uint32_t offset =
WasmArray::kHeaderSize + index * element_type.element_size_bytes();
return ReadValueAt(isolate, array, element_type, offset);
}
void WasmTypeInfo::clear_foreign_address(Isolate* isolate) {
#ifdef V8_HEAP_SANDBOX
// Due to the type-specific pointer tags for external pointers, we need to
......
......@@ -2181,18 +2181,20 @@ bool TypecheckJSObject(Isolate* isolate, const WasmModule* module,
case HeapType::kI31: {
// 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();
LookupIterator it(isolate, value, key,
LookupIterator::OWN_SKIP_INTERCEPTOR);
if (it.state() != LookupIterator::DATA) {
*error_message =
"eqref/dataref/i31ref object must be null (if nullable) or "
"wrapped with the wasm object wrapper";
return false;
if (!FLAG_wasm_gc_js_interop) {
Handle<Name> key = isolate->factory()->wasm_wrapped_object_symbol();
LookupIterator it(isolate, value, key,
LookupIterator::OWN_SKIP_INTERCEPTOR);
if (it.state() != LookupIterator::DATA) {
*error_message =
"eqref/dataref/i31ref object must be null (if nullable) or "
"wrapped with the wasm object wrapper";
return false;
}
value = it.GetDataValue();
}
if (expected.is_reference_to(HeapType::kEq)) return true;
Handle<Object> value = it.GetDataValue();
if (expected.is_reference_to(HeapType::kData)) {
if (value->IsSmi()) {
......
......@@ -941,6 +941,14 @@ class WasmObject : public JSReceiver {
DECL_CAST(WasmObject)
DECL_VERIFIER(WasmObject)
protected:
// Returns boxed value of the object's field/element with given type and
// offset.
static inline Handle<Object> ReadValueAt(Isolate* isolate,
Handle<HeapObject> obj,
wasm::ValueType type,
uint32_t offset);
OBJECT_CONSTRUCTORS(WasmObject, JSReceiver);
};
......@@ -960,6 +968,11 @@ class WasmStruct : public TorqueGeneratedWasmStruct<WasmStruct, WasmObject> {
wasm::WasmValue GetFieldValue(uint32_t field_index);
// Returns boxed value of the object's field.
static inline Handle<Object> GetField(Isolate* isolate,
Handle<WasmStruct> obj,
uint32_t field_index);
DECL_CAST(WasmStruct)
DECL_PRINTER(WasmStruct)
......@@ -979,6 +992,11 @@ class WasmArray : public TorqueGeneratedWasmArray<WasmArray, WasmObject> {
static inline int SizeFor(Map map, int length);
static inline int GcSafeSizeFor(Map map, int length);
// Returns boxed value of the array's element.
static inline Handle<Object> GetElement(Isolate* isolate,
Handle<WasmArray> array,
uint32_t index);
DECL_CAST(WasmArray)
DECL_PRINTER(WasmArray)
......
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --experimental-wasm-gc --wasm-gc-js-interop
// Flags: --expose-gc
// Flags: --no-lazy-feedback-allocation
load("test/mjsunit/wasm/wasm-module-builder.js");
// TODO(ishell): remove once leaked maps could keep NativeModule alive.
let instances = [];
function createArray_i() {
let builder = new WasmModuleBuilder();
const type_index = builder.addArray(kWasmI32);
let sig_a_i = makeSig_r_x(kWasmDataRef, kWasmI32);
let sig_i_ai = makeSig([kWasmDataRef, kWasmI32], [kWasmI32]);
let sig_v_aii = makeSig([kWasmDataRef, kWasmI32, kWasmI32], []);
builder.addFunction("new_array", sig_a_i)
.addBody([
kExprLocalGet, 0, // --
kExprI32Const, 10, // --
kGCPrefix, kExprRttCanon, type_index, // --
kGCPrefix, kExprArrayNewWithRtt, type_index]) // --
.exportAs("new_array");
builder.addFunction("array_get", sig_i_ai)
.addBody([
kExprLocalGet, 0, // --
kGCPrefix, kExprRttCanon, type_index, // --
kGCPrefix, kExprRefCast, // --
kExprLocalGet, 1, // --
kGCPrefix, kExprArrayGet, type_index]) // --
.exportAs("array_get");
builder.addFunction("array_set", sig_v_aii)
.addBody([
kExprLocalGet, 0, // --
kGCPrefix, kExprRttCanon, type_index, // --
kGCPrefix, kExprRefCast, // --
kExprLocalGet, 1, // --
kExprLocalGet, 2, // --
kGCPrefix, kExprArraySet, type_index]) // --
.exportAs("array_set");
let instance = builder.instantiate();
instances.push(instance);
let new_array = instance.exports.new_array;
let array_get = instance.exports.array_get;
let array_set = instance.exports.array_set;
let value = 42;
let o = new_array(value);
%DebugPrint(o);
assertEquals(10, o.length);
for (let i = 0; i < o.length; i++) {
let res;
res = array_get(o, i);
assertEquals(value, res);
array_set(o, i, i);
res = array_get(o, i);
assertEquals(i, res);
}
return o;
}
(function TestSimpleArrayInterop() {
function f(o) {
for (let i = 0; i < 4; i++) {
let len = o.length;
print("len = " + len);
// Keyed loads are not supported yet
// let v0 = o[0];
// %DebugPrint(v0);
// let v1 = o[1];
// %DebugPrint(v1);
}
}
let o = createArray_i();
%DebugPrint(o);
f(o);
gc();
})();
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --experimental-wasm-gc --wasm-gc-js-interop
// Flags: --expose-gc
// Flags: --no-lazy-feedback-allocation
load("test/mjsunit/wasm/wasm-module-builder.js");
// TODO(ishell): remove once leaked maps could keep NativeModule alive.
let instances = [];
function createStruct_i() {
let builder = new WasmModuleBuilder();
const type_index = builder.addStruct([
{type: kWasmI32, mutability: true},
]);
let sig_a_i = makeSig_r_x(kWasmDataRef, kWasmI32);
let sig_i_a = makeSig_r_x(kWasmI32, kWasmDataRef);
let sig_v_ai = makeSig([kWasmDataRef, kWasmI32], []);
builder.addFunction("new_struct", sig_a_i)
.addBody([
kExprLocalGet, 0, // --
kGCPrefix, kExprRttCanon, type_index, // --
kGCPrefix, kExprStructNewWithRtt, type_index]) // --
.exportAs("new_struct");
builder.addFunction("get_field", sig_i_a)
.addBody([
kExprLocalGet, 0, // --
kGCPrefix, kExprRttCanon, type_index, // --
kGCPrefix, kExprRefCast, // --
kGCPrefix, kExprStructGet, type_index, 0]) // --
.exportAs("get_field");
builder.addFunction("set_field", sig_v_ai)
.addBody([
kExprLocalGet, 0, // --
kGCPrefix, kExprRttCanon, type_index, // --
kGCPrefix, kExprRefCast, // --
kExprLocalGet, 1, // --
kGCPrefix, kExprStructSet, type_index, 0]) // --
.exportAs("set_field");
let instance = builder.instantiate();
instances.push(instance);
let new_struct = instance.exports.new_struct;
let get_field = instance.exports.get_field;
let set_field = instance.exports.set_field;
let value = 42;
let o = new_struct(value);
%DebugPrint(o);
let res;
res = get_field(o);
assertEquals(value, res);
set_field(o, 153);
res = get_field(o);
assertEquals(153, res);
return o;
}
(function TestSimpleStructInterop() {
function f(o) {
for (let i = 0; i < 4; i++) {
let v = o.$field0;
assertEquals(v, 153);
}
}
let o = createStruct_i();
%DebugPrint(o);
f(o);
gc();
})();
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