no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / js / src / wasm / WasmAnyRef.h
blob1675a9fa8d23348ba9c4fa21a3c7911adebb9bbf
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:
4 * Copyright 2023 Mozilla Foundation
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 #ifndef wasm_anyref_h
20 #define wasm_anyref_h
22 #include "mozilla/FloatingPoint.h"
24 #include <utility>
26 #include "js/HeapAPI.h"
27 #include "js/RootingAPI.h"
28 #include "js/TypeDecls.h"
29 #include "js/Value.h"
31 // #include "NamespaceImports.h"
33 class JSObject;
34 class JSString;
36 namespace js {
37 namespace gc {
38 struct Cell;
39 }; // namespace gc
41 namespace wasm {
43 // [SMDOC] AnyRef
45 // An AnyRef is a boxed value that can represent any wasm reference type and any
46 // host type that the host system allows to flow into and out of wasm
47 // transparently. It is a pointer-sized datum that has the same representation
48 // as all its subtypes (funcref, externref, eqref, (ref T), et al) due to the
49 // non-coercive subtyping of the wasm type system.
51 // The C++/wasm boundary always uses a 'void*' type to express AnyRef values, to
52 // emphasize the pointer-ness of the value. The C++ code must transform the
53 // void* into an AnyRef by calling AnyRef::fromCompiledCode(), and transform an
54 // AnyRef into a void* by calling AnyRef::toCompiledCode(). Once in C++, we use
55 // AnyRef everywhere. A JS Value is transformed into an AnyRef by calling
56 // AnyRef::fromJSValue(), and the AnyRef is transformed into a JS Value by
57 // calling AnyRef::toJSValue().
59 // NOTE that AnyRef values may point to GC'd storage and as such need to be
60 // rooted if they are kept live in boxed form across code that may cause GC!
61 // Use RootedAnyRef / HandleAnyRef / MutableHandleAnyRef where necessary.
63 // The lowest bits of the pointer value are used for tagging, to allow for some
64 // representation optimizations and to distinguish various types.
66 // The current tagging scheme is:
67 // if (pointer == 0) then 'null'
68 // if (pointer & 0x1) then 'i31'
69 // if (pointer & 0x2) then 'string'
70 // else 'object'
72 // NOTE: there is sequencing required when checking tags. If bit 0x1 is set,
73 // then bit 0x2 is part of the i31 value and does not imply string.
75 // An i31ref value has no sign interpretation within wasm, where instructions
76 // specify the signedness. When converting to/from a JS value, an i31ref value
77 // is treated as a signed 31-bit value.
79 // The kind of value stored in an AnyRef. This is not 1:1 with the pointer tag
80 // of AnyRef as this separates the 'Null' and 'Object' cases which are
81 // collapsed in the pointer tag.
82 enum class AnyRefKind : uint8_t {
83 Null,
84 Object,
85 String,
86 I31,
89 // The pointer tag of an AnyRef.
90 enum class AnyRefTag : uint8_t {
91 // This value is either a JSObject& or a null pointer.
92 ObjectOrNull = 0x0,
93 // This value is a 31-bit integer.
94 I31 = 0x1,
95 // This value is a JSString*.
96 String = 0x2,
99 // A reference to any wasm reference type or host (JS) value. AnyRef is
100 // optimized for efficient access to objects, strings, and 31-bit integers.
102 // See the above documentation comment for more details.
103 class AnyRef {
104 uintptr_t value_;
106 // Get the pointer tag stored in value_.
107 AnyRefTag pointerTag() const { return GetUintptrTag(value_); }
109 explicit AnyRef(uintptr_t value) : value_(value) {}
111 static constexpr uintptr_t TagUintptr(uintptr_t value, AnyRefTag tag) {
112 MOZ_ASSERT(!(value & TagMask));
113 return value | uintptr_t(tag);
115 static constexpr uintptr_t UntagUintptr(uintptr_t value) {
116 return value & ~TagMask;
118 static constexpr AnyRefTag GetUintptrTag(uintptr_t value) {
119 // Mask off all but the lowest two-bits (the tag)
120 uintptr_t rawTag = value & TagMask;
121 // If the lowest bit is set, we want to normalize and only return
122 // AnyRefTag::I31. Mask off the high-bit iff the low-bit was set.
123 uintptr_t normalizedI31 = rawTag & ~(value << 1);
124 return AnyRefTag(normalizedI31);
127 // Given a 32-bit signed integer within 31-bit signed bounds, turn it into
128 // an AnyRef.
129 static AnyRef fromInt32(int32_t value) {
130 MOZ_ASSERT(!int32NeedsBoxing(value));
131 return AnyRef::fromUint32Truncate(uint32_t(value));
134 public:
135 static constexpr uintptr_t TagMask = 0x3;
136 static constexpr uintptr_t TagShift = 2;
137 static_assert(TagShift <= gc::CellAlignShift, "not enough free bits");
138 // A mask for getting the GC thing an AnyRef represents.
139 static constexpr uintptr_t GCThingMask = ~TagMask;
140 // A combined mask for getting the gc::Chunk for an AnyRef that is a GC
141 // thing.
142 static constexpr uintptr_t GCThingChunkMask =
143 GCThingMask & ~js::gc::ChunkMask;
145 // The representation of a null reference value throughout the compiler for
146 // when we need an integer constant. This is asserted to be equivalent to
147 // nullptr in wasm::Init.
148 static constexpr uintptr_t NullRefValue = 0;
149 static constexpr uintptr_t InvalidRefValue = UINTPTR_MAX << TagShift;
151 // The inclusive maximum 31-bit signed integer, 2^30 - 1.
152 static constexpr int32_t MaxI31Value = (2 << 29) - 1;
153 // The inclusive minimum 31-bit signed integer, -2^30.
154 static constexpr int32_t MinI31Value = -(2 << 29);
156 explicit AnyRef() : value_(NullRefValue) {}
157 MOZ_IMPLICIT AnyRef(std::nullptr_t) : value_(NullRefValue) {}
159 // The null AnyRef value.
160 static AnyRef null() { return AnyRef(NullRefValue); }
162 // An invalid AnyRef cannot arise naturally from wasm and so can be used as
163 // a sentinel value to indicate failure from an AnyRef-returning function.
164 static AnyRef invalid() { return AnyRef(InvalidRefValue); }
166 // Given a JSObject* that comes from JS, turn it into AnyRef.
167 static AnyRef fromJSObjectOrNull(JSObject* objectOrNull) {
168 MOZ_ASSERT(GetUintptrTag((uintptr_t)objectOrNull) ==
169 AnyRefTag::ObjectOrNull);
170 return AnyRef((uintptr_t)objectOrNull);
173 // Given a JSObject& that comes from JS, turn it into AnyRef.
174 static AnyRef fromJSObject(JSObject& object) {
175 MOZ_ASSERT(GetUintptrTag((uintptr_t)&object) == AnyRefTag::ObjectOrNull);
176 return AnyRef((uintptr_t)&object);
179 // Given a JSString* that comes from JS, turn it into AnyRef.
180 static AnyRef fromJSString(JSString* string) {
181 return AnyRef(TagUintptr((uintptr_t)string, AnyRefTag::String));
184 // Given a void* that comes from compiled wasm code, turn it into AnyRef.
185 static AnyRef fromCompiledCode(void* pointer) {
186 return AnyRef((uintptr_t)pointer);
189 // Given a JS value, turn it into AnyRef. This returns false if boxing the
190 // value failed due to an OOM.
191 static bool fromJSValue(JSContext* cx, JS::HandleValue value,
192 JS::MutableHandle<AnyRef> result);
194 // fromUint32Truncate will produce an i31 from an int32 by truncating the
195 // highest bit. For values in the 31-bit range, this losslessly preserves the
196 // value. For values outside the 31-bit range, this performs 31-bit
197 // wraparound.
199 // There are four cases here based on the two high bits:
200 // 00 - [0, MaxI31Value]
201 // 01 - (MaxI31Value, INT32_MAX]
202 // 10 - [INT32_MIN, MinI31Value)
203 // 11 - [MinI31Value, -1]
205 // The middle two cases can be ruled out if the value is guaranteed to be
206 // within the i31 range. Therefore if we truncate the high bit upon converting
207 // to i31 and perform a signed widening upon converting back to i32, we can
208 // losslessly represent all i31 values.
209 static AnyRef fromUint32Truncate(uint32_t value) {
210 // See 64-bit GPRs carrying 32-bit values invariants in MacroAssember.h
211 #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64)
212 // Truncate the value to the 31-bit value size.
213 uintptr_t wideValue = uintptr_t(value & 0x7FFFFFFF);
214 #elif defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_MIPS64) || \
215 defined(JS_CODEGEN_RISCV64)
216 // Sign extend the value to the native pointer size.
217 uintptr_t wideValue = uintptr_t(int64_t((uint64_t(value) << 33)) >> 33);
218 #else
219 // Transfer 32-bit value as is.
220 uintptr_t wideValue = (uintptr_t)value;
221 #endif
223 // Left shift the value by 1, truncating the high bit.
224 uintptr_t shiftedValue = wideValue << 1;
225 uintptr_t taggedValue = shiftedValue | (uintptr_t)AnyRefTag::I31;
226 return AnyRef(taggedValue);
229 static bool int32NeedsBoxing(int32_t value) {
230 // We can represent every signed 31-bit number without boxing
231 return value < MinI31Value || value > MaxI31Value;
234 static bool doubleNeedsBoxing(double value) {
235 int32_t intValue;
236 if (!mozilla::NumberIsInt32(value, &intValue)) {
237 return true;
239 return int32NeedsBoxing(value);
242 // Returns whether a JS value will need to be boxed.
243 static bool valueNeedsBoxing(JS::HandleValue value) {
244 if (value.isObjectOrNull() || value.isString()) {
245 return false;
247 if (value.isInt32()) {
248 return int32NeedsBoxing(value.toInt32());
250 if (value.isDouble()) {
251 return doubleNeedsBoxing(value.toDouble());
253 return true;
256 // Box a JS Value that needs boxing.
257 static JSObject* boxValue(JSContext* cx, JS::HandleValue value);
259 bool operator==(const AnyRef& rhs) const {
260 return this->value_ == rhs.value_;
262 bool operator!=(const AnyRef& rhs) const { return !(*this == rhs); }
264 // Check if this AnyRef is the invalid value.
265 bool isInvalid() const { return *this == AnyRef::invalid(); }
267 AnyRefKind kind() const {
268 if (value_ == NullRefValue) {
269 return AnyRefKind::Null;
271 switch (pointerTag()) {
272 case AnyRefTag::ObjectOrNull: {
273 // The invalid pattern uses the ObjectOrNull tag, check for it here.
274 MOZ_ASSERT(!isInvalid());
275 // We ruled out the null case above
276 return AnyRefKind::Object;
278 case AnyRefTag::String: {
279 return AnyRefKind::String;
281 case AnyRefTag::I31: {
282 return AnyRefKind::I31;
284 default: {
285 MOZ_CRASH("unknown AnyRef tag");
290 bool isNull() const { return value_ == NullRefValue; }
291 bool isGCThing() const { return !isNull() && !isI31(); }
292 bool isJSObject() const { return kind() == AnyRefKind::Object; }
293 bool isJSString() const { return kind() == AnyRefKind::String; }
294 bool isI31() const { return kind() == AnyRefKind::I31; }
296 gc::Cell* toGCThing() const {
297 MOZ_ASSERT(isGCThing());
298 return (gc::Cell*)UntagUintptr(value_);
300 JSObject& toJSObject() const {
301 MOZ_ASSERT(isJSObject());
302 return *(JSObject*)value_;
304 JSObject* toJSObjectOrNull() const {
305 MOZ_ASSERT(!isInvalid());
306 MOZ_ASSERT(pointerTag() == AnyRefTag::ObjectOrNull);
307 return (JSObject*)value_;
309 JSString* toJSString() const {
310 MOZ_ASSERT(isJSString());
311 return (JSString*)UntagUintptr(value_);
313 // Unpack an i31, interpreting the integer as signed.
314 int32_t toI31() const {
315 MOZ_ASSERT(isI31());
316 // On 64-bit targets, we only care about the low 4-bytes.
317 uint32_t truncatedValue = *reinterpret_cast<const uint32_t*>(&value_);
318 // Perform a right arithmetic shift (see AnyRef::fromI31 for more details),
319 // avoiding undefined behavior by using an unsigned type.
320 uint32_t shiftedValue = value_ >> 1;
321 if ((truncatedValue & (1 << 31)) != 0) {
322 shiftedValue |= (1 << 31);
324 // Perform a bitwise cast to see the result as a signed value.
325 return *reinterpret_cast<int32_t*>(&shiftedValue);
328 // Convert from AnyRef to a JS Value. This currently does not require any
329 // allocation. If this changes in the future, this function will become
330 // more complicated.
331 JS::Value toJSValue() const;
333 // Get the raw value for returning to wasm code.
334 void* forCompiledCode() const { return (void*)value_; }
336 // Get the raw value for diagnostics.
337 uintptr_t rawValue() const { return value_; }
339 // Internal details of the boxing format used by WasmStubs.cpp
340 static const JSClass* valueBoxClass();
341 static size_t valueBoxOffsetOfValue();
344 using RootedAnyRef = JS::Rooted<AnyRef>;
345 using HandleAnyRef = JS::Handle<AnyRef>;
346 using MutableHandleAnyRef = JS::MutableHandle<AnyRef>;
348 } // namespace wasm
350 template <class Wrapper>
351 class WrappedPtrOperations<wasm::AnyRef, Wrapper> {
352 const wasm::AnyRef& value() const {
353 return static_cast<const Wrapper*>(this)->get();
356 public:
357 bool isNull() const { return value().isNull(); }
358 bool isI31() const { return value().isI31(); }
359 bool isJSObject() const { return value().isJSObject(); }
360 bool isJSString() const { return value().isJSString(); }
361 JSObject& toJSObject() const { return value().toJSObject(); }
362 JSString* toJSString() const { return value().toJSString(); }
365 // If the Value is a GC pointer type, call |f| with the pointer cast to that
366 // type and return the result wrapped in a Maybe, otherwise return None().
367 template <typename F>
368 auto MapGCThingTyped(const wasm::AnyRef& val, F&& f) {
369 switch (val.kind()) {
370 case wasm::AnyRefKind::Object:
371 return mozilla::Some(f(&val.toJSObject()));
372 case wasm::AnyRefKind::String:
373 return mozilla::Some(f(val.toJSString()));
374 case wasm::AnyRefKind::I31:
375 case wasm::AnyRefKind::Null: {
376 using ReturnType = decltype(f(static_cast<JSObject*>(nullptr)));
377 return mozilla::Maybe<ReturnType>();
380 MOZ_CRASH();
383 template <typename F>
384 bool ApplyGCThingTyped(const wasm::AnyRef& val, F&& f) {
385 return MapGCThingTyped(val,
386 [&f](auto t) {
387 f(t);
388 return true;
390 .isSome();
393 } // namespace js
395 namespace JS {
397 template <>
398 struct GCPolicy<js::wasm::AnyRef> {
399 static void trace(JSTracer* trc, js::wasm::AnyRef* v, const char* name) {
400 // This should only be called as part of root marking since that's the only
401 // time we should trace unbarriered GC thing pointers. This will assert if
402 // called at other times.
403 TraceRoot(trc, v, name);
405 static bool isValid(const js::wasm::AnyRef& v) {
406 return !v.isGCThing() || js::gc::IsCellPointerValid(v.toGCThing());
410 } // namespace JS
412 #endif // wasm_anyref_h