diff options
Diffstat (limited to 'js/src/jsapi-tests/testJitABIcalls.cpp')
-rw-r--r-- | js/src/jsapi-tests/testJitABIcalls.cpp | 718 |
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 |