sim: Add a ProxyPtr test.
Change-Id: If71cc374030a5ef0dab62d351bc83960ff509af7 Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/29401 Reviewed-by: Matthew Poremba <matthew.poremba@amd.com> Maintainer: Gabe Black <gabeblack@google.com> Tested-by: kokoro <noreply+kokoro@google.com>
This commit is contained in:
@@ -82,6 +82,7 @@ Source('power_domain.cc')
|
||||
|
||||
GTest('byteswap.test', 'byteswap.test.cc', '../base/types.cc')
|
||||
GTest('guest_abi.test', 'guest_abi.test.cc')
|
||||
GTest('proxy_ptr.test', 'proxy_ptr.test.cc')
|
||||
|
||||
if env['TARGET_ISA'] != 'null':
|
||||
SimObject('InstTracer.py')
|
||||
|
||||
519
src/sim/proxy_ptr.test.cc
Normal file
519
src/sim/proxy_ptr.test.cc
Normal file
@@ -0,0 +1,519 @@
|
||||
/*
|
||||
* Copyright 2020 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 <vector>
|
||||
|
||||
#include "sim/proxy_ptr.hh"
|
||||
|
||||
struct Access
|
||||
{
|
||||
bool read;
|
||||
Addr addr;
|
||||
Addr size;
|
||||
|
||||
Access(bool _read, Addr _addr, Addr _size) :
|
||||
read(_read), addr(_addr), size(_size)
|
||||
{}
|
||||
|
||||
bool
|
||||
operator == (const Access &other) const
|
||||
{
|
||||
return read == other.read &&
|
||||
addr == other.addr &&
|
||||
size == other.size;
|
||||
}
|
||||
|
||||
bool
|
||||
operator != (const Access &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
using Accesses = std::vector<Access>;
|
||||
|
||||
class BackingStore
|
||||
{
|
||||
public:
|
||||
std::vector<uint8_t> store;
|
||||
Addr base;
|
||||
|
||||
BackingStore(Addr _base, size_t _size) : store(_size, 0), base(_base) {}
|
||||
|
||||
void
|
||||
rangeCheck(Addr addr, Addr size)
|
||||
{
|
||||
panic_if(addr < base || addr + size > base + store.size(),
|
||||
"Range [%#x,%#x) outside of [%#x,%#x).",
|
||||
addr, addr + size, base, base + store.size());
|
||||
}
|
||||
|
||||
mutable Accesses accesses;
|
||||
|
||||
::testing::AssertionResult
|
||||
expect_access(size_t idx, const Access &other) const
|
||||
{
|
||||
if (idx >= accesses.size()) {
|
||||
return ::testing::AssertionFailure() << "index " << idx <<
|
||||
" out of bounds";
|
||||
}
|
||||
|
||||
if (accesses[idx] != other) {
|
||||
return ::testing::AssertionFailure() << "access[" << idx <<
|
||||
"] was " << accesses[idx] << ", expected " << other;
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
::testing::AssertionResult
|
||||
expect_accesses(Accesses expected) const
|
||||
{
|
||||
if (accesses.size() != expected.size()) {
|
||||
return ::testing::AssertionFailure() <<
|
||||
"Wrong number of accesses, was " << accesses.size() <<
|
||||
" expected " << expected.size();
|
||||
}
|
||||
|
||||
auto failure = ::testing::AssertionFailure();
|
||||
bool success = true;
|
||||
if (accesses.size() == expected.size()) {
|
||||
for (size_t idx = 0; idx < expected.size(); idx++) {
|
||||
auto result = expect_access(idx, expected[idx]);
|
||||
if (!result) {
|
||||
failure << result.message();
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
return failure;
|
||||
else
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
void
|
||||
writeBlob(Addr ptr, const void *data, int size)
|
||||
{
|
||||
rangeCheck(ptr, size);
|
||||
accesses.emplace_back(false, ptr, size);
|
||||
memcpy(store.data() + (ptr - base), data, size);
|
||||
}
|
||||
|
||||
void
|
||||
readBlob(Addr ptr, void *data, int size)
|
||||
{
|
||||
rangeCheck(ptr, size);
|
||||
accesses.emplace_back(true, ptr, size);
|
||||
memcpy(data, store.data() + (ptr - base), size);
|
||||
}
|
||||
};
|
||||
|
||||
::testing::AssertionResult
|
||||
accessed(const char *expr1, const char *expr2,
|
||||
const BackingStore &store, const Accesses &expected)
|
||||
{
|
||||
return store.expect_accesses(expected);
|
||||
}
|
||||
|
||||
#define EXPECT_ACCESSES(store, ...) \
|
||||
do { \
|
||||
Accesses expected({__VA_ARGS__}); \
|
||||
EXPECT_PRED_FORMAT2(accessed, store, expected); \
|
||||
store.accesses.clear(); \
|
||||
} while (false)
|
||||
|
||||
std::ostream &
|
||||
operator << (std::ostream &os, const Access &access)
|
||||
{
|
||||
ccprintf(os, "%s(%#x, %d)", access.read ? "read" : "write",
|
||||
access.addr, access.size);
|
||||
return os;
|
||||
}
|
||||
|
||||
class TestProxy
|
||||
{
|
||||
public:
|
||||
BackingStore &store;
|
||||
|
||||
TestProxy(BackingStore &_store) : store(_store) {}
|
||||
// Sneaky constructor for testing GuestABI integration.
|
||||
TestProxy(ThreadContext *tc) : store(*(BackingStore *)tc) {}
|
||||
|
||||
void
|
||||
writeBlob(Addr ptr, const void *data, int size)
|
||||
{
|
||||
store.writeBlob(ptr, data, size);
|
||||
}
|
||||
|
||||
void
|
||||
readBlob(Addr ptr, void *data, int size)
|
||||
{
|
||||
store.readBlob(ptr, data, size);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using TestPtr = ProxyPtr<T, TestProxy>;
|
||||
|
||||
template <typename T>
|
||||
using ConstTestPtr = ConstProxyPtr<T, TestProxy>;
|
||||
|
||||
TEST(ProxyPtr, Clean)
|
||||
{
|
||||
BackingStore store(0x1000, 0x1000);
|
||||
|
||||
EXPECT_ACCESSES(store);
|
||||
|
||||
{
|
||||
ConstTestPtr<uint32_t> test_ptr(0x1100, store);
|
||||
|
||||
EXPECT_ACCESSES(store, { true, test_ptr.addr(), sizeof(uint32_t) });
|
||||
}
|
||||
|
||||
EXPECT_ACCESSES(store);
|
||||
|
||||
{
|
||||
TestPtr<uint32_t> test_ptr(0x1100, store);
|
||||
|
||||
EXPECT_ACCESSES(store, { true, test_ptr.addr(), sizeof(uint32_t) });
|
||||
}
|
||||
|
||||
EXPECT_ACCESSES(store);
|
||||
}
|
||||
|
||||
TEST(ProxyPtr, Dirty)
|
||||
{
|
||||
BackingStore store(0x1000, 0x1100);
|
||||
|
||||
EXPECT_ACCESSES(store);
|
||||
|
||||
{
|
||||
TestPtr<uint32_t> test_ptr(0x1100, store);
|
||||
|
||||
*test_ptr = 0xa5a5a5a5;
|
||||
|
||||
EXPECT_ACCESSES(store, { true, test_ptr.addr(), sizeof(uint32_t) });
|
||||
}
|
||||
|
||||
EXPECT_ACCESSES(store, { false, 0x1100, sizeof(uint32_t) });
|
||||
EXPECT_EQ(store.store[0x100], 0xa5);
|
||||
EXPECT_EQ(store.store[0x101], 0xa5);
|
||||
EXPECT_EQ(store.store[0x102], 0xa5);
|
||||
EXPECT_EQ(store.store[0x103], 0xa5);
|
||||
}
|
||||
|
||||
|
||||
TEST(ProxyPtr, LoadAndFlush)
|
||||
{
|
||||
BackingStore store(0x1000, 0x1100);
|
||||
|
||||
store.store[0x100] = 0xa5;
|
||||
store.store[0x101] = 0xa5;
|
||||
store.store[0x102] = 0xa5;
|
||||
store.store[0x103] = 0xa5;
|
||||
|
||||
TestPtr<uint32_t> test_ptr(0x1100, store);
|
||||
|
||||
// Check that the backing store is unmodified.
|
||||
EXPECT_EQ(store.store[0x100], 0xa5);
|
||||
EXPECT_EQ(store.store[0x101], 0xa5);
|
||||
EXPECT_EQ(store.store[0x102], 0xa5);
|
||||
EXPECT_EQ(store.store[0x103], 0xa5);
|
||||
|
||||
// Change the value in our local buffered copy.
|
||||
*test_ptr = 0x5a5a5a5a;
|
||||
|
||||
// Verify that the backing store hasn't been changed.
|
||||
EXPECT_EQ(store.store[0x100], 0xa5);
|
||||
EXPECT_EQ(store.store[0x101], 0xa5);
|
||||
EXPECT_EQ(store.store[0x102], 0xa5);
|
||||
EXPECT_EQ(store.store[0x103], 0xa5);
|
||||
|
||||
// Flush out our modifications.
|
||||
test_ptr.flush();
|
||||
|
||||
// Verify that they've been written back to the store.
|
||||
EXPECT_EQ(store.store[0x100], 0x5a);
|
||||
EXPECT_EQ(store.store[0x101], 0x5a);
|
||||
EXPECT_EQ(store.store[0x102], 0x5a);
|
||||
EXPECT_EQ(store.store[0x103], 0x5a);
|
||||
|
||||
// Update the store and try to flush again.
|
||||
store.store[0x100] = 0xaa;
|
||||
test_ptr.flush();
|
||||
|
||||
// Verify that no flush happened, since our ptr was "clean".
|
||||
EXPECT_EQ(store.store[0x100], 0xaa);
|
||||
|
||||
// Force a flush.
|
||||
test_ptr.flush(true);
|
||||
|
||||
// Verify that the flush happened even though the ptr was "clean".
|
||||
EXPECT_EQ(store.store[0x100], 0x5a);
|
||||
|
||||
// Update the store.
|
||||
store.store[0x100] = 0xa5;
|
||||
store.store[0x101] = 0xa5;
|
||||
store.store[0x102] = 0xa5;
|
||||
store.store[0x103] = 0xa5;
|
||||
|
||||
// Verify that our local copy hasn't changed.
|
||||
EXPECT_EQ(*(const uint32_t *)test_ptr, 0x5a5a5a5a);
|
||||
|
||||
// Reload the pointer from the store.
|
||||
test_ptr.load();
|
||||
EXPECT_EQ(*(const uint32_t *)test_ptr, 0xa5a5a5a5);
|
||||
}
|
||||
|
||||
TEST(ProxyPtr, ConstOperators)
|
||||
{
|
||||
bool is_same;
|
||||
|
||||
BackingStore store(0x1000, 0x1000);
|
||||
|
||||
const Addr addr1 = 0x1100;
|
||||
const Addr addr2 = 0x1200;
|
||||
|
||||
using PtrType = uint32_t;
|
||||
|
||||
ConstTestPtr<PtrType> test_ptr1(addr1, store);
|
||||
EXPECT_EQ(test_ptr1.addr(), addr1);
|
||||
|
||||
ConstTestPtr<PtrType> test_ptr2(addr2, store);
|
||||
EXPECT_EQ(test_ptr2.addr(), addr2);
|
||||
|
||||
// Pointer +/- integer.
|
||||
auto next_ptr = test_ptr1 + 2;
|
||||
EXPECT_EQ(next_ptr.addr(), addr1 + 2 * sizeof(PtrType));
|
||||
|
||||
auto reverse_next_ptr = 2 + test_ptr1;
|
||||
EXPECT_EQ(reverse_next_ptr.addr(), addr1 + 2 * sizeof(PtrType));
|
||||
|
||||
auto prev_ptr = test_ptr1 - 2;
|
||||
EXPECT_EQ(prev_ptr.addr(), addr1 - 2 * sizeof(PtrType));
|
||||
|
||||
// Pointer-pointer subtraction.
|
||||
auto diff = test_ptr2 - test_ptr1;
|
||||
EXPECT_EQ(diff, (addr2 - addr1) / sizeof(PtrType));
|
||||
|
||||
// Assignment.
|
||||
ConstTestPtr<PtrType> target(addr2, store);
|
||||
EXPECT_EQ(target.addr(), addr2);
|
||||
|
||||
target = test_ptr1;
|
||||
EXPECT_EQ(target.addr(), addr1);
|
||||
|
||||
// Conversions.
|
||||
EXPECT_TRUE(test_ptr1);
|
||||
ConstTestPtr<PtrType> null(0, store);
|
||||
EXPECT_FALSE(null);
|
||||
|
||||
EXPECT_NE((const PtrType *)test_ptr1, nullptr);
|
||||
EXPECT_EQ((const PtrType *)null, nullptr);
|
||||
|
||||
// Dereferences.
|
||||
is_same = std::is_same<decltype(*test_ptr1), const PtrType &>::value;
|
||||
EXPECT_TRUE(is_same);
|
||||
|
||||
store.store[0x100] = 0x55;
|
||||
store.store[0x101] = 0x55;
|
||||
store.store[0x102] = 0x55;
|
||||
store.store[0x103] = 0x55;
|
||||
|
||||
// Force an update since we changed the backing store behind our ptrs back.
|
||||
test_ptr1.load();
|
||||
|
||||
EXPECT_EQ(*test_ptr1, 0x55555555);
|
||||
|
||||
store.store[0x100] = 0x11;
|
||||
store.store[0x101] = 0x22;
|
||||
store.store[0x102] = 0x33;
|
||||
store.store[0x103] = 0x44;
|
||||
|
||||
struct TestStruct
|
||||
{
|
||||
uint8_t a;
|
||||
uint8_t b;
|
||||
uint8_t c;
|
||||
uint8_t d;
|
||||
};
|
||||
|
||||
ConstTestPtr<TestStruct> struct_ptr(addr1, store);
|
||||
EXPECT_EQ(struct_ptr->a, 0x11);
|
||||
EXPECT_EQ(struct_ptr->b, 0x22);
|
||||
EXPECT_EQ(struct_ptr->c, 0x33);
|
||||
EXPECT_EQ(struct_ptr->d, 0x44);
|
||||
|
||||
is_same = std::is_same<decltype((struct_ptr->a)), const uint8_t &>::value;
|
||||
EXPECT_TRUE(is_same);
|
||||
}
|
||||
|
||||
TEST(ProxyPtr, NonConstOperators)
|
||||
{
|
||||
bool is_same;
|
||||
|
||||
BackingStore store(0x1000, 0x1000);
|
||||
|
||||
const Addr addr1 = 0x1100;
|
||||
const Addr addr2 = 0x1200;
|
||||
|
||||
using PtrType = uint32_t;
|
||||
|
||||
TestPtr<PtrType> test_ptr1(addr1, store);
|
||||
EXPECT_EQ(test_ptr1.addr(), addr1);
|
||||
|
||||
TestPtr<PtrType> test_ptr2(addr2, store);
|
||||
EXPECT_EQ(test_ptr2.addr(), addr2);
|
||||
|
||||
// Pointer +/- integer.
|
||||
auto next_ptr = test_ptr1 + 2;
|
||||
EXPECT_EQ(next_ptr.addr(), addr1 + 2 * sizeof(PtrType));
|
||||
|
||||
auto reverse_next_ptr = 2 + test_ptr1;
|
||||
EXPECT_EQ(reverse_next_ptr.addr(), addr1 + 2 * sizeof(PtrType));
|
||||
|
||||
auto prev_ptr = test_ptr1 - 2;
|
||||
EXPECT_EQ(prev_ptr.addr(), addr1 - 2 * sizeof(PtrType));
|
||||
|
||||
// Pointer-pointer subtraction.
|
||||
auto diff = test_ptr2 - test_ptr1;
|
||||
EXPECT_EQ(diff, (addr2 - addr1) / sizeof(PtrType));
|
||||
|
||||
// Assignment.
|
||||
TestPtr<PtrType> target(addr2, store);
|
||||
EXPECT_EQ(target.addr(), addr2);
|
||||
|
||||
target = test_ptr1;
|
||||
EXPECT_EQ(target.addr(), addr1);
|
||||
|
||||
// Conversions.
|
||||
EXPECT_TRUE(test_ptr1);
|
||||
TestPtr<PtrType> null(0, store);
|
||||
EXPECT_FALSE(null);
|
||||
|
||||
EXPECT_NE((PtrType *)test_ptr1, nullptr);
|
||||
EXPECT_EQ((PtrType *)null, nullptr);
|
||||
EXPECT_NE((const PtrType *)test_ptr1, nullptr);
|
||||
EXPECT_EQ((const PtrType *)null, nullptr);
|
||||
|
||||
// Dereferences.
|
||||
is_same = std::is_same<decltype(*test_ptr1), PtrType &>::value;
|
||||
EXPECT_TRUE(is_same);
|
||||
|
||||
// Flush test_ptr1, which has been conservatively marked as dirty.
|
||||
test_ptr1.flush();
|
||||
|
||||
store.store[0x100] = 0x55;
|
||||
store.store[0x101] = 0x55;
|
||||
store.store[0x102] = 0x55;
|
||||
store.store[0x103] = 0x55;
|
||||
|
||||
// Force an update since we changed the backing store behind our ptrs back.
|
||||
test_ptr1.load();
|
||||
|
||||
EXPECT_EQ(*test_ptr1, 0x55555555);
|
||||
|
||||
store.store[0x100] = 0x11;
|
||||
store.store[0x101] = 0x22;
|
||||
store.store[0x102] = 0x33;
|
||||
store.store[0x103] = 0x44;
|
||||
|
||||
struct TestStruct
|
||||
{
|
||||
uint8_t a;
|
||||
uint8_t b;
|
||||
uint8_t c;
|
||||
uint8_t d;
|
||||
};
|
||||
|
||||
TestPtr<TestStruct> struct_ptr(addr1, store);
|
||||
EXPECT_EQ(struct_ptr->a, 0x11);
|
||||
EXPECT_EQ(struct_ptr->b, 0x22);
|
||||
EXPECT_EQ(struct_ptr->c, 0x33);
|
||||
EXPECT_EQ(struct_ptr->d, 0x44);
|
||||
|
||||
is_same = std::is_same<decltype((struct_ptr->a)), uint8_t &>::value;
|
||||
EXPECT_TRUE(is_same);
|
||||
}
|
||||
|
||||
struct TestABI
|
||||
{
|
||||
using State = int;
|
||||
};
|
||||
|
||||
namespace GuestABI
|
||||
{
|
||||
|
||||
template <>
|
||||
struct Argument<TestABI, Addr>
|
||||
{
|
||||
static Addr
|
||||
get(ThreadContext *tc, typename TestABI::State &state)
|
||||
{
|
||||
return 0x1000;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
bool abiCalled = false;
|
||||
bool abiCalledConst = false;
|
||||
|
||||
void
|
||||
abiTestFunc(ThreadContext *tc, TestPtr<uint8_t> ptr)
|
||||
{
|
||||
abiCalled = true;
|
||||
EXPECT_EQ(ptr.addr(), 0x1000);
|
||||
}
|
||||
|
||||
void
|
||||
abiTestFuncConst(ThreadContext *tc, ConstTestPtr<uint8_t> ptr)
|
||||
{
|
||||
abiCalledConst = true;
|
||||
EXPECT_EQ(ptr.addr(), 0x1000);
|
||||
}
|
||||
|
||||
TEST(ProxyPtr, GuestABI)
|
||||
{
|
||||
BackingStore store(0x1000, 0x1000);
|
||||
|
||||
EXPECT_FALSE(abiCalled);
|
||||
EXPECT_FALSE(abiCalledConst);
|
||||
|
||||
invokeSimcall<TestABI>((ThreadContext *)&store, abiTestFunc);
|
||||
|
||||
EXPECT_TRUE(abiCalled);
|
||||
EXPECT_FALSE(abiCalledConst);
|
||||
|
||||
invokeSimcall<TestABI>((ThreadContext *)&store, abiTestFuncConst);
|
||||
|
||||
EXPECT_TRUE(abiCalled);
|
||||
EXPECT_TRUE(abiCalledConst);
|
||||
}
|
||||
Reference in New Issue
Block a user