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