blob: fe58fef9226abb909420243a4ceb271b765592ba [file] [log] [blame]
/*
* Copyright 2019 Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met: redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer;
* redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution;
* neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __ARCH_ARM_AAPCS64_HH__
#define __ARCH_ARM_AAPCS64_HH__
#include <algorithm>
#include <array>
#include <type_traits>
#include <utility>
#include "arch/arm/regs/int.hh"
#include "arch/arm/utility.hh"
#include "base/intmath.hh"
#include "cpu/thread_context.hh"
#include "sim/guest_abi.hh"
#include "sim/proxy_ptr.hh"
namespace gem5
{
class ThreadContext;
struct Aapcs64
{
using UintPtr = uint64_t;
struct State
{
int ngrn=0; // Next general purpose register number.
int nsrn=0; // Next SIMD and floating point register number.
Addr nsaa; // Next stacked argument address.
// The maximum allowed general purpose register number.
static const int MAX_GRN = 7;
// The maximum allowed SIMD and floating point register number.
static const int MAX_SRN = 7;
explicit State(const ThreadContext *tc) :
nsaa(tc->getReg(ArmISA::int_reg::Spx))
{}
};
};
GEM5_DEPRECATED_NAMESPACE(GuestABI, guest_abi);
namespace guest_abi
{
/*
* Short Vectors
*/
// A short vector is a machine type that is composed of repeated instances of
// one fundamental integral or floating- point type. It may be 8 or 16 bytes
// in total size.
template <typename T, typename Enabled=void>
struct IsAapcs64ShortVector : public std::false_type {};
template <typename E, size_t N>
struct IsAapcs64ShortVector<E[N],
typename std::enable_if_t<
(std::is_integral_v<E> || std::is_floating_point_v<E>) &&
(sizeof(E) * N == 8 || sizeof(E) * N == 16)>> :
public std::true_type
{};
template <typename T>
constexpr bool IsAapcs64ShortVectorV = IsAapcs64ShortVector<T>::value;
/*
* Composite Types
*/
template <typename T, typename Enabled=void>
struct IsAapcs64Composite : public std::false_type {};
template <typename T>
struct IsAapcs64Composite<T, typename std::enable_if_t<
(std::is_array_v<T> || std::is_class_v<T> || std::is_union_v<T>) &&
// VarArgs is technically a composite type, but it's not a normal argument.
!IsVarArgsV<T> &&
// Short vectors are also composite types, but don't treat them as one.
!IsAapcs64ShortVectorV<T>
>> : public std::true_type
{};
template <typename T>
constexpr bool IsAapcs64CompositeV = IsAapcs64Composite<T>::value;
// Homogeneous Aggregates
// These *should* be any aggregate type which has only one type of member, but
// we can't actually detect that or manipulate that with templates. Instead,
// we approximate that by detecting only arrays with that property.
// An Homogeneous Floating-Point Aggregate (HFA) is an Homogeneous Aggregate
// with a Fundemental Data Type that is a Floating-Point type and at most four
// uniquely addressable members.
template <typename T, typename Enabled=void>
struct IsAapcs64Hfa : public std::false_type {};
template <typename E, size_t N>
struct IsAapcs64Hfa<E[N],
typename std::enable_if_t<std::is_floating_point_v<E> && N <= 4>> :
public std::true_type
{};
template <typename T>
constexpr bool IsAapcs64HfaV = IsAapcs64Hfa<T>::value;
// An Homogeneous Short-Vector Aggregate (HVA) is an Homogeneous Aggregate with
// a Fundamental Data Type that is a Short-Vector type and at most four
// uniquely addressable members.
template <typename T, typename Enabled=void>
struct IsAapcs64Hva : public std::false_type {};
template <typename E, size_t N>
struct IsAapcs64Hva<E[N],
typename std::enable_if_t<IsAapcs64ShortVectorV<E> && N <= 4>> :
public std::true_type
{};
template <typename T>
constexpr bool IsAapcs64HvaV = IsAapcs64Hva<T>::value;
// A shorthand to test if a type is an HVA or an HFA.
template <typename T, typename Enabled=void>
struct IsAapcs64Hxa : public std::false_type {};
template <typename T>
struct IsAapcs64Hxa<T, typename std::enable_if_t<
IsAapcs64HfaV<T> || IsAapcs64HvaV<T>>> :
public std::true_type
{};
template <typename T>
constexpr bool IsAapcs64HxaV = IsAapcs64Hxa<T>::value;
struct Aapcs64ArgumentBase
{
template <typename T>
static T
loadFromStack(ThreadContext *tc, Aapcs64::State &state)
{
// The alignment is the larger of 8 or the natural alignment of T.
size_t align = std::max<size_t>(8, alignof(T));
// Increase the size to the next multiple of 8.
size_t size = roundUp(sizeof(T), 8);
// Align the stack.
state.nsaa = roundUp(state.nsaa, align);
// Extract the value from it.
ConstVPtr<T> val(state.nsaa, tc);
// Move the nsaa past this argument.
state.nsaa += size;
// Return the value we extracted.
return gtoh(*val, ArmISA::byteOrder(tc));
}
};
/*
* Floating point and Short-Vector arguments and return values.
*/
template <typename Float>
struct Argument<Aapcs64, Float, typename std::enable_if_t<
std::is_floating_point_v<Float> || IsAapcs64ShortVectorV<Float>>> :
public Aapcs64ArgumentBase
{
static Float
get(ThreadContext *tc, Aapcs64::State &state)
{
if (state.nsrn <= state.MAX_SRN) {
RegId id(VecRegClass, state.nsrn++);
ArmISA::VecRegContainer vc;
tc->getReg(id, &vc);
return vc.as<Float>()[0];
}
return loadFromStack<Float>(tc, state);
}
};
template <typename Float>
struct Result<Aapcs64, Float, typename std::enable_if_t<
std::is_floating_point_v<Float> || IsAapcs64ShortVectorV<Float>>>
{
static void
store(ThreadContext *tc, const Float &f)
{
RegId id(VecRegClass, 0);
ArmISA::VecRegContainer reg;
tc->getReg(id, &reg);
reg.as<Float>()[0] = f;
tc->setReg(id, &reg);
}
};
/*
* Integer arguments and return values.
*/
// This will pick up Addr as well, which should be used for guest pointers.
template <typename Integer>
struct Argument<Aapcs64, Integer, typename std::enable_if_t<
std::is_integral_v<Integer> && (sizeof(Integer) <= 8)>> :
public Aapcs64ArgumentBase
{
static Integer
get(ThreadContext *tc, Aapcs64::State &state)
{
if (state.ngrn <= state.MAX_GRN)
return tc->getReg(RegId(IntRegClass, state.ngrn++));
// Max out ngrn since we've effectively saturated it.
state.ngrn = state.MAX_GRN + 1;
return loadFromStack<Integer>(tc, state);
}
};
template <typename Integer>
struct Argument<Aapcs64, Integer, typename std::enable_if_t<
std::is_integral_v<Integer> && (sizeof(Integer) > 8)>> :
public Aapcs64ArgumentBase
{
static Integer
get(ThreadContext *tc, Aapcs64::State &state)
{
if (alignof(Integer) == 16 && (state.ngrn % 2))
state.ngrn++;
if (sizeof(Integer) == 16 && state.ngrn + 1 <= state.MAX_GRN) {
Integer low = tc->getReg(RegId(IntRegClass, state.ngrn++));
Integer high = tc->getReg(RegId(IntRegClass, state.ngrn++));
high = high << 64;
return high | low;
}
// Max out ngrn since we've effectively saturated it.
state.ngrn = state.MAX_GRN + 1;
return loadFromStack<Integer>(tc, state);
}
};
template <typename Integer>
struct Result<Aapcs64, Integer, typename std::enable_if_t<
std::is_integral_v<Integer> && (sizeof(Integer) <= 8)>>
{
static void
store(ThreadContext *tc, const Integer &i)
{
tc->setReg(ArmISA::int_reg::X0, i);
}
};
template <typename Integer>
struct Result<Aapcs64, Integer, typename std::enable_if_t<
std::is_integral_v<Integer> && (sizeof(Integer) > 8)>>
{
static void
store(ThreadContext *tc, const Integer &i)
{
tc->setReg(ArmISA::int_reg::X0, (uint64_t)i);
tc->setReg(ArmISA::int_reg::X1, (uint64_t)(i >> 64));
}
};
/*
* Homogeneous Floating-Point and Short-Vector Aggregates (HFAs and HVAs)
* argument and return values.
*/
template <typename T>
struct Aapcs64ArrayType { using Type = void; };
template <typename E, size_t N>
struct Aapcs64ArrayType<E[N]> { using Type = E; };
template <typename HA>
struct Argument<Aapcs64, HA, typename std::enable_if_t<
IsAapcs64HxaV<HA>>> : public Aapcs64ArgumentBase
{
static HA
get(ThreadContext *tc, Aapcs64::State &state)
{
using Elem = typename Aapcs64ArrayType<HA>::Type;
constexpr size_t Count = sizeof(HA) / sizeof(Elem);
if (state.nsrn + Count - 1 <= state.MAX_SRN) {
HA ha;
for (int i = 0; i < Count; i++)
ha[i] = Argument<Aapcs64, Elem>::get(tc, state);
return ha;
}
// Max out the nsrn since we effectively exhausted it.
state.nsrn = state.MAX_SRN + 1;
return loadFromStack<HA>(tc, state);
}
};
template <typename HA>
struct Result<Aapcs64, HA, typename std::enable_if_t<IsAapcs64HxaV<HA>>>
{
static HA
store(ThreadContext *tc, const HA &ha)
{
using Elem = typename Aapcs64ArrayType<HA>::Type;
constexpr size_t Count = sizeof(HA) / sizeof(Elem);
for (int i = 0; i < Count; i++)
Result<Aapcs64, Elem>::store(tc, ha[i]);
}
};
/*
* Composite arguments and return values which are not HVAs or HFAs.
*/
template <typename Composite>
struct Argument<Aapcs64, Composite, typename std::enable_if_t<
IsAapcs64CompositeV<Composite> && !IsAapcs64HxaV<Composite>>> :
public Aapcs64ArgumentBase
{
static Composite
get(ThreadContext *tc, Aapcs64::State &state)
{
if (sizeof(Composite) > 16) {
// Composite values larger than 16 which aren't HFAs or HVAs are
// kept in a buffer, and the argument is actually a pointer to that
// buffer.
Addr addr = Argument<Aapcs64, Addr>::get(tc, state);
ConstVPtr<Composite> composite(addr, tc);
return gtoh(*composite, ArmISA::byteOrder(tc));
}
// The size of Composite must be 16 bytes or less after this point.
size_t bytes = sizeof(Composite);
using Chunk = uint64_t;
const int chunk_size = sizeof(Chunk);
const int regs = (bytes + chunk_size - 1) / chunk_size;
// Can it fit in GPRs?
if (state.ngrn + regs - 1 <= state.MAX_GRN) {
alignas(alignof(Composite)) uint8_t buf[bytes];
for (int i = 0; i < regs; i++) {
Chunk val = tc->getReg(RegId(IntRegClass, state.ngrn++));
val = htog(val, ArmISA::byteOrder(tc));
size_t to_copy = std::min<size_t>(bytes, chunk_size);
memcpy(buf + i * chunk_size, &val, to_copy);
bytes -= to_copy;
}
return gtoh(*(Composite *)buf, ArmISA::byteOrder(tc));
}
// Max out the ngrn since we effectively exhausted it.
state.ngrn = state.MAX_GRN;
return loadFromStack<Composite>(tc, state);
}
};
template <typename Composite>
struct Result<Aapcs64, Composite, typename std::enable_if_t<
IsAapcs64CompositeV<Composite> && !IsAapcs64HxaV<Composite>>>
{
static void
store(ThreadContext *tc, const Composite &c)
{
if (sizeof(Composite) > 16) {
Addr addr = tc->getReg(ArmISA::int_reg::X8);
VPtr<Composite> composite(addr, tc);
*composite = htog(c, ArmISA::byteOrder(tc));
return;
}
// The size of Composite must be 16 bytes or less after this point.
size_t bytes = sizeof(Composite);
using Chunk = uint64_t;
const int chunk_size = sizeof(Chunk);
const int regs = (bytes + chunk_size - 1) / chunk_size;
Composite cp = htog(c, ArmISA::byteOrder(tc));
uint8_t *buf = (uint8_t *)&cp;
for (int i = 0; i < regs; i++) {
size_t to_copy = std::min<size_t>(bytes, chunk_size);
Chunk val;
memcpy(&val, buf, to_copy);
val = gtoh(val, ArmISA::byteOrder(tc));
tc->setReg(ArmISA::int_reg::x(i), val);
bytes -= to_copy;
buf += to_copy;
}
}
};
} // namespace guest_abi
} // namespace gem5
#endif // __ARCH_ARM_AAPCS64_HH__