Now that we're using c++17, the type_traits with a ::value member have a _v alias which reduces verbosity. Or on other words std::is_integral<T>::value can be replaced with std::is_integral_v<T> Make this substitution throughout the code base. In places where gem5 introduced it's own similar templates, add a V alias, spelled differently to match gem5's internal style. gem5: :IsVarArgs<T>::value => gem5::IsVarArgsV<T> Change-Id: I1d84ffc4a236ad699471569e7916ec17fe5f109a Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/48604 Reviewed-by: Daniel Carvalho <odanrc@yahoo.com.br> Maintainer: Bobby R. Bruce <bbruce@ucdavis.edu> Tested-by: kokoro <noreply+kokoro@google.com>
528 lines
13 KiB
C++
528 lines
13 KiB
C++
/*
|
|
* 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"
|
|
|
|
using namespace gem5;
|
|
|
|
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 guest_abi 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_v<decltype(*test_ptr1), const PtrType &>;
|
|
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_v<decltype((struct_ptr->a)), const uint8_t &>;
|
|
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_v<decltype(*test_ptr1), PtrType &>;
|
|
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_v<decltype((struct_ptr->a)), uint8_t &>;
|
|
EXPECT_TRUE(is_same);
|
|
}
|
|
|
|
struct TestABI
|
|
{
|
|
using UintPtr = uint64_t;
|
|
using State = int;
|
|
};
|
|
|
|
namespace gem5
|
|
{
|
|
|
|
GEM5_DEPRECATED_NAMESPACE(GuestABI, guest_abi);
|
|
namespace guest_abi
|
|
{
|
|
|
|
template <>
|
|
struct Argument<TestABI, Addr>
|
|
{
|
|
static Addr
|
|
get(ThreadContext *tc, typename TestABI::State &state)
|
|
{
|
|
return 0x1000;
|
|
}
|
|
};
|
|
|
|
} // namespace guest_abi
|
|
} // namespace gem5
|
|
|
|
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(ProxyPtrTest, 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);
|
|
}
|