forked from eden-emu/eden
		
	Merge pull request #9487 from liamwhite/look-at-the-time
time: add LockFreeAtomicType
This commit is contained in:
		
						commit
						ea70d9c79e
					
				
					 3 changed files with 65 additions and 40 deletions
				
			
		|  | @ -49,6 +49,7 @@ struct SteadyClockContext { | ||||||
| static_assert(sizeof(SteadyClockContext) == 0x18, "SteadyClockContext is incorrect size"); | static_assert(sizeof(SteadyClockContext) == 0x18, "SteadyClockContext is incorrect size"); | ||||||
| static_assert(std::is_trivially_copyable_v<SteadyClockContext>, | static_assert(std::is_trivially_copyable_v<SteadyClockContext>, | ||||||
|               "SteadyClockContext must be trivially copyable"); |               "SteadyClockContext must be trivially copyable"); | ||||||
|  | using StandardSteadyClockTimePointType = SteadyClockContext; | ||||||
| 
 | 
 | ||||||
| struct SystemClockContext { | struct SystemClockContext { | ||||||
|     s64 offset; |     s64 offset; | ||||||
|  |  | ||||||
|  | @ -26,23 +26,24 @@ void SharedMemory::SetupStandardSteadyClock(const Common::UUID& clock_source_id, | ||||||
|     const Clock::SteadyClockContext context{ |     const Clock::SteadyClockContext context{ | ||||||
|         static_cast<u64>(current_time_point.nanoseconds - ticks_time_span.nanoseconds), |         static_cast<u64>(current_time_point.nanoseconds - ticks_time_span.nanoseconds), | ||||||
|         clock_source_id}; |         clock_source_id}; | ||||||
|     shared_memory_format.standard_steady_clock_timepoint.StoreData( |     StoreToLockFreeAtomicType(&GetFormat()->standard_steady_clock_timepoint, context); | ||||||
|         system.Kernel().GetTimeSharedMem().GetPointer(), context); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void SharedMemory::UpdateLocalSystemClockContext(const Clock::SystemClockContext& context) { | void SharedMemory::UpdateLocalSystemClockContext(const Clock::SystemClockContext& context) { | ||||||
|     shared_memory_format.standard_local_system_clock_context.StoreData( |     StoreToLockFreeAtomicType(&GetFormat()->standard_local_system_clock_context, context); | ||||||
|         system.Kernel().GetTimeSharedMem().GetPointer(), context); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void SharedMemory::UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context) { | void SharedMemory::UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context) { | ||||||
|     shared_memory_format.standard_network_system_clock_context.StoreData( |     StoreToLockFreeAtomicType(&GetFormat()->standard_network_system_clock_context, context); | ||||||
|         system.Kernel().GetTimeSharedMem().GetPointer(), context); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void SharedMemory::SetAutomaticCorrectionEnabled(bool is_enabled) { | void SharedMemory::SetAutomaticCorrectionEnabled(bool is_enabled) { | ||||||
|     shared_memory_format.standard_user_system_clock_automatic_correction.StoreData( |     StoreToLockFreeAtomicType( | ||||||
|         system.Kernel().GetTimeSharedMem().GetPointer(), is_enabled); |         &GetFormat()->is_standard_user_system_clock_automatic_correction_enabled, is_enabled); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SharedMemory::Format* SharedMemory::GetFormat() { | ||||||
|  |     return reinterpret_cast<SharedMemory::Format*>(system.Kernel().GetTimeSharedMem().GetPointer()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace Service::Time
 | } // namespace Service::Time
 | ||||||
|  |  | ||||||
|  | @ -10,45 +10,68 @@ | ||||||
| 
 | 
 | ||||||
| namespace Service::Time { | namespace Service::Time { | ||||||
| 
 | 
 | ||||||
|  | // Note: this type is not safe for concurrent writes.
 | ||||||
|  | template <typename T> | ||||||
|  | struct LockFreeAtomicType { | ||||||
|  |     u32 counter_; | ||||||
|  |     std::array<T, 2> value_; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | template <typename T> | ||||||
|  | static inline void StoreToLockFreeAtomicType(LockFreeAtomicType<T>* p, const T& value) { | ||||||
|  |     // Get the current counter.
 | ||||||
|  |     auto counter = p->counter_; | ||||||
|  | 
 | ||||||
|  |     // Increment the counter.
 | ||||||
|  |     ++counter; | ||||||
|  | 
 | ||||||
|  |     // Store the updated value.
 | ||||||
|  |     p->value_[counter % 2] = value; | ||||||
|  | 
 | ||||||
|  |     // Fence memory.
 | ||||||
|  |     std::atomic_thread_fence(std::memory_order_release); | ||||||
|  | 
 | ||||||
|  |     // Set the updated counter.
 | ||||||
|  |     p->counter_ = counter; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | template <typename T> | ||||||
|  | static inline T LoadFromLockFreeAtomicType(const LockFreeAtomicType<T>* p) { | ||||||
|  |     while (true) { | ||||||
|  |         // Get the counter.
 | ||||||
|  |         auto counter = p->counter_; | ||||||
|  | 
 | ||||||
|  |         // Get the value.
 | ||||||
|  |         auto value = p->value_[counter % 2]; | ||||||
|  | 
 | ||||||
|  |         // Fence memory.
 | ||||||
|  |         std::atomic_thread_fence(std::memory_order_acquire); | ||||||
|  | 
 | ||||||
|  |         // Check that the counter matches.
 | ||||||
|  |         if (counter == p->counter_) { | ||||||
|  |             return value; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| class SharedMemory final { | class SharedMemory final { | ||||||
| public: | public: | ||||||
|     explicit SharedMemory(Core::System& system_); |     explicit SharedMemory(Core::System& system_); | ||||||
|     ~SharedMemory(); |     ~SharedMemory(); | ||||||
| 
 | 
 | ||||||
|     // TODO(ogniK): We have to properly simulate memory barriers, how are we going to do this?
 |  | ||||||
|     template <typename T, std::size_t Offset> |  | ||||||
|     struct MemoryBarrier { |  | ||||||
|         static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable"); |  | ||||||
|         u32_le read_attempt{}; |  | ||||||
|         std::array<T, 2> data{}; |  | ||||||
| 
 |  | ||||||
|         // These are not actually memory barriers at the moment as we don't have multicore and all
 |  | ||||||
|         // HLE is mutexed. This will need to properly be implemented when we start updating the time
 |  | ||||||
|         // points on threads. As of right now, we'll be updated both values synchronously and just
 |  | ||||||
|         // incrementing the read_attempt to indicate that we waited.
 |  | ||||||
|         void StoreData(u8* shared_memory, T data_to_store) { |  | ||||||
|             std::memcpy(this, shared_memory + Offset, sizeof(*this)); |  | ||||||
|             read_attempt++; |  | ||||||
|             data[read_attempt & 1] = data_to_store; |  | ||||||
|             std::memcpy(shared_memory + Offset, this, sizeof(*this)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // For reading we're just going to read the last stored value. If there was no value stored
 |  | ||||||
|         // it will just end up reading an empty value as intended.
 |  | ||||||
|         T ReadData(u8* shared_memory) { |  | ||||||
|             std::memcpy(this, shared_memory + Offset, sizeof(*this)); |  | ||||||
|             return data[(read_attempt - 1) & 1]; |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     // Shared memory format
 |     // Shared memory format
 | ||||||
|     struct Format { |     struct Format { | ||||||
|         MemoryBarrier<Clock::SteadyClockContext, 0x0> standard_steady_clock_timepoint; |         LockFreeAtomicType<Clock::StandardSteadyClockTimePointType> standard_steady_clock_timepoint; | ||||||
|         MemoryBarrier<Clock::SystemClockContext, 0x38> standard_local_system_clock_context; |         LockFreeAtomicType<Clock::SystemClockContext> standard_local_system_clock_context; | ||||||
|         MemoryBarrier<Clock::SystemClockContext, 0x80> standard_network_system_clock_context; |         LockFreeAtomicType<Clock::SystemClockContext> standard_network_system_clock_context; | ||||||
|         MemoryBarrier<bool, 0xc8> standard_user_system_clock_automatic_correction; |         LockFreeAtomicType<bool> is_standard_user_system_clock_automatic_correction_enabled; | ||||||
|         u32_le format_version; |         u32 format_version; | ||||||
|     }; |     }; | ||||||
|  |     static_assert(offsetof(Format, standard_steady_clock_timepoint) == 0x0); | ||||||
|  |     static_assert(offsetof(Format, standard_local_system_clock_context) == 0x38); | ||||||
|  |     static_assert(offsetof(Format, standard_network_system_clock_context) == 0x80); | ||||||
|  |     static_assert(offsetof(Format, is_standard_user_system_clock_automatic_correction_enabled) == | ||||||
|  |                   0xc8); | ||||||
|     static_assert(sizeof(Format) == 0xd8, "Format is an invalid size"); |     static_assert(sizeof(Format) == 0xd8, "Format is an invalid size"); | ||||||
| 
 | 
 | ||||||
|     void SetupStandardSteadyClock(const Common::UUID& clock_source_id, |     void SetupStandardSteadyClock(const Common::UUID& clock_source_id, | ||||||
|  | @ -56,10 +79,10 @@ public: | ||||||
|     void UpdateLocalSystemClockContext(const Clock::SystemClockContext& context); |     void UpdateLocalSystemClockContext(const Clock::SystemClockContext& context); | ||||||
|     void UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context); |     void UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context); | ||||||
|     void SetAutomaticCorrectionEnabled(bool is_enabled); |     void SetAutomaticCorrectionEnabled(bool is_enabled); | ||||||
|  |     Format* GetFormat(); | ||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
|     Core::System& system; |     Core::System& system; | ||||||
|     Format shared_memory_format{}; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace Service::Time
 | } // namespace Service::Time
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 liamwhite
						liamwhite