summaryrefslogtreecommitdiffstats
path: root/js/src/wasm/WasmAnyRef.h
blob: cbd3fe75e0f0a69ca310829bc4f1c33315c5eda7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 *
 * Copyright 2023 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef wasm_anyref_h
#define wasm_anyref_h

#include "mozilla/FloatingPoint.h"

#include <utility>

#include "js/HeapAPI.h"
#include "js/RootingAPI.h"
#include "js/TypeDecls.h"
#include "js/Value.h"

// #include "NamespaceImports.h"

class JSObject;
class JSString;

namespace js {
namespace gc {
struct Cell;
};  // namespace gc

namespace wasm {

// [SMDOC] AnyRef
//
// An AnyRef is a boxed value that can represent any wasm reference type and any
// host type that the host system allows to flow into and out of wasm
// transparently.  It is a pointer-sized datum that has the same representation
// as all its subtypes (funcref, externref, eqref, (ref T), et al) due to the
// non-coercive subtyping of the wasm type system.
//
// The C++/wasm boundary always uses a 'void*' type to express AnyRef values, to
// emphasize the pointer-ness of the value.  The C++ code must transform the
// void* into an AnyRef by calling AnyRef::fromCompiledCode(), and transform an
// AnyRef into a void* by calling AnyRef::toCompiledCode().  Once in C++, we use
// AnyRef everywhere.  A JS Value is transformed into an AnyRef by calling
// AnyRef::fromJSValue(), and the AnyRef is transformed into a JS Value by
// calling AnyRef::toJSValue().
//
// NOTE that AnyRef values may point to GC'd storage and as such need to be
// rooted if they are kept live in boxed form across code that may cause GC!
// Use RootedAnyRef / HandleAnyRef / MutableHandleAnyRef where necessary.
//
// The lowest bits of the pointer value are used for tagging, to allow for some
// representation optimizations and to distinguish various types.
//
// The current tagging scheme is:
//   if (pointer == 0) then 'null'
//   if (pointer & 0x1) then 'i31'
//   if (pointer & 0x2) then 'string'
//   else 'object'
//
// NOTE: there is sequencing required when checking tags. If bit 0x1 is set,
// then bit 0x2 is part of the i31 value and does not imply string.
//
// An i31ref value has no sign interpretation within wasm, where instructions
// specify the signedness. When converting to/from a JS value, an i31ref value
// is treated as a signed 31-bit value.

// The kind of value stored in an AnyRef. This is not 1:1 with the pointer tag
// of AnyRef as this separates the 'Null' and 'Object' cases which are
// collapsed in the pointer tag.
enum class AnyRefKind : uint8_t {
  Null,
  Object,
  String,
  I31,
};

// The pointer tag of an AnyRef.
enum class AnyRefTag : uint8_t {
  // This value is either a JSObject& or a null pointer.
  ObjectOrNull = 0x0,
  // This value is a 31-bit integer.
  I31 = 0x1,
  // This value is a JSString*.
  String = 0x2,
};

// A reference to any wasm reference type or host (JS) value. AnyRef is
// optimized for efficient access to objects, strings, and 31-bit integers.
//
// See the above documentation comment for more details.
class AnyRef {
  uintptr_t value_;

  // Get the pointer tag stored in value_.
  AnyRefTag pointerTag() const { return GetUintptrTag(value_); }

  explicit AnyRef(uintptr_t value) : value_(value) {}

  static constexpr uintptr_t TagUintptr(uintptr_t value, AnyRefTag tag) {
    MOZ_ASSERT(!(value & TagMask));
    return value | uintptr_t(tag);
  }
  static constexpr uintptr_t UntagUintptr(uintptr_t value) {
    return value & ~TagMask;
  }
  static constexpr AnyRefTag GetUintptrTag(uintptr_t value) {
    // Mask off all but the lowest two-bits (the tag)
    uintptr_t rawTag = value & TagMask;
    // If the lowest bit is set, we want to normalize and only return
    // AnyRefTag::I31. Mask off the high-bit iff the low-bit was set.
    uintptr_t normalizedI31 = rawTag & ~(value << 1);
    return AnyRefTag(normalizedI31);
  }

  // Given a 32-bit signed integer within 31-bit signed bounds, turn it into
  // an AnyRef.
  static AnyRef fromInt32(int32_t value) {
    MOZ_ASSERT(!int32NeedsBoxing(value));
    return AnyRef::fromUint32Truncate(uint32_t(value));
  }

 public:
  static constexpr uintptr_t TagMask = 0x3;
  static constexpr uintptr_t TagShift = 2;
  static_assert(TagShift <= gc::CellAlignShift, "not enough free bits");
  // A mask for getting the GC thing an AnyRef represents.
  static constexpr uintptr_t GCThingMask = ~TagMask;
  // A combined mask for getting the gc::Chunk for an AnyRef that is a GC
  // thing.
  static constexpr uintptr_t GCThingChunkMask =
      GCThingMask & ~js::gc::ChunkMask;

  // The representation of a null reference value throughout the compiler for
  // when we need an integer constant. This is asserted to be equivalent to
  // nullptr in wasm::Init.
  static constexpr uintptr_t NullRefValue = 0;
  static constexpr uintptr_t InvalidRefValue = UINTPTR_MAX << TagShift;

  // The inclusive maximum 31-bit signed integer, 2^30 - 1.
  static constexpr int32_t MaxI31Value = (2 << 29) - 1;
  // The inclusive minimum 31-bit signed integer, -2^30.
  static constexpr int32_t MinI31Value = -(2 << 29);

  explicit AnyRef() : value_(NullRefValue) {}
  MOZ_IMPLICIT AnyRef(std::nullptr_t) : value_(NullRefValue) {}

  // The null AnyRef value.
  static AnyRef null() { return AnyRef(NullRefValue); }

  // An invalid AnyRef cannot arise naturally from wasm and so can be used as
  // a sentinel value to indicate failure from an AnyRef-returning function.
  static AnyRef invalid() { return AnyRef(InvalidRefValue); }

  // Given a JSObject* that comes from JS, turn it into AnyRef.
  static AnyRef fromJSObjectOrNull(JSObject* objectOrNull) {
    MOZ_ASSERT(GetUintptrTag((uintptr_t)objectOrNull) ==
               AnyRefTag::ObjectOrNull);
    return AnyRef((uintptr_t)objectOrNull);
  }

  // Given a JSObject& that comes from JS, turn it into AnyRef.
  static AnyRef fromJSObject(JSObject& object) {
    MOZ_ASSERT(GetUintptrTag((uintptr_t)&object) == AnyRefTag::ObjectOrNull);
    return AnyRef((uintptr_t)&object);
  }

  // Given a JSString* that comes from JS, turn it into AnyRef.
  static AnyRef fromJSString(JSString* string) {
    return AnyRef(TagUintptr((uintptr_t)string, AnyRefTag::String));
  }

  // Given a void* that comes from compiled wasm code, turn it into AnyRef.
  static AnyRef fromCompiledCode(void* pointer) {
    return AnyRef((uintptr_t)pointer);
  }

  // Given a JS value, turn it into AnyRef. This returns false if boxing the
  // value failed due to an OOM.
  static bool fromJSValue(JSContext* cx, JS::HandleValue value,
                          JS::MutableHandle<AnyRef> result);

  // fromUint32Truncate will produce an i31 from an int32 by truncating the
  // highest bit. For values in the 31-bit range, this losslessly preserves the
  // value. For values outside the 31-bit range, this performs 31-bit
  // wraparound.
  //
  // There are four cases here based on the two high bits:
  //   00 - [0, MaxI31Value]
  //   01 - (MaxI31Value, INT32_MAX]
  //   10 - [INT32_MIN, MinI31Value)
  //   11 - [MinI31Value, -1]
  //
  // The middle two cases can be ruled out if the value is guaranteed to be
  // within the i31 range. Therefore if we truncate the high bit upon converting
  // to i31 and perform a signed widening upon converting back to i32, we can
  // losslessly represent all i31 values.
  static AnyRef fromUint32Truncate(uint32_t value) {
    // See 64-bit GPRs carrying 32-bit values invariants in MacroAssember.h
#if defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_X64) || \
    defined(JS_CODEGEN_ARM64)
    // Truncate the value to the 31-bit value size.
    uintptr_t wideValue = uintptr_t(value & 0x7FFFFFFF);
#elif defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_MIPS64) || \
    defined(JS_CODEGEN_RISCV64)
    // Sign extend the value to the native pointer size.
    uintptr_t wideValue = uintptr_t(int64_t((uint64_t(value) << 33)) >> 33);
#elif !defined(JS_64BIT)
    // Transfer 32-bit value as is.
    uintptr_t wideValue = (uintptr_t)value;
#else
#  error "unknown architecture"
#endif

    // Left shift the value by 1, truncating the high bit.
    uintptr_t shiftedValue = wideValue << 1;
    uintptr_t taggedValue = shiftedValue | (uintptr_t)AnyRefTag::I31;
    return AnyRef(taggedValue);
  }

  static bool int32NeedsBoxing(int32_t value) {
    // We can represent every signed 31-bit number without boxing
    return value < MinI31Value || value > MaxI31Value;
  }

  static bool doubleNeedsBoxing(double value) {
    int32_t intValue;
    if (!mozilla::NumberIsInt32(value, &intValue)) {
      return true;
    }
    return int32NeedsBoxing(value);
  }

  // Returns whether a JS value will need to be boxed.
  static bool valueNeedsBoxing(JS::HandleValue value) {
    if (value.isObjectOrNull() || value.isString()) {
      return false;
    }
    if (value.isInt32()) {
      return int32NeedsBoxing(value.toInt32());
    }
    if (value.isDouble()) {
      return doubleNeedsBoxing(value.toDouble());
    }
    return true;
  }

  // Box a JS Value that needs boxing.
  static JSObject* boxValue(JSContext* cx, JS::HandleValue value);

  bool operator==(const AnyRef& rhs) const {
    return this->value_ == rhs.value_;
  }
  bool operator!=(const AnyRef& rhs) const { return !(*this == rhs); }

  // Check if this AnyRef is the invalid value.
  bool isInvalid() const { return *this == AnyRef::invalid(); }

  AnyRefKind kind() const {
    if (value_ == NullRefValue) {
      return AnyRefKind::Null;
    }
    switch (pointerTag()) {
      case AnyRefTag::ObjectOrNull: {
        // The invalid pattern uses the ObjectOrNull tag, check for it here.
        MOZ_ASSERT(!isInvalid());
        // We ruled out the null case above
        return AnyRefKind::Object;
      }
      case AnyRefTag::String: {
        return AnyRefKind::String;
      }
      case AnyRefTag::I31: {
        return AnyRefKind::I31;
      }
      default: {
        MOZ_CRASH("unknown AnyRef tag");
      }
    }
  }

  bool isNull() const { return value_ == NullRefValue; }
  bool isGCThing() const { return !isNull() && !isI31(); }
  bool isJSObject() const { return kind() == AnyRefKind::Object; }
  bool isJSString() const { return kind() == AnyRefKind::String; }
  bool isI31() const { return kind() == AnyRefKind::I31; }

  gc::Cell* toGCThing() const {
    MOZ_ASSERT(isGCThing());
    return (gc::Cell*)UntagUintptr(value_);
  }
  JSObject& toJSObject() const {
    MOZ_ASSERT(isJSObject());
    return *(JSObject*)value_;
  }
  JSObject* toJSObjectOrNull() const {
    MOZ_ASSERT(!isInvalid());
    MOZ_ASSERT(pointerTag() == AnyRefTag::ObjectOrNull);
    return (JSObject*)value_;
  }
  JSString* toJSString() const {
    MOZ_ASSERT(isJSString());
    return (JSString*)UntagUintptr(value_);
  }
  // Unpack an i31, interpreting the integer as signed.
  int32_t toI31() const {
    MOZ_ASSERT(isI31());
    // On 64-bit targets, we only care about the low 4-bytes.
    uint32_t truncatedValue = *reinterpret_cast<const uint32_t*>(&value_);
    // Perform a right arithmetic shift (see AnyRef::fromI31 for more details),
    // avoiding undefined behavior by using an unsigned type.
    uint32_t shiftedValue = value_ >> 1;
    if ((truncatedValue & (1 << 31)) != 0) {
      shiftedValue |= (1 << 31);
    }
    // Perform a bitwise cast to see the result as a signed value.
    return *reinterpret_cast<int32_t*>(&shiftedValue);
  }

  // Convert from AnyRef to a JS Value. This currently does not require any
  // allocation. If this changes in the future, this function will become
  // more complicated.
  JS::Value toJSValue() const;

  // Get the raw value for returning to wasm code.
  void* forCompiledCode() const { return (void*)value_; }

  // Get the raw value for diagnostics.
  uintptr_t rawValue() const { return value_; }

  // Internal details of the boxing format used by WasmStubs.cpp
  static const JSClass* valueBoxClass();
  static size_t valueBoxOffsetOfValue();
};

using RootedAnyRef = JS::Rooted<AnyRef>;
using HandleAnyRef = JS::Handle<AnyRef>;
using MutableHandleAnyRef = JS::MutableHandle<AnyRef>;

}  // namespace wasm

template <class Wrapper>
class WrappedPtrOperations<wasm::AnyRef, Wrapper> {
  const wasm::AnyRef& value() const {
    return static_cast<const Wrapper*>(this)->get();
  }

 public:
  bool isNull() const { return value().isNull(); }
  bool isI31() const { return value().isI31(); }
  bool isJSObject() const { return value().isJSObject(); }
  bool isJSString() const { return value().isJSString(); }
  JSObject& toJSObject() const { return value().toJSObject(); }
  JSString* toJSString() const { return value().toJSString(); }
};

// If the Value is a GC pointer type, call |f| with the pointer cast to that
// type and return the result wrapped in a Maybe, otherwise return None().
template <typename F>
auto MapGCThingTyped(const wasm::AnyRef& val, F&& f) {
  switch (val.kind()) {
    case wasm::AnyRefKind::Object:
      return mozilla::Some(f(&val.toJSObject()));
    case wasm::AnyRefKind::String:
      return mozilla::Some(f(val.toJSString()));
    case wasm::AnyRefKind::I31:
    case wasm::AnyRefKind::Null: {
      using ReturnType = decltype(f(static_cast<JSObject*>(nullptr)));
      return mozilla::Maybe<ReturnType>();
    }
  }
  MOZ_CRASH();
}

template <typename F>
bool ApplyGCThingTyped(const wasm::AnyRef& val, F&& f) {
  return MapGCThingTyped(val,
                         [&f](auto t) {
                           f(t);
                           return true;
                         })
      .isSome();
}

}  // namespace js

namespace JS {

template <>
struct GCPolicy<js::wasm::AnyRef> {
  static void trace(JSTracer* trc, js::wasm::AnyRef* v, const char* name) {
    // This should only be called as part of root marking since that's the only
    // time we should trace unbarriered GC thing pointers. This will assert if
    // called at other times.
    TraceRoot(trc, v, name);
  }
  static bool isValid(const js::wasm::AnyRef& v) {
    return !v.isGCThing() || js::gc::IsCellPointerValid(v.toGCThing());
  }
};

}  // namespace JS

#endif  // wasm_anyref_h