| /* | |
| * 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); | |
| } |