summaryrefslogtreecommitdiffstats
path: root/js/src/jsapi-tests/testJitABIcalls.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/jsapi-tests/testJitABIcalls.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jsapi-tests/testJitABIcalls.cpp')
-rw-r--r--js/src/jsapi-tests/testJitABIcalls.cpp718
1 files changed, 718 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/testJitABIcalls.cpp b/js/src/jsapi-tests/testJitABIcalls.cpp
new file mode 100644
index 0000000000..88f5e31134
--- /dev/null
+++ b/js/src/jsapi-tests/testJitABIcalls.cpp
@@ -0,0 +1,718 @@
+/* -*- 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, TAIL_CALL_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) (#Fun, decltype(&::Fun))
+#define TC_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) \
+ TAIL_CALL_VMFUNCTION_LIST(PREFIX##_TC_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 MoveOp type used by passABIArg to know how to
+// interpret the value which are given as arguments.
+template <typename Type>
+constexpr MoveOp::Type TypeToMoveOp() {
+ if constexpr (std::is_same_v<Type, float>) {
+ return MoveOp::FLOAT32;
+ } else if constexpr (std::is_same_v<Type, double>) {
+ return MoveOp::DOUBLE;
+ } else {
+ return MoveOp::GENERAL;
+ }
+}
+
+// Generate a sequence which contains the associated MoveOp 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 <MoveOp::Type... Val>
+class MoveOpSequence {};
+
+template <typename... Args>
+using ArgsMoveOps_t = MoveOpSequence<TypeToMoveOp<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 MoveOp types of
+// arguments are given as the fourth argument and are expected to be generated
+// by `ArgsMoveOp`.
+template <uint64_t... Off, MoveOp::Type... Move>
+static void passABIArgs(MacroAssembler& masm, Register base,
+ std::integer_sequence<uint64_t, Off...>,
+ MoveOpSequence<Move...>) {
+ (masm.passABIArg(MoveOperand(base, size_t(Off)), Move), ...);
+}
+
+// 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 MoveOps = ArgsMoveOps_t<Args...>;
+ passABIArgs(masm, base, Offsets(), MoveOps());
+ masm.callWithABI(DynFn{JS_FUNC_TO_DATA_PTR(void*, test.fun)},
+ TypeToMoveOp<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__))
+#define TEST_INSTANCE_TC_VMFUN_TO_ALLFUN(...) \
+ APPLY(TEST_INSTANCE, TC_VMFUN_TO_ALLFUN(__VA_ARGS__))
+
+ALL_FUNCTIONS(TEST_INSTANCE)
+
+#if MOZ_IS_GCC
+# pragma GCC diagnostic pop
+#endif