Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "wasm/WasmGcObject-inl.h"
#include <algorithm>
#include "gc/Tracer.h"
#include "js/CharacterEncoding.h"
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/PropertySpec.h"
#include "js/ScalarType.h" // js::Scalar::Type
#include "js/Vector.h"
#include "util/StringBuilder.h"
#include "vm/GlobalObject.h"
#include "vm/JSFunction.h"
#include "vm/JSObject.h"
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/PropertyResult.h"
#include "vm/Realm.h"
#include "vm/SelfHosting.h"
#include "vm/StringType.h"
#include "vm/TypedArrayObject.h"
#include "vm/Uint8Clamped.h"
#include "gc/BufferAllocator-inl.h"
#include "gc/GCContext-inl.h" // GCContext::removeCellMemory
#include "gc/ObjectKind-inl.h"
#include "vm/JSContext-inl.h"
using namespace js;
using namespace wasm;
// [SMDOC] Management of OOL storage areas for Wasm{Array,Struct}Object.
//
// WasmArrayObject always has its payload data stored in a block which is
// pointed to from the WasmArrayObject. The same is true for WasmStructObject in
// the case where the fields cannot fit in the object itself. These blocks are
// in some places referred to as "trailer blocks".
//
// These blocks are allocated in the same way as JSObects slots and element
// buffers, either using the GC's buffer allocator or directly in the nursery if
// they are small enough.
//
// They require the use of WasmArrayObject/WasmStructObject::obj_moved hooks to
// update the pointer and mark the allocation when the object gets tenured, and
// also update the pointer in case it is an internal pointer when the object is
// moved.
//
// The blocks are freed by the GC when no longer referenced.
//=========================================================================
// WasmGcObject
const ObjectOps WasmGcObject::objectOps_ = {
WasmGcObject::obj_lookupProperty, // lookupProperty
WasmGcObject::obj_defineProperty, // defineProperty
WasmGcObject::obj_hasProperty, // hasProperty
WasmGcObject::obj_getProperty, // getProperty
WasmGcObject::obj_setProperty, // setProperty
WasmGcObject::obj_getOwnPropertyDescriptor, // getOwnPropertyDescriptor
WasmGcObject::obj_deleteProperty, // deleteProperty
nullptr, // getElements
nullptr, // funToString
};
/* static */
bool WasmGcObject::obj_lookupProperty(JSContext* cx, HandleObject obj,
HandleId id, MutableHandleObject objp,
PropertyResult* propp) {
objp.set(nullptr);
propp->setNotFound();
return true;
}
bool WasmGcObject::obj_defineProperty(JSContext* cx, HandleObject obj,
HandleId id,
Handle<PropertyDescriptor> desc,
ObjectOpResult& result) {
result.failReadOnly();
return true;
}
bool WasmGcObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id,
bool* foundp) {
*foundp = false;
return true;
}
bool WasmGcObject::obj_getProperty(JSContext* cx, HandleObject obj,
HandleValue receiver, HandleId id,
MutableHandleValue vp) {
vp.setUndefined();
return true;
}
bool WasmGcObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id,
HandleValue v, HandleValue receiver,
ObjectOpResult& result) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_MODIFIED_GC_OBJECT);
return false;
}
bool WasmGcObject::obj_getOwnPropertyDescriptor(
JSContext* cx, HandleObject obj, HandleId id,
MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) {
desc.reset();
return true;
}
bool WasmGcObject::obj_deleteProperty(JSContext* cx, HandleObject obj,
HandleId id, ObjectOpResult& result) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_MODIFIED_GC_OBJECT);
return false;
}
bool WasmGcObject::lookUpProperty(JSContext* cx, Handle<WasmGcObject*> obj,
jsid id, WasmGcObject::PropOffset* offset,
StorageType* type) {
switch (obj->kind()) {
case wasm::TypeDefKind::Struct: {
const auto& structType = obj->typeDef().structType();
uint32_t index;
if (!IdIsIndex(id, &index)) {
return false;
}
if (index >= structType.fields_.length()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_OUT_OF_BOUNDS);
return false;
}
offset->set(structType.fieldOffset(index));
*type = structType.fields_[index].type;
return true;
}
case wasm::TypeDefKind::Array: {
const auto& arrayType = obj->typeDef().arrayType();
uint32_t index;
if (!IdIsIndex(id, &index)) {
return false;
}
uint32_t numElements = obj->as<WasmArrayObject>().numElements_;
if (index >= numElements) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_OUT_OF_BOUNDS);
return false;
}
uint64_t scaledIndex =
uint64_t(index) * uint64_t(arrayType.elementType().size());
if (scaledIndex >= uint64_t(UINT32_MAX)) {
// It's unrepresentable as an WasmGcObject::PropOffset. Give up.
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_OUT_OF_BOUNDS);
return false;
}
offset->set(uint32_t(scaledIndex));
*type = arrayType.elementType();
return true;
}
default:
MOZ_ASSERT_UNREACHABLE();
return false;
}
}
bool WasmGcObject::loadValue(JSContext* cx, Handle<WasmGcObject*> obj, jsid id,
MutableHandleValue vp) {
WasmGcObject::PropOffset offset;
StorageType type;
if (!lookUpProperty(cx, obj, id, &offset, &type)) {
return false;
}
// Temporary hack, (ref T) is not exposable to JS yet but some tests would
// like to access it so we erase (ref T) with eqref when loading. This is
// safe as (ref T) <: eqref and we're not in the writing case where we
// would need to perform a type check.
if (type.isTypeRef()) {
type = RefType::fromTypeCode(TypeCode::EqRef, true);
}
if (!type.isExposable()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_VAL_TYPE);
return false;
}
if (obj->is<WasmStructObject>()) {
// `offset` is the field offset, without regard to the in/out-line split.
// That is handled by the call to `fieldOffsetToAddress`.
WasmStructObject& structObj = obj->as<WasmStructObject>();
// Ensure no out-of-range access possible
MOZ_RELEASE_ASSERT(structObj.kind() == TypeDefKind::Struct);
MOZ_RELEASE_ASSERT(offset.get() + type.size() <=
structObj.typeDef().structType().size_);
return ToJSValue(cx, structObj.fieldOffsetToAddress(type, offset.get()),
type, vp);
}
MOZ_ASSERT(obj->is<WasmArrayObject>());
const WasmArrayObject& arrayObj = obj->as<WasmArrayObject>();
return ToJSValue(cx, arrayObj.data_ + offset.get(), type, vp);
}
bool WasmGcObject::isRuntimeSubtypeOf(
const wasm::TypeDef* parentTypeDef) const {
return TypeDef::isSubTypeOf(&typeDef(), parentTypeDef);
}
bool WasmGcObject::obj_newEnumerate(JSContext* cx, HandleObject obj,
MutableHandleIdVector properties,
bool enumerableOnly) {
return true;
}
static void WriteValTo(const Val& val, StorageType ty, void* dest) {
switch (ty.kind()) {
case StorageType::I8:
*((uint8_t*)dest) = val.i32();
break;
case StorageType::I16:
*((uint16_t*)dest) = val.i32();
break;
case StorageType::I32:
*((uint32_t*)dest) = val.i32();
break;
case StorageType::I64:
*((uint64_t*)dest) = val.i64();
break;
case StorageType::F32:
*((float*)dest) = val.f32();
break;
case StorageType::F64:
*((double*)dest) = val.f64();
break;
case StorageType::V128:
*((V128*)dest) = val.v128();
break;
case StorageType::Ref:
*((GCPtr<AnyRef>*)dest) = val.ref();
break;
}
}
//=========================================================================
// WasmArrayObject
/* static */
size_t js::WasmArrayObject::sizeOfExcludingThis() const {
if (!isDataInline() || !gc::IsBufferAlloc(dataHeader())) {
return 0;
}
return gc::GetAllocSize(zone(), dataHeader());
}
/* static */
void WasmArrayObject::obj_trace(JSTracer* trc, JSObject* object) {
WasmArrayObject& arrayObj = object->as<WasmArrayObject>();
uint8_t* data = arrayObj.data_;
if (!arrayObj.isDataInline()) {
uint8_t* outlineAlloc = (uint8_t*)dataHeaderFromDataPointer(arrayObj.data_);
uint8_t* prior = outlineAlloc;
TraceBufferEdge(trc, &arrayObj, &outlineAlloc, "WasmArrayObject storage");
if (outlineAlloc != prior) {
arrayObj.data_ = (uint8_t*)(((DataHeader*)outlineAlloc) + 1);
}
}
const auto& typeDef = arrayObj.typeDef();
const auto& arrayType = typeDef.arrayType();
if (!arrayType.elementType().isRefRepr()) {
return;
}
uint32_t numElements = arrayObj.numElements_;
uint32_t elemSize = arrayType.elementType().size();
for (uint32_t i = 0; i < numElements; i++) {
AnyRef* elementPtr = reinterpret_cast<AnyRef*>(data + i * elemSize);
TraceManuallyBarrieredEdge(trc, elementPtr, "wasm-array-element");
}
}
/* static */
size_t WasmArrayObject::obj_moved(JSObject* obj, JSObject* old) {
// Moving inline arrays requires us to update the data pointer.
WasmArrayObject& arrayObj = obj->as<WasmArrayObject>();
WasmArrayObject& oldArrayObj = old->as<WasmArrayObject>();
if (oldArrayObj.isDataInline()) {
// The old array had inline storage, which has been copied.
// Fix up the data pointer on the new array to point to it.
arrayObj.data_ = WasmArrayObject::addressOfInlineData(&arrayObj);
}
MOZ_ASSERT(arrayObj.isDataInline() == oldArrayObj.isDataInline());
if (IsInsideNursery(old)) {
Nursery& nursery = obj->runtimeFromMainThread()->gc.nursery();
// It's been tenured.
if (!arrayObj.isDataInline()) {
const TypeDef& typeDef = arrayObj.typeDef();
MOZ_ASSERT(typeDef.isArrayType());
// arrayObj.numElements_ was validated not to overflow when constructing
// the array
size_t trailerSize = calcStorageBytesUnchecked(
typeDef.arrayType().elementType().size(), arrayObj.numElements_);
// Ensured by WasmArrayObject::createArrayOOL.
MOZ_RELEASE_ASSERT(trailerSize <= size_t(MaxArrayPayloadBytes));
uint8_t* outlineAlloc =
(uint8_t*)dataHeaderFromDataPointer(arrayObj.data_);
uint8_t* prior = outlineAlloc;
nursery.maybeMoveBufferOnPromotion(&outlineAlloc, obj, trailerSize);
if (outlineAlloc != prior) {
arrayObj.data_ = (uint8_t*)(((DataHeader*)outlineAlloc) + 1);
}
}
}
return 0;
}
void WasmArrayObject::storeVal(const Val& val, uint32_t itemIndex) {
const ArrayType& arrayType = typeDef().arrayType();
size_t elementSize = arrayType.elementType().size();
MOZ_ASSERT(itemIndex < numElements_);
uint8_t* data = data_ + elementSize * itemIndex;
WriteValTo(val, arrayType.elementType(), data);
}
void WasmArrayObject::fillVal(const Val& val, uint32_t itemIndex,
uint32_t len) {
const ArrayType& arrayType = typeDef().arrayType();
size_t elementSize = arrayType.elementType().size();
uint8_t* data = data_ + elementSize * itemIndex;
MOZ_ASSERT(itemIndex <= numElements_ && len <= numElements_ - itemIndex);
for (uint32_t i = 0; i < len; i++) {
WriteValTo(val, arrayType.elementType(), data);
data += elementSize;
}
}
static const JSClassOps WasmArrayObjectClassOps = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* enumerate */
WasmGcObject::obj_newEnumerate,
nullptr, /* resolve */
nullptr, /* mayResolve */
nullptr, /* finalize */
nullptr, /* call */
nullptr, /* construct */
WasmArrayObject::obj_trace,
};
static const ClassExtension WasmArrayObjectClassExt = {
WasmArrayObject::obj_moved, /* objectMovedOp */
};
const JSClass WasmArrayObject::class_ = {
"WasmArrayObject",
JSClass::NON_NATIVE | JSCLASS_DELAY_METADATA_BUILDER,
&WasmArrayObjectClassOps,
JS_NULL_CLASS_SPEC,
&WasmArrayObjectClassExt,
&WasmGcObject::objectOps_,
};
//=========================================================================
// WasmStructObject
/* static */
const JSClass* js::WasmStructObject::classForTypeDef(
const wasm::TypeDef* typeDef) {
MOZ_ASSERT(typeDef->kind() == wasm::TypeDefKind::Struct);
size_t nbytes = typeDef->structType().size_;
return nbytes > WasmStructObject_MaxInlineBytes
? &WasmStructObject::classOutline_
: &WasmStructObject::classInline_;
}
/* static */
js::gc::AllocKind js::WasmStructObject::allocKindForTypeDef(
const wasm::TypeDef* typeDef) {
MOZ_ASSERT(typeDef->kind() == wasm::TypeDefKind::Struct);
size_t nbytes = typeDef->structType().size_;
// `nbytes` is the total required size for all struct fields, including
// padding. What we need is the size of resulting WasmStructObject,
// ignoring any space used for out-of-line data. First, restrict `nbytes`
// to cover just the inline data.
if (nbytes > WasmStructObject_MaxInlineBytes) {
nbytes = WasmStructObject_MaxInlineBytes;
}
// Now convert it to size of the WasmStructObject as a whole.
nbytes = sizeOfIncludingInlineData(nbytes);
return gc::GetGCObjectKindForBytes(nbytes);
}
/* static */
size_t js::WasmStructObject::sizeOfExcludingThis() const {
if (!outlineData_ || !gc::IsBufferAlloc(outlineData_)) {
return 0;
}
return gc::GetAllocSize(zone(), outlineData_);
}
bool WasmStructObject::getField(JSContext* cx, uint32_t index,
MutableHandle<Value> val) {
const StructType& resultType = typeDef().structType();
MOZ_ASSERT(index <= resultType.fields_.length());
const FieldType& field = resultType.fields_[index];
uint32_t fieldOffset = resultType.fieldOffset(index);
StorageType ty = field.type.storageType();
return ToJSValue(cx, fieldOffsetToAddress(ty, fieldOffset), ty, val);
}
/* static */
void WasmStructObject::obj_trace(JSTracer* trc, JSObject* object) {
WasmStructObject& structObj = object->as<WasmStructObject>();
const auto& structType = structObj.typeDef().structType();
for (uint32_t offset : structType.inlineTraceOffsets_) {
AnyRef* fieldPtr =
reinterpret_cast<AnyRef*>(structObj.inlineData() + offset);
TraceManuallyBarrieredEdge(trc, fieldPtr, "wasm-struct-field");
}
if (structObj.outlineData_) {
TraceBufferEdge(trc, &structObj, &structObj.outlineData_,
"WasmStructObject outline data");
for (uint32_t offset : structType.outlineTraceOffsets_) {
AnyRef* fieldPtr =
reinterpret_cast<AnyRef*>(structObj.outlineData_ + offset);
TraceManuallyBarrieredEdge(trc, fieldPtr, "wasm-struct-field");
}
}
}
/* static */
size_t WasmStructObject::obj_moved(JSObject* obj, JSObject* old) {
// See also, corresponding comments in WasmArrayObject::obj_moved.
if (IsInsideNursery(old)) {
Nursery& nursery = obj->runtimeFromMainThread()->gc.nursery();
WasmStructObject& structObj = obj->as<WasmStructObject>();
const TypeDef& typeDef = structObj.typeDef();
MOZ_ASSERT(typeDef.isStructType());
uint32_t totalBytes = typeDef.structType().size_;
uint32_t inlineBytes, outlineBytes;
WasmStructObject::getDataByteSizes(totalBytes, &inlineBytes, &outlineBytes);
MOZ_ASSERT(inlineBytes == WasmStructObject_MaxInlineBytes);
MOZ_ASSERT(outlineBytes > 0);
nursery.maybeMoveBufferOnPromotion(&structObj.outlineData_, obj,
outlineBytes);
}
return 0;
}
void WasmStructObject::storeVal(const Val& val, uint32_t fieldIndex) {
const StructType& structType = typeDef().structType();
StorageType fieldType = structType.fields_[fieldIndex].type;
uint32_t fieldOffset = structType.fieldOffset(fieldIndex);
MOZ_ASSERT(fieldIndex < structType.fields_.length());
bool areaIsOutline;
uint32_t areaOffset;
fieldOffsetToAreaAndOffset(fieldType, fieldOffset, &areaIsOutline,
&areaOffset);
uint8_t* data;
if (areaIsOutline) {
data = outlineData_ + areaOffset;
} else {
data = inlineData() + areaOffset;
}
WriteValTo(val, fieldType, data);
}
static const JSClassOps WasmStructObjectOutlineClassOps = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* enumerate */
WasmGcObject::obj_newEnumerate,
nullptr, /* resolve */
nullptr, /* mayResolve */
nullptr, /* finalize */
nullptr, /* call */
nullptr, /* construct */
WasmStructObject::obj_trace,
};
static const ClassExtension WasmStructObjectOutlineClassExt = {
WasmStructObject::obj_moved, /* objectMovedOp */
};
const JSClass WasmStructObject::classOutline_ = {
"WasmStructObject",
JSClass::NON_NATIVE | JSCLASS_DELAY_METADATA_BUILDER,
&WasmStructObjectOutlineClassOps,
JS_NULL_CLASS_SPEC,
&WasmStructObjectOutlineClassExt,
&WasmGcObject::objectOps_,
};
// Structs that only have inline data get a different class without a
// finalizer. This class should otherwise be identical to the class for
// structs with outline data.
static const JSClassOps WasmStructObjectInlineClassOps = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* enumerate */
WasmGcObject::obj_newEnumerate,
nullptr, /* resolve */
nullptr, /* mayResolve */
nullptr, /* finalize */
nullptr, /* call */
nullptr, /* construct */
WasmStructObject::obj_trace,
};
static const ClassExtension WasmStructObjectInlineClassExt = {
nullptr, /* objectMovedOp */
};
const JSClass WasmStructObject::classInline_ = {
"WasmStructObject",
JSClass::NON_NATIVE | JSCLASS_DELAY_METADATA_BUILDER,
&WasmStructObjectInlineClassOps,
JS_NULL_CLASS_SPEC,
&WasmStructObjectInlineClassExt,
&WasmGcObject::objectOps_,
};