1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef wasm_WasmGcObject_h
8 #define wasm_WasmGcObject_h
10 #include "mozilla/Attributes.h"
11 #include "mozilla/CheckedInt.h"
12 #include "mozilla/Maybe.h"
14 #include "gc/GCProbes.h"
15 #include "gc/Pretenuring.h"
16 #include "gc/ZoneAllocator.h" // AddCellMemory
17 #include "vm/JSContext.h"
18 #include "vm/JSObject.h"
19 #include "vm/Probes.h"
20 #include "wasm/WasmInstanceData.h"
21 #include "wasm/WasmMemory.h"
22 #include "wasm/WasmTypeDef.h"
23 #include "wasm/WasmValType.h"
25 using js::wasm::StorageType
;
29 // For trailer blocks whose owning Wasm{Struct,Array}Objects make it into the
30 // tenured heap, we have to tell the tenured heap how big those trailers are
31 // in order to get major GCs to happen sufficiently frequently. In an attempt
32 // to make the numbers more accurate, for each block we overstate the size by
33 // the following amount, on the assumption that:
35 // * mozjemalloc has an overhead of at least one word per block
37 // * the malloc-cache mechanism rounds up small block sizes to the nearest 16;
38 // hence the average increase is 16 / 2.
39 static const size_t TrailerBlockOverhead
= (16 / 2) + (1 * sizeof(void*));
41 } // namespace js::wasm
45 //=========================================================================
48 class WasmGcObject
: public JSObject
{
50 const wasm::SuperTypeVector
* superTypeVector_
;
52 static const ObjectOps objectOps_
;
54 [[nodiscard
]] static bool obj_lookupProperty(JSContext
* cx
, HandleObject obj
,
56 MutableHandleObject objp
,
57 PropertyResult
* propp
);
59 [[nodiscard
]] static bool obj_defineProperty(JSContext
* cx
, HandleObject obj
,
61 Handle
<PropertyDescriptor
> desc
,
62 ObjectOpResult
& result
);
64 [[nodiscard
]] static bool obj_hasProperty(JSContext
* cx
, HandleObject obj
,
65 HandleId id
, bool* foundp
);
67 [[nodiscard
]] static bool obj_getProperty(JSContext
* cx
, HandleObject obj
,
68 HandleValue receiver
, HandleId id
,
69 MutableHandleValue vp
);
71 [[nodiscard
]] static bool obj_setProperty(JSContext
* cx
, HandleObject obj
,
72 HandleId id
, HandleValue v
,
74 ObjectOpResult
& result
);
76 [[nodiscard
]] static bool obj_getOwnPropertyDescriptor(
77 JSContext
* cx
, HandleObject obj
, HandleId id
,
78 MutableHandle
<mozilla::Maybe
<PropertyDescriptor
>> desc
);
80 [[nodiscard
]] static bool obj_deleteProperty(JSContext
* cx
, HandleObject obj
,
82 ObjectOpResult
& result
);
84 // PropOffset is a uint32_t that is used to carry information about the
85 // location of an value from WasmGcObject::lookupProperty to
86 // WasmGcObject::loadValue. It is distinct from a normal uint32_t to
87 // emphasise the fact that it cannot be interpreted as an offset in any
88 // single contiguous area of memory:
90 // * If the object in question is a WasmStructObject, it is the value of
91 // `wasm::StructField::offset` for the relevant field, without regard to
92 // the inline/outline split.
94 // * If the object in question is a WasmArrayObject, then
95 // - u32 == UINT32_MAX (0xFFFF'FFFF) means the "length" property
97 // - u32 < UINT32_MAX means the array element starting at that byte
98 // offset in WasmArrayObject::data_. It is not an array index value.
99 // See WasmGcObject::lookupProperty for details.
104 PropOffset() : u32_(0) {}
105 uint32_t get() const { return u32_
; }
106 void set(uint32_t u32
) { u32_
= u32
; }
109 [[nodiscard
]] static bool lookUpProperty(JSContext
* cx
,
110 Handle
<WasmGcObject
*> obj
, jsid id
,
115 [[nodiscard
]] static bool loadValue(JSContext
* cx
, Handle
<WasmGcObject
*> obj
,
116 jsid id
, MutableHandleValue vp
);
118 const wasm::SuperTypeVector
& superTypeVector() const {
119 return *superTypeVector_
;
122 static constexpr size_t offsetOfSuperTypeVector() {
123 return offsetof(WasmGcObject
, superTypeVector_
);
126 // These are both expensive in that they involve a double indirection.
127 // Avoid them if possible.
128 const wasm::TypeDef
& typeDef() const { return *superTypeVector().typeDef(); }
129 wasm::TypeDefKind
kind() const { return superTypeVector().typeDef()->kind(); }
131 [[nodiscard
]] bool isRuntimeSubtypeOf(
132 const wasm::TypeDef
* parentTypeDef
) const;
134 [[nodiscard
]] static bool obj_newEnumerate(JSContext
* cx
, HandleObject obj
,
135 MutableHandleIdVector properties
,
136 bool enumerableOnly
);
139 //=========================================================================
142 // Class for a wasm array. It contains a pointer to the array contents, that
143 // lives in the C++ heap.
145 class WasmArrayObject
: public WasmGcObject
{
147 static const JSClass class_
;
149 // The number of elements in the array.
150 uint32_t numElements_
;
152 // Owned data pointer, holding `numElements_` entries. This is null if
153 // `numElements_` is zero; otherwise it must be non-null. See bug 1812283.
156 // AllocKind for object creation
157 static gc::AllocKind
allocKind();
159 // Creates a new non-empty array typed object, optionally initialized to
160 // zero, for the specified number of elements. Reports an error if the
161 // number of elements is too large, or if there is an out of memory error.
162 // The element type, shape, class pointer, alloc site and alloc kind are
163 // taken from `typeDefData`; the initial heap must be specified separately.
164 // The number of elements is assumed and debug-asserted to be non-zero.
165 template <bool ZeroFields
>
166 static WasmArrayObject
* createArrayNonEmpty(
167 JSContext
* cx
, wasm::TypeDefInstanceData
* typeDefData
,
168 js::gc::Heap initialHeap
, uint32_t numElements
);
170 // Creates a new empty array typed object, for zero elements. Reports an
171 // error if there is an out of memory error. The element type, shape, class
172 // pointer, alloc site and alloc kind are taken from `typeDefData`; the
173 // initial heap must be specified separately. The number of elements is
174 // assumed and debug-asserted to be zero.
175 static WasmArrayObject
* createArrayEmpty(
176 JSContext
* cx
, wasm::TypeDefInstanceData
* typeDefData
,
177 js::gc::Heap initialHeap
);
179 // This just selects one of the above two routines, depending on
181 template <bool ZeroFields
>
182 static MOZ_ALWAYS_INLINE WasmArrayObject
* createArray(
183 JSContext
* cx
, wasm::TypeDefInstanceData
* typeDefData
,
184 js::gc::Heap initialHeap
, uint32_t numElements
) {
185 return numElements
== 0 ? createArrayEmpty(cx
, typeDefData
, initialHeap
)
186 : createArrayNonEmpty
<ZeroFields
>(
187 cx
, typeDefData
, initialHeap
, numElements
);
191 static constexpr size_t offsetOfNumElements() {
192 return offsetof(WasmArrayObject
, numElements_
);
194 static constexpr size_t offsetOfData() {
195 return offsetof(WasmArrayObject
, data_
);
198 // Tracing and finalization
199 static void obj_trace(JSTracer
* trc
, JSObject
* object
);
200 static void obj_finalize(JS::GCContext
* gcx
, JSObject
* object
);
201 static size_t obj_moved(JSObject
* obj
, JSObject
* old
);
203 void storeVal(const wasm::Val
& val
, uint32_t itemIndex
);
204 void fillVal(const wasm::Val
& val
, uint32_t itemIndex
, uint32_t len
);
207 // Helper to mark all locations that assume that the type of
208 // WasmArrayObject::numElements is uint32_t.
209 #define STATIC_ASSERT_WASMARRAYELEMENTS_NUMELEMENTS_IS_U32 \
210 static_assert(sizeof(js::WasmArrayObject::numElements_) == sizeof(uint32_t))
212 //=========================================================================
215 // Class for a wasm struct. It has inline data and, if the inline area is
216 // insufficient, a pointer to outline data that lives in the C++ heap.
217 // Computing the field offsets is somewhat tricky; see block comment on `class
218 // StructLayout` for background.
220 class WasmStructObject
: public WasmGcObject
{
222 static const JSClass classInline_
;
223 static const JSClass classOutline_
;
225 // Owned pointer to a malloc'd block containing out-of-line fields, or
226 // nullptr if none. Note that MIR alias analysis assumes this is readonly
227 // for the life of the object; do not change it once the object is created.
228 // See MWasmLoadObjectField::congruentTo.
229 uint8_t* outlineData_
;
231 // The inline (wasm-struct-level) data fields. This must be a multiple of
232 // 16 bytes long in order to ensure that no field gets split across the
233 // inline-outline boundary. As a refinement, we request this field to begin
234 // at an 8-aligned offset relative to the start of the object, so as to
235 // guarantee that `double` typed fields are not subject to misaligned-access
236 // penalties on any target, whilst wasting at maximum 4 bytes of space.
238 // `inlineData_` is in reality a variable length block with maximum size
239 // WasmStructObject_MaxInlineBytes bytes. Do not add any (C++-level) fields
241 alignas(8) uint8_t inlineData_
[0];
243 // This tells us how big the object is if we know the number of inline bytes
244 // it was created with.
245 static inline size_t sizeOfIncludingInlineData(size_t sizeOfInlineData
) {
246 size_t n
= sizeof(WasmStructObject
) + sizeOfInlineData
;
247 MOZ_ASSERT(n
<= JSObject::MAX_BYTE_SIZE
);
251 static const JSClass
* classForTypeDef(const wasm::TypeDef
* typeDef
);
252 static js::gc::AllocKind
allocKindForTypeDef(const wasm::TypeDef
* typeDef
);
254 // Creates a new struct typed object, optionally initialized to zero.
255 // Reports if there is an out of memory error. The structure's type, shape,
256 // class pointer, alloc site and alloc kind are taken from `typeDefData`;
257 // the initial heap must be specified separately. It is assumed and debug-
258 // asserted that `typeDefData` refers to a type that does not need OOL
260 template <bool ZeroFields
>
261 static MOZ_ALWAYS_INLINE WasmStructObject
* createStructIL(
262 JSContext
* cx
, wasm::TypeDefInstanceData
* typeDefData
,
263 js::gc::Heap initialHeap
);
265 // Same as ::createStructIL, except it is assumed and debug-asserted that
266 // `typeDefData` refers to a type that does need OOL storage.
267 template <bool ZeroFields
>
268 static MOZ_ALWAYS_INLINE WasmStructObject
* createStructOOL(
269 JSContext
* cx
, wasm::TypeDefInstanceData
* typeDefData
,
270 js::gc::Heap initialHeap
);
272 // Given the total number of data bytes required (including alignment
273 // holes), return the number of inline and outline bytes required.
274 static inline void getDataByteSizes(uint32_t totalBytes
,
275 uint32_t* inlineBytes
,
276 uint32_t* outlineBytes
);
278 // Convenience function; returns true iff ::getDataByteSizes would set
279 // *outlineBytes to a non-zero value.
280 static inline bool requiresOutlineBytes(uint32_t totalBytes
);
282 // Given the offset of a field, produce the offset in `inlineData_` or
283 // `*outlineData_` to use, plus a bool indicating which area it is.
284 // `fieldType` is for assertional purposes only.
285 static inline void fieldOffsetToAreaAndOffset(StorageType fieldType
,
286 uint32_t fieldOffset
,
288 uint32_t* areaOffset
);
290 // Given the offset of a field, return its actual address. `fieldType` is
291 // for assertional purposes only.
292 inline uint8_t* fieldOffsetToAddress(StorageType fieldType
,
293 uint32_t fieldOffset
) const;
296 static constexpr size_t offsetOfOutlineData() {
297 return offsetof(WasmStructObject
, outlineData_
);
299 static constexpr size_t offsetOfInlineData() {
300 return offsetof(WasmStructObject
, inlineData_
);
303 // Tracing and finalization
304 static void obj_trace(JSTracer
* trc
, JSObject
* object
);
305 static void obj_finalize(JS::GCContext
* gcx
, JSObject
* object
);
306 static size_t obj_moved(JSObject
* obj
, JSObject
* old
);
308 void storeVal(const wasm::Val
& val
, uint32_t fieldIndex
);
311 // This is ensured by the use of `alignas` on `WasmStructObject::inlineData_`.
312 static_assert((offsetof(WasmStructObject
, inlineData_
) % 8) == 0);
314 // MaxInlineBytes must be a multiple of 16 for reasons described in the
315 // comment on `class StructLayout`. This unfortunately can't be defined
316 // inside the class definition itself because the sizeof(..) expression isn't
317 // valid until after the end of the class definition.
318 const size_t WasmStructObject_MaxInlineBytes
=
319 ((JSObject::MAX_BYTE_SIZE
- sizeof(WasmStructObject
)) / 16) * 16;
321 static_assert((WasmStructObject_MaxInlineBytes
% 16) == 0);
324 inline void WasmStructObject::getDataByteSizes(uint32_t totalBytes
,
325 uint32_t* inlineBytes
,
326 uint32_t* outlineBytes
) {
327 if (MOZ_UNLIKELY(totalBytes
> WasmStructObject_MaxInlineBytes
)) {
328 *inlineBytes
= WasmStructObject_MaxInlineBytes
;
329 *outlineBytes
= totalBytes
- WasmStructObject_MaxInlineBytes
;
331 *inlineBytes
= totalBytes
;
337 inline bool WasmStructObject::requiresOutlineBytes(uint32_t totalBytes
) {
338 uint32_t inlineBytes
, outlineBytes
;
339 WasmStructObject::getDataByteSizes(totalBytes
, &inlineBytes
, &outlineBytes
);
340 return outlineBytes
> 0;
344 inline void WasmStructObject::fieldOffsetToAreaAndOffset(StorageType fieldType
,
345 uint32_t fieldOffset
,
347 uint32_t* areaOffset
) {
348 if (fieldOffset
< WasmStructObject_MaxInlineBytes
) {
349 *areaIsOutline
= false;
350 *areaOffset
= fieldOffset
;
352 *areaIsOutline
= true;
353 *areaOffset
= fieldOffset
- WasmStructObject_MaxInlineBytes
;
355 // Assert that the first and last bytes for the field agree on which side of
356 // the inline/outline boundary they live.
358 (fieldOffset
< WasmStructObject_MaxInlineBytes
) ==
359 ((fieldOffset
+ fieldType
.size() - 1) < WasmStructObject_MaxInlineBytes
));
362 inline uint8_t* WasmStructObject::fieldOffsetToAddress(
363 StorageType fieldType
, uint32_t fieldOffset
) const {
366 fieldOffsetToAreaAndOffset(fieldType
, fieldOffset
, &areaIsOutline
,
368 return ((uint8_t*)(areaIsOutline
? outlineData_
: &inlineData_
[0])) +
372 // Ensure that faulting loads/stores for WasmStructObject and WasmArrayObject
373 // are in the NULL pointer guard page.
374 static_assert(WasmStructObject_MaxInlineBytes
<= wasm::NullPtrGuardSize
);
375 static_assert(sizeof(WasmArrayObject
) <= wasm::NullPtrGuardSize
);
379 //=========================================================================
384 inline bool IsWasmGcObjectClass(const JSClass
* class_
) {
385 return class_
== &WasmArrayObject::class_
||
386 class_
== &WasmStructObject::classInline_
||
387 class_
== &WasmStructObject::classOutline_
;
393 inline bool JSObject::is
<js::WasmGcObject
>() const {
394 return js::IsWasmGcObjectClass(getClass());
398 inline bool JSObject::is
<js::WasmStructObject
>() const {
399 const JSClass
* class_
= getClass();
400 return class_
== &js::WasmStructObject::classInline_
||
401 class_
== &js::WasmStructObject::classOutline_
;
404 #endif /* wasm_WasmGcObject_h */