commit c88a96ad26fc6e4173abd6809665bfdd63d4ec8f
parent 03f48afc8df3520e4a87ef204aa0c2f81c891d4a
Author: Julian Seward <jseward@acm.org>
Date: Fri, 19 Dec 2025 06:52:46 +0000
Bug 1986858 part 1: refactor wasm structure layout machinery. r=bvisness.
This patch almost completely decouples structure layout decisions from the
details of WasmStructObject. It also puts the layout machinery behind a simple
interface, that the second patch in this set will re-implement.
Summary of changes:
* New files WasmStructLayout.h, WasmStructLayout.cpp
* A SMDOC in WasmStructLayout.h explaining the new scheme
* New type FieldAccessPath, that holds one or two offsets. One of these is
computed for each structure field. A FieldAccessPath can also describe the
OOL-Pointer field, so that is no longer handled specially.
* class StructType: now holds the FieldAccessPaths for its fields, plus some
other auxiliary data (the AccessKind etc)
* these are computed by StructType::init, when parsing the module environment
* Baseline and Ion modified to use FieldAccessPath to generate field accesses
* new class StructLayout, that does the actual layout, with an implementation
of the required interface that produces the same layouts as before this
patch.
* struct TypeDefInstanceData: some layout info for structs is cached there at
instantiation time, by Instance::init.
* class WasmStructObject:
- all layout-related methods have been removed
- The OOL pointer field has been removed. This is no longer needed since
Baseline/Ion are given a complete access path for each field access.
- new methods hasOOLPointer, {get,set,addressOf}OOLPointer
- revised statements about structure alignment constraints
* Exception tag layouts. These have used the old StructLayout class. This is
copied and renamed TagLayout; hence its behaviour should be unchanged.
* A cleanup: remove all mentions of Wasm{Struct,Array}Object::obj_finalize;
this is no longer needed now that bug 1978645 has landed.
Differential Revision: https://phabricator.services.mozilla.com/D267395
Diffstat:
16 files changed, 749 insertions(+), 501 deletions(-)
diff --git a/js/src/jit/MIR-wasm.h b/js/src/jit/MIR-wasm.h
@@ -3232,14 +3232,10 @@ class MWasmNewStructObject : public MBinaryInstruction,
}
const wasm::TypeDef& typeDef() { return *typeDef_; }
const wasm::StructType& structType() const { return typeDef_->structType(); }
- bool isOutline() const {
- return WasmStructObject::requiresOutlineBytes(typeDef_->structType().size_);
- }
+ bool isOutline() const { return typeDef_->structType().hasOOL(); }
bool zeroFields() const { return zeroFields_; }
const wasm::TrapSiteDesc& trapSiteDesc() const { return trapSiteDesc_; }
- gc::AllocKind allocKind() const {
- return WasmStructObject::allocKindForTypeDef(typeDef_);
- }
+ gc::AllocKind allocKind() const { return typeDef_->structType().allocKind_; }
};
class MWasmNewArrayObject : public MTernaryInstruction,
diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp
@@ -7627,12 +7627,11 @@ void MacroAssembler::wasmNewStructObject(Register instance, Register result,
wasm::TypeDefInstanceData::offsetOfSuperTypeVector()),
temp);
storePtr(temp, Address(result, WasmArrayObject::offsetOfSuperTypeVector()));
- storePtr(ImmWord(0),
- Address(result, WasmStructObject::offsetOfOutlineData()));
if (zeroFields) {
+ static_assert(wasm::WasmStructObject_Size_ASSUMED % sizeof(void*) == 0);
MOZ_ASSERT(sizeBytes % sizeof(void*) == 0);
- for (size_t i = WasmStructObject::offsetOfInlineData(); i < sizeBytes;
+ for (size_t i = wasm::WasmStructObject_Size_ASSUMED; i < sizeBytes;
i += sizeof(void*)) {
storePtr(ImmWord(0), Address(result, i));
}
diff --git a/js/src/vm/JSObject.cpp b/js/src/vm/JSObject.cpp
@@ -3200,7 +3200,7 @@ js::gc::AllocKind JSObject::allocKindForTenure(
if (is<WasmStructObject>()) {
// Figure out the size of this object, from the object's TypeDef.
const wasm::TypeDef* typeDef = &as<WasmStructObject>().typeDef();
- AllocKind kind = WasmStructObject::allocKindForTypeDef(typeDef);
+ AllocKind kind = typeDef->structType().allocKind_;
return GetFinalizedAllocKindForClass(kind, getClass());
}
diff --git a/js/src/wasm/WasmBaselineCompile.cpp b/js/src/wasm/WasmBaselineCompile.cpp
@@ -7739,9 +7739,9 @@ bool BaseCompiler::emitStructAlloc(uint32_t typeIndex, RegRef* object,
uint32_t allocSiteIndex) {
const TypeDef& typeDef = (*codeMeta_.types)[typeIndex];
const StructType& structType = typeDef.structType();
- gc::AllocKind allocKind = WasmStructObject::allocKindForTypeDef(&typeDef);
+ gc::AllocKind allocKind = structType.allocKind_;
- *isOutlineStruct = WasmStructObject::requiresOutlineBytes(structType.size_);
+ *isOutlineStruct = structType.hasOOL();
// Reserve this register early if we will need it so that it is not taken by
// any register used in this function.
@@ -7865,12 +7865,7 @@ bool BaseCompiler::emitStructNew() {
while (fieldIndex-- > 0) {
const FieldType& field = structType.fields_[fieldIndex];
StorageType type = field.type;
- uint32_t fieldOffset = structType.fieldOffset(fieldIndex);
-
- bool areaIsOutline;
- uint32_t areaOffset;
- WasmStructObject::fieldOffsetToAreaAndOffset(type, fieldOffset,
- &areaIsOutline, &areaOffset);
+ FieldAccessPath path = structType.fieldAccessPaths_[fieldIndex];
// Reserve the barrier reg if we might need it for this store
if (type.isRefRepr()) {
@@ -7882,22 +7877,21 @@ bool BaseCompiler::emitStructNew() {
freePtr(RegPtr(PreBarrierReg));
}
- if (areaIsOutline) {
- // Load the outline data pointer
- masm.loadPtr(Address(object, WasmStructObject::offsetOfOutlineData()),
- outlineBase);
+ if (path.hasOOL()) {
+ // Load the outline data pointer.
+ // The path has two components, of which the first (the IL component) is
+ // the offset where the OOL pointer is stored. Hence `path.ilOffset()`.
+ masm.loadPtr(Address(object, path.ilOffset()), outlineBase);
// Consumes value and outline data, object is preserved by this call.
- if (!emitGcStructSet<NoNullCheck>(object, outlineBase, areaOffset, type,
- value, PreBarrierKind::None)) {
+ if (!emitGcStructSet<NoNullCheck>(object, outlineBase, path.oolOffset(),
+ type, value, PreBarrierKind::None)) {
return false;
}
} else {
// Consumes value. object is unchanged by this call.
- if (!emitGcStructSet<NoNullCheck>(
- object, RegPtr(object),
- WasmStructObject::offsetOfInlineData() + areaOffset, type, value,
- PreBarrierKind::None)) {
+ if (!emitGcStructSet<NoNullCheck>(object, RegPtr(object), path.ilOffset(),
+ type, value, PreBarrierKind::None)) {
return false;
}
}
@@ -7955,31 +7949,26 @@ bool BaseCompiler::emitStructGet(FieldWideningOp wideningOp) {
}
const StructType& structType = (*codeMeta_.types)[typeIndex].structType();
-
- // Decide whether we're accessing inline or outline, and at what offset
- StorageType fieldType = structType.fields_[fieldIndex].type;
- uint32_t fieldOffset = structType.fieldOffset(fieldIndex);
-
- bool areaIsOutline;
- uint32_t areaOffset;
- WasmStructObject::fieldOffsetToAreaAndOffset(fieldType, fieldOffset,
- &areaIsOutline, &areaOffset);
+ const FieldType& structField = structType.fields_[fieldIndex];
+ StorageType fieldType = structField.type;
+ FieldAccessPath path = structType.fieldAccessPaths_[fieldIndex];
RegRef object = popRef();
- if (areaIsOutline) {
+ if (path.hasOOL()) {
+ // The path has two components, of which the first (the IL component) is
+ // the offset where the OOL pointer is stored. Hence `path.ilOffset()`.
RegPtr outlineBase = needPtr();
- FaultingCodeOffset fco = masm.loadPtr(
- Address(object, WasmStructObject::offsetOfOutlineData()), outlineBase);
+ FaultingCodeOffset fco =
+ masm.loadPtr(Address(object, path.ilOffset()), outlineBase);
SignalNullCheck::emitTrapSite(this, fco, TrapMachineInsnForLoadWord());
// Load the value
emitGcGet<Address, NoNullCheck>(fieldType, wideningOp,
- Address(outlineBase, areaOffset));
+ Address(outlineBase, path.oolOffset()));
freePtr(outlineBase);
} else {
// Load the value
- emitGcGet<Address, SignalNullCheck>(
- fieldType, wideningOp,
- Address(object, WasmStructObject::offsetOfInlineData() + areaOffset));
+ emitGcGet<Address, SignalNullCheck>(fieldType, wideningOp,
+ Address(object, path.ilOffset()));
}
freeRef(object);
@@ -8000,52 +7989,48 @@ bool BaseCompiler::emitStructSet() {
const StructType& structType = (*codeMeta_.types)[typeIndex].structType();
const FieldType& structField = structType.fields_[fieldIndex];
-
- // Decide whether we're accessing inline or outline, and at what offset
- StorageType fieldType = structType.fields_[fieldIndex].type;
- uint32_t fieldOffset = structType.fieldOffset(fieldIndex);
-
- bool areaIsOutline;
- uint32_t areaOffset;
- WasmStructObject::fieldOffsetToAreaAndOffset(fieldType, fieldOffset,
- &areaIsOutline, &areaOffset);
+ StorageType fieldType = structField.type;
+ FieldAccessPath path = structType.fieldAccessPaths_[fieldIndex];
// Reserve this register early if we will need it so that it is not taken by
// any register used in this function.
- if (structField.type.isRefRepr()) {
+ if (fieldType.isRefRepr()) {
needPtr(RegPtr(PreBarrierReg));
}
- RegPtr outlineBase = areaIsOutline ? needPtr() : RegPtr();
+ // Set up other required registers
+ RegPtr outlineBase = path.hasOOL() ? needPtr() : RegPtr();
AnyReg value = popAny();
RegRef object = popRef();
// Free the barrier reg after we've allocated all registers
- if (structField.type.isRefRepr()) {
+ if (fieldType.isRefRepr()) {
freePtr(RegPtr(PreBarrierReg));
}
- // Make outlineBase point at the first byte of the relevant area
- if (areaIsOutline) {
- FaultingCodeOffset fco = masm.loadPtr(
- Address(object, WasmStructObject::offsetOfOutlineData()), outlineBase);
+ if (path.hasOOL()) {
+ // Make `outlineBase` point at the first byte of the relevant area.
+ // The path has two components, of which the first (the IL component) is
+ // the offset where the OOL pointer is stored. Hence `path.ilOffset()`.
+ FaultingCodeOffset fco =
+ masm.loadPtr(Address(object, path.ilOffset()), outlineBase);
SignalNullCheck::emitTrapSite(this, fco, TrapMachineInsnForLoadWord());
- if (!emitGcStructSet<NoNullCheck>(object, outlineBase, areaOffset,
+ // Consumes `value`. `object` is unchanged by this call.
+ if (!emitGcStructSet<NoNullCheck>(object, outlineBase, path.oolOffset(),
fieldType, value,
PreBarrierKind::Normal)) {
return false;
}
} else {
- // Consumes value. object is unchanged by this call.
- if (!emitGcStructSet<SignalNullCheck>(
- object, RegPtr(object),
- WasmStructObject::offsetOfInlineData() + areaOffset, fieldType,
- value, PreBarrierKind::Normal)) {
+ // Consumes `value`. `object` is unchanged by this call.
+ if (!emitGcStructSet<SignalNullCheck>(object, RegPtr(object),
+ path.ilOffset(), fieldType, value,
+ PreBarrierKind::Normal)) {
return false;
}
}
- if (areaIsOutline) {
+ if (path.hasOOL()) {
freePtr(outlineBase);
}
freeRef(object);
diff --git a/js/src/wasm/WasmGcObject-inl.h b/js/src/wasm/WasmGcObject-inl.h
@@ -42,11 +42,15 @@ MOZ_ALWAYS_INLINE WasmStructObject* WasmStructObject::createStructIL(
gc::AllocSite* allocSite, js::gc::Heap initialHeap) {
// It is up to our caller to ensure that `typeDefData` refers to a type that
// doesn't need OOL storage.
+ MOZ_ASSERT(typeDefData->cached.strukt.totalSizeOOL == 0);
MOZ_ASSERT(IsWasmGcObjectClass(typeDefData->clasp));
MOZ_ASSERT(!typeDefData->clasp->isNativeObject());
+ MOZ_ASSERT(!IsFinalizedKind(typeDefData->cached.strukt.allocKind));
+
AutoSetNewObjectMetadata metadata(cx);
- debugCheckNewObject(typeDefData->shape, typeDefData->allocKind, initialHeap);
+ debugCheckNewObject(typeDefData->shape, typeDefData->cached.strukt.allocKind,
+ initialHeap);
mozilla::DebugOnly<const wasm::TypeDef*> typeDef = typeDefData->typeDef;
MOZ_ASSERT(typeDef->kind() == wasm::TypeDefKind::Struct);
@@ -54,22 +58,19 @@ MOZ_ALWAYS_INLINE WasmStructObject* WasmStructObject::createStructIL(
// This doesn't need to be rooted, since all we do with it prior to
// return is to zero out the fields (and then only if ZeroFields is true).
WasmStructObject* structObj = (WasmStructObject*)cx->newCell<WasmGcObject>(
- typeDefData->allocKind, initialHeap, typeDefData->clasp, allocSite);
+ typeDefData->cached.strukt.allocKind, initialHeap, typeDefData->clasp,
+ allocSite);
if (MOZ_UNLIKELY(!structObj)) {
ReportOutOfMemory(cx);
return nullptr;
}
- MOZ_ASSERT((uintptr_t(structObj->inlineData()) % sizeof(uintptr_t)) == 0);
structObj->initShape(typeDefData->shape);
structObj->superTypeVector_ = typeDefData->superTypeVector;
- structObj->outlineData_ = nullptr;
if constexpr (ZeroFields) {
- uint32_t totalBytes = typeDefData->structTypeSize;
- MOZ_ASSERT(totalBytes == typeDef->structType().size_);
- MOZ_ASSERT(totalBytes <= WasmStructObject_MaxInlineBytes);
- MOZ_ASSERT((totalBytes % sizeof(uintptr_t)) == 0);
- memset(structObj->inlineData(), 0, totalBytes);
+ size_t headerSize = typeDefData->cached.strukt.payloadOffsetIL;
+ memset((uint8_t*)structObj + headerSize, 0,
+ typeDefData->cached.strukt.totalSizeIL - headerSize);
}
MOZ_ASSERT(typeDefData->clasp->shouldDelayMetadataBuilder());
@@ -88,52 +89,53 @@ MOZ_ALWAYS_INLINE WasmStructObject* WasmStructObject::createStructOOL(
gc::AllocSite* allocSite, js::gc::Heap initialHeap) {
// It is up to our caller to ensure that `typeDefData` refers to a type that
// needs OOL storage.
+ MOZ_ASSERT(typeDefData->cached.strukt.totalSizeOOL > 0);
MOZ_ASSERT(IsWasmGcObjectClass(typeDefData->clasp));
MOZ_ASSERT(!typeDefData->clasp->isNativeObject());
+ MOZ_ASSERT(!IsFinalizedKind(typeDefData->cached.strukt.allocKind));
+
AutoSetNewObjectMetadata metadata(cx);
- debugCheckNewObject(typeDefData->shape, typeDefData->allocKind, initialHeap);
+ debugCheckNewObject(typeDefData->shape, typeDefData->cached.strukt.allocKind,
+ initialHeap);
mozilla::DebugOnly<const wasm::TypeDef*> typeDef = typeDefData->typeDef;
MOZ_ASSERT(typeDef->kind() == wasm::TypeDefKind::Struct);
- uint32_t totalBytes = typeDefData->structTypeSize;
- MOZ_ASSERT(totalBytes == typeDef->structType().size_);
- MOZ_ASSERT(totalBytes > WasmStructObject_MaxInlineBytes);
- MOZ_ASSERT((totalBytes % sizeof(uintptr_t)) == 0);
-
- uint32_t inlineBytes, outlineBytes;
- WasmStructObject::getDataByteSizes(totalBytes, &inlineBytes, &outlineBytes);
- MOZ_ASSERT(inlineBytes == WasmStructObject_MaxInlineBytes);
- MOZ_ASSERT(outlineBytes > 0);
+ uint32_t outlineBytes = typeDefData->cached.strukt.totalSizeOOL;
- // See corresponding comment in WasmArrayObject::createArray.
- Rooted<WasmStructObject*> structObj(cx);
- structObj = (WasmStructObject*)cx->newCell<WasmGcObject>(
- typeDefData->allocKind, initialHeap, typeDefData->clasp, allocSite);
+ // This doesn't need to be Rooted because the AllocateCellBuffer call that
+ // follows can't trigger GC.
+ auto* structObj = (WasmStructObject*)cx->newCell<WasmGcObject>(
+ typeDefData->cached.strukt.allocKind, initialHeap, typeDefData->clasp,
+ allocSite);
if (MOZ_UNLIKELY(!structObj)) {
ReportOutOfMemory(cx);
return nullptr;
}
+ structObj->initShape(typeDefData->shape);
+ structObj->superTypeVector_ = typeDefData->superTypeVector;
+
uint8_t* outlineData = AllocateCellBuffer<uint8_t>(
cx, structObj, outlineBytes, MaxNurseryTrailerSize);
if (MOZ_UNLIKELY(!outlineData)) {
- structObj->outlineData_ = nullptr;
+ // AllocateCellBuffer will have called ReportOutOfMemory(cx) itself,
+ // so no need to do that here.
+ structObj->setOOLPointer(typeDefData, nullptr);
return nullptr;
}
- MOZ_ASSERT((uintptr_t(structObj->inlineData()) % sizeof(uintptr_t)) == 0);
- structObj->initShape(typeDefData->shape);
- structObj->superTypeVector_ = typeDefData->superTypeVector;
-
- // Initialize the outline data fields
- structObj->outlineData_ = outlineData;
+ // Initialize the inline and outline data fields
if constexpr (ZeroFields) {
- memset(structObj->inlineData(), 0, inlineBytes);
+ size_t headerSize = typeDefData->cached.strukt.payloadOffsetIL;
+ memset((uint8_t*)structObj + headerSize, 0,
+ typeDefData->cached.strukt.totalSizeIL - headerSize);
memset(outlineData, 0, outlineBytes);
}
+ structObj->setOOLPointer(typeDefData, outlineData);
+
MOZ_ASSERT(typeDefData->clasp->shouldDelayMetadataBuilder());
cx->realm()->setObjectPendingMetadata(structObj);
@@ -181,7 +183,6 @@ MOZ_ALWAYS_INLINE WasmArrayObject* WasmArrayObject::createArrayOOL(
MOZ_ASSERT(IsWasmGcObjectClass(typeDefData->clasp));
MOZ_ASSERT(!typeDefData->clasp->isNativeObject());
- MOZ_ASSERT(typeDefData->allocKind == gc::AllocKind::INVALID);
gc::AllocKind allocKind = allocKindForOOL();
AutoSetNewObjectMetadata metadata(cx);
debugCheckNewObject(typeDefData->shape, allocKind, initialHeap);
@@ -219,7 +220,7 @@ MOZ_ALWAYS_INLINE WasmArrayObject* WasmArrayObject::createArrayOOL(
arrayObj->data_ = outlineData;
if constexpr (ZeroFields) {
uint32_t dataBytes = storageBytes - sizeof(DataHeader);
- MOZ_ASSERT(dataBytes >= numElements * typeDefData->arrayElemSize);
+ MOZ_ASSERT(dataBytes >= numElements * typeDefData->cached.array.elemSize);
memset(arrayObj->data_, 0, dataBytes);
}
@@ -253,7 +254,6 @@ MOZ_ALWAYS_INLINE WasmArrayObject* WasmArrayObject::createArrayIL(
MOZ_ASSERT(IsWasmGcObjectClass(typeDefData->clasp));
MOZ_ASSERT(!typeDefData->clasp->isNativeObject());
- MOZ_ASSERT(typeDefData->allocKind == gc::AllocKind::INVALID);
AutoSetNewObjectMetadata metadata(cx);
gc::AllocKind allocKind = allocKindForIL(storageBytes);
debugCheckNewObject(typeDefData->shape, allocKind, initialHeap);
@@ -284,7 +284,7 @@ MOZ_ALWAYS_INLINE WasmArrayObject* WasmArrayObject::createArrayIL(
if constexpr (ZeroFields) {
uint32_t dataBytes = storageBytes - sizeof(DataHeader);
- MOZ_ASSERT(dataBytes >= numElements * typeDefData->arrayElemSize);
+ MOZ_ASSERT(dataBytes >= numElements * typeDefData->cached.array.elemSize);
if (numElements > 0) {
memset(arrayObj->data_, 0, dataBytes);
@@ -317,10 +317,10 @@ MOZ_ALWAYS_INLINE WasmArrayObject* WasmArrayObject::createArray(
JSContext* cx, wasm::TypeDefInstanceData* typeDefData,
js::gc::AllocSite* allocSite, js::gc::Heap initialHeap,
uint32_t numElements) {
- MOZ_ASSERT(typeDefData->arrayElemSize ==
+ MOZ_ASSERT(typeDefData->cached.array.elemSize ==
typeDefData->typeDef->arrayType().elementType().size());
mozilla::CheckedUint32 storageBytes =
- calcStorageBytesChecked(typeDefData->arrayElemSize, numElements);
+ calcStorageBytesChecked(typeDefData->cached.array.elemSize, numElements);
if (!storageBytes.isValid() ||
storageBytes.value() > uint32_t(wasm::MaxArrayPayloadBytes)) {
js::ReportOversizedAllocation(cx, JSMSG_WASM_ARRAY_IMP_LIMIT);
diff --git a/js/src/wasm/WasmGcObject.cpp b/js/src/wasm/WasmGcObject.cpp
@@ -127,12 +127,14 @@ bool WasmGcObject::lookUpProperty(JSContext* cx, Handle<WasmGcObject*> obj,
if (!IdIsIndex(id, &index)) {
return false;
}
+ MOZ_ASSERT(structType.fields_.length() ==
+ structType.fieldAccessPaths_.length());
if (index >= structType.fields_.length()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_OUT_OF_BOUNDS);
return false;
}
- offset->set(structType.fieldOffset(index));
+ offset->set(index);
*type = structType.fields_[index].type;
return true;
}
@@ -190,15 +192,13 @@ bool WasmGcObject::loadValue(JSContext* cx, Handle<WasmGcObject*> obj, jsid id,
}
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`.
+ // `offset` is the field index.
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);
+ // The above call to `lookUpProperty` will reject a request for a struct
+ // field whose index is out of range. Hence the following will be safe
+ // providing the FieldAccessPaths are correct.
+ return ToJSValue(cx, structObj.fieldIndexToAddress(offset.get()), type, vp);
}
MOZ_ASSERT(obj->is<WasmArrayObject>());
@@ -414,52 +414,47 @@ const JSClass WasmArrayObject::class_ = {
// 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_)) {
+ if (!hasOOLPointer()) {
+ return 0;
+ }
+ const uint8_t* oolPointer = getOOLPointer();
+ if (!gc::IsBufferAlloc((void*)oolPointer)) {
return 0;
}
- return gc::GetAllocSize(zone(), outlineData_);
+ return gc::GetAllocSize(zone(), oolPointer);
}
+/* static */
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);
+ return ToJSValue(cx, fieldIndexToAddress(index), ty, val);
+}
+
+/* static */
+uint8_t* WasmStructObject::fieldIndexToAddress(uint32_t fieldIndex) {
+ const wasm::SuperTypeVector* stv = superTypeVector_;
+ const wasm::TypeDef* typeDef = stv->typeDef();
+ MOZ_ASSERT(typeDef->superTypeVector() == stv);
+ const wasm::StructType& structType = typeDef->structType();
+ const wasm::FieldAccessPathVector& fieldAccessPaths =
+ structType.fieldAccessPaths_;
+ MOZ_RELEASE_ASSERT(fieldIndex < fieldAccessPaths.length());
+ wasm::FieldAccessPath path = fieldAccessPaths[fieldIndex];
+ uint32_t ilOffset = path.ilOffset();
+ MOZ_RELEASE_ASSERT(ilOffset != wasm::StructType::InvalidOffset);
+ if (MOZ_LIKELY(!path.hasOOL())) {
+ return (uint8_t*)this + ilOffset;
+ }
+ uint8_t* oolBlock = *(uint8_t**)((uint8_t*)this + ilOffset);
+ uint32_t oolOffset = path.oolOffset();
+ MOZ_RELEASE_ASSERT(oolOffset != wasm::StructType::InvalidOffset);
+ return oolBlock + oolOffset;
}
/* static */
@@ -468,18 +463,16 @@ void WasmStructObject::obj_trace(JSTracer* trc, JSObject* object) {
const auto& structType = structObj.typeDef().structType();
for (uint32_t offset : structType.inlineTraceOffsets_) {
- AnyRef* fieldPtr =
- reinterpret_cast<AnyRef*>(structObj.inlineData() + offset);
+ AnyRef* fieldPtr = reinterpret_cast<AnyRef*>((uint8_t*)&structObj + offset);
TraceManuallyBarrieredEdge(trc, fieldPtr, "wasm-struct-field");
}
-
- if (structObj.outlineData_) {
- TraceBufferEdge(trc, &structObj, &structObj.outlineData_,
+ if (MOZ_UNLIKELY(structType.totalSizeOOL_ > 0)) {
+ uint8_t** addressOfOOLPtr = structObj.addressOfOOLPointer();
+ TraceBufferEdge(trc, &structObj, addressOfOOLPtr,
"WasmStructObject outline data");
-
+ uint8_t* oolBase = *addressOfOOLPtr;
for (uint32_t offset : structType.outlineTraceOffsets_) {
- AnyRef* fieldPtr =
- reinterpret_cast<AnyRef*>(structObj.outlineData_ + offset);
+ AnyRef* fieldPtr = reinterpret_cast<AnyRef*>(oolBase + offset);
TraceManuallyBarrieredEdge(trc, fieldPtr, "wasm-struct-field");
}
}
@@ -493,16 +486,17 @@ size_t WasmStructObject::obj_moved(JSObject* objNew, JSObject* objOld) {
WasmStructObject& structNew = objNew->as<WasmStructObject>();
WasmStructObject& structOld = objOld->as<WasmStructObject>();
- MOZ_ASSERT(structNew.outlineData_ && structOld.outlineData_);
+ MOZ_ASSERT(structNew.hasOOLPointer() && structOld.hasOOLPointer());
const TypeDef* typeDefNew = &structNew.typeDef();
mozilla::DebugOnly<const TypeDef*> typeDefOld = &structOld.typeDef();
+ MOZ_ASSERT(typeDefNew == typeDefOld);
MOZ_ASSERT(typeDefNew->isStructType());
MOZ_ASSERT(typeDefOld == typeDefNew);
// At this point, the object has been copied, but the OOL storage area has
- // not been copied, nor has the outlineData_ pointer been updated. Hence:
- MOZ_ASSERT(structNew.outlineData_ == structOld.outlineData_);
+ // not been copied, nor has the OOL pointer been updated. Hence:
+ MOZ_ASSERT(structNew.getOOLPointer() == structOld.getOOLPointer());
bool newIsInNursery = IsInsideNursery(objNew);
bool oldIsInNursery = IsInsideNursery(objOld);
@@ -522,23 +516,28 @@ size_t WasmStructObject::obj_moved(JSObject* objNew, JSObject* objOld) {
// to the tenured heap. Either way, we have to ask the nursery if it wants
// to move the OOL block too, and if so set up a forwarding record for it.
- uint32_t totalBytes = typeDefNew->structType().size_;
- uint32_t inlineBytes, outlineBytes;
- WasmStructObject::getDataByteSizes(totalBytes, &inlineBytes, &outlineBytes);
- MOZ_ASSERT(inlineBytes == WasmStructObject_MaxInlineBytes);
+ const StructType& structType = typeDefNew->structType();
+ uint32_t outlineBytes = structType.totalSizeOOL_;
+ // These must always agree.
+ MOZ_ASSERT((outlineBytes > 0) == structNew.hasOOLPointer());
+ // Because the WasmStructObjectInlineClassExt doesn't reference this
+ // method; only WasmStructObjectOutlineClassExt does.
MOZ_ASSERT(outlineBytes > 0);
// Ask the nursery if it wants to relocate the OOL area, and if so capture
- // its new location in `structNew.outlineData_`.
+ // its new location in `addressOfOOLPointerNew`.
Nursery& nursery = structNew.runtimeFromMainThread()->gc.nursery();
- nursery.maybeMoveBufferOnPromotion(&structNew.outlineData_, objNew,
+ uint8_t** addressOfOOLPointerNew = structNew.addressOfOOLPointer();
+ nursery.maybeMoveBufferOnPromotion(addressOfOOLPointerNew, objNew,
outlineBytes);
+
// Set up forwarding for the OOL area. Use indirect forwarding. As in
// WasmArrayObject::obj_moved, if the call to `.setForwardingPointer..` OOMs,
// there's no way to recover.
- if (structOld.outlineData_ != structNew.outlineData_) {
- nursery.setForwardingPointerWhileTenuring(structOld.outlineData_,
- structNew.outlineData_,
+ uint8_t* oolPointerOld = structOld.getOOLPointer();
+ uint8_t* oolPointerNew = structNew.getOOLPointer();
+ if (oolPointerOld != oolPointerNew) {
+ nursery.setForwardingPointerWhileTenuring(oolPointerOld, oolPointerNew,
/*direct=*/false);
}
@@ -547,21 +546,10 @@ size_t WasmStructObject::obj_moved(JSObject* objNew, JSObject* objOld) {
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;
- }
+
+ StorageType fieldType = structType.fields_[fieldIndex].type;
+ uint8_t* data = fieldIndexToAddress(fieldIndex);
WriteValTo(val, fieldType, data);
}
diff --git a/js/src/wasm/WasmGcObject.h b/js/src/wasm/WasmGcObject.h
@@ -69,9 +69,8 @@ class WasmGcObject : public JSObject {
// emphasise the fact that it cannot be interpreted as an offset in any
// single contiguous area of memory:
//
- // * If the object in question is a WasmStructObject, it is the value of
- // `wasm::StructType::fieldOffset()` for the relevant field, without regard
- // to the inline/outline split.
+ // * If the object in question is a WasmStructObject, it is the index of
+ // the relevant field.
//
// * If the object in question is a WasmArrayObject, then
// - u32 == UINT32_MAX (0xFFFF'FFFF) means the "length" property
@@ -319,8 +318,30 @@ static_assert((WasmArrayObject::offsetOfInlineStorage() % 8) == 0);
// Class for a wasm struct. It has inline data and, if the inline area is
// insufficient, a pointer to outline data that lives in the C++ heap.
-// Computing the field offsets is somewhat tricky; see block comment on `class
-// StructLayout` for background.
+// Computing the field offsets is somewhat tricky; see SMDOC in
+// WasmStructLayout.h.
+//
+// From a C++ viewpoint, WasmStructObject just holds two pointers, a shape
+// pointer and the supertype vector pointer. Because of class-total-size
+// roundup effects, it is 16 bytes on both 64- and 32-bit targets.
+//
+// For our purposes a WasmStructObject is always followed immediately by an
+// in-line data area, with maximum size WasmStructObject_MaxInlineBytes. Both
+// the two-word header and the inline data area have 8-aligned sizes. The GC's
+// allocation routines only guarantee 8-byte alignment. This means a
+// WasmStructObject can offer naturally aligned storage for fields of size 8,
+// 4, 2 and 1, but not for fields of size 16, even though the header size is 16
+// bytes.
+//
+// If the available inline storage is insufficient, some part of the inline
+// data are will be used as a pointer to the out of line area. This however is
+// not WasmStructObject's concern: it is unaware of the in-line area layout,
+// all details of which are stored in the associated StructType, and partially
+// cached in TypeDefInstanceData.cached.strukt.
+//
+// Note that MIR alias analysis assumes the OOL-pointer field, if any, is
+// readonly for the life of the object; do not change it once the object is
+// created. See MWasmLoadField::congruentTo.
class WasmStructObject : public WasmGcObject,
public TrailingArray<WasmStructObject> {
@@ -328,41 +349,12 @@ class WasmStructObject : public WasmGcObject,
static const JSClass classInline_;
static const JSClass classOutline_;
- // Owned pointer to a malloc'd block containing out-of-line fields, or
- // nullptr if none. Note that MIR alias analysis assumes this is readonly
- // for the life of the object; do not change it once the object is created.
- // See MWasmLoadObjectField::congruentTo.
- uint8_t* outlineData_;
-
- // The inline (wasm-struct-level) data fields, stored as a trailing array.
- // This must be a multiple of 16 bytes long in order to ensure that no field
- // gets split across the inline-outline boundary. As a refinement, we request
- // this field to begin at an 8-aligned offset relative to the start of the
- // object, so as to guarantee that `double` typed fields are not subject to
- // misaligned-access penalties on any target, whilst wasting at maximum 4
- // bytes of space.
- //
- // Remember that `inlineData` is in reality a variable length block with
- // maximum size WasmStructObject_MaxInlineBytes bytes. Do not add any
- // (C++-level) fields after this point!
- uint8_t* inlineData() {
- return offsetToPointer<uint8_t>(offsetOfInlineData());
- }
-
- // This tells us how big the object is if we know the number of inline bytes
- // it was created with.
- static inline constexpr size_t sizeOfIncludingInlineData(
- size_t sizeOfInlineData) {
- size_t n = sizeof(WasmStructObject) + sizeOfInlineData;
- MOZ_ASSERT(n <= JSObject::MAX_BYTE_SIZE);
- return n;
+ static const JSClass* classFromOOLness(bool needsOOLstorage) {
+ return needsOOLstorage ? &classOutline_ : &classInline_;
}
size_t sizeOfExcludingThis() const;
- static const JSClass* classForTypeDef(const wasm::TypeDef* typeDef);
- static js::gc::AllocKind allocKindForTypeDef(const wasm::TypeDef* typeDef);
-
// Creates a new struct typed object, optionally initialized to zero.
// Reports if there is an out of memory error. The structure's type, shape,
// class pointer, alloc site and alloc kind are taken from `typeDefData`;
@@ -381,61 +373,60 @@ class WasmStructObject : public WasmGcObject,
JSContext* cx, wasm::TypeDefInstanceData* typeDefData,
gc::AllocSite* allocSite, js::gc::Heap initialHeap);
- // Given the total number of data bytes required (including alignment
- // holes), return the number of inline and outline bytes required.
- static inline void getDataByteSizes(uint32_t totalBytes,
- uint32_t* inlineBytes,
- uint32_t* outlineBytes);
-
- // Convenience function; returns true iff ::getDataByteSizes would set
- // *outlineBytes to a non-zero value.
- static inline bool requiresOutlineBytes(uint32_t totalBytes);
-
- // Given the offset of a field, produce the offset in `inlineData` or
- // `*outlineData_` to use, plus a bool indicating which area it is.
- // `fieldType` is for assertional purposes only.
- static inline void fieldOffsetToAreaAndOffset(wasm::StorageType fieldType,
- uint32_t fieldOffset,
- bool* areaIsOutline,
- uint32_t* areaOffset);
-
- // Given the offset of a field, return its actual address. `fieldType` is
- // for assertional purposes only.
- inline uint8_t* fieldOffsetToAddress(wasm::StorageType fieldType,
- uint32_t fieldOffset);
+ // Given the index of a field, return its actual address.
+ uint8_t* fieldIndexToAddress(uint32_t fieldIndex);
+
+ // Operations relating to the OOL block pointer. These involve chain-chasing
+ // starting from `superTypeVector_` and shouldn't be used in very hot paths.
+ bool hasOOLPointer() const;
+ // These will release-assert if called when `!hasOOLPointer()`.
+ uint8_t** addressOfOOLPointer() const;
+ uint8_t* getOOLPointer() const;
+ void setOOLPointer(uint8_t* newOOLpointer);
+
+ // Similar to the above, but find the OOL pointer by looking in the supplied
+ // TypeDefInstanceData. This requires less chain-chasing.
+ uint8_t** addressOfOOLPointer(
+ const wasm::TypeDefInstanceData* typeDefData) const;
+ void setOOLPointer(const wasm::TypeDefInstanceData* typeDefData,
+ uint8_t* newOOLpointer);
// Gets JS Value of the structure field.
bool getField(JSContext* cx, uint32_t index, MutableHandle<Value> val);
- // JIT accessors
- static const uint32_t inlineDataAlignment = 8;
- static constexpr size_t offsetOfOutlineData() {
- return offsetof(WasmStructObject, outlineData_);
- }
- static constexpr size_t offsetOfInlineData() {
- return AlignBytes(sizeof(WasmStructObject), inlineDataAlignment);
- }
-
// Tracing and finalization
static void obj_trace(JSTracer* trc, JSObject* object);
- static void obj_finalize(JS::GCContext* gcx, JSObject* object);
static size_t obj_moved(JSObject* objNew, JSObject* objOld);
void storeVal(const wasm::Val& val, uint32_t fieldIndex);
};
-static_assert((WasmStructObject::offsetOfInlineData() % 8) == 0);
+// This isn't specifically required. Is merely here to make it obvious when
+// the size does change.
+static_assert(sizeof(WasmStructObject) == 16);
+
+// Both `sizeof(WasmStructObject)` and WasmStructObject_MaxInlineBytes
+// must be multiples of 8 for reasons described in the comment on
+// `class WasmStructObject` above.
+static_assert((sizeof(WasmStructObject) % 8) == 0);
-// MaxInlineBytes must be a multiple of 16 for reasons described in the
-// comment on `class StructLayout`. This unfortunately can't be defined
-// inside the class definition itself because the sizeof(..) expression isn't
-// valid until after the end of the class definition.
const size_t WasmStructObject_MaxInlineBytes =
- ((JSObject::MAX_BYTE_SIZE - sizeof(WasmStructObject)) / 16) * 16;
+ ((JSObject::MAX_BYTE_SIZE - sizeof(WasmStructObject)) / 8) * 8;
+
+static_assert((WasmStructObject_MaxInlineBytes % 8) == 0);
+
+// These are EXTREMELY IMPORTANT. Do not remove them. Without them, there is
+// nothing that ensures that the object layouts created by StructType::init()
+// will actually be in accordance with the WasmStructObject layout constraints
+// described above. If either fails, the _ASSUMED values are wrong and will
+// need to be updated.
+static_assert(wasm::WasmStructObject_Size_ASSUMED == sizeof(WasmStructObject));
+static_assert(wasm::WasmStructObject_MaxInlineBytes_ASSUMED ==
+ WasmStructObject_MaxInlineBytes);
+
const size_t WasmArrayObject_MaxInlineBytes =
((JSObject::MAX_BYTE_SIZE - sizeof(WasmArrayObject)) / 16) * 16;
-static_assert((WasmStructObject_MaxInlineBytes % 16) == 0);
static_assert((WasmArrayObject_MaxInlineBytes % 16) == 0);
/* static */
@@ -453,51 +444,47 @@ inline constexpr uint32_t WasmArrayObject::maxInlineElementsForElemSize(
return result;
}
-/*static*/
-inline void WasmStructObject::getDataByteSizes(uint32_t totalBytes,
- uint32_t* inlineBytes,
- uint32_t* outlineBytes) {
- if (MOZ_UNLIKELY(totalBytes > WasmStructObject_MaxInlineBytes)) {
- *inlineBytes = WasmStructObject_MaxInlineBytes;
- *outlineBytes = totalBytes - WasmStructObject_MaxInlineBytes;
- } else {
- *inlineBytes = totalBytes;
- *outlineBytes = 0;
- }
+inline bool WasmStructObject::hasOOLPointer() const {
+ const wasm::SuperTypeVector* stv = superTypeVector_;
+ const wasm::TypeDef* typeDef = stv->typeDef();
+ MOZ_ASSERT(typeDef->superTypeVector() == stv);
+ const wasm::StructType& structType = typeDef->structType();
+ uint32_t offset = structType.oolPointerOffset_;
+ return offset != wasm::StructType::InvalidOffset;
}
-/* static */
-inline bool WasmStructObject::requiresOutlineBytes(uint32_t totalBytes) {
- uint32_t inlineBytes, outlineBytes;
- WasmStructObject::getDataByteSizes(totalBytes, &inlineBytes, &outlineBytes);
- return outlineBytes > 0;
+inline uint8_t** WasmStructObject::addressOfOOLPointer() const {
+ const wasm::SuperTypeVector* stv = superTypeVector_;
+ const wasm::TypeDef* typeDef = stv->typeDef();
+ MOZ_ASSERT(typeDef->superTypeVector() == stv);
+ const wasm::StructType& structType = typeDef->structType();
+ uint32_t offset = structType.oolPointerOffset_;
+ MOZ_RELEASE_ASSERT(offset != wasm::StructType::InvalidOffset);
+ return (uint8_t**)((uint8_t*)this + offset);
}
-/*static*/
-inline void WasmStructObject::fieldOffsetToAreaAndOffset(
- wasm::StorageType fieldType, uint32_t fieldOffset, bool* areaIsOutline,
- uint32_t* areaOffset) {
- if (fieldOffset < WasmStructObject_MaxInlineBytes) {
- *areaIsOutline = false;
- *areaOffset = fieldOffset;
- } else {
- *areaIsOutline = true;
- *areaOffset = fieldOffset - WasmStructObject_MaxInlineBytes;
- }
- // Assert that the first and last bytes for the field agree on which side of
- // the inline/outline boundary they live.
- MOZ_RELEASE_ASSERT(
- (fieldOffset < WasmStructObject_MaxInlineBytes) ==
- ((fieldOffset + fieldType.size() - 1) < WasmStructObject_MaxInlineBytes));
+inline uint8_t* WasmStructObject::getOOLPointer() const {
+ return *addressOfOOLPointer();
+}
+
+inline void WasmStructObject::setOOLPointer(uint8_t* newOOLpointer) {
+ *addressOfOOLPointer() = newOOLpointer;
+}
+
+inline uint8_t** WasmStructObject::addressOfOOLPointer(
+ const wasm::TypeDefInstanceData* typeDefData) const {
+ uint32_t offset = typeDefData->cached.strukt.oolPointerOffset;
+ MOZ_RELEASE_ASSERT(offset != wasm::StructType::InvalidOffset);
+ uint8_t** addr = (uint8_t**)((uint8_t*)this + offset);
+ // Don't turn this into a release-assert; that would defeat the purpose of
+ // having this method.
+ MOZ_ASSERT(addr == addressOfOOLPointer());
+ return addr;
}
-inline uint8_t* WasmStructObject::fieldOffsetToAddress(
- wasm::StorageType fieldType, uint32_t fieldOffset) {
- bool areaIsOutline;
- uint32_t areaOffset;
- fieldOffsetToAreaAndOffset(fieldType, fieldOffset, &areaIsOutline,
- &areaOffset);
- return (areaIsOutline ? outlineData_ : inlineData()) + areaOffset;
+inline void WasmStructObject::setOOLPointer(
+ const wasm::TypeDefInstanceData* typeDefData, uint8_t* newOOLpointer) {
+ *addressOfOOLPointer(typeDefData) = newOOLpointer;
}
// Ensure that faulting loads/stores for WasmStructObject and WasmArrayObject
diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp
@@ -2473,44 +2473,51 @@ bool Instance::init(JSContext* cx, const JSObjectVector& funcImports,
if (typeDef.kind() == TypeDefKind::Struct ||
typeDef.kind() == TypeDefKind::Array) {
- // Compute the parameters that allocation will use. First, the class
- // and alloc kind for the type definition.
- const JSClass* clasp;
- gc::AllocKind allocKind;
-
+ // Compute the parameters that allocation will use. First, the class for
+ // the type definition.
if (typeDef.kind() == TypeDefKind::Struct) {
- clasp = WasmStructObject::classForTypeDef(&typeDef);
- allocKind = WasmStructObject::allocKindForTypeDef(&typeDef);
- allocKind = gc::GetFinalizedAllocKindForClass(allocKind, clasp);
+ const StructType& structType = typeDef.structType();
+ bool needsOOLstorage = structType.hasOOL();
+ typeDefData->clasp =
+ WasmStructObject::classFromOOLness(needsOOLstorage);
} else {
- clasp = &WasmArrayObject::class_;
- allocKind = gc::AllocKind::INVALID;
+ typeDefData->clasp = &WasmArrayObject::class_;
}
// Find the shape using the class and recursion group
const ObjectFlags objectFlags = {ObjectFlag::NotExtensible};
- typeDefData->shape =
- WasmGCShape::getShape(cx, clasp, cx->realm(), TaggedProto(),
- &typeDef.recGroup(), objectFlags);
+ typeDefData->shape = WasmGCShape::getShape(
+ cx, typeDefData->clasp, cx->realm(), TaggedProto(),
+ &typeDef.recGroup(), objectFlags);
if (!typeDefData->shape) {
return false;
}
- typeDefData->clasp = clasp;
- typeDefData->allocKind = allocKind;
-
- // If `typeDef` is a struct, cache its size here, so that allocators
- // don't have to chase back through `typeDef` to determine that.
- // Similarly, if `typeDef` is an array, cache its array element size
- // here.
- MOZ_ASSERT(typeDefData->unused == 0);
+ // If `typeDef` is a struct, cache some layout info here, so that
+ // allocators don't have to chase back through `typeDef` to determine
+ // that. Similarly, if `typeDef` is an array, cache its array element
+ // size here.
if (typeDef.kind() == TypeDefKind::Struct) {
- typeDefData->structTypeSize = typeDef.structType().size_;
- // StructLayout::close ensures this is an integral number of words.
- MOZ_ASSERT((typeDefData->structTypeSize % sizeof(uintptr_t)) == 0);
+ const StructType& structType = typeDef.structType();
+ typeDefData->cached.strukt.payloadOffsetIL =
+ structType.payloadOffsetIL_;
+ typeDefData->cached.strukt.totalSizeIL = structType.totalSizeIL_;
+ typeDefData->cached.strukt.totalSizeOOL = structType.totalSizeOOL_;
+ typeDefData->cached.strukt.oolPointerOffset =
+ structType.oolPointerOffset_;
+ typeDefData->cached.strukt.allocKind =
+ gc::GetFinalizedAllocKindForClass(structType.allocKind_,
+ typeDefData->clasp);
+ MOZ_ASSERT(!IsFinalizedKind(typeDefData->cached.strukt.allocKind));
+ // StructLayout::totalSizeIL/OOL() ensures these are an integral number
+ // of words.
+ MOZ_ASSERT(
+ (typeDefData->cached.strukt.totalSizeIL % sizeof(uintptr_t)) == 0);
+ MOZ_ASSERT(
+ (typeDefData->cached.strukt.totalSizeOOL % sizeof(uintptr_t)) == 0);
} else {
uint32_t arrayElemSize = typeDef.arrayType().elementType().size();
- typeDefData->arrayElemSize = arrayElemSize;
+ typeDefData->cached.array.elemSize = arrayElemSize;
MOZ_ASSERT(arrayElemSize == 16 || arrayElemSize == 8 ||
arrayElemSize == 4 || arrayElemSize == 2 ||
arrayElemSize == 1);
@@ -4016,9 +4023,8 @@ WasmStructObject* Instance::constantStructNewDefault(JSContext* cx,
TypeDefInstanceData* typeDefData = typeDefInstanceData(typeIndex);
const wasm::TypeDef* typeDef = typeDefData->typeDef;
MOZ_ASSERT(typeDef->kind() == wasm::TypeDefKind::Struct);
- uint32_t totalBytes = typeDef->structType().size_;
- bool needsOOL = WasmStructObject::requiresOutlineBytes(totalBytes);
+ bool needsOOL = typeDef->structType().hasOOL();
return needsOOL ? WasmStructObject::createStructOOL<true>(
cx, typeDefData, nullptr, gc::Heap::Tenured)
: WasmStructObject::createStructIL<true>(
diff --git a/js/src/wasm/WasmInstanceData.h b/js/src/wasm/WasmInstanceData.h
@@ -52,9 +52,10 @@ struct TypeDefInstanceData {
: typeDef(nullptr),
superTypeVector(nullptr),
shape(nullptr),
- clasp(nullptr),
- allocKind(gc::AllocKind::LIMIT),
- unused(0) {}
+ clasp(nullptr) {
+ memset(&cached, 0, sizeof(cached));
+ cached.strukt.allocKind = gc::AllocKind::INVALID;
+ }
// The canonicalized pointer to this type definition. This is kept alive by
// the type context associated with the instance.
@@ -62,32 +63,34 @@ struct TypeDefInstanceData {
// The supertype vector for this type definition. This is also kept alive
// by the type context associated with the instance.
- //
const wasm::SuperTypeVector* superTypeVector;
- // The next four fields are only meaningful for, and used by, structs and
+ // The next three fields are only meaningful for, and used by, structs and
// arrays.
GCPtr<Shape*> shape;
const JSClass* clasp;
- // Only valid for structs.
- gc::AllocKind allocKind;
- // This union is only meaningful for structs and arrays, and should
- // otherwise be set to zero:
- //
- // * if `typeDef` refers to a struct type, then it caches the value of
- // `typeDef->structType().size_` (a size in bytes)
- //
- // * if `typeDef` refers to an array type, then it caches the value of
- // `typeDef->arrayType().elementType_.size()` (also a size in bytes)
- //
- // This is so that allocators of structs and arrays don't need to chase from
- // this TypeDefInstanceData through `typeDef` to find the value.
+ // This union is only meaningful for structs and arrays, and should otherwise
+ // be zeroed out. It exists so that allocators of structs and arrays don't
+ // need to chase through `typeDef` to find this info.
union {
- uint32_t structTypeSize;
- uint32_t arrayElemSize;
- uint32_t unused;
- };
+ struct {
+ // When `typeDef` refers to a struct type, these are copied unchanged
+ // from fields of the same name in StructType.
+ uint32_t payloadOffsetIL;
+ uint32_t totalSizeIL;
+ uint32_t totalSizeOOL;
+ uint32_t oolPointerOffset;
+ // Copied from StructType, and updated by GetFinalizedAllocKindForClass
+ // (see comment on StructType::allocKind_).
+ gc::AllocKind allocKind;
+ } strukt;
+ struct {
+ // When `typeDef` refers to an array type, this caches the value of
+ // `typeDef->arrayType().fieldType_.size()` (a size in bytes).
+ uint32_t elemSize;
+ } array;
+ } cached;
static constexpr size_t offsetOfShape() {
return offsetof(TypeDefInstanceData, shape);
@@ -96,7 +99,7 @@ struct TypeDefInstanceData {
return offsetof(TypeDefInstanceData, superTypeVector);
}
static constexpr size_t offsetOfArrayElemSize() {
- return offsetof(TypeDefInstanceData, arrayElemSize);
+ return offsetof(TypeDefInstanceData, cached.array.elemSize);
}
};
diff --git a/js/src/wasm/WasmIonCompile.cpp b/js/src/wasm/WasmIonCompile.cpp
@@ -5133,22 +5133,18 @@ class FunctionCompiler {
uint32_t fieldIndex, MDefinition* structObject, MDefinition* value,
WasmPreBarrierKind preBarrierKind) {
StorageType fieldType = structType.fields_[fieldIndex].type;
- uint32_t fieldOffset = structType.fieldOffset(fieldIndex);
-
- bool areaIsOutline;
- uint32_t areaOffset;
- WasmStructObject::fieldOffsetToAreaAndOffset(fieldType, fieldOffset,
- &areaIsOutline, &areaOffset);
+ FieldAccessPath path = structType.fieldAccessPaths_[fieldIndex];
+ uint32_t areaOffset = path.hasOOL() ? path.oolOffset() : path.ilOffset();
// Make `base` point at the first byte of either the struct object as a
- // whole or of the out-of-line data area. And adjust `areaOffset`
- // accordingly.
+ // whole or of the out-of-line data area.
MDefinition* base;
bool needsTrapInfo;
- if (areaIsOutline) {
+ if (path.hasOOL()) {
+ // The path has two components, of which the first (the IL component) is
+ // the offset where the OOL pointer is stored. Hence `path.ilOffset()`.
auto* loadDataPointer = MWasmLoadField::New(
- alloc(), structObject, nullptr,
- WasmStructObject::offsetOfOutlineData(), mozilla::Nothing(),
+ alloc(), structObject, nullptr, path.ilOffset(), mozilla::Nothing(),
MIRType::WasmStructData, MWideningOp::None,
AliasSet::Load(AliasSet::WasmStructOutlineDataPointer),
mozilla::Some(trapSiteDesc()));
@@ -5161,14 +5157,12 @@ class FunctionCompiler {
} else {
base = structObject;
needsTrapInfo = true;
- areaOffset += WasmStructObject::offsetOfInlineData();
}
// The transaction is to happen at `base + areaOffset`, so to speak.
- // After this point we must ignore `fieldOffset`.
// The alias set denoting the field's location, although lacking a
// Load-vs-Store indication at this point.
- AliasSet::Flag fieldAliasSet = areaIsOutline
+ AliasSet::Flag fieldAliasSet = path.hasOOL()
? AliasSet::WasmStructOutlineDataArea
: AliasSet::WasmStructInlineDataArea;
@@ -5185,22 +5179,18 @@ class FunctionCompiler {
const StructType& structType, uint32_t fieldIndex,
FieldWideningOp wideningOp, MDefinition* structObject) {
StorageType fieldType = structType.fields_[fieldIndex].type;
- uint32_t fieldOffset = structType.fieldOffset(fieldIndex);
-
- bool areaIsOutline;
- uint32_t areaOffset;
- WasmStructObject::fieldOffsetToAreaAndOffset(fieldType, fieldOffset,
- &areaIsOutline, &areaOffset);
+ FieldAccessPath path = structType.fieldAccessPaths_[fieldIndex];
+ uint32_t areaOffset = path.hasOOL() ? path.oolOffset() : path.ilOffset();
// Make `base` point at the first byte of either the struct object as a
- // whole or of the out-of-line data area. And adjust `areaOffset`
- // accordingly.
+ // whole or of the out-of-line data area.
MDefinition* base;
bool needsTrapInfo;
- if (areaIsOutline) {
+ if (path.hasOOL()) {
+ // The path has two components, of which the first (the IL component) is
+ // the offset where the OOL pointer is stored. Hence `path.ilOffset()`.
auto* loadDataPointer = MWasmLoadField::New(
- alloc(), structObject, nullptr,
- WasmStructObject::offsetOfOutlineData(), mozilla::Nothing(),
+ alloc(), structObject, nullptr, path.ilOffset(), mozilla::Nothing(),
MIRType::WasmStructData, MWideningOp::None,
AliasSet::Load(AliasSet::WasmStructOutlineDataPointer),
mozilla::Some(trapSiteDesc()));
@@ -5213,14 +5203,12 @@ class FunctionCompiler {
} else {
base = structObject;
needsTrapInfo = true;
- areaOffset += WasmStructObject::offsetOfInlineData();
}
// The transaction is to happen at `base + areaOffset`, so to speak.
- // After this point we must ignore `fieldOffset`.
// The alias set denoting the field's location, although lacking a
// Load-vs-Store indication at this point.
- AliasSet::Flag fieldAliasSet = areaIsOutline
+ AliasSet::Flag fieldAliasSet = path.hasOOL()
? AliasSet::WasmStructOutlineDataArea
: AliasSet::WasmStructInlineDataArea;
diff --git a/js/src/wasm/WasmModuleTypes.cpp b/js/src/wasm/WasmModuleTypes.cpp
@@ -30,6 +30,69 @@ using namespace js::wasm;
using mozilla::CheckedInt32;
using mozilla::MallocSizeOf;
+//=========================================================================
+// TagLayout
+
+static CheckedInt32 RoundUpToAlignment(CheckedInt32 address, uint32_t align) {
+ MOZ_ASSERT(mozilla::IsPowerOfTwo(align));
+
+ // Note: Be careful to order operators such that we first make the
+ // value smaller and then larger, so that we don't get false
+ // overflow errors due to (e.g.) adding `align` and then
+ // subtracting `1` afterwards when merely adding `align-1` would
+ // not have overflowed. Note that due to the nature of two's
+ // complement representation, if `address` is already aligned,
+ // then adding `align-1` cannot itself cause an overflow.
+
+ return ((address + (align - 1)) / align) * align;
+}
+
+class TagLayout {
+ mozilla::CheckedInt32 sizeSoFar = 0;
+ uint32_t tagAlignment = 1;
+
+ public:
+ // The field adders return the offset of the the field.
+ mozilla::CheckedInt32 addField(StorageType type) {
+ uint32_t fieldSize = type.size();
+ uint32_t fieldAlignment = type.alignmentInStruct();
+
+ MOZ_ASSERT(fieldSize >= 1 && fieldSize <= 16);
+ MOZ_ASSERT((fieldSize & (fieldSize - 1)) == 0); // is a power of 2
+ MOZ_ASSERT(fieldAlignment == fieldSize); // is naturally aligned
+
+ // Alignment of the tag is the max of the alignment of its fields.
+ tagAlignment = std::max(tagAlignment, fieldAlignment);
+
+ // Align the pointer.
+ CheckedInt32 offset = RoundUpToAlignment(sizeSoFar, fieldAlignment);
+ if (!offset.isValid()) {
+ return offset;
+ }
+
+ // Allocate space.
+ sizeSoFar = offset + fieldSize;
+ if (!sizeSoFar.isValid()) {
+ return sizeSoFar;
+ }
+
+ return offset;
+ }
+
+ // The close method rounds up the structure size to the appropriate
+ // alignment and returns that size.
+ mozilla::CheckedInt32 close() {
+ CheckedInt32 size = RoundUpToAlignment(sizeSoFar, tagAlignment);
+ // Make the overall size be an integral number of machine words.
+ if (tagAlignment < sizeof(uintptr_t)) {
+ size = RoundUpToAlignment(size, sizeof(uintptr_t));
+ }
+ return size;
+ }
+};
+
+//=========================================================================
+
/* static */
CacheableName CacheableName::fromUTF8Chars(UniqueChars&& utf8Chars) {
size_t length = strlen(utf8Chars.get());
@@ -136,7 +199,7 @@ bool TagType::initialize(const SharedTypeDef& funcType) {
return false;
}
- StructLayout layout;
+ TagLayout layout;
for (size_t i = 0; i < args.length(); i++) {
CheckedInt32 offset = layout.addField(StorageType(args[i].packed()));
if (!offset.isValid()) {
diff --git a/js/src/wasm/WasmStructLayout.cpp b/js/src/wasm/WasmStructLayout.cpp
@@ -0,0 +1,80 @@
+/* -*- 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/WasmStructLayout.h"
+
+#include "jstypes.h" // RoundUp
+
+// This is a simple implementation of a layouter. It places the OOL pointer
+// at the start of the IL payload area, regardless of whether an OOL area is
+// actually necessary.
+
+namespace js::wasm {
+
+#ifdef DEBUG
+static bool IsWordAligned(uintptr_t x) { return (x % sizeof(void*)) == 0; }
+#endif
+
+bool StructLayout::init(uint32_t firstUsableILOffset, uint32_t usableILSize) {
+ // Not actually necessary, but it would be strange if this wasn't so.
+ MOZ_ASSERT(IsWordAligned(firstUsableILOffset));
+ MOZ_ASSERT(IsWordAligned(usableILSize));
+ // Must have at least enough space to hold the OOL pointer
+ MOZ_ASSERT(usableILSize >= sizeof(void*));
+ // Set up mutable state
+ startILO_ = firstUsableILOffset;
+ endPlusILO_ = firstUsableILOffset + usableILSize;
+ // Install the OOL pointer immediately after the start of the usable area
+ oolptrILO_ = js::RoundUp(startILO_, sizeof(void*));
+ MOZ_ASSERT(IsWordAligned(oolptrILO_));
+ nextILO_ = oolptrILO_ + sizeof(void*);
+ return true;
+}
+
+// Add a field of the specified size, and get back its access path. The two
+// release assertions together guarantee that the maximum offset that could be
+// generated is roughly `16 * js::wasm::MaxStructFields`, so there is no need
+// to use checked integers in the layout computations.
+
+bool StructLayout::addField(uint32_t fieldSize, FieldAccessPath* path) {
+ MOZ_ASSERT(fieldSize == 16 || fieldSize == 8 || fieldSize == 4 ||
+ fieldSize == 2 || fieldSize == 1);
+ // Guard against field-offset overflow.
+ numFieldsProcessed_++;
+ MOZ_RELEASE_ASSERT(numFieldsProcessed_ <= js::wasm::MaxStructFields);
+ MOZ_RELEASE_ASSERT(fieldSize <= 16);
+ // Figure out where nextILO_ would advance to if the field were placed in
+ // the inline area.
+ uint32_t nextNextILO = js::RoundUp(nextILO_, fieldSize) + fieldSize;
+ if (nextNextILO <= endPlusILO_) {
+ // It'll fit in-line
+ nextILO_ = nextNextILO;
+ *path = FieldAccessPath(nextILO_ - fieldSize);
+ return true;
+ }
+ // Otherwise out-of-line
+ nextOOLO_ = js::RoundUp(nextOOLO_, fieldSize) + fieldSize;
+ *path = FieldAccessPath(oolptrILO_, nextOOLO_ - fieldSize);
+ return true;
+}
+
+uint32_t StructLayout::totalSizeIL() const {
+ return js::RoundUp(nextILO_, sizeof(void*));
+}
+
+bool StructLayout::hasOOL() const { return nextOOLO_ > 0; }
+
+uint32_t StructLayout::totalSizeOOL() const {
+ MOZ_ASSERT(hasOOL());
+ return js::RoundUp(nextOOLO_, sizeof(void*));
+}
+
+FieldAccessPath StructLayout::oolPointerPath() const {
+ MOZ_ASSERT(hasOOL());
+ return FieldAccessPath(oolptrILO_);
+}
+
+} // namespace js::wasm
diff --git a/js/src/wasm/WasmStructLayout.h b/js/src/wasm/WasmStructLayout.h
@@ -0,0 +1,189 @@
+/* -*- 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/. */
+
+#ifndef wasm_WasmStructLayout_h
+#define wasm_WasmStructLayout_h
+
+#include "mozilla/Assertions.h"
+
+#include <stdint.h>
+
+#include "wasm/WasmConstants.h" // MaxStructFields
+
+// [SMDOC] Wasm Struct Layout Overview
+//
+// (1) Struct layout is almost entirely decoupled from the details of
+// WasmStructObject. Layout needs only to know the fixed-header-size and
+// inline-payload-size for WasmStructObject. To avoid header-file cycle
+// complexity, the values WasmStructObject_Size_ASSUMED and
+// WasmStructObject_MaxInlineBytes_ASSUMED are defined in this file. These
+// assumptions are made safe by static assertions in WasmGcObject.h; see
+// comments there.
+//
+// (2) A structure layout consists of a FieldAccessPath value (see below) for
+// each field, together with the total object size and inline payload size
+// for the WasmStructObject, and possibly the total OOL size required.
+//
+// (3) Struct layout info is stored in `class wasm::StructType`. It is
+// computed early in the compilation process, by `StructType::init`, when
+// decoding the module environment. The struct's AllocKind is also
+// computed.
+//
+// (4) Structs that do not need an OOL pointer are not forced to have one.
+// Whether one is required can be determined by calling
+// `StructType::hasOOL`. If one is present, its offset relative to the
+// start of the WasmStructObject is stored in
+// `StructType::oolPointerOffset_`.
+//
+// (5) When generating code for field accesses, info (4) is not needed.
+// Instead a field access is described by its FieldAccessPath; this is all
+// that is needed (apart from the field's type) to generate the relevant
+// loads/stores.
+//
+// (6) StructType is the single point of truth for struct layouts. However,
+// for performance reasons, at instantiation time, some fields of a
+// StructType are copied into the associated TypeDefInstanceData's
+// `cached.strukt` union, mainly so as to make struct allocation faster.
+// This copying is done by `Instance::init`.
+//
+// (7) WasmStructObject::createStructIL and related machinery look at fields in
+// the TypeDefInstanceData to get relevant run-time info (the AllocKind,
+// etc).
+//
+// (8) At run-time, it may be necessary to find the OOL pointer for arbitrary
+// WasmStructObjects, mostly in the GC-support routines. This can be
+// obtained (at some expense) from
+// `WasmStructObject::hasOOLPointer/getOOLPointer` and related methods.
+//
+// (9) Note: the allocation machinery will ensure that fields of size 1, 2, 4
+// and 8 bytes are naturally aligned. However, 16 byte fields are only
+// guaranteed 8 byte alignment. This is because the underlying heap
+// allocator only provides 8 byte alignment, so even if 16 byte fields were
+// 16-aligned relative to the start of a WasmStructObject, there's no
+// guarantee they would be 16-aligned when actually written to the heap.
+
+namespace js::wasm {
+
+// These values are defined by WasmStructObject's layout but are needed early
+// in the compilation pipeline in order to compute struct layouts. Rather than
+// create a header file cycle involving WasmGcObject.h, WasmTypeDef.h and this
+// file, it seems simpler to assume what they are and static_assert this is
+// correct on WasmGcObject.h. These values are expected to change rarely, if
+// ever.
+//
+// See comment on static_assert involving these in WasmGcObject.h.
+const size_t WasmStructObject_Size_ASSUMED = 16;
+
+#ifdef JS_64BIT
+const size_t WasmStructObject_MaxInlineBytes_ASSUMED = 136;
+#else
+const size_t WasmStructObject_MaxInlineBytes_ASSUMED = 128;
+#endif
+
+//=========================================================================
+// FieldAccessPath
+
+// FieldAccessPath describes the offsets needed to access a field in a
+// StructType. It contains either one or two values.
+//
+// Let `obj` be a `WasmStructObject*`. Then, for a field with path `p`:
+//
+// * if !p.hasOOL(), the data is at obj + p.ilOffset().
+//
+// * if p.hasOOL(), let oolptr = *(obj + p.ilOffset()).
+// The data is at oolptr + p.oolOffset().
+//
+// It is implied from this that the `ilOffset()` values incorporate the fixed
+// header size of WasmStructObject; that does not need to be added on here.
+
+class FieldAccessPath {
+ uint32_t path_;
+ static constexpr uint32_t ILBits = 9;
+ static constexpr uint32_t OOLBits = 32 - ILBits;
+ static constexpr uint32_t MaxValidILOffset = (1 << ILBits) - 1;
+ static constexpr uint32_t MaxValidOOLOffset = (1 << OOLBits) - 1 - 1;
+ static constexpr uint32_t InvalidOOLOffset = MaxValidOOLOffset + 1;
+ uint32_t getIL() const { return path_ & MaxValidILOffset; }
+ uint32_t getOOL() const { return path_ >> ILBits; }
+ // Ensure ILBits is sufficient for any valid IL offset.
+ static_assert((WasmStructObject_Size_ASSUMED +
+ WasmStructObject_MaxInlineBytes_ASSUMED) < MaxValidILOffset);
+ // A crude check to ensure that OOLBits is sufficient for any situation,
+ // assuming a worst-case future scenario where fields can be up to 64 bytes
+ // long (eg for Intel AVX512 fields).
+ static_assert(js::wasm::MaxStructFields * 64 < MaxValidOOLOffset);
+
+ public:
+ FieldAccessPath() : path_(0) {}
+ explicit FieldAccessPath(uint32_t offsetIL)
+ : path_((InvalidOOLOffset << ILBits) | offsetIL) {
+ MOZ_ASSERT(offsetIL <= MaxValidILOffset);
+ }
+ FieldAccessPath(uint32_t offsetIL, uint32_t offsetOOL)
+ : path_((offsetOOL << ILBits) | offsetIL) {
+ MOZ_ASSERT(offsetIL <= MaxValidILOffset);
+ MOZ_ASSERT(offsetOOL <= MaxValidOOLOffset);
+ }
+ bool hasOOL() const { return getOOL() != InvalidOOLOffset; }
+ uint32_t ilOffset() const { return getIL(); }
+ uint32_t oolOffset() const {
+ MOZ_ASSERT(hasOOL());
+ return getOOL();
+ }
+};
+
+static_assert(sizeof(FieldAccessPath) == sizeof(uint32_t));
+
+//=========================================================================
+// StructLayout, the top level interface for structure layout machinery.
+
+class StructLayout {
+ //---------------- Interface ----------------
+ public:
+ // Initialises the layouter. `firstUsableILOffset` is the first allowable
+ // payload byte offset in the IL object; offsets prior to that are considered
+ // reserved. `usableILSize` is the maximum allowed size of the IL payload
+ // area.
+ bool init(uint32_t firstUsableILOffset, uint32_t usableILSize);
+
+ // Add a field of the specified size, and get back its access path, or
+ // `false` to indicate OOM.
+ bool addField(uint32_t fieldSize, FieldAccessPath* path);
+
+ // Return the total IL object size (including reserved area) so far, rounded
+ // up to an integral number of words.
+ uint32_t totalSizeIL() const;
+
+ // Returns true iff an OOL area is needed.
+ bool hasOOL() const;
+
+ // Returns the total OOL block size so far, rounded up to an integral number
+ // of words. Invalid to call if `!hasOOL()`.
+ uint32_t totalSizeOOL() const;
+
+ // Returns the access path in the IL area, to get the OOL pointer.
+ // Invalid to call if `!hasOOL()`.
+ FieldAccessPath oolPointerPath() const;
+
+ //---------------- Implementation ----------------
+ private:
+ // Set at the start and then unchanged:
+ // [start, end) for allowable inline offsets
+ uint32_t startILO_ = 0;
+ uint32_t endPlusILO_ = 0;
+ // The offset of the OOL pointer
+ uint32_t oolptrILO_ = 0;
+ // These change as fields are added:
+ // The next available inline and out-of-line offset
+ uint32_t nextILO_ = 0;
+ uint32_t nextOOLO_ = 0;
+ // The total number of fields processed so far
+ uint32_t numFieldsProcessed_ = 0;
+};
+
+} // namespace js::wasm
+
+#endif /* wasm_WasmStructLayout_h */
diff --git a/js/src/wasm/WasmTypeDef.cpp b/js/src/wasm/WasmTypeDef.cpp
@@ -32,6 +32,8 @@
#include "wasm/WasmGcObject.h"
#include "wasm/WasmJS.h"
+#include "gc/ObjectKind-inl.h"
+
using namespace js;
using namespace js::jit;
using namespace js::wasm;
@@ -267,115 +269,75 @@ size_t FuncType::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const {
}
//=========================================================================
-// StructType and StructLayout
-
-static inline CheckedInt32 RoundUpToAlignment(CheckedInt32 address,
- uint32_t align) {
- MOZ_ASSERT(IsPowerOfTwo(align));
-
- // Note: Be careful to order operators such that we first make the
- // value smaller and then larger, so that we don't get false
- // overflow errors due to (e.g.) adding `align` and then
- // subtracting `1` afterwards when merely adding `align-1` would
- // not have overflowed. Note that due to the nature of two's
- // complement representation, if `address` is already aligned,
- // then adding `align-1` cannot itself cause an overflow.
-
- return ((address + (align - 1)) / align) * align;
-}
-
-CheckedInt32 StructLayout::addField(StorageType type) {
- uint32_t fieldSize = type.size();
- uint32_t fieldAlignment = type.alignmentInStruct();
+// StructType
- // We have to ensure that `offset` is chosen so that no field crosses the
- // inline/outline boundary. The assertions here ensure that. See comment
- // on `class StructLayout` for background.
- MOZ_ASSERT(fieldSize >= 1 && fieldSize <= 16);
- MOZ_ASSERT((fieldSize & (fieldSize - 1)) == 0); // is a power of 2
- MOZ_ASSERT(fieldAlignment == fieldSize); // is naturally aligned
+bool StructType::init() {
+ isDefaultable_ = true;
- // Alignment of the struct is the max of the alignment of its fields.
- structAlignment = std::max(structAlignment, fieldAlignment);
+ // Ensures the inline storage area is word-aligned.
+ static_assert((sizeof(WasmStructObject) % sizeof(uintptr_t)) == 0);
- // Align the pointer.
- CheckedInt32 offset = RoundUpToAlignment(sizeSoFar, fieldAlignment);
- if (!offset.isValid()) {
- return offset;
- }
-
- // Allocate space.
- sizeSoFar = offset + fieldSize;
- if (!sizeSoFar.isValid()) {
- return sizeSoFar;
+ MOZ_ASSERT(fieldAccessPaths_.empty() && outlineTraceOffsets_.empty() &&
+ inlineTraceOffsets_.empty());
+ if (!fieldAccessPaths_.reserve(fields_.length())) {
+ return false;
}
- // The following should hold if the three assertions above hold.
- MOZ_ASSERT(offset / 16 == (offset + fieldSize - 1) / 16);
- return offset;
-}
+ // So as to guarantee the value will fit in payloadOffsetIL_, since it is
+ // a uint8_t.
+ static_assert(WasmStructObject_Size_ASSUMED <
+ (1 << (8 * sizeof(StructType::payloadOffsetIL_))));
+ payloadOffsetIL_ = WasmStructObject_Size_ASSUMED;
-CheckedInt32 StructLayout::close() {
- CheckedInt32 size = RoundUpToAlignment(sizeSoFar, structAlignment);
- // What we are computing into `size` is the size of
- // WasmGcObject::inlineData_, or the size of the outline data area. Either
- // way, it is helpful if the area size is an integral number of machine
- // words, since this would make any initialisation loop for inline
- // allocation able to operate on machine word sized units, should we decide
- // to do inline allocation.
- if (structAlignment < sizeof(uintptr_t)) {
- size = RoundUpToAlignment(size, sizeof(uintptr_t));
+ StructLayout layout;
+ if (!layout.init(payloadOffsetIL_, WasmStructObject_MaxInlineBytes_ASSUMED)) {
+ return false;
}
- return size;
-}
-bool StructType::init() {
- bool isDefaultable = true;
-
- StructLayout layout;
for (FieldType& field : fields_) {
- CheckedInt32 offset = layout.addField(field.type);
- if (!offset.isValid()) {
+ // Get a placement decision for the field
+ FieldAccessPath path;
+ if (!layout.addField(field.type.size(), &path)) {
return false;
}
- // Add the offset to the list
- if (!fieldOffsets_.append(offset.value())) {
- return false;
- }
+ // Add the access path to the vector thereof
+ fieldAccessPaths_.infallibleAppend(path);
// If any field is not defaultable, this whole struct is not defaultable
if (!field.type.isDefaultable()) {
- isDefaultable = false;
+ isDefaultable_ = false;
}
- // If this field is not a ref, then don't add it to the trace lists
- if (!field.type.isRefRepr()) {
- continue;
- }
-
- bool isOutline;
- uint32_t adjustedOffset;
- WasmStructObject::fieldOffsetToAreaAndOffset(field.type, offset.value(),
- &isOutline, &adjustedOffset);
- if (isOutline) {
- if (!outlineTraceOffsets_.append(adjustedOffset)) {
- return false;
- }
- } else {
- if (!inlineTraceOffsets_.append(adjustedOffset)) {
- return false;
+ // If this field is a ref, add it to the trace vectors
+ if (field.type.isRefRepr()) {
+ if (path.hasOOL()) {
+ if (!outlineTraceOffsets_.append(path.oolOffset())) {
+ return false;
+ }
+ } else {
+ if (!inlineTraceOffsets_.append(path.ilOffset())) {
+ return false;
+ }
}
}
}
- CheckedInt32 size = layout.close();
- if (!size.isValid()) {
- return false;
+ if (layout.hasOOL()) {
+ totalSizeOOL_ = layout.totalSizeOOL();
+ FieldAccessPath oolPointerPath = layout.oolPointerPath();
+ MOZ_ASSERT(!oolPointerPath.hasOOL());
+ oolPointerOffset_ = oolPointerPath.ilOffset();
+ } else {
+ totalSizeOOL_ = 0;
+ oolPointerOffset_ = StructType::InvalidOffset;
}
- size_ = size.value();
- isDefaultable_ = isDefaultable;
+ totalSizeIL_ = layout.totalSizeIL();
+ // payloadOffsetIL_ set above
+ allocKind_ = gc::GetGCObjectKindForBytes(totalSizeIL_);
+ // isDefaultable_ set above
+
return true;
}
diff --git a/js/src/wasm/WasmTypeDef.h b/js/src/wasm/WasmTypeDef.h
@@ -20,7 +20,6 @@
#define wasm_type_def_h
#include "mozilla/Assertions.h"
-#include "mozilla/CheckedInt.h"
#include "mozilla/HashTable.h"
#include "js/RefCounted.h"
@@ -29,6 +28,7 @@
#include "wasm/WasmCompileArgs.h"
#include "wasm/WasmConstants.h"
#include "wasm/WasmSerialize.h"
+#include "wasm/WasmStructLayout.h"
#include "wasm/WasmUtility.h"
#include "wasm/WasmValType.h"
@@ -298,7 +298,7 @@ struct FieldType {
using FieldTypeVector = Vector<FieldType, 0, SystemAllocPolicy>;
-using FieldOffsetVector = Vector<uint32_t, 2, SystemAllocPolicy>;
+using FieldAccessPathVector = Vector<FieldAccessPath, 2, SystemAllocPolicy>;
using InlineTraceOffsetVector = Vector<uint32_t, 2, SystemAllocPolicy>;
using OutlineTraceOffsetVector = Vector<uint32_t, 0, SystemAllocPolicy>;
@@ -306,24 +306,56 @@ class StructType {
public:
// Vector of the fields in this struct
FieldTypeVector fields_;
- // The total size of this struct in bytes
- uint32_t size_;
- // The offset for every struct field
- FieldOffsetVector fieldOffsets_;
+ // The access path for every struct field. Same length as `fields_`.
+ FieldAccessPathVector fieldAccessPaths_;
+
// The offsets of fields that must be traced in the inline portion of wasm
- // struct object.
+ // struct object. Offsets are from the base of the WasmStructObject.
InlineTraceOffsetVector inlineTraceOffsets_;
// The offsets of fields that must be traced in the outline portion of wasm
// struct object.
OutlineTraceOffsetVector outlineTraceOffsets_;
+
+ // The total block size for the WasmStructObject's OOL data area, or zero if
+ // no OOL data is required.
+ uint32_t totalSizeOOL_;
+ // The offset from the start of the WasmStructObject to the OOL pointer
+ // field, or InvalidOffset if no OOL data is required.
+ uint32_t oolPointerOffset_;
+
+ // The total object size for the WasmStructObject, including fixed overheads
+ // (its header words).
+ uint32_t totalSizeIL_;
+ // The offset from the start of the WasmStructObject to the first payload
+ // byte.
+ uint8_t payloadOffsetIL_;
+
+ // The AllocKind for the object, computed directly from `totalSizeIL_`. Note
+ // this requires further processing with GetFinalizedAllocKindForClass before
+ // use.
+ gc::AllocKind allocKind_;
+
// Whether this struct only contains defaultable fields.
bool isDefaultable_;
- public:
- StructType() : size_(0), isDefaultable_(false) {}
+ static const uint32_t InvalidOffset = 0xFFFFFFFF;
+
+ StructType()
+ : totalSizeOOL_(0),
+ oolPointerOffset_(InvalidOffset),
+ totalSizeIL_(0),
+ payloadOffsetIL_(0),
+ allocKind_(gc::AllocKind::INVALID),
+ isDefaultable_(false) {}
explicit StructType(FieldTypeVector&& fields)
- : fields_(std::move(fields)), size_(0) {
+ : fields_(std::move(fields)),
+ totalSizeOOL_(0),
+ oolPointerOffset_(InvalidOffset),
+ totalSizeIL_(0),
+ payloadOffsetIL_(0),
+ allocKind_(gc::AllocKind::INVALID),
+ isDefaultable_(false) {
MOZ_ASSERT(fields_.length() <= MaxStructFields);
}
@@ -334,9 +366,7 @@ class StructType {
bool isDefaultable() const { return isDefaultable_; }
- uint32_t fieldOffset(uint32_t fieldIndex) const {
- return fieldOffsets_[fieldIndex];
- }
+ bool hasOOL() const { return totalSizeOOL_ > 0; }
HashNumber hash(const RecGroup* recGroup) const {
HashNumber hn = 0;
@@ -390,35 +420,6 @@ class StructType {
using StructTypeVector = Vector<StructType, 0, SystemAllocPolicy>;
-// Utility for computing field offset and alignments, and total size for
-// structs and tags. This is complicated by fact that a WasmStructObject has
-// an inline area, which is used first, and if that fills up an optional
-// C++-heap-allocated outline area is used. We need to be careful not to
-// split any data item across the boundary. This is ensured as follows:
-//
-// (1) the possible field sizes are 1, 2, 4, 8 and 16 only.
-// (2) each field is "naturally aligned" -- aligned to its size.
-// (3) MaxInlineBytes (the size of the inline area) % 16 == 0.
-//
-// From (1) and (2), it follows that all fields are placed so that their first
-// and last bytes fall within the same 16-byte chunk. That is,
-// offset_of_first_byte_of_field / 16 == offset_of_last_byte_of_field / 16.
-//
-// Given that, it follows from (3) that all fields fall completely within
-// either the inline or outline areas; no field crosses the boundary.
-class StructLayout {
- mozilla::CheckedInt32 sizeSoFar = 0;
- uint32_t structAlignment = 1;
-
- public:
- // The field adders return the offset of the the field.
- mozilla::CheckedInt32 addField(StorageType type);
-
- // The close method rounds up the structure size to the appropriate
- // alignment and returns that size.
- mozilla::CheckedInt32 close();
-};
-
//=========================================================================
// Array types
diff --git a/js/src/wasm/moz.build b/js/src/wasm/moz.build
@@ -49,6 +49,7 @@ UNIFIED_SOURCES += [
"WasmSerialize.cpp",
"WasmSignalHandlers.cpp",
"WasmStaticTypeDefs.cpp",
+ "WasmStructLayout.cpp",
"WasmStubs.cpp",
"WasmSummarizeInsn.cpp",
"WasmTable.cpp",