mirror of
https://github.com/TomHarte/CLK.git
synced 2026-04-19 19:16:34 +00:00
Factor out spin lock, get a bit stricter on PointerSets.
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// SpinLock.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/02/2026.
|
||||
// Copyright © 2026 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "SpinLock.hpp"
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// SpinLock.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/02/2026.
|
||||
// Copyright © 2026 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Concurrency {
|
||||
|
||||
enum class Barrier {
|
||||
Relaxed,
|
||||
AcquireRelease,
|
||||
};
|
||||
|
||||
/*!
|
||||
A basic spin lock. Applies a memory barrier as per the template.
|
||||
|
||||
Standard warnings apply: having revealed nothing to the scheduler, a holder of this lock might sleep
|
||||
and block other eligble work.
|
||||
*/
|
||||
template <Barrier type>
|
||||
class SpinLock {
|
||||
public:
|
||||
void lock() {
|
||||
while(flag_.test_and_set(LockMemoryOrder));
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
flag_.clear(UnlockMemoryOrder);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr auto LockMemoryOrder =
|
||||
type == Barrier::Relaxed ? std::memory_order_relaxed : std::memory_order_acquire;
|
||||
static constexpr auto UnlockMemoryOrder =
|
||||
type == Barrier::Relaxed ? std::memory_order_relaxed : std::memory_order_release;
|
||||
|
||||
// Note to self: this is guaranteed to construct in a clear state since C++20.
|
||||
std::atomic_flag flag_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1671,6 +1671,7 @@
|
||||
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = "<group>"; };
|
||||
4B55DD8020DF06680043F2E5 /* MachinePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePicker.swift; sourceTree = "<group>"; };
|
||||
4B55DD8220DF06680043F2E5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MachinePicker.xib; sourceTree = "<group>"; };
|
||||
4B56172C2F40D446003CB7FE /* SpinLock.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SpinLock.hpp; sourceTree = "<group>"; };
|
||||
4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MFMSectorDump.cpp; sourceTree = "<group>"; };
|
||||
4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MFMSectorDump.hpp; sourceTree = "<group>"; };
|
||||
4B59199A1DAC6C46005BB85C /* OricTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricTAP.cpp; sourceTree = "<group>"; };
|
||||
@@ -3245,6 +3246,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B3940E61DA83C8300427841 /* AsyncTaskQueue.hpp */,
|
||||
4B56172C2F40D446003CB7FE /* SpinLock.hpp */,
|
||||
);
|
||||
name = Concurrency;
|
||||
path = ../../Concurrency;
|
||||
|
||||
@@ -32,9 +32,6 @@ BufferingScanTarget::BufferingScanTarget() {
|
||||
// Ensure proper initialisation of the two atomic pointer sets.
|
||||
read_pointers_.store(write_pointers_, std::memory_order_relaxed);
|
||||
submit_pointers_.store(write_pointers_, std::memory_order_relaxed);
|
||||
|
||||
// Establish initial state for is_updating_.
|
||||
is_updating_.clear(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// MARK: - Producer; pixel data.
|
||||
@@ -223,6 +220,8 @@ void BufferingScanTarget::announce(
|
||||
is_first_in_frame_ = true;
|
||||
previous_frame_was_complete_ = frame_is_complete_;
|
||||
frame_is_complete_ = true;
|
||||
|
||||
// frames_[frame_pointer_]
|
||||
}
|
||||
|
||||
// Proceed from here only if a change in visibility has occurred.
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "Outputs/ScanTarget.hpp"
|
||||
#include "Outputs/DisplayMetrics.hpp"
|
||||
#include "Concurrency/SpinLock.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
@@ -88,12 +89,6 @@ public:
|
||||
size_t first_scan;
|
||||
};
|
||||
|
||||
struct Frame {
|
||||
size_t first_scan;
|
||||
size_t first_line;
|
||||
bool previous_frame_was_complete;
|
||||
};
|
||||
|
||||
/// Sets the area of memory to use as a scan buffer.
|
||||
void set_scan_buffer(Scan *buffer, size_t size);
|
||||
|
||||
@@ -157,9 +152,8 @@ public:
|
||||
/// change to modals, occurs simultaneously.
|
||||
template <typename FuncT>
|
||||
void perform(FuncT &&function) {
|
||||
while(is_updating_.test_and_set(std::memory_order_acquire));
|
||||
std::lock_guard guard(is_updating_);
|
||||
function();
|
||||
is_updating_.clear(std::memory_order_release);
|
||||
}
|
||||
|
||||
/// @returns new Modals if any have been set since the last call to get_new_modals().
|
||||
@@ -289,8 +283,8 @@ private:
|
||||
// an ambiguity in the C++ standard; cf. https://stackoverflow.com/questions/17430377
|
||||
PointerSet() noexcept = default;
|
||||
|
||||
// Squeezing this struct into 64 bits makes the std::atomics more likely
|
||||
// to be lock free; they are under LLVM x86-64.
|
||||
// Squeezing this struct into 128 bits makes the std::atomics more likely
|
||||
// to be lock free; cf. `lock cmpxchg16b`.
|
||||
|
||||
// Points to the vended area in the write area texture.
|
||||
// The vended area is always preceded by a guard pixel, so a
|
||||
@@ -302,20 +296,23 @@ private:
|
||||
|
||||
// Points into the line buffer.
|
||||
uint16_t line = 0;
|
||||
|
||||
// Points into the frame list.
|
||||
uint8_t frame = 0;
|
||||
};
|
||||
static_assert(std::atomic<PointerSet>::is_always_lock_free);
|
||||
|
||||
/// A pointer to the final thing currently cleared for submission.
|
||||
std::atomic<PointerSet> submit_pointers_;
|
||||
alignas(64) std::atomic<PointerSet> submit_pointers_;
|
||||
|
||||
/// A pointer to the first thing not yet submitted for display; this is
|
||||
/// atomic since it also acts as the buffer into which the write_pointers_
|
||||
/// may run and is therefore used by both producer and consumer.
|
||||
std::atomic<PointerSet> read_pointers_;
|
||||
alignas(64) std::atomic<PointerSet> read_pointers_;
|
||||
|
||||
std::atomic<PointerSet> read_ahead_pointers_;
|
||||
alignas(64) std::atomic<PointerSet> read_ahead_pointers_;
|
||||
|
||||
/// This is used as a spinlock to guard `perform` calls.
|
||||
std::atomic_flag is_updating_;
|
||||
Concurrency::SpinLock<Concurrency::Barrier::AcquireRelease> is_updating_;
|
||||
|
||||
/// A mutex for gettng access to anything the producer modifies — i.e. the write_pointers_,
|
||||
/// data_type_size_ and write_area_texture_, and all other state to do with capturing
|
||||
@@ -353,6 +350,14 @@ private:
|
||||
size_t output_area_counter_ = 0;
|
||||
size_t output_area_next_returned_ = 0;
|
||||
#endif
|
||||
|
||||
struct Frame {
|
||||
size_t first_scan;
|
||||
size_t first_line;
|
||||
bool previous_frame_was_complete;
|
||||
};
|
||||
std::array<Frame, 5> frames_;
|
||||
size_t frame_pointer_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user