1
0
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:
Thomas Harte
2026-02-14 11:14:20 -05:00
parent 6d52fdea4d
commit ed061fa9b3
5 changed files with 78 additions and 18 deletions
+9
View File
@@ -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"
+45
View File
@@ -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;
+2 -3
View File
@@ -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.
+20 -15
View File
@@ -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;
};
}