Skip to content

fix(concurrency): atomic UNIV::TIME / DEVICE_TIME / m_libsLoaded#421

Merged
benswift merged 1 commit intomasterfrom
tsan-fixes-time-libsloaded
Apr 19, 2026
Merged

fix(concurrency): atomic UNIV::TIME / DEVICE_TIME / m_libsLoaded#421
benswift merged 1 commit intomasterfrom
tsan-fixes-time-libsloaded

Conversation

@benswift
Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #420's TSan run. Three of the four reported races had obvious fixes:

  • UNIV::TIME — plain volatile uint64_t, written by the scheduler, read from every other thread → std::atomic<uint64_t>.
  • UNIV::DEVICE_TIME — same shape, written by the audio callback → std::atomic<uint64_t>.
  • SchemeProcess::m_libsLoaded — spin-waited bool set once at startup → std::atomic<bool>.

std::atomic<uint64_t> is lock-free on every tier-1 platform (added a static_assert) and has the same size/alignment as uint64_t, so the xtlang JIT's @TIME = external global i64 + load i64 in runtime/bitcode.ll stays layout-compatible. A handful of callers were tweaked for atomic's deleted copy assignment:

  • UNIV::TIME = UNIV::DEVICE_TIME → explicit .store(…load()).
  • Three auto time(UNIV::TIME)uint64_t time(UNIV::TIME) (auto would deduce atomic, which isn't copyable).

Verification

Re-ran libs-core | cpp-unit | compiler-unit under EXTEMPORE_SANITIZE=tsan:

Before After
Unique races reported 4 1
Total TSan warnings 16 1

The remaining race is the SchemeTask deque iterator one that belongs to TASK-042 (audio-thread dispatcher races) — left as-is here.

Test plan

  • All 20 local tests pass (libs-core, libs-external, cpp-unit, compiler-unit)
  • TSan: SchemeS7.cpp:309 (TIME), SchemeS7.cpp:79 (TIME), SchemeProcess.cpp:293 (m_libsLoaded) all clean
  • CI: 4-platform matrix

TSan flagged three data races on the cpp-modernisation-followup branch
(all pre-existing, not introduced by that PR):

- UNIV::TIME (plain volatile uint64_t) is written by the scheduler
  thread in TaskScheduler::timeSlice and read from every other thread;
  the volatile keyword prevents compiler reordering but does nothing
  for atomicity across threads.
- UNIV::DEVICE_TIME has the same shape, written by the audio callback
  and read from other threads.
- SchemeProcess::m_libsLoaded is a bool set once by the task thread
  after runtime libs finish loading and spin-waited on by the server
  thread in serverImpl().

Promote all three to std::atomic.  std::atomic<uint64_t> is lock-free
on every tier-1 platform (added a static_assert) and has the same
size/alignment as uint64_t, so the xtlang JIT's plain `load i64, i64*
@TIME` in runtime/bitcode.ll remains layout-compatible.

A couple of callers had to change:
- AudioDevice.cpp's `UNIV::TIME = UNIV::DEVICE_TIME` is now explicit
  load/store to avoid the deleted atomic copy assignment.
- Three `auto time(UNIV::TIME)` sites become `uint64_t time(UNIV::TIME)`
  for the same reason (auto would deduce atomic, which isn't copyable).

Re-ran the full libs-core + cpp-unit + compiler-unit suite under TSan:
the four previously-reported unique races are down to one (the
SchemeTask deque iterator race, which belongs with TASK-042).
@benswift benswift merged commit ef0b2b6 into master Apr 19, 2026
4 checks passed
@benswift benswift deleted the tsan-fixes-time-libsloaded branch April 19, 2026 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant