* of this method which actually does something and is public.
*/
static void store(ThreadContext *tc, const Ret &ret);
+
+ /*
+ * Adjust the position of arguments based on the return type, if necessary.
+ *
+ * This method can be excluded if no adjustment is necessary.
+ */
+ static void allocate(ThreadContext *tc, typename ABI::Position &position);
};
+/*
+ * This partial specialization prevents having to special case 'void' when
+ * working with return types.
+ */
+template <typename ABI>
+struct Result<ABI, void>
+{};
+
template <typename ABI, typename Arg, typename Enabled=void>
struct Argument
{
};
+/*
+ * This struct template provides a default allocate() method in case the
+ * Result template doesn't provide one. This is the default in cases where the
+ * return type doesn't affect how arguments are laid out.
+ */
+template <typename ABI, typename Ret, typename Enabled=void>
+struct ResultAllocator
+{
+ static void
+ allocate(ThreadContext *tc, typename ABI::Position &position)
+ {}
+};
+
+/*
+ * If the return type *does* affect how the arguments are laid out, the ABI
+ * can implement an allocate() method for the various return types, and this
+ * specialization will call into it.
+ */
+template <typename ABI, typename Ret>
+struct ResultAllocator<ABI, Ret, decltype((void)&Result<ABI, Ret>::allocate)>
+{
+ static void
+ allocate(ThreadContext *tc, typename ABI::Position &position)
+ {
+ Result<ABI, Ret>::allocate(tc, position);
+ }
+};
+
+
/*
* These templates implement a variadic argument mechanism for guest ABI
* functions. A function might be written like this:
// Default construct a Position to track consumed resources. Built in
// types will be zero initialized.
auto position = typename ABI::Position();
+ GuestABI::ResultAllocator<ABI, Ret>::allocate(tc, position);
return GuestABI::callFrom<ABI, Ret, Args...>(tc, position, target);
}
auto position = typename ABI::Position();
std::ostringstream ss;
+ GuestABI::ResultAllocator<ABI, Ret>::allocate(tc, position);
ss << name;
GuestABI::dumpArgsFrom<ABI, Ret, Args...>(0, ss, tc, position);
return ss.str();
using Position = int;
};
+// ABI anchor for an ABI which allocates a register for non-void return types.
+struct TestABI_RetReg
+{
+ using Position = 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.
}
};
+// Hooks for the return value allocating ABI. It uses the same rules as the
+// 1D ABI for arguments, but allocates space for and discards return values.
+template <typename Arg>
+struct Argument<TestABI_RetReg, Arg> : public Argument<TestABI_1D, Arg> {};
+
+template <typename Ret>
+struct Result<TestABI_RetReg, Ret>
+{
+ static void store(ThreadContext *tc, const Ret &ret) {}
+ static void
+ allocate(ThreadContext *tc, TestABI_RetReg::Position &position)
+ {
+ position++;
+ }
+};
+
// 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.
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
+testRetRegVoid(ThreadContext *tc, int a)
+{
+ EXPECT_EQ(a, tc->ints[0]);
+}
+
+int
+testRetRegInt(ThreadContext *tc, int a)
+{
+ EXPECT_EQ(a, tc->ints[1]);
+ return 0;
+}
+
// Test function which verifies that its arguments reflect the 2D ABI and
// which doesn't return anything.
void
EXPECT_EQ(tc.floatResult, tc.DefaultFloatResult);
}
+TEST(GuestABI, ABI_RetReg)
+{
+ ThreadContext tc;
+ invokeSimcall<TestABI_RetReg>(&tc, testRetRegVoid);
+ invokeSimcall<TestABI_RetReg>(&tc, testRetRegInt);
+}
+
TEST(GuestABI, ABI_2D_args)
{
ThreadContext tc;