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