tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mjs/src/jit/MIR-wasm.h | 8++------
Mjs/src/jit/MacroAssembler.cpp | 5++---
Mjs/src/vm/JSObject.cpp | 2+-
Mjs/src/wasm/WasmBaselineCompile.cpp | 99++++++++++++++++++++++++++++++++++---------------------------------------------
Mjs/src/wasm/WasmGcObject-inl.h | 74+++++++++++++++++++++++++++++++++++++-------------------------------------
Mjs/src/wasm/WasmGcObject.cpp | 140++++++++++++++++++++++++++++++++++++-------------------------------------------
Mjs/src/wasm/WasmGcObject.h | 215+++++++++++++++++++++++++++++++++++++------------------------------------------
Mjs/src/wasm/WasmInstance.cpp | 60+++++++++++++++++++++++++++++++++---------------------------
Mjs/src/wasm/WasmInstanceData.h | 49++++++++++++++++++++++++++-----------------------
Mjs/src/wasm/WasmIonCompile.cpp | 44++++++++++++++++----------------------------
Mjs/src/wasm/WasmModuleTypes.cpp | 65++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Ajs/src/wasm/WasmStructLayout.cpp | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajs/src/wasm/WasmStructLayout.h | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mjs/src/wasm/WasmTypeDef.cpp | 134++++++++++++++++++++++++++++---------------------------------------------------
Mjs/src/wasm/WasmTypeDef.h | 85++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mjs/src/wasm/moz.build | 1+
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",