| // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| #ifndef RUNTIME_VM_SIMULATOR_MEMORY_H_ | |
| #define RUNTIME_VM_SIMULATOR_MEMORY_H_ | |
| #include <atomic> | |
| #include "platform/unaligned.h" | |
| #include "vm/globals.h" | |
| namespace dart { | |
| class DirectSimulatorMemory { | |
| public: | |
| template <typename T> | |
| T Load(uword addr) { | |
| return LoadUnaligned(reinterpret_cast<T*>(addr)); | |
| } | |
| template <typename T> | |
| void Store(uword addr, T value) { | |
| StoreUnaligned(reinterpret_cast<T*>(addr), value); | |
| } | |
| template <typename T> | |
| T Load(uword addr, std::memory_order order) { | |
| // TODO(42074): Once we switch to C++20 we should change this to use use | |
| // `std::atomic_ref<T>` which supports performing atomic operations on | |
| // non-atomic data. | |
| static_assert(sizeof(std::atomic<T>) == sizeof(T)); | |
| return reinterpret_cast<std::atomic<T>*>(addr)->load(order); | |
| } | |
| template <typename T> | |
| void Store(uword addr, T value, std::memory_order order) { | |
| // TODO(42074): Once we switch to C++20 we should change this to use use | |
| // `std::atomic_ref<T>` which supports performing atomic operations on | |
| // non-atomic data. | |
| static_assert(sizeof(std::atomic<T>) == sizeof(T)); | |
| reinterpret_cast<std::atomic<T>*>(addr)->store(value, order); | |
| } | |
| template <typename T> | |
| bool CompareExchange(uword addr, | |
| T& old_value, | |
| T value, | |
| std::memory_order order) { | |
| // TODO(42074): Once we switch to C++20 we should change this to use use | |
| // `std::atomic_ref<T>` which supports performing atomic operations on | |
| // non-atomic data. | |
| static_assert(sizeof(std::atomic<T>) == sizeof(T)); | |
| return reinterpret_cast<std::atomic<T>*>(addr)->compare_exchange_weak( | |
| old_value, value, order); | |
| } | |
| void FlushAddress(uword addr) {} | |
| void FlushAll() {} | |
| }; | |
| class BufferedSimulatorMemory { | |
| public: | |
| template <typename T> | |
| T Load(uword addr) { | |
| if (UNLIKELY((sizeof(T) > sizeof(uword)) || | |
| ((addr & (sizeof(T) - 1)) != 0))) { | |
| FlushAll(); | |
| return LoadUnaligned(reinterpret_cast<T*>(addr)); | |
| } | |
| uword word_addr = addr & ~(sizeof(uword) - 1); | |
| uword word_offset = addr - word_addr; | |
| for (intptr_t i = size_ - 1; i >= 0; i--) { | |
| if (buffer_[i].addr == word_addr) { | |
| uword buffer_addr = | |
| reinterpret_cast<uword>(&buffer_[i].value) + word_offset; | |
| return *reinterpret_cast<T*>(buffer_addr); | |
| } | |
| } | |
| return *reinterpret_cast<T*>(addr); | |
| } | |
| template <typename T> | |
| void Store(uword addr, T value) { | |
| if (UNLIKELY((sizeof(T) > sizeof(uword)) || | |
| ((addr & (sizeof(T) - 1)) != 0))) { | |
| FlushAll(); | |
| StoreUnaligned(reinterpret_cast<T*>(addr), value); | |
| return; | |
| } | |
| uword word_addr = addr & ~(sizeof(uword) - 1); | |
| uword word_offset = addr - word_addr; | |
| for (intptr_t i = size_ - 1; i >= 0; i--) { | |
| if (buffer_[i].addr == word_addr) { | |
| uword buffer_addr = | |
| reinterpret_cast<uword>(&buffer_[i].value) + word_offset; | |
| *reinterpret_cast<T*>(buffer_addr) = value; | |
| return; | |
| } | |
| } | |
| buffer_[size_] = {word_addr, *reinterpret_cast<uword*>(word_addr)}; | |
| uword buffer_addr = | |
| reinterpret_cast<uword>(&buffer_[size_].value) + word_offset; | |
| *reinterpret_cast<T*>(buffer_addr) = value; | |
| size_++; | |
| if (size_ == kCapacity) { | |
| FlushSome(); | |
| } | |
| } | |
| template <typename T> | |
| T Load(uword addr, std::memory_order order) { | |
| FlushAddress(addr); | |
| // TODO(42074): Once we switch to C++20 we should change this to use use | |
| // `std::atomic_ref<T>` which supports performing atomic operations on | |
| // non-atomic data. | |
| static_assert(sizeof(std::atomic<T>) == sizeof(T)); | |
| return reinterpret_cast<std::atomic<T>*>(addr)->load(order); | |
| } | |
| template <typename T> | |
| void Store(uword addr, T value, std::memory_order order) { | |
| FlushAddress(addr); | |
| // TODO(42074): Once we switch to C++20 we should change this to use use | |
| // `std::atomic_ref<T>` which supports performing atomic operations on | |
| // non-atomic data. | |
| static_assert(sizeof(std::atomic<T>) == sizeof(T)); | |
| reinterpret_cast<std::atomic<T>*>(addr)->store(value, order); | |
| } | |
| template <typename T> | |
| bool CompareExchange(uword addr, | |
| T& old_value, | |
| T value, | |
| std::memory_order order) { | |
| FlushAddress(addr); | |
| // TODO(42074): Once we switch to C++20 we should change this to use use | |
| // `std::atomic_ref<T>` which supports performing atomic operations on | |
| // non-atomic data. | |
| static_assert(sizeof(std::atomic<T>) == sizeof(T)); | |
| return reinterpret_cast<std::atomic<T>*>(addr)->compare_exchange_weak( | |
| old_value, value, order); | |
| } | |
| void FlushAddress(uword addr) { | |
| for (intptr_t i = 0; i < size_; i++) { | |
| if (buffer_[i].addr == addr) { | |
| *reinterpret_cast<uword*>(buffer_[i].addr) = buffer_[i].value; | |
| buffer_[i] = buffer_[size_ - 1]; | |
| size_--; | |
| } | |
| } | |
| } | |
| void FlushAll() { | |
| for (intptr_t i = 0; i < size_; i++) { | |
| *reinterpret_cast<uword*>(buffer_[i].addr) = buffer_[i].value; | |
| } | |
| size_ = 0; | |
| } | |
| private: | |
| void FlushSome() { | |
| while (size_ > (kCapacity / 2)) { | |
| intptr_t i = buffer_[0].value; | |
| i ^= (i >> 4); | |
| i ^= (i >> 8); | |
| i = i % size_; | |
| *reinterpret_cast<uword*>(buffer_[i].addr) = buffer_[i].value; | |
| buffer_[i] = buffer_[size_ - 1]; | |
| size_--; | |
| } | |
| } | |
| struct Entry { | |
| uword addr; | |
| uword value; | |
| }; | |
| static constexpr intptr_t kCapacity = 16; | |
| intptr_t size_ = 0; | |
| Entry buffer_[kCapacity]; | |
| }; | |
| class SimulatorMemory { | |
| public: | |
| explicit SimulatorMemory(bool use_buffered) : use_buffered_(use_buffered) {} | |
| template <typename T> | |
| T Load(uword addr) { | |
| if (UNLIKELY(use_buffered_)) { | |
| return buffered_.Load<T>(addr); | |
| } else { | |
| return direct_.Load<T>(addr); | |
| } | |
| } | |
| template <typename T> | |
| void Store(uword addr, T value) { | |
| if (UNLIKELY(use_buffered_)) { | |
| return buffered_.Store<T>(addr, value); | |
| } else { | |
| return direct_.Store<T>(addr, value); | |
| } | |
| } | |
| template <typename T> | |
| T Load(uword addr, std::memory_order order) { | |
| if (UNLIKELY(use_buffered_)) { | |
| return buffered_.Load<T>(addr, order); | |
| } else { | |
| return direct_.Load<T>(addr, order); | |
| } | |
| } | |
| template <typename T> | |
| void Store(uword addr, T value, std::memory_order order) { | |
| if (UNLIKELY(use_buffered_)) { | |
| return buffered_.Store<T>(addr, value, order); | |
| } else { | |
| return direct_.Store<T>(addr, value, order); | |
| } | |
| } | |
| template <typename T> | |
| bool CompareExchange(uword addr, | |
| T& old_value, | |
| T value, | |
| std::memory_order order) { | |
| if (UNLIKELY(use_buffered_)) { | |
| return buffered_.CompareExchange<T>(addr, old_value, value, order); | |
| } else { | |
| return direct_.CompareExchange<T>(addr, old_value, value, order); | |
| } | |
| } | |
| void FlushAddress(uword addr) { | |
| if (UNLIKELY(use_buffered_)) { | |
| return buffered_.FlushAddress(addr); | |
| } else { | |
| return direct_.FlushAddress(addr); | |
| } | |
| } | |
| void FlushAll() { | |
| if (UNLIKELY(use_buffered_)) { | |
| return buffered_.FlushAll(); | |
| } else { | |
| return direct_.FlushAll(); | |
| } | |
| } | |
| DirectSimulatorMemory direct_; | |
| BufferedSimulatorMemory buffered_; | |
| const bool use_buffered_; | |
| }; | |
| } // namespace dart | |
| #endif // RUNTIME_VM_SIMULATOR_MEMORY_H_ |