Coverage Report

Created: 2025-12-12 07:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/hermes/include/hermes/Support/BigIntSupport.h
Line
Count
Source
1
/*
2
 * Copyright (c) Meta Platforms, Inc. and affiliates.
3
 *
4
 * This source code is licensed under the MIT license found in the
5
 * LICENSE file in the root directory of this source tree.
6
 */
7
8
#ifndef HERMES_SUPPORT_BIGINT_H
9
#define HERMES_SUPPORT_BIGINT_H
10
11
#include "hermes/Support/Compiler.h"
12
13
#include "llvh/ADT/ArrayRef.h"
14
#include "llvh/ADT/DenseMap.h"
15
#include "llvh/ADT/SmallVector.h"
16
#include "llvh/ADT/StringRef.h"
17
#include "llvh/Support/MathExtras.h"
18
19
#include <deque>
20
#include <optional>
21
#include <string>
22
#include <vector>
23
#pragma GCC diagnostic push
24
25
#ifdef HERMES_COMPILER_SUPPORTS_WSHORTEN_64_TO_32
26
#pragma GCC diagnostic ignored "-Wshorten-64-to-32"
27
#endif
28
namespace hermes {
29
namespace bigint {
30
31
enum class ParsedSign {
32
  Minus = -1,
33
  None = 0,
34
  Plus = 1,
35
};
36
37
/// https://tc39.es/ecma262/#sec-stringintegerliteral-grammar
38
/// Parse \p src as a StringIntegerLiteral.
39
///
40
/// \return an empty optional if \p src is not a valid StringIntegerLiteral, in
41
/// which case \p outError (if not null) will contain a description of the
42
/// error; or, if \p src is a valid StringIntegerLiteral, then a string with the
43
/// bigint digits is returned; \p radix is set to the literal's radix; \p sign
44
/// represents which sign was parsed, if any.
45
std::optional<std::string> getStringIntegerLiteralDigitsAndSign(
46
    llvh::ArrayRef<char> src,
47
    uint8_t &radix,
48
    ParsedSign &sign,
49
    std::string *outError = nullptr);
50
std::optional<std::string> getStringIntegerLiteralDigitsAndSign(
51
    llvh::ArrayRef<char16_t> src,
52
    uint8_t &radix,
53
    ParsedSign &sign,
54
    std::string *outError = nullptr);
55
56
/// https://tc39.es/ecma262/#sec-numericvalue
57
/// Parse \p src as a NumericValue.
58
///
59
/// \return an empty optional if \p src is not a valid NumericValue, in which
60
/// case \p outError (if not null) will contain a description of the error; or,
61
/// if \p src is a valid NumericValue, then a string with the bigint digits is
62
/// returned; \p radix is set to the literal's radix.
63
std::optional<std::string> getNumericValueDigits(
64
    llvh::StringRef src,
65
    uint8_t &radix,
66
    std::string *outError = nullptr);
67
68
using SignedBigIntDigitType = int64_t;
69
using BigIntDigitType = uint64_t;
70
71
inline constexpr uint32_t BigIntDigitSizeInBytes =
72
    static_cast<uint32_t>(sizeof(BigIntDigitType));
73
inline constexpr uint32_t BigIntDigitSizeInBits = BigIntDigitSizeInBytes * 8;
74
75
/// Arbitrary upper limit on number of Digits a bigint may have.
76
inline constexpr uint32_t BigIntMaxSizeInDigits =
77
    0x400; // 1k digits == 8k bytes
78
79
/// Arbitrary upper limit on number of bytes a bigint may have.
80
inline constexpr uint32_t BigIntMaxSizeInBytes =
81
    BigIntDigitSizeInBytes * BigIntMaxSizeInDigits;
82
83
/// Helper function that should be called before allocating a Digits array on
84
/// the stack.
85
636k
inline constexpr bool tooManyDigits(uint32_t numDigits) {
86
636k
  return BigIntMaxSizeInDigits < numDigits;
87
636k
}
88
89
/// Helper function that returns if the given number of bytes exceeds the
90
/// maximum BigInt size in bytes.
91
99
inline constexpr bool tooManyBytes(uint32_t numBytes) {
92
99
  return BigIntMaxSizeInBytes < numBytes;
93
99
}
94
95
/// \return number of BigInt digits to represent \p v bits.
96
99
inline uint32_t numDigitsForSizeInBits(uint32_t v) {
97
99
  return static_cast<uint32_t>(llvh::alignTo(v, BigIntDigitSizeInBits)) /
98
99
      BigIntDigitSizeInBits;
99
99
}
100
101
/// \return number of BigInt digits to represent \p v bytes.
102
885k
inline uint32_t numDigitsForSizeInBytes(uint32_t v) {
103
885k
  return static_cast<uint32_t>(llvh::alignTo(v, BigIntDigitSizeInBytes)) /
104
885k
      BigIntDigitSizeInBytes;
105
885k
}
106
107
/// \return how many chars in base \p radix fit a BigIntDigitType.
108
124k
inline uint32_t constexpr maxCharsPerDigitInRadix(uint8_t radix) {
109
  // To compute the lower bound of bits in a BigIntDigitType "covered" by a
110
  // char. For power of 2 radixes, it is known (exactly) that each character
111
  // covers log2(radix) bits. For non-power of 2 radixes, a lower bound is
112
  // log2(greatest power of 2 that is less than radix).
113
124k
  uint32_t minNumBitsPerChar = radix < 4 ? 1
114
124k
      : radix < 8                        ? 2
115
124k
      : radix < 16                       ? 3
116
124k
      : radix < 32                       ? 4
117
0
                                         : 5;
118
119
  // With minNumBitsPerChar being the lower bound estimate of how many bits each
120
  // char can represent, the upper bound of how many chars "fit" in a bigint
121
  // digit is ceil(sizeofInBits(bigint digit) / minNumBitsPerChar).
122
124k
  uint32_t numCharsPerDigits = BigIntDigitSizeInBits / (1 << minNumBitsPerChar);
123
124
124k
  return numCharsPerDigits;
125
124k
}
126
127
/// Returns another view of \p src where high order bytes that are just used
128
/// for sign extension are dropped. Returns an empty ArrayRef if all bytes in \p
129
/// src are zero.
130
llvh::ArrayRef<uint8_t> dropExtraSignBits(llvh::ArrayRef<uint8_t> src);
131
132
/// \return the BigIntDigitType value that represents the sign extension of \p
133
/// byte. I.e., returns 0 if \p value is 0b0xxx....xxx, and ~0, if \p value is
134
/// 0x1xxx....xxx.
135
template <typename D, typename T, typename UT = std::make_unsigned_t<T>>
136
inline constexpr std::enable_if_t<std::is_integral_v<T>, D> getSignExtValue(
137
623k
    const T &value) {
138
623k
  uint32_t UnsignedTSizeInBits = sizeof(UT) * 8;
139
623k
  UT unsignedValue = value;
140
141
  // We rely on the unsigned (i.e., "logical") shift right to convert the sign
142
  // bit to [0, 1], then do 0 - [0, 1] to get 0ull or ~0ull as the sign
143
  // extension value.
144
623k
  uint64_t signExtValue = 0ull - (unsignedValue >> (UnsignedTSizeInBits - 1));
145
146
  // But still possibly truncate the value as requested by the caller.
147
623k
  return static_cast<D>(signExtValue);
148
623k
}
_ZN6hermes6bigint15getSignExtValueIhhhEENSt3__19enable_ifIXsr3stdE13is_integral_vIT0_EET_E4typeERKS4_
Line
Count
Source
137
623k
    const T &value) {
138
623k
  uint32_t UnsignedTSizeInBits = sizeof(UT) * 8;
139
623k
  UT unsignedValue = value;
140
141
  // We rely on the unsigned (i.e., "logical") shift right to convert the sign
142
  // bit to [0, 1], then do 0 - [0, 1] to get 0ull or ~0ull as the sign
143
  // extension value.
144
623k
  uint64_t signExtValue = 0ull - (unsignedValue >> (UnsignedTSizeInBits - 1));
145
146
  // But still possibly truncate the value as requested by the caller.
147
623k
  return static_cast<D>(signExtValue);
148
623k
}
Unexecuted instantiation: _ZN6hermes6bigint15getSignExtValueImmmEENSt3__19enable_ifIXsr3stdE13is_integral_vIT0_EET_E4typeERKS4_
149
150
/// ImmutableBigIntRef is used to represent bigint payloads that are not mutated
151
/// by any of the API functions below.
152
struct ImmutableBigIntRef {
153
  const BigIntDigitType *digits;
154
  uint32_t numDigits;
155
};
156
157
/// MutableBigIntRef is used to represent bigint payloads that are mutated
158
/// by any of the API functions below. Note how numDigits is a reference to
159
/// numDigits - the API may modify that in order to canonicalize the payloads.
160
struct MutableBigIntRef {
161
  BigIntDigitType *digits;
162
  uint32_t &numDigits;
163
};
164
165
/// The "catch-all" enum type with the possible return type from the bigint
166
/// APIs. It contains all possible errors that any bigint API function could
167
/// return (e.g., division by zero), even for functions that never return them.
168
enum class OperationStatus : uint32_t {
169
  RETURNED,
170
  DEST_TOO_SMALL,
171
  TOO_MANY_DIGITS,
172
  DIVISION_BY_ZERO,
173
  NEGATIVE_EXPONENT,
174
} HERMES_ATTRIBUTE_WARN_UNUSED_RESULT_TYPE;
175
176
/// Initializes \p dst with \p data, sign-extending the bits as needed.
177
OperationStatus initWithBytes(
178
    MutableBigIntRef dst,
179
    llvh::ArrayRef<uint8_t> data);
180
181
/// \return true if \p src is a negative bigint, and false otherwise.
182
bool isNegative(ImmutableBigIntRef src);
183
184
/// \return number of digits needed to perform Z( R( \p src ) ).
185
uint32_t fromDoubleResultSize(double src);
186
187
/// \return \p dst = Z( R( \p src ) )
188
OperationStatus fromDouble(MutableBigIntRef dst, double src);
189
190
/// \return \p src converted to double.
191
OperationStatus toDouble(double &dst, ImmutableBigIntRef src);
192
193
/// Holds the bytes in a parsed BigInt value.
194
class ParsedBigInt {
195
  ParsedBigInt(const ParsedBigInt &) = delete;
196
  ParsedBigInt &operator=(const ParsedBigInt &) = delete;
197
198
 public:
199
  using Storage = std::vector<uint8_t>;
200
201
435
  ParsedBigInt(ParsedBigInt &&) = default;
202
0
  ParsedBigInt &operator=(ParsedBigInt &&) = default;
203
204
  static std::optional<ParsedBigInt> parsedBigIntFromStringIntegerLiteral(
205
      llvh::ArrayRef<char> input,
206
      std::string *outError = nullptr);
207
208
  static std::optional<ParsedBigInt> parsedBigIntFromStringIntegerLiteral(
209
      llvh::ArrayRef<char16_t> input,
210
      std::string *outError = nullptr);
211
212
  /// Tries to parse \p input assuming it is the text in a BigInt literal (see
213
  /// ES2022 12.8.3.2 SS: NumericValue for mor information). \p outError is set
214
  /// with a description of the first error encountered while parsing \p input.
215
  /// \return A ParsedBigInt if \p input can be parsed, or an empty optional
216
  /// otherwise.
217
  static std::optional<ParsedBigInt> parsedBigIntFromNumericValue(
218
      llvh::StringRef input,
219
      std::string *outError = nullptr);
220
221
  /// \return A compact representation of the BigInt. Compact means all most
222
  /// significant bytes in storage_ that can be inferred with a
223
  /// sign-extension are dropped.
224
315
  llvh::ArrayRef<uint8_t> getBytes() const {
225
315
    return dropExtraSignBits(storage_);
226
315
  }
227
228
0
  llvh::ArrayRef<uint8_t> getRawDataFull() const {
229
0
    return storage_;
230
0
  }
231
232
 private:
233
99
  ParsedBigInt(llvh::ArrayRef<uint8_t> bytes) : storage_(bytes) {}
234
235
  Storage storage_;
236
};
237
238
/// Helper class for allocating temporary storage needed for BigInt operations.
239
/// It uses inline storage for "small enough" temporary requirements, and heap
240
/// allocates
241
class TmpStorage {
242
  TmpStorage() = delete;
243
  TmpStorage(const TmpStorage &) = delete;
244
  TmpStorage(TmpStorage &&) = delete;
245
246
  TmpStorage &operator=(const TmpStorage &) = delete;
247
  TmpStorage &operator=(TmpStorage &&) = delete;
248
249
 public:
250
  explicit TmpStorage(uint32_t sizeInDigits)
251
0
      : storage_(sizeInDigits), data_(storage_.begin()) {}
252
253
0
  ~TmpStorage() = default;
254
255
  /// \return a pointer to \p size digits in the temporary storage.
256
0
  BigIntDigitType *requestNumDigits(uint32_t size) {
257
0
    BigIntDigitType *ret = data_;
258
0
    data_ += size;
259
0
    assert(data_ <= storage_.end() && "too many temporary digits requested.");
260
0
    return ret;
261
0
  }
262
263
 private:
264
  static constexpr uint32_t MaxStackTmpStorageInDigits = 4;
265
  llvh::SmallVector<BigIntDigitType, MaxStackTmpStorageInDigits> storage_;
266
267
  BigIntDigitType *data_;
268
};
269
270
/// \return \p src's representation in \p radix.
271
std::string toString(ImmutableBigIntRef src, uint8_t radix);
272
273
/// same as toString() above, but prints a byte payload.
274
OperationStatus
275
toString(std::string &out, llvh::ArrayRef<uint8_t> bytes, uint8_t radix);
276
277
/// Computes number of digits needed to perform asUintN(\p n, \p src), storing
278
/// the result in \p resultSize.
279
/// \returns OperationStatus::TOO_MANY_DIGITS if the result would be too large
280
/// to represent; and OperationStatus::RETURNED otherwise.
281
OperationStatus
282
asUintNResultSize(uint64_t n, ImmutableBigIntRef src, uint32_t &resultSize);
283
284
/// \return \p src % (2n ** \p n), zero extended.
285
OperationStatus
286
asUintN(MutableBigIntRef dst, uint64_t n, ImmutableBigIntRef src);
287
288
/// \return number of digits needed to perform asIntN(\p n, \p src).
289
uint32_t asIntNResultSize(uint64_t n, ImmutableBigIntRef src);
290
291
/// \return \p src % (2n ** \p n), sign extended; \p N-th bit is the sign bit.
292
OperationStatus
293
asIntN(MutableBigIntRef dst, uint64_t n, ImmutableBigIntRef src);
294
295
/// \return (\p lhs < \p rhs) ? negative value : (\p lhs > \p rhs) ? positive
296
/// value : zero.
297
int compare(ImmutableBigIntRef lhs, ImmutableBigIntRef rhs);
298
int compare(ImmutableBigIntRef lhs, SignedBigIntDigitType rhs);
299
300
/// \return Whether \p src can be losslessly truncated to a single
301
/// SignedBigIntDigitType (if signedTruncation == true) or BigIntDigitType
302
/// (signedTruncation == false) digit.
303
bool isSingleDigitTruncationLossless(
304
    ImmutableBigIntRef src,
305
    bool signedTruncation);
306
307
/// \return The first digit in \p src, or 0 if src.numDigits == 0.
308
0
inline BigIntDigitType truncateToSingleDigit(ImmutableBigIntRef src) {
309
0
  return src.numDigits == 0 ? 0 : src.digits[0];
310
0
}
311
312
/// \return number of digits needed to perform \p - src
313
uint32_t unaryMinusResultSize(ImmutableBigIntRef src);
314
315
/// \return \p dst = - \p src
316
OperationStatus unaryMinus(MutableBigIntRef dst, ImmutableBigIntRef src);
317
318
/// \return number of digits needed to perform \p ~ src
319
uint32_t unaryNotResultSize(ImmutableBigIntRef src);
320
321
/// \return \p dst = ~ \p src.
322
OperationStatus unaryNot(MutableBigIntRef dst, ImmutableBigIntRef src);
323
324
/// \return number of digits needed to perform \p lhs + \p rhs
325
uint32_t addResultSize(ImmutableBigIntRef lhs, ImmutableBigIntRef rhs);
326
327
/// \return \p dst = \p lhs + \p rhs
328
OperationStatus
329
add(MutableBigIntRef dst, ImmutableBigIntRef lhs, ImmutableBigIntRef rhs);
330
331
/// \return number of digits needed to perform \p lhs + \p sImm
332
uint32_t addSignedResultSize(
333
    ImmutableBigIntRef lhs,
334
    SignedBigIntDigitType sImm);
335
336
/// \return \p dst = \p lhs + \p sImm
337
OperationStatus addSigned(
338
    MutableBigIntRef dst,
339
    ImmutableBigIntRef lhs,
340
    SignedBigIntDigitType sImm);
341
342
/// \return number of digits needed to perform \p lhs - \p rhs
343
uint32_t subtractResultSize(ImmutableBigIntRef lhs, ImmutableBigIntRef rhs);
344
345
/// \return \p dst = \p lhs - \p rhs
346
OperationStatus
347
subtract(MutableBigIntRef dst, ImmutableBigIntRef lhs, ImmutableBigIntRef rhs);
348
349
/// \return number of digits needed to perform \p lhs - \p sImm
350
uint32_t subtractSignedResultSize(
351
    ImmutableBigIntRef lhs,
352
    SignedBigIntDigitType sImm);
353
354
/// \return \p dst = \p lhs - \p sImm
355
OperationStatus subtractSigned(
356
    MutableBigIntRef dst,
357
    ImmutableBigIntRef lhs,
358
    SignedBigIntDigitType sImm);
359
360
/// \return number of digits needed to perform \p lhs * \p rhs with full
361
/// precision
362
uint32_t multiplyResultSize(ImmutableBigIntRef lhs, ImmutableBigIntRef rhs);
363
364
/// \return \p dst = \p lhs * \p rhs (full precision)
365
OperationStatus
366
multiply(MutableBigIntRef dst, ImmutableBigIntRef lhs, ImmutableBigIntRef rhs);
367
368
/// \return number of digits needed to perform \p lhs / \p rhs
369
uint32_t divideResultSize(ImmutableBigIntRef lhs, ImmutableBigIntRef rhs);
370
371
/// \return \p dst = \p lhs / \p rhs
372
OperationStatus
373
divide(MutableBigIntRef dst, ImmutableBigIntRef lhs, ImmutableBigIntRef rhs);
374
375
/// \return number of digits needed to perform \p lhs % \p rhs
376
uint32_t remainderResultSize(ImmutableBigIntRef lhs, ImmutableBigIntRef rhs);
377
378
/// \return \p dst = \p lhs % \p rhs
379
OperationStatus
380
remainder(MutableBigIntRef dst, ImmutableBigIntRef lhs, ImmutableBigIntRef rhs);
381
382
/// \return \p dst = \p lhs ** \p rhs
383
/// N.B.: There is no easy way to compute a reasonable upper bound on how
384
/// many digits are needed to store the result of the exponentiate operation,
385
/// thus callers are expected to call with a "big enough" dst, and the operation
386
/// will try to compute the result in that buffer.
387
OperationStatus exponentiate(
388
    MutableBigIntRef dst,
389
    ImmutableBigIntRef lhs,
390
    ImmutableBigIntRef rhs);
391
392
/// \return number of digits needed to perform \p lhs & \p rhs
393
uint32_t bitwiseANDResultSize(ImmutableBigIntRef lhs, ImmutableBigIntRef rhs);
394
395
/// \return dst = lhs & rhs
396
OperationStatus bitwiseAND(
397
    MutableBigIntRef dst,
398
    ImmutableBigIntRef lhs,
399
    ImmutableBigIntRef rhs);
400
401
/// \return number of digits needed to perform \p lhs | \p rhs
402
uint32_t bitwiseORResultSize(ImmutableBigIntRef lhs, ImmutableBigIntRef rhs);
403
404
/// \return \p dst = \p lhs | \p rhs
405
OperationStatus
406
bitwiseOR(MutableBigIntRef dst, ImmutableBigIntRef lhs, ImmutableBigIntRef rhs);
407
408
/// \return number of digits needed to perform \p lhs ^ \p rhs
409
uint32_t bitwiseXORResultSize(ImmutableBigIntRef lhs, ImmutableBigIntRef rhs);
410
411
/// \return \p dst = \p lhs ^ \p rhs
412
OperationStatus bitwiseXOR(
413
    MutableBigIntRef dst,
414
    ImmutableBigIntRef lhs,
415
    ImmutableBigIntRef rhs);
416
417
/// \return number of digits needed to perform \p lhs << \p rhs
418
uint32_t leftShiftResultSize(ImmutableBigIntRef lhs, ImmutableBigIntRef rhs);
419
420
/// \return \p dst = \p lhs << \p rhs
421
OperationStatus
422
leftShift(MutableBigIntRef dst, ImmutableBigIntRef lhs, ImmutableBigIntRef rhs);
423
424
/// \return number of digits needed to perform \p lhs >> \p rhs
425
uint32_t signedRightShiftResultSize(
426
    ImmutableBigIntRef lhs,
427
    ImmutableBigIntRef rhs);
428
429
/// \return dst = \p lhs >> \p rhs
430
OperationStatus signedRightShift(
431
    MutableBigIntRef dst,
432
    ImmutableBigIntRef lhs,
433
    ImmutableBigIntRef rhs);
434
435
/// A list of bytes representing all bigints known by UniquingBigIntTable.
436
using BigIntBytes = std::vector<uint8_t>;
437
438
/// A BigIntTableEntry is simply an (offset, length) pair for indexing the
439
/// bigint tables in the bytecode.
440
struct BigIntTableEntry {
441
  uint32_t offset;
442
  uint32_t length;
443
};
444
445
inline bool operator==(
446
    const BigIntTableEntry &lhs,
447
0
    const BigIntTableEntry &rhs) {
448
0
  return lhs.offset == rhs.offset && lhs.length == rhs.length;
449
0
}
450
451
/// A class responsible for assigning IDs to bigints.
452
class UniquingBigIntTable {
453
  /// List of created bigints. Use deque because it does not move elements
454
  /// when appending, which might invalidate the ArrayRefs.
455
  std::deque<ParsedBigInt> bigints_;
456
457
  using KeyType = llvh::ArrayRef<uint8_t>;
458
459
  /// Map from parsed bigint to its index. The
460
  /// StringRefs reference data owned by the bigints_ field.
461
  llvh::DenseMap<KeyType, uint32_t> keysToIndex_;
462
463
  /// A UniquingBigIntTable may not be copied.
464
  UniquingBigIntTable(const UniquingBigIntTable &) = delete;
465
  void operator=(const UniquingBigIntTable &) = delete;
466
467
 public:
468
147
  UniquingBigIntTable() = default;
469
470
  /// Adds a bigint to the table if not already present.
471
  /// \return the ID of the bigint.
472
99
  uint32_t addBigInt(ParsedBigInt bigint) {
473
99
    auto iter = keysToIndex_.find(keyFor(bigint));
474
99
    if (iter != keysToIndex_.end()) {
475
60
      return iter->second;
476
60
    }
477
478
39
    const uint32_t index = bigints_.size();
479
39
    bigints_.push_back(std::move(bigint));
480
39
    keysToIndex_[keyFor(bigints_.back())] = index;
481
39
    return index;
482
99
  }
483
484
  /// \return whether the table is empty.
485
0
  bool empty() const {
486
0
    return bigints_.empty();
487
0
  }
488
489
  /// Return the bigint entry list.
490
  std::vector<BigIntTableEntry> getEntryList() const;
491
492
  /// Return the combined bytecode buffer.
493
  BigIntBytes getDigitsBuffer() const;
494
495
 private:
496
138
  static KeyType keyFor(const ParsedBigInt &parsedBigInt) {
497
138
    return parsedBigInt.getBytes();
498
138
  }
499
};
500
501
} // namespace bigint
502
} // namespace hermes
503
#pragma GCC diagnostic pop
504
505
#endif // HERMES_SUPPORT_BIGINT_H