summaryrefslogtreecommitdiffstats
path: root/js/src/jsapi-tests/testJitABIcalls.cpp
blob: 1bd0468aa575fd933300a84fbf8afd5673041e93 (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
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/FloatingPoint.h"
#include "mozilla/IntegerTypeTraits.h"

#include <iterator>

#include "jit/ABIFunctions.h"
#include "jit/IonAnalysis.h"
#include "jit/Linker.h"
#include "jit/MacroAssembler.h"
#include "jit/MIRGenerator.h"
#include "jit/MIRGraph.h"
#include "jit/ValueNumbering.h"
#include "jit/VMFunctions.h"
#include "js/Value.h"

#include "jsapi-tests/tests.h"
#include "jsapi-tests/testsJit.h"

#include "jit/ABIFunctionList-inl.h"
#include "jit/MacroAssembler-inl.h"
#include "jit/VMFunctionList-inl.h"

using namespace js;
using namespace js::jit;

// This test case relies on VMFUNCTION_LIST, ABIFUNCTION_LIST,
// ABIFUNCTION_AND_TYPE_LIST and ABIFUNCTIONSIG_LIST, to create a test case for
// each function registered, in order to check if the arguments are properly
// being interpreted after a call from the JIT.
//
// This test checks that the interpretation of the C++ compiler matches the
// interpretation of the JIT. It works by generating a call to a function which
// has the same signature as the tested function. The function being called
// re-interprets the arguments' content to ensure that it matches the content
// given as arguments by the JIT.
//
// These tests cases succeed if the content provided by the JIT matches the
// content read by the C++ code. Otherwise, a failure implies that either the
// MacroAssembler is not used properly, or that the code used by the JIT to
// generate the function call does not match the ABI of the targeted system.

// Convert the content of each macro list to a single and unique format which is
// (Name, Type).
#define ABIFUN_TO_ALLFUN(Fun) (#Fun, decltype(&::Fun))
#define ABIFUN_AND_SIG_TO_ALLFUN(Fun, Sig) (#Fun " as " #Sig, Sig)
#define ABISIG_TO_ALLFUN(Sig) ("(none) as " #Sig, Sig)
#define VMFUN_TO_ALLFUN(Name, Fun, Pop...) (#Fun, decltype(&::Fun))

#define APPLY(A, B) A B

// Generate macro calls for all the lists which are used to allow, directly or
// indirectly, calls performed with callWithABI.
//
// This macro will delegate to a different macro call based on the type of the
// list the element is extracted from.
#define ALL_FUNCTIONS(PREFIX)                                  \
  ABIFUNCTION_LIST(PREFIX##_ABIFUN_TO_ALLFUN)                  \
  ABIFUNCTION_AND_TYPE_LIST(PREFIX##_ABIFUN_AND_SIG_TO_ALLFUN) \
  ABIFUNCTIONSIG_LIST(PREFIX##_ABISIG_TO_ALLFUN)               \
  VMFUNCTION_LIST(PREFIX##_VMFUN_TO_ALLFUN)

// sizeof(const T&) is not equal to sizeof(const T*), but references are passed
// as pointers.
//
// "When applied to a reference or a reference type, the result is the size of
// the referenced type." [expr.sizeof] (5.3.3.2)
//
// The following functions avoid this issue by wrapping the type in a structure
// which will share the same property, even if the wrapped type is a reference.
template <typename T>
constexpr size_t ActualSizeOf() {
  struct Wrapper {
    T _unused;
  };
  return sizeof(Wrapper);
}

template <typename T>
constexpr size_t ActualAlignOf() {
  struct Wrapper {
    T _unused;
  };
  return alignof(Wrapper);
}

// Given a type, return the integer type which has the same size.
template <typename T>
using IntTypeOf_t =
    typename mozilla::UnsignedStdintTypeForSize<ActualSizeOf<T>()>::Type;

// Concatenate 2 std::integer_sequence, and return an std::integer_sequence with
// the content of both parameters.
template <typename Before, typename After>
struct Concat;

template <typename Int, Int... Before, Int... After>
struct Concat<std::integer_sequence<Int, Before...>,
              std::integer_sequence<Int, After...>> {
  using type = std::integer_sequence<Int, Before..., After...>;
};

template <typename Before, typename After>
using Concat_t = typename Concat<Before, After>::type;

static_assert(std::is_same_v<Concat_t<std::integer_sequence<uint8_t, 1, 2>,
                                      std::integer_sequence<uint8_t, 3, 4>>,
                             std::integer_sequence<uint8_t, 1, 2, 3, 4>>);

// Generate an std::integer_sequence of `N` elements, where each element is an
// uint8_t integer with value `Value`.
template <size_t N, uint8_t Value>
constexpr auto CstSeq() {
  if constexpr (N == 0) {
    return std::integer_sequence<uint8_t>{};
  } else {
    return Concat_t<std::integer_sequence<uint8_t, Value>,
                    decltype(CstSeq<N - 1, Value>())>{};
  }
}

template <size_t N, uint8_t Value>
using CstSeq_t = decltype(CstSeq<N, Value>());

static_assert(
    std::is_same_v<CstSeq_t<4, 2>, std::integer_sequence<uint8_t, 2, 2, 2, 2>>);

// Computes the number of bytes to add before a type in order to align it in
// memory.
constexpr size_t PadBytes(size_t size, size_t align) {
  return (align - (size % align)) % align;
}

// Request a minimum alignment for the values added to a buffer in order to
// account for the read size used by the MoveOperand given as an argument of
// passWithABI. The MoveOperand does not take into consideration the size of
// the data being transfered, and might load a larger amount of data.
//
// This function ensures that the MoveOperand would read the 0x55 padding added
// after each value, when it reads too much.
constexpr size_t AtLeastSize() { return sizeof(uintptr_t); }

// Returns the size which needs to be added in addition to the memory consumed
// by the type, from which the size if given as argument.
template <typename Type>
constexpr size_t BackPadBytes() {
  return std::max(AtLeastSize(), ActualSizeOf<Type>()) - ActualSizeOf<Type>();
}

// Adds the padding and the reserved size for storing a value in a buffer which
// can be read by a MoveOperand.
template <typename Type>
constexpr size_t PadSize(size_t size) {
  return PadBytes(size, ActualAlignOf<Type>()) + ActualSizeOf<Type>() +
         BackPadBytes<Type>();
}

// Generate an std::integer_sequence of 0:uint8_t elements of the size of the
// padding needed to align a type in memory.
template <size_t Align, size_t CurrSize>
using PadSeq_t = decltype(CstSeq<PadBytes(CurrSize, Align), 0>());

static_assert(std::is_same_v<PadSeq_t<4, 0>, std::integer_sequence<uint8_t>>);
static_assert(
    std::is_same_v<PadSeq_t<4, 3>, std::integer_sequence<uint8_t, 0>>);
static_assert(
    std::is_same_v<PadSeq_t<4, 2>, std::integer_sequence<uint8_t, 0, 0>>);
static_assert(
    std::is_same_v<PadSeq_t<4, 1>, std::integer_sequence<uint8_t, 0, 0, 0>>);

// Spread an integer value `Value` into a new std::integer_sequence of `N`
// uint8_t elements, using Little Endian ordering of bytes.
template <size_t N, uint64_t Value, uint8_t... Rest>
constexpr auto FillLESeq() {
  if constexpr (N == 0) {
    return std::integer_sequence<uint8_t, Rest...>{};
  } else {
    return FillLESeq<N - 1, (Value >> 8), Rest..., uint8_t(Value & 0xff)>();
  }
}

template <size_t N, uint64_t Value>
using FillSeq_t = decltype(FillLESeq<N, Value>());

static_assert(std::is_same_v<FillSeq_t<4, 2>,
                             std::integer_sequence<uint8_t, 2, 0, 0, 0>>);

// Given a list of template parameters, generate an std::integer_sequence of
// size_t, where each element is 1 larger than the previous one. The generated
// sequence starts at 0.
template <typename... Args>
using ArgsIndexes_t =
    std::make_integer_sequence<uint64_t, uint64_t(sizeof...(Args))>;

static_assert(std::is_same_v<ArgsIndexes_t<uint8_t, uint64_t>,
                             std::integer_sequence<uint64_t, 0, 1>>);

// Extract a single bit for each element of an std::integer_sequence. This is
// used to work around some restrictions with providing boolean arguments,
// which might be truncated to a single bit.
template <size_t Bit, typename IntSeq>
struct ExtractBit;

template <size_t Bit, uint64_t... Values>
struct ExtractBit<Bit, std::integer_sequence<uint64_t, Values...>> {
  using type = std::integer_sequence<uint64_t, (Values >> Bit) & 1 ...>;
};

// Generate an std::integer_sequence of indexes which are filtered for a single
// bit, such that it can be used with boolean types.
template <size_t Bit, typename... Args>
using ArgsBitOfIndexes_t =
    typename ExtractBit<Bit, ArgsIndexes_t<Args...>>::type;

static_assert(std::is_same_v<ArgsBitOfIndexes_t<0, int, int, int, int>,
                             std::integer_sequence<uint64_t, 0, 1, 0, 1>>);
static_assert(std::is_same_v<ArgsBitOfIndexes_t<1, int, int, int, int>,
                             std::integer_sequence<uint64_t, 0, 0, 1, 1>>);

// Compute the offset of each argument in a buffer produced by GenArgsBuffer,
// this is used to fill the MoveOperand displacement field when loading value
// out of the buffer produced by GenArgsBuffer.
template <uint64_t Size, typename... Args>
struct ArgsOffsets;

template <uint64_t Size>
struct ArgsOffsets<Size> {
  using type = std::integer_sequence<uint64_t>;
};

template <uint64_t Size, typename Arg, typename... Args>
struct ArgsOffsets<Size, Arg, Args...> {
  using type =
      Concat_t<std::integer_sequence<
                   uint64_t, Size + PadBytes(Size, ActualAlignOf<Arg>())>,
               typename ArgsOffsets<Size + PadSize<Arg>(Size), Args...>::type>;
};

template <uint64_t Size, typename... Args>
using ArgsOffsets_t = typename ArgsOffsets<Size, Args...>::type;

// Not all 32bits architecture align uint64_t type on 8 bytes, so check the
// validity of the stored content based on the alignment of the architecture.
static_assert(ActualAlignOf<uint64_t>() != 8 ||
              std::is_same_v<ArgsOffsets_t<0, uint8_t, uint64_t, bool>,
                             std::integer_sequence<uint64_t, 0, 8, 16>>);

static_assert(ActualAlignOf<uint64_t>() != 4 ||
              std::is_same_v<ArgsOffsets_t<0, uint8_t, uint64_t, bool>,
                             std::integer_sequence<uint64_t, 0, 4, 12>>);

// Generate an std::integer_sequence containing the size of each argument in
// memory.
template <typename... Args>
using ArgsSizes_t = std::integer_sequence<uint64_t, ActualSizeOf<Args>()...>;

// Generate an std::integer_sequence containing values where all valid bits for
// each type are set to 1.
template <typename Type>
constexpr uint64_t FillBits() {
  constexpr uint64_t topBit = uint64_t(1) << ((8 * ActualSizeOf<Type>()) - 1);
  if constexpr (std::is_same_v<Type, bool>) {
    return uint64_t(1);
  } else if constexpr (std::is_same_v<Type, double> ||
                       std::is_same_v<Type, float>) {
    // A NaN has all the bits of its exponent set to 1. The CPU / C++ does not
    // garantee keeping the payload of NaN values, when interpreted as floating
    // point, which could cause some random failures. This removes one bit from
    // the exponent, such that the floating point value is not converted to a
    // canonicalized NaN by the time we compare it.
    constexpr uint64_t lowExpBit =
        uint64_t(1) << mozilla::FloatingPoint<Type>::kExponentShift;
    return uint64_t((topBit - 1) + topBit - lowExpBit);
  } else {
    // Note: Keep parentheses to avoid unwanted overflow.
    return uint64_t((topBit - 1) + topBit);
  }
}

template <typename... Args>
using ArgsFillBits_t = std::integer_sequence<uint64_t, FillBits<Args>()...>;

// Given a type, return the ABIType used by passABIArg to know how to
// interpret the value which are given as arguments.
template <typename Type>
constexpr ABIType TypeToABIType() {
  if constexpr (std::is_same_v<Type, float>) {
    return ABIType::Float32;
  } else if constexpr (std::is_same_v<Type, double>) {
    return ABIType::Float64;
  } else {
    return ABIType::General;
  }
}

// Generate a sequence which contains the associated ABIType of each argument.
// Note, a new class is defined because C++ header of clang are rejecting the
// option of having an enumerated type as argument of std::integer_sequence.
template <ABIType... Val>
class ABITypeSequence {};

template <typename... Args>
using ArgsABITypes_t = ABITypeSequence<TypeToABIType<Args>()...>;

// Generate an std::integer_sequence which corresponds to a buffer containing
// values which are spread at the location where each arguments type would be
// stored in a buffer.
template <typename Buffer, typename Values, typename... Args>
struct ArgsBuffer;

template <uint8_t... Buffer, typename Arg, typename... Args, uint64_t Val,
          uint64_t... Values>
struct ArgsBuffer<std::integer_sequence<uint8_t, Buffer...>,
                  std::integer_sequence<uint64_t, Val, Values...>, Arg,
                  Args...> {
  using type = typename ArgsBuffer<
      Concat_t<std::integer_sequence<uint8_t, Buffer...>,
               Concat_t<PadSeq_t<ActualAlignOf<Arg>(), sizeof...(Buffer)>,
                        Concat_t<FillSeq_t<ActualSizeOf<Arg>(), Val>,
                                 CstSeq_t<BackPadBytes<Arg>(), 0x55>>>>,
      std::integer_sequence<uint64_t, Values...>, Args...>::type;
};

template <typename Buffer>
struct ArgsBuffer<Buffer, std::integer_sequence<uint64_t>> {
  using type = Buffer;
};

template <typename Values, typename... Args>
using ArgsBuffer_t =
    typename ArgsBuffer<std::integer_sequence<uint8_t>, Values, Args...>::type;

// NOTE: The representation of the boolean might be surprising in this test
// case, see AtLeastSize function for an explanation.
#ifdef JS_64BIT
static_assert(sizeof(uintptr_t) == 8);
static_assert(
    std::is_same_v<
        ArgsBuffer_t<std::integer_sequence<uint64_t, 42, 51>, uint64_t, bool>,
        std::integer_sequence<uint8_t, 42, 0, 0, 0, 0, 0, 0, 0, 51, 0x55, 0x55,
                              0x55, 0x55, 0x55, 0x55, 0x55>>);
static_assert(
    std::is_same_v<
        ArgsBuffer_t<std::integer_sequence<uint64_t, 0xffffffff, 0xffffffff>,
                     uint8_t, uint16_t>,
        std::integer_sequence<uint8_t, 0xff, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
                              0x55, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0x55,
                              0x55>>);
#else
static_assert(sizeof(uintptr_t) == 4);
static_assert(
    std::is_same_v<
        ArgsBuffer_t<std::integer_sequence<uint64_t, 42, 51>, uint64_t, bool>,
        std::integer_sequence<uint8_t, 42, 0, 0, 0, 0, 0, 0, 0, 51, 0x55, 0x55,
                              0x55>>);
static_assert(
    std::is_same_v<
        ArgsBuffer_t<std::integer_sequence<uint64_t, 0xffffffff, 0xffffffff>,
                     uint8_t, uint16_t>,
        std::integer_sequence<uint8_t, 0xff, 0x55, 0x55, 0x55, 0xff, 0xff, 0x55,
                              0x55>>);
#endif

// Test used to check if any of the types given as template parameters are a
// `bool`, which is a corner case where a raw integer might be truncated by the
// C++ compiler.
template <typename... Args>
constexpr bool AnyBool_v = (std::is_same_v<Args, bool> || ...);

// Instantiate an std::integer_sequence as a buffer which is readable and
// addressable at runtime, for reading argument values from the generated code.
template <typename Seq>
struct InstanceSeq;

template <typename Int, Int... Values>
struct InstanceSeq<std::integer_sequence<Int, Values...>> {
  static constexpr Int table[sizeof...(Values)] = {Values...};
  static constexpr size_t size = sizeof...(Values);
};

// Instantiate a buffer for testing the position of arguments when calling a
// function.
template <typename... Args>
using TestArgsPositions =
    InstanceSeq<ArgsBuffer_t<ArgsIndexes_t<Args...>, Args...>>;

// Instantiate a buffer for testing the position of arguments, one bit at a
// time, when calling a function.
template <size_t Bit, typename... Args>
using TestArgsBitOfPositions =
    InstanceSeq<ArgsBuffer_t<ArgsBitOfIndexes_t<Bit, Args...>, Args...>>;

// Instantiate a buffer to check that the size of each argument is interpreted
// correctly when calling a function.
template <typename... Args>
using TestArgsSizes = InstanceSeq<ArgsBuffer_t<ArgsSizes_t<Args...>, Args...>>;

// Instantiate a buffer to check that all bits of each argument goes through.
template <typename... Args>
using TestArgsFillBits =
    InstanceSeq<ArgsBuffer_t<ArgsFillBits_t<Args...>, Args...>>;

// ConvertToInt returns the raw value of any argument as an integer value which
// can be compared with the expected values.
template <typename Type>
IntTypeOf_t<Type> ConvertToInt(Type v) {
  // Simplify working with types by casting the address of the value to the
  // equivalent `const void*`.
  auto address = reinterpret_cast<const void*>(&v);
  // Convert the `void*` to an integer pointer of the same size as the input
  // type, and return the raw value stored in the integer interpretation.
  static_assert(ActualSizeOf<Type>() == ActualSizeOf<IntTypeOf_t<Type>>());
  if constexpr (std::is_reference_v<Type>) {
    return reinterpret_cast<const IntTypeOf_t<Type>>(address);
  } else {
    return *reinterpret_cast<const IntTypeOf_t<Type>*>(address);
  }
}

// Attributes used to disable some parts of Undefined Behavior sanitizer. This
// is needed to keep the signature identical to what is used in production,
// instead of working around these limitations.
//
// * no_sanitize("enum"): Enumerated values given as arguments are checked to
//     see if the value given as argument matches any of the enumerated values.
//     The patterns used to check whether the values are correctly transmitted
//     from the JIT to C++ might go beyond the set of enumerated values, and
//     break this sanitizer check.
#if defined(__clang__) && defined(__has_attribute) && \
    __has_attribute(no_sanitize)
#  define NO_ARGS_CHECKS __attribute__((no_sanitize("enum")))
#else
#  define NO_ARGS_CHECKS
#endif

// Check if the raw values of arguments are equal to the numbers given in the
// std::integer_sequence given as the first argument.
template <typename... Args, typename Int, Int... Val>
NO_ARGS_CHECKS bool CheckArgsEqual(JSAPIRuntimeTest* instance, int lineno,
                                   std::integer_sequence<Int, Val...>,
                                   Args... args) {
  return (instance->checkEqual(ConvertToInt<Args>(args), IntTypeOf_t<Args>(Val),
                               "ConvertToInt<Args>(args)",
                               "IntTypeOf_t<Args>(Val)", __FILE__, lineno) &&
          ...);
}

// Generate code to register the value of each argument of the called function.
// Each argument's content is read from a buffer whose address is stored in the
// `base` register. The offsets of arguments are given as a third argument
// which is expected to be generated by `ArgsOffsets`. The ABIType types of
// arguments are given as the fourth argument and are expected to be generated
// by `ArgsABIType`.
template <uint64_t... Off, ABIType... Type>
static void passABIArgs(MacroAssembler& masm, Register base,
                        std::integer_sequence<uint64_t, Off...>,
                        ABITypeSequence<Type...>) {
  (masm.passABIArg(MoveOperand(base, size_t(Off)), Type), ...);
}

// For each function type given as a parameter, create a few functions with the
// given type, to be called by the JIT code produced by `generateCalls`. These
// functions report the result through the instance registered with the
// `set_instance` function, as we cannot add extra arguments to these functions.
template <typename Type>
struct DefineCheckArgs;

template <typename Res, typename... Args>
struct DefineCheckArgs<Res (*)(Args...)> {
  void set_instance(JSAPIRuntimeTest* instance, bool* reportTo) {
    MOZ_ASSERT((!instance_) != (!instance));
    instance_ = instance;
    MOZ_ASSERT((!reportTo_) != (!reportTo));
    reportTo_ = reportTo;
  }
  static void report(bool value) { *reportTo_ = *reportTo_ && value; }

  // Check that arguments are interpreted in the same order at compile time and
  // at runtime.
  static NO_ARGS_CHECKS Res CheckArgsPositions(Args... args) {
    AutoUnsafeCallWithABI unsafe;
    using Indexes = std::index_sequence_for<Args...>;
    report(CheckArgsEqual<Args...>(instance_, __LINE__, Indexes(),
                                   std::forward<Args>(args)...));
    return Res();
  }

  // This is the same test as above, but some compilers might clean the boolean
  // values using `& 1` operations, which corrupt the operand index, thus to
  // properly check for the position of boolean operands, we have to check the
  // position of the boolean operand using a single bit at a time.
  static NO_ARGS_CHECKS Res CheckArgsBitOfPositions0(Args... args) {
    AutoUnsafeCallWithABI unsafe;
    using Indexes = ArgsBitOfIndexes_t<0, Args...>;
    report(CheckArgsEqual<Args...>(instance_, __LINE__, Indexes(),
                                   std::forward<Args>(args)...));
    return Res();
  }

  static NO_ARGS_CHECKS Res CheckArgsBitOfPositions1(Args... args) {
    AutoUnsafeCallWithABI unsafe;
    using Indexes = ArgsBitOfIndexes_t<1, Args...>;
    report(CheckArgsEqual<Args...>(instance_, __LINE__, Indexes(),
                                   std::forward<Args>(args)...));
    return Res();
  }

  static NO_ARGS_CHECKS Res CheckArgsBitOfPositions2(Args... args) {
    AutoUnsafeCallWithABI unsafe;
    using Indexes = ArgsBitOfIndexes_t<2, Args...>;
    report(CheckArgsEqual<Args...>(instance_, __LINE__, Indexes(),
                                   std::forward<Args>(args)...));
    return Res();
  }

  static NO_ARGS_CHECKS Res CheckArgsBitOfPositions3(Args... args) {
    AutoUnsafeCallWithABI unsafe;
    using Indexes = ArgsBitOfIndexes_t<3, Args...>;
    report(CheckArgsEqual<Args...>(instance_, __LINE__, Indexes(),
                                   std::forward<Args>(args)...));
    return Res();
  }

  // Check that the compile time and run time sizes of each argument are the
  // same.
  static NO_ARGS_CHECKS Res CheckArgsSizes(Args... args) {
    AutoUnsafeCallWithABI unsafe;
    using Sizes = ArgsSizes_t<Args...>;
    report(CheckArgsEqual<Args...>(instance_, __LINE__, Sizes(),
                                   std::forward<Args>(args)...));
    return Res();
  }

  // Check that the compile time and run time all bits of each argument are
  // correctly passed through.
  static NO_ARGS_CHECKS Res CheckArgsFillBits(Args... args) {
    AutoUnsafeCallWithABI unsafe;
    using FillBits = ArgsFillBits_t<Args...>;
    report(CheckArgsEqual<Args...>(instance_, __LINE__, FillBits(),
                                   std::forward<Args>(args)...));
    return Res();
  }

  using FunType = Res (*)(Args...);
  struct Test {
    const uint8_t* buffer;
    const size_t size;
    const FunType fun;
  };

  // Generate JIT code for calling the above test functions where each argument
  // is given a different raw value that can be compared by each called
  // function.
  void generateCalls(MacroAssembler& masm, Register base, Register setup) {
    using ArgsPositions = TestArgsPositions<Args...>;
    using ArgsBitOfPositions0 = TestArgsBitOfPositions<0, Args...>;
    using ArgsBitOfPositions1 = TestArgsBitOfPositions<1, Args...>;
    using ArgsBitOfPositions2 = TestArgsBitOfPositions<2, Args...>;
    using ArgsBitOfPositions3 = TestArgsBitOfPositions<3, Args...>;
    using ArgsSizes = TestArgsSizes<Args...>;
    using ArgsFillBits = TestArgsFillBits<Args...>;
    static const Test testsWithoutBoolArgs[3] = {
        {ArgsPositions::table, ArgsPositions::size, CheckArgsPositions},
        {ArgsSizes::table, ArgsSizes::size, CheckArgsSizes},
        {ArgsFillBits::table, ArgsFillBits::size, CheckArgsFillBits},
    };
    static const Test testsWithBoolArgs[6] = {
        {ArgsBitOfPositions0::table, ArgsBitOfPositions0::size,
         CheckArgsBitOfPositions0},
        {ArgsBitOfPositions1::table, ArgsBitOfPositions1::size,
         CheckArgsBitOfPositions1},
        {ArgsBitOfPositions2::table, ArgsBitOfPositions2::size,
         CheckArgsBitOfPositions2},
        {ArgsBitOfPositions3::table, ArgsBitOfPositions3::size,
         CheckArgsBitOfPositions3},
        {ArgsSizes::table, ArgsSizes::size, CheckArgsSizes},
        {ArgsFillBits::table, ArgsFillBits::size, CheckArgsFillBits},
    };
    const Test* tests = testsWithoutBoolArgs;
    size_t numTests = std::size(testsWithoutBoolArgs);
    if (AnyBool_v<Args...>) {
      tests = testsWithBoolArgs;
      numTests = std::size(testsWithBoolArgs);
    }

    for (size_t i = 0; i < numTests; i++) {
      const Test& test = tests[i];
      masm.movePtr(ImmPtr(test.buffer), base);

      masm.setupUnalignedABICall(setup);
      using Offsets = ArgsOffsets_t<0, Args...>;
      using ABITypes = ArgsABITypes_t<Args...>;
      passABIArgs(masm, base, Offsets(), ABITypes());
      masm.callWithABI(DynFn{JS_FUNC_TO_DATA_PTR(void*, test.fun)},
                       TypeToABIType<Res>(),
                       CheckUnsafeCallWithABI::DontCheckOther);
    }
  }

 private:
  // As we are checking specific function signature, we cannot add extra
  // parameters, thus we rely on static variables to pass the value of the
  // instance that we are testing.
  static JSAPIRuntimeTest* instance_;
  static bool* reportTo_;
};

template <typename Res, typename... Args>
JSAPIRuntimeTest* DefineCheckArgs<Res (*)(Args...)>::instance_ = nullptr;

template <typename Res, typename... Args>
bool* DefineCheckArgs<Res (*)(Args...)>::reportTo_ = nullptr;

// This is a child class of JSAPIRuntimeTest, which is used behind the scenes to
// register test cases in jsapi-tests. Each instance of it creates a new test
// case. This class is specialized with the type of the function to check, and
// initialized with the name of the function with the given signature.
//
// When executed, it generates the JIT code to call functions with the same
// signature and checks that the JIT interpretation of arguments location
// matches the C++ interpretation. If it differs, the test case will fail.
template <typename Sig>
class JitABICall final : public JSAPIRuntimeTest, public DefineCheckArgs<Sig> {
 public:
  explicit JitABICall(const char* name) : name_(name) { reuseGlobal = true; }
  virtual const char* name() override { return name_; }
  virtual bool run(JS::HandleObject) override {
    bool result = true;
    this->set_instance(this, &result);

    TempAllocator temp(&cx->tempLifoAlloc());
    JitContext jcx(cx);
    StackMacroAssembler masm(cx, temp);
    AutoCreatedBy acb(masm, __func__);
    PrepareJit(masm);

    AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
    // Initialize the base register the same way this is done while reading
    // arguments in generateVMWrapper, in order to avoid MOZ_RELEASE_ASSERT in
    // the MoveResolver.
#if defined(JS_CODEGEN_X86)
    Register base = regs.takeAny();
#elif defined(JS_CODEGEN_X64)
    Register base = r10;
    regs.take(base);
#elif defined(JS_CODEGEN_ARM)
    Register base = r5;
    regs.take(base);
#elif defined(JS_CODEGEN_ARM64)
    Register base = r8;
    regs.take(base);
#elif defined(JS_CODEGEN_MIPS32)
    Register base = t1;
    regs.take(base);
#elif defined(JS_CODEGEN_MIPS64)
    Register base = t1;
    regs.take(base);
#elif defined(JS_CODEGEN_LOONG64)
    Register base = t0;
    regs.take(base);
#elif defined(JS_CODEGEN_RISCV64)
    Register base = t0;
    regs.take(base);
#else
#  error "Unknown architecture!"
#endif

    Register setup = regs.takeAny();

    this->generateCalls(masm, base, setup);

    if (!ExecuteJit(cx, masm)) {
      return false;
    }
    this->set_instance(nullptr, nullptr);
    return result;
  };

 private:
  const char* name_;
};

// GCC warns when the signature does not have matching attributes (for example
// [[nodiscard]]). Squelch this warning to avoid a GCC-only footgun.
#if MOZ_IS_GCC
#  pragma GCC diagnostic push
#  pragma GCC diagnostic ignored "-Wignored-attributes"
#endif

// For each VMFunction and ABIFunction, create an instance of a JitABICall
// class to register a jsapi-tests test case.
#define TEST_INSTANCE(Name, Sig)                                             \
  static JitABICall<Sig> MOZ_CONCAT(MOZ_CONCAT(cls_jitabicall, __COUNTER__), \
                                    _instance)("JIT ABI for " Name);
#define TEST_INSTANCE_ABIFUN_TO_ALLFUN(...) \
  APPLY(TEST_INSTANCE, ABIFUN_TO_ALLFUN(__VA_ARGS__))
#define TEST_INSTANCE_ABIFUN_AND_SIG_TO_ALLFUN(...) \
  APPLY(TEST_INSTANCE, ABIFUN_AND_SIG_TO_ALLFUN(__VA_ARGS__))
#define TEST_INSTANCE_ABISIG_TO_ALLFUN(...) \
  APPLY(TEST_INSTANCE, ABISIG_TO_ALLFUN(__VA_ARGS__))
#define TEST_INSTANCE_VMFUN_TO_ALLFUN(...) \
  APPLY(TEST_INSTANCE, VMFUN_TO_ALLFUN(__VA_ARGS__))

ALL_FUNCTIONS(TEST_INSTANCE)

#if MOZ_IS_GCC
#  pragma GCC diagnostic pop
#endif