| /* | |
| * 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. | |
| */ | |
| #include <gtest/gtest.h> | |
| #include <type_traits> | |
| #include <utility> | |
| #include "sim/guest_abi.hh" | |
| using namespace gem5; | |
| namespace gem5 | |
| { | |
| // Fake ThreadContext which holds data and captures results. | |
| class ThreadContext | |
| { | |
| public: | |
| static const int ints[]; | |
| static const double floats[]; | |
| static const int DefaultIntResult; | |
| static const double DefaultFloatResult; | |
| int intResult = DefaultIntResult; | |
| double floatResult = DefaultFloatResult; | |
| int intOffset = 0; | |
| }; | |
| const int ThreadContext::ints[] = { | |
| 0, 1, 2, 3, 4, 5, 6, 7 | |
| }; | |
| const double ThreadContext::floats[] = { | |
| 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0 | |
| }; | |
| const int ThreadContext::DefaultIntResult = 0; | |
| const double ThreadContext::DefaultFloatResult = 0.0; | |
| } // namespace gem5 | |
| // ABI anchor for an ABI which has 1D progress. Conceptually, this could be | |
| // because integer and floating point arguments are stored in the same | |
| // registers. | |
| struct TestABI_1D | |
| { | |
| using State = int; | |
| }; | |
| // ABI anchor for an ABI which uses the prepare() hook. | |
| struct TestABI_Prepare | |
| { | |
| using State = int; | |
| }; | |
| // ABI anchor for an ABI which has 2D progress. Conceptually, this could be | |
| // because integer and floating point arguments are stored in separate | |
| // registers. | |
| struct TestABI_2D | |
| { | |
| using State = std::pair<int, int>; | |
| }; | |
| struct TestABI_TcInit | |
| { | |
| struct State | |
| { | |
| int pos; | |
| State(const ThreadContext *tc) : pos(tc->intOffset) {} | |
| }; | |
| }; | |
| namespace gem5 | |
| { | |
| namespace guest_abi | |
| { | |
| // Hooks for the 1D ABI arguments and return value. Add 1 or 1.0 to return | |
| // values so we can tell they went through the right set of hooks. | |
| template <> | |
| struct Argument<TestABI_1D, int> | |
| { | |
| static int | |
| get(ThreadContext *tc, TestABI_1D::State &state) | |
| { | |
| return tc->ints[state++]; | |
| } | |
| }; | |
| template <typename Arg> | |
| struct Argument<TestABI_1D, Arg, | |
| typename std::enable_if_t<std::is_floating_point_v<Arg>>> | |
| { | |
| static Arg | |
| get(ThreadContext *tc, TestABI_1D::State &state) | |
| { | |
| return tc->floats[state++]; | |
| } | |
| }; | |
| template <> | |
| struct Result<TestABI_1D, int> | |
| { | |
| static void | |
| store(ThreadContext *tc, const int &ret) | |
| { | |
| tc->intResult = ret + 1; | |
| } | |
| }; | |
| template <typename Ret> | |
| struct Result<TestABI_1D, Ret, | |
| typename std::enable_if_t<std::is_floating_point_v<Ret>>> | |
| { | |
| static void | |
| store(ThreadContext *tc, const Ret &ret) | |
| { | |
| tc->floatResult = ret + 1.0; | |
| } | |
| }; | |
| // Hooks for the ABI which uses prepare(). It uses the same rules as the | |
| // 1D ABI for arguments, but allocates space for and discards return values | |
| // and returns integer arguments in reverse order. | |
| template <> | |
| struct Argument<TestABI_Prepare, int> | |
| { | |
| static int | |
| get(ThreadContext *tc, TestABI_Prepare::State &state) | |
| { | |
| return tc->ints[--state]; | |
| } | |
| static void | |
| prepare(ThreadContext *tc, TestABI_Prepare::State &state) | |
| { | |
| state++; | |
| } | |
| }; | |
| template <typename Ret> | |
| struct Result<TestABI_Prepare, Ret> | |
| { | |
| static void store(ThreadContext *tc, const Ret &ret) {} | |
| static void | |
| prepare(ThreadContext *tc, TestABI_Prepare::State &state) | |
| { | |
| state++; | |
| } | |
| }; | |
| // Hooks for the 2D ABI arguments and return value. Add 2 or 2.0 to return | |
| // values so we can tell they went through the right set of hooks. | |
| template <> | |
| struct Argument<TestABI_2D, int> | |
| { | |
| static int | |
| get(ThreadContext *tc, TestABI_2D::State &state) | |
| { | |
| return tc->ints[state.first++]; | |
| } | |
| }; | |
| template <typename Arg> | |
| struct Argument<TestABI_2D, Arg, | |
| typename std::enable_if_t<std::is_floating_point_v<Arg>>> | |
| { | |
| static Arg | |
| get(ThreadContext *tc, TestABI_2D::State &state) | |
| { | |
| return tc->floats[state.second++]; | |
| } | |
| }; | |
| template <> | |
| struct Result<TestABI_2D, int> | |
| { | |
| static void | |
| store(ThreadContext *tc, const int &ret) | |
| { | |
| tc->intResult = ret + 2; | |
| } | |
| }; | |
| template <typename Ret> | |
| struct Result<TestABI_2D, Ret, | |
| typename std::enable_if_t<std::is_floating_point_v<Ret>>> | |
| { | |
| static void | |
| store(ThreadContext *tc, const Ret &ret) | |
| { | |
| tc->floatResult = ret + 2.0; | |
| } | |
| }; | |
| // Hooks for the TcInit ABI arguments. | |
| template <> | |
| struct Argument<TestABI_TcInit, int> | |
| { | |
| static int | |
| get(ThreadContext *tc, TestABI_TcInit::State &state) | |
| { | |
| return tc->ints[state.pos++]; | |
| } | |
| }; | |
| } // namespace guest_abi | |
| } // namespace gem5 | |
| // Test function which verifies that its arguments reflect the 1D ABI and | |
| // which doesn't return anything. | |
| void | |
| testIntVoid(ThreadContext *tc, int a, float b, int c, double d, | |
| guest_abi::VarArgs<int,float,double> varargs) | |
| { | |
| EXPECT_EQ(a, tc->ints[0]); | |
| EXPECT_EQ(b, tc->floats[1]); | |
| EXPECT_EQ(c, tc->ints[2]); | |
| EXPECT_EQ(d, tc->floats[3]); | |
| EXPECT_EQ(varargs.get<int>(), tc->ints[4]); | |
| EXPECT_EQ(varargs.get<float>(), tc->floats[5]); | |
| EXPECT_EQ(varargs.get<double>(), tc->floats[6]); | |
| } | |
| // Test functions which verify that the return allocating ABI allocates space | |
| // for its return value successfully. | |
| void | |
| testPrepareVoid(ThreadContext *tc, int a, int b) | |
| { | |
| EXPECT_EQ(a, tc->ints[1]); | |
| EXPECT_EQ(b, tc->ints[0]); | |
| } | |
| int | |
| testPrepareInt(ThreadContext *tc, int a, int b) | |
| { | |
| EXPECT_EQ(a, tc->ints[2]); | |
| EXPECT_EQ(b, tc->ints[1]); | |
| return 0; | |
| } | |
| // Test function which verifies that its arguments reflect the 2D ABI and | |
| // which doesn't return anything. | |
| void | |
| test2DVoid(ThreadContext *tc, int a, float b, int c, double d, | |
| guest_abi::VarArgs<int,float,double> varargs) | |
| { | |
| EXPECT_EQ(a, tc->ints[0]); | |
| EXPECT_EQ(b, tc->floats[0]); | |
| EXPECT_EQ(c, tc->ints[1]); | |
| EXPECT_EQ(d, tc->floats[1]); | |
| EXPECT_EQ(varargs.get<int>(), tc->ints[2]); | |
| EXPECT_EQ(varargs.get<float>(), tc->floats[2]); | |
| EXPECT_EQ(varargs.get<double>(), tc->floats[3]); | |
| } | |
| void | |
| testTcInit(ThreadContext *tc, int a) | |
| { | |
| EXPECT_EQ(tc->intOffset, 2); | |
| EXPECT_EQ(a, tc->ints[2]); | |
| } | |
| // Test functions which returns various types of values. | |
| const int IntRetValue = 50; | |
| const float FloatRetValue = 3.14; | |
| const double DoubleRetValue = 12.34; | |
| int testIntRet(ThreadContext *tc) { return IntRetValue; } | |
| float testFloatRet(ThreadContext *tc) { return FloatRetValue; } | |
| double testDoubleRet(ThreadContext *tc) { return DoubleRetValue; } | |
| // The actual test bodies. | |
| TEST(GuestABITest, ABI_1D_args) | |
| { | |
| ThreadContext tc; | |
| invokeSimcall<TestABI_1D>(&tc, testIntVoid); | |
| EXPECT_EQ(tc.intResult, tc.DefaultIntResult); | |
| EXPECT_EQ(tc.floatResult, tc.DefaultFloatResult); | |
| } | |
| TEST(GuestABITest, ABI_Prepare) | |
| { | |
| ThreadContext tc; | |
| invokeSimcall<TestABI_Prepare>(&tc, testPrepareVoid); | |
| invokeSimcall<TestABI_Prepare>(&tc, testPrepareInt); | |
| } | |
| TEST(GuestABITest, ABI_2D_args) | |
| { | |
| ThreadContext tc; | |
| invokeSimcall<TestABI_2D>(&tc, test2DVoid); | |
| EXPECT_EQ(tc.intResult, tc.DefaultIntResult); | |
| EXPECT_EQ(tc.floatResult, tc.DefaultFloatResult); | |
| } | |
| TEST(GuestABITest, ABI_TC_init) | |
| { | |
| ThreadContext tc; | |
| tc.intOffset = 2; | |
| invokeSimcall<TestABI_TcInit>(&tc, testTcInit); | |
| } | |
| TEST(GuestABITest, ABI_returns) | |
| { | |
| // 1D returns. | |
| { | |
| ThreadContext tc; | |
| int ret = invokeSimcall<TestABI_1D>(&tc, testIntRet); | |
| EXPECT_EQ(ret, IntRetValue); | |
| EXPECT_EQ(tc.intResult, IntRetValue + 1); | |
| EXPECT_EQ(tc.floatResult, tc.DefaultFloatResult); | |
| } | |
| { | |
| ThreadContext tc; | |
| float ret = invokeSimcall<TestABI_1D>(&tc, testFloatRet); | |
| EXPECT_EQ(ret, FloatRetValue); | |
| EXPECT_EQ(tc.intResult, tc.DefaultIntResult); | |
| EXPECT_EQ(tc.floatResult, FloatRetValue + 1.0); | |
| } | |
| { | |
| ThreadContext tc; | |
| double ret = invokeSimcall<TestABI_1D>(&tc, testDoubleRet); | |
| EXPECT_EQ(ret, DoubleRetValue); | |
| EXPECT_EQ(tc.intResult, tc.DefaultIntResult); | |
| EXPECT_EQ(tc.floatResult, DoubleRetValue + 1.0); | |
| } | |
| { | |
| // Disable storing the return value in the ThreadContext. | |
| ThreadContext tc; | |
| int ret = invokeSimcall<TestABI_1D, false>(&tc, testIntRet); | |
| EXPECT_EQ(ret, IntRetValue); | |
| EXPECT_EQ(tc.intResult, tc.DefaultIntResult); | |
| EXPECT_EQ(tc.floatResult, tc.DefaultFloatResult); | |
| } | |
| // 2D returns. | |
| { | |
| ThreadContext tc; | |
| int ret = invokeSimcall<TestABI_2D>(&tc, testIntRet); | |
| EXPECT_EQ(ret, IntRetValue); | |
| EXPECT_EQ(tc.intResult, IntRetValue + 2); | |
| EXPECT_EQ(tc.floatResult, tc.DefaultFloatResult); | |
| } | |
| { | |
| ThreadContext tc; | |
| float ret = invokeSimcall<TestABI_2D>(&tc, testFloatRet); | |
| EXPECT_EQ(ret, FloatRetValue); | |
| EXPECT_EQ(tc.intResult, tc.DefaultIntResult); | |
| EXPECT_EQ(tc.floatResult, FloatRetValue + 2.0); | |
| } | |
| { | |
| ThreadContext tc; | |
| double ret = invokeSimcall<TestABI_2D>(&tc, testDoubleRet); | |
| EXPECT_EQ(ret, DoubleRetValue); | |
| EXPECT_EQ(tc.intResult, tc.DefaultIntResult); | |
| EXPECT_EQ(tc.floatResult, DoubleRetValue + 2.0); | |
| } | |
| } | |
| TEST(GuestABITest, dumpSimcall) | |
| { | |
| ThreadContext tc; | |
| std::string dump = dumpSimcall<TestABI_1D>("test", &tc, testIntVoid); | |
| EXPECT_EQ(dump, "test(0, 11, 2, 13, ...)"); | |
| } | |
| TEST(GuestABITest, isVarArgs) | |
| { | |
| EXPECT_TRUE(guest_abi::IsVarArgsV<guest_abi::VarArgs<int>>); | |
| EXPECT_FALSE(guest_abi::IsVarArgsV<int>); | |
| EXPECT_FALSE(guest_abi::IsVarArgsV<double>); | |
| struct FooStruct {}; | |
| EXPECT_FALSE(guest_abi::IsVarArgsV<FooStruct>); | |
| union FooUnion {}; | |
| EXPECT_FALSE(guest_abi::IsVarArgsV<FooUnion>); | |
| } |