From 9ae78f8a4cadda30ad1062829105fe118bd9e358 Mon Sep 17 00:00:00 2001 From: Relay Date: Fri, 7 Aug 2026 21:34:33 -0400 Subject: [PATCH 1/6] chore: apply threading correctness follow-up --- .../workflows/apply-threading-correctness.yml | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 .github/workflows/apply-threading-correctness.yml diff --git a/.github/workflows/apply-threading-correctness.yml b/.github/workflows/apply-threading-correctness.yml new file mode 100644 index 0000000..74671a0 --- /dev/null +++ b/.github/workflows/apply-threading-correctness.yml @@ -0,0 +1,117 @@ +name: Apply threading correctness follow-up + +on: + push: + branches: ["agent/threading-correctness-followup"] + +permissions: + contents: write + +jobs: + patch: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + ref: agent/threading-correctness-followup + fetch-depth: 0 + + - name: Apply focused fixes + shell: python + run: | + from pathlib import Path + + def replace_once(path, old, new): + p = Path(path) + text = p.read_text() + if text.count(old) != 1: + raise SystemExit(f"expected exactly one match in {path}, found {text.count(old)}") + p.write_text(text.replace(old, new, 1)) + + # Restore actual stb-style opt-in implementation semantics. + replace_once( + "src/polesitter.h", + "#endif // POLESITTER_H\n\n#define POLESITTER_IMPLEMENTATION\n#ifdef POLESITTER_IMPLEMENTATION", + "#endif // POLESITTER_H\n\n#ifdef POLESITTER_IMPLEMENTATION", + ) + + # Do not rely on AVX headers merely to issue a spin-wait hint. + replace_once( + "src/polesitter.h", + """#if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || \\ + defined(_M_IX86)\n\n#define PS_YIELD() _mm_pause()\n#elif defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__)\n#define PS_YIELD() __asm__ volatile(\"yield\" ::: \"memory\")\n#else\n#define PS_YIELD()\n#endif""", + """#if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || \\ + defined(_M_IX86)\n\n#ifdef _WIN32\n#define PS_YIELD() YieldProcessor()\n#else\n#define PS_YIELD() __asm__ volatile(\"pause\" ::: \"memory\")\n#endif\n#elif defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__)\n#define PS_YIELD() __asm__ volatile(\"yield\" ::: \"memory\")\n#else\n#define PS_YIELD()\n#endif""", + ) + + # Pool state is protected by the spinlock; volatile is not synchronization. + replace_once( + "src/polesitter.h", + """ ps_job_t queue[PS_MAX_JOBS];\n volatile int hd;\n volatile int tl;\n volatile int cnt;\n volatile int active_jobs;\n volatile int shutdown_flag;\n\n ps_spinlock_t lock;""", + """ ps_job_t queue[PS_MAX_JOBS];\n int hd;\n int tl;\n int cnt;\n int active_jobs;\n int shutdown_flag;\n\n ps_spinlock_t lock;""", + ) + + replace_once( + "src/polesitter.h", + """ // block if buffer is full\n while (pool->cnt == PS_MAX_JOBS && !pool->shutdown_flag) {\n PS_YIELD();\n }\n\n // enqueue\n ps_spin_lock(&pool->lock);\n pool->queue[pool->tl] = job;\n pool->tl = (pool->tl + 1) % PS_MAX_JOBS;\n pool->cnt++;\n ps_spin_unlock(&pool->lock);\n return;""", + """ // Queue state is shared with workers; inspect and update it only\n // while holding the spinlock. volatile does not make these accesses\n // atomic or establish inter-thread ordering in C.\n while (1) {\n ps_spin_lock(&pool->lock);\n\n if (pool->shutdown_flag) {\n ps_spin_unlock(&pool->lock);\n return;\n }\n\n if (pool->cnt < PS_MAX_JOBS) {\n pool->queue[pool->tl] = job;\n pool->tl = (pool->tl + 1) % PS_MAX_JOBS;\n pool->cnt++;\n ps_spin_unlock(&pool->lock);\n return;\n }\n\n ps_spin_unlock(&pool->lock);\n PS_YIELD();\n }""", + ) + + replace_once( + "src/polesitter.h", + """ while (1) {\n if (pool->cnt == 0 && pool->active_jobs == 0) {\n\n ps_spin_lock(&pool->lock);\n int done = (pool->cnt == 0 && pool->active_jobs == 0);\n ps_spin_unlock(&pool->lock);\n\n if (done)\n return;\n }\n PS_YIELD();\n }""", + """ while (1) {\n ps_spin_lock(&pool->lock);\n int done = (pool->cnt == 0 && pool->active_jobs == 0);\n ps_spin_unlock(&pool->lock);\n\n if (done) {\n return;\n }\n\n PS_YIELD();\n }""", + ) + + replace_once( + "src/polesitter.h", + """ while (1) {\n while (pool->cnt == 0 && !pool->shutdown_flag) {\n PS_YIELD();\n }\n\n ps_spin_lock(&pool->lock);\n\n if (pool->cnt == 0) {\n ps_spin_unlock(&pool->lock);\n if (pool->shutdown_flag) {\n break;\n }\n\n continue;\n }\n\n // dequeue\n ps_job_t job = pool->queue[pool->hd];\n pool->hd = (pool->hd + 1) % PS_MAX_JOBS;\n\n pool->active_jobs++;\n pool->cnt--;\n ps_spin_unlock(&pool->lock);""", + """ while (1) {\n ps_spin_lock(&pool->lock);\n\n if (pool->cnt == 0) {\n int shutdown = pool->shutdown_flag;\n ps_spin_unlock(&pool->lock);\n\n if (shutdown) {\n break;\n }\n\n PS_YIELD();\n continue;\n }\n\n // dequeue\n ps_job_t job = pool->queue[pool->hd];\n pool->hd = (pool->hd + 1) % PS_MAX_JOBS;\n\n pool->active_jobs++;\n pool->cnt--;\n ps_spin_unlock(&pool->lock);""", + ) + + # A force mismatch must actually fail the ST-vs-MT equivalence check. + replace_once( + "tests/accuracy.c", + """ printf(\"[%s] Force mismatch at array index %zu (ID %u): \"\n \"ST(%.2f,%.2f,%.2f) MT(%.2f,%.2f,%.2f)\",\n phase, i, st->id[i], st->fx[i], st->fy[i], st->fz[i],\n mt->fx[i], mt->fy[i], mt->fz[i]);\n\n if (mismatches >= 5) {""", + """ printf(\"[%s] Force mismatch at array index %zu (ID %u): \"\n \"ST(%.2f,%.2f,%.2f) MT(%.2f,%.2f,%.2f)\",\n phase, i, st->id[i], st->fx[i], st->fy[i], st->fz[i],\n mt->fx[i], mt->fy[i], mt->fz[i]);\n mismatches++;\n\n if (mismatches >= 5) {""", + ) + + # Bring the README quickstart up to the v2 API. + replace_once( + "README.md", + """Include the header in one C file with `POLESITTER_IMPLEMENTATION` defined.\n\n```c\n#define POLESITTER_IMPLEMENTATION\n#include \"polesitter.h\"\n```""", + """Include the header in one C file with `POLESITTER_IMPLEMENTATION` defined. Define `PS_MULTITHREADING` as well when using more than one thread.\n\n```c\n#define PS_MULTITHREADING\n#define POLESITTER_IMPLEMENTATION\n#include \"polesitter.h\"\n```""", + ) + + replace_once( + "README.md", + """#include \n#include """, + """#include \n#include \n#include """, + ) + + replace_once( + "README.md", + """ // initialize the context\n ps_config_t cfg = { memory_block, MEMORY_SIZE };\n ps_context_t* ctx = NULL;\n ps_init(&ctx, &cfg);""", + """ // initialize the context\n ps_config_t cfg = {\n .buff = memory_block,\n .buff_size = MEMORY_SIZE,\n .max_particles = PARTICLE_CNT,\n .theta = 2.0F,\n .thrd_cnt = 4,\n };\n ps_context_t* ctx = NULL;\n ps_init(&ctx, &cfg);""", + ) + + replace_once( + "README.md", + """ // reset arena and force accumulators for the new frame\n ps_arena_clear(&ctx->arena);\n for (int i = 0; i < PARTICLE_CNT; ++i) {""", + """ // reset IDs and force accumulators for the new frame\n for (int i = 0; i < PARTICLE_CNT; ++i) {""", + ) + + replace_once( + "README.md", + """ // cleanup\n free(memory_block);\n return 0;""", + """ // cleanup\n ps_destroy(ctx);\n free(memory_block);\n return 0;""", + ) + + - name: Commit focused fixes and remove helper workflow + run: | + rm .github/workflows/apply-threading-correctness.yml + git config user.name "project-relay" + git config user.email "csheldrick+relay@gmail.com" + git add README.md src/polesitter.h tests/accuracy.c .github/workflows/apply-threading-correctness.yml + git commit -m "fix: tighten threading correctness and v2 docs" + git push origin HEAD:agent/threading-correctness-followup From c0eef9328eb0b925b065aae67c3931841d8719d3 Mon Sep 17 00:00:00 2001 From: Relay Date: Fri, 7 Aug 2026 21:35:46 -0400 Subject: [PATCH 2/6] chore: run threading correctness patch --- .../run-threading-correctness-patch.yml | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 .github/workflows/run-threading-correctness-patch.yml diff --git a/.github/workflows/run-threading-correctness-patch.yml b/.github/workflows/run-threading-correctness-patch.yml new file mode 100644 index 0000000..728d870 --- /dev/null +++ b/.github/workflows/run-threading-correctness-patch.yml @@ -0,0 +1,116 @@ +name: Run threading correctness patch + +on: + push: + branches: + - agent/threading-correctness-followup + +permissions: + contents: write + +jobs: + patch: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + ref: agent/threading-correctness-followup + fetch-depth: 0 + + - name: Apply focused fixes + run: | + python - <<'PY' + from pathlib import Path + + def replace_once(path, old, new): + p = Path(path) + text = p.read_text() + count = text.count(old) + if count != 1: + raise SystemExit(f"expected exactly one match in {path}, found {count}") + p.write_text(text.replace(old, new, 1)) + + replace_once( + "src/polesitter.h", + "#endif // POLESITTER_H\n\n#define POLESITTER_IMPLEMENTATION\n#ifdef POLESITTER_IMPLEMENTATION", + "#endif // POLESITTER_H\n\n#ifdef POLESITTER_IMPLEMENTATION", + ) + + replace_once( + "src/polesitter.h", + """#if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || \\ + defined(_M_IX86)\n\n#define PS_YIELD() _mm_pause()\n#elif defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__)\n#define PS_YIELD() __asm__ volatile(\"yield\" ::: \"memory\")\n#else\n#define PS_YIELD()\n#endif""", + """#if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || \\ + defined(_M_IX86)\n\n#ifdef _WIN32\n#define PS_YIELD() YieldProcessor()\n#else\n#define PS_YIELD() __asm__ volatile(\"pause\" ::: \"memory\")\n#endif\n#elif defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__)\n#define PS_YIELD() __asm__ volatile(\"yield\" ::: \"memory\")\n#else\n#define PS_YIELD()\n#endif""", + ) + + replace_once( + "src/polesitter.h", + """ ps_job_t queue[PS_MAX_JOBS];\n volatile int hd;\n volatile int tl;\n volatile int cnt;\n volatile int active_jobs;\n volatile int shutdown_flag;\n\n ps_spinlock_t lock;""", + """ ps_job_t queue[PS_MAX_JOBS];\n int hd;\n int tl;\n int cnt;\n int active_jobs;\n int shutdown_flag;\n\n ps_spinlock_t lock;""", + ) + + replace_once( + "src/polesitter.h", + """ // block if buffer is full\n while (pool->cnt == PS_MAX_JOBS && !pool->shutdown_flag) {\n PS_YIELD();\n }\n\n // enqueue\n ps_spin_lock(&pool->lock);\n pool->queue[pool->tl] = job;\n pool->tl = (pool->tl + 1) % PS_MAX_JOBS;\n pool->cnt++;\n ps_spin_unlock(&pool->lock);\n return;""", + """ // Queue state is shared with workers; inspect and update it only\n // while holding the spinlock. volatile does not make these accesses\n // atomic or establish inter-thread ordering in C.\n while (1) {\n ps_spin_lock(&pool->lock);\n\n if (pool->shutdown_flag) {\n ps_spin_unlock(&pool->lock);\n return;\n }\n\n if (pool->cnt < PS_MAX_JOBS) {\n pool->queue[pool->tl] = job;\n pool->tl = (pool->tl + 1) % PS_MAX_JOBS;\n pool->cnt++;\n ps_spin_unlock(&pool->lock);\n return;\n }\n\n ps_spin_unlock(&pool->lock);\n PS_YIELD();\n }""", + ) + + replace_once( + "src/polesitter.h", + """ while (1) {\n if (pool->cnt == 0 && pool->active_jobs == 0) {\n\n ps_spin_lock(&pool->lock);\n int done = (pool->cnt == 0 && pool->active_jobs == 0);\n ps_spin_unlock(&pool->lock);\n\n if (done)\n return;\n }\n PS_YIELD();\n }""", + """ while (1) {\n ps_spin_lock(&pool->lock);\n int done = (pool->cnt == 0 && pool->active_jobs == 0);\n ps_spin_unlock(&pool->lock);\n\n if (done) {\n return;\n }\n\n PS_YIELD();\n }""", + ) + + replace_once( + "src/polesitter.h", + """ while (1) {\n while (pool->cnt == 0 && !pool->shutdown_flag) {\n PS_YIELD();\n }\n\n ps_spin_lock(&pool->lock);\n\n if (pool->cnt == 0) {\n ps_spin_unlock(&pool->lock);\n if (pool->shutdown_flag) {\n break;\n }\n\n continue;\n }\n\n // dequeue\n ps_job_t job = pool->queue[pool->hd];\n pool->hd = (pool->hd + 1) % PS_MAX_JOBS;\n\n pool->active_jobs++;\n pool->cnt--;\n ps_spin_unlock(&pool->lock);""", + """ while (1) {\n ps_spin_lock(&pool->lock);\n\n if (pool->cnt == 0) {\n int shutdown = pool->shutdown_flag;\n ps_spin_unlock(&pool->lock);\n\n if (shutdown) {\n break;\n }\n\n PS_YIELD();\n continue;\n }\n\n // dequeue\n ps_job_t job = pool->queue[pool->hd];\n pool->hd = (pool->hd + 1) % PS_MAX_JOBS;\n\n pool->active_jobs++;\n pool->cnt--;\n ps_spin_unlock(&pool->lock);""", + ) + + replace_once( + "tests/accuracy.c", + """ printf(\"[%s] Force mismatch at array index %zu (ID %u): \"\n \"ST(%.2f,%.2f,%.2f) MT(%.2f,%.2f,%.2f)\",\n phase, i, st->id[i], st->fx[i], st->fy[i], st->fz[i],\n mt->fx[i], mt->fy[i], mt->fz[i]);\n\n if (mismatches >= 5) {""", + """ printf(\"[%s] Force mismatch at array index %zu (ID %u): \"\n \"ST(%.2f,%.2f,%.2f) MT(%.2f,%.2f,%.2f)\",\n phase, i, st->id[i], st->fx[i], st->fy[i], st->fz[i],\n mt->fx[i], mt->fy[i], mt->fz[i]);\n mismatches++;\n\n if (mismatches >= 5) {""", + ) + + replace_once( + "README.md", + """Include the header in one C file with `POLESITTER_IMPLEMENTATION` defined.\n\n```c\n#define POLESITTER_IMPLEMENTATION\n#include \"polesitter.h\"\n```""", + """Include the header in one C file with `POLESITTER_IMPLEMENTATION` defined. Define `PS_MULTITHREADING` as well when using more than one thread.\n\n```c\n#define PS_MULTITHREADING\n#define POLESITTER_IMPLEMENTATION\n#include \"polesitter.h\"\n```""", + ) + + replace_once( + "README.md", + """#include \n#include """, + """#include \n#include \n#include """, + ) + + replace_once( + "README.md", + """ // initialize the context\n ps_config_t cfg = { memory_block, MEMORY_SIZE };\n ps_context_t* ctx = NULL;\n ps_init(&ctx, &cfg);""", + """ // initialize the context\n ps_config_t cfg = {\n .buff = memory_block,\n .buff_size = MEMORY_SIZE,\n .max_particles = PARTICLE_CNT,\n .theta = 2.0F,\n .thrd_cnt = 4,\n };\n ps_context_t* ctx = NULL;\n ps_init(&ctx, &cfg);""", + ) + + replace_once( + "README.md", + """ // reset arena and force accumulators for the new frame\n ps_arena_clear(&ctx->arena);\n for (int i = 0; i < PARTICLE_CNT; ++i) {""", + """ // reset IDs and force accumulators for the new frame\n for (int i = 0; i < PARTICLE_CNT; ++i) {""", + ) + + replace_once( + "README.md", + """ // cleanup\n free(memory_block);\n return 0;""", + """ // cleanup\n ps_destroy(ctx);\n free(memory_block);\n return 0;""", + ) + PY + + - name: Commit focused fixes and remove helper files + run: | + rm .github/workflows/apply-threading-correctness.yml + rm .github/workflows/run-threading-correctness-patch.yml + git config user.name "project-relay" + git config user.email "csheldrick+relay@gmail.com" + git add README.md src/polesitter.h tests/accuracy.c .github/workflows + git commit -m "fix: tighten threading correctness and v2 docs" + git push origin HEAD:agent/threading-correctness-followup From 3a93407676fa27e96b6c8620d5761323d6d349e6 Mon Sep 17 00:00:00 2001 From: Relay Date: Fri, 7 Aug 2026 21:38:29 -0400 Subject: [PATCH 3/6] chore: probe fork actions --- .github/workflows/probe.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/workflows/probe.yml diff --git a/.github/workflows/probe.yml b/.github/workflows/probe.yml new file mode 100644 index 0000000..9fc63c5 --- /dev/null +++ b/.github/workflows/probe.yml @@ -0,0 +1,10 @@ +name: Probe +on: + push: + branches: + - agent/threading-correctness-followup +jobs: + probe: + runs-on: ubuntu-latest + steps: + - run: echo hello From 64ca3a1b243c21a959bbce7705761b60d356e981 Mon Sep 17 00:00:00 2001 From: Relay Date: Fri, 7 Aug 2026 21:39:22 -0400 Subject: [PATCH 4/6] chore: stage threading correctness patch script --- .github/apply_threading_correctness.py | 85 ++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 .github/apply_threading_correctness.py diff --git a/.github/apply_threading_correctness.py b/.github/apply_threading_correctness.py new file mode 100644 index 0000000..15f975c --- /dev/null +++ b/.github/apply_threading_correctness.py @@ -0,0 +1,85 @@ +from pathlib import Path + + +def replace_once(path: str, old: str, new: str) -> None: + p = Path(path) + text = p.read_text() + count = text.count(old) + if count != 1: + raise SystemExit(f"expected exactly one match in {path}, found {count}") + p.write_text(text.replace(old, new, 1)) + + +replace_once( + "src/polesitter.h", + "#endif // POLESITTER_H\n\n#define POLESITTER_IMPLEMENTATION\n#ifdef POLESITTER_IMPLEMENTATION", + "#endif // POLESITTER_H\n\n#ifdef POLESITTER_IMPLEMENTATION", +) + +replace_once( + "src/polesitter.h", + """#if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || \\ + defined(_M_IX86)\n\n#define PS_YIELD() _mm_pause()\n#elif defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__)\n#define PS_YIELD() __asm__ volatile(\"yield\" ::: \"memory\")\n#else\n#define PS_YIELD()\n#endif""", + """#if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || \\ + defined(_M_IX86)\n\n#ifdef _WIN32\n#define PS_YIELD() YieldProcessor()\n#else\n#define PS_YIELD() __asm__ volatile(\"pause\" ::: \"memory\")\n#endif\n#elif defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__)\n#define PS_YIELD() __asm__ volatile(\"yield\" ::: \"memory\")\n#else\n#define PS_YIELD()\n#endif""", +) + +replace_once( + "src/polesitter.h", + """ ps_job_t queue[PS_MAX_JOBS];\n volatile int hd;\n volatile int tl;\n volatile int cnt;\n volatile int active_jobs;\n volatile int shutdown_flag;\n\n ps_spinlock_t lock;""", + """ ps_job_t queue[PS_MAX_JOBS];\n int hd;\n int tl;\n int cnt;\n int active_jobs;\n int shutdown_flag;\n\n ps_spinlock_t lock;""", +) + +replace_once( + "src/polesitter.h", + """ // block if buffer is full\n while (pool->cnt == PS_MAX_JOBS && !pool->shutdown_flag) {\n PS_YIELD();\n }\n\n // enqueue\n ps_spin_lock(&pool->lock);\n pool->queue[pool->tl] = job;\n pool->tl = (pool->tl + 1) % PS_MAX_JOBS;\n pool->cnt++;\n ps_spin_unlock(&pool->lock);\n return;""", + """ // Queue state is shared with workers; inspect and update it only\n // while holding the spinlock. volatile does not make these accesses\n // atomic or establish inter-thread ordering in C.\n while (1) {\n ps_spin_lock(&pool->lock);\n\n if (pool->shutdown_flag) {\n ps_spin_unlock(&pool->lock);\n return;\n }\n\n if (pool->cnt < PS_MAX_JOBS) {\n pool->queue[pool->tl] = job;\n pool->tl = (pool->tl + 1) % PS_MAX_JOBS;\n pool->cnt++;\n ps_spin_unlock(&pool->lock);\n return;\n }\n\n ps_spin_unlock(&pool->lock);\n PS_YIELD();\n }""", +) + +replace_once( + "src/polesitter.h", + """ while (1) {\n if (pool->cnt == 0 && pool->active_jobs == 0) {\n\n ps_spin_lock(&pool->lock);\n int done = (pool->cnt == 0 && pool->active_jobs == 0);\n ps_spin_unlock(&pool->lock);\n\n if (done)\n return;\n }\n PS_YIELD();\n }""", + """ while (1) {\n ps_spin_lock(&pool->lock);\n int done = (pool->cnt == 0 && pool->active_jobs == 0);\n ps_spin_unlock(&pool->lock);\n\n if (done) {\n return;\n }\n\n PS_YIELD();\n }""", +) + +replace_once( + "src/polesitter.h", + """ while (1) {\n while (pool->cnt == 0 && !pool->shutdown_flag) {\n PS_YIELD();\n }\n\n ps_spin_lock(&pool->lock);\n\n if (pool->cnt == 0) {\n ps_spin_unlock(&pool->lock);\n if (pool->shutdown_flag) {\n break;\n }\n\n continue;\n }\n\n // dequeue\n ps_job_t job = pool->queue[pool->hd];\n pool->hd = (pool->hd + 1) % PS_MAX_JOBS;\n\n pool->active_jobs++;\n pool->cnt--;\n ps_spin_unlock(&pool->lock);""", + """ while (1) {\n ps_spin_lock(&pool->lock);\n\n if (pool->cnt == 0) {\n int shutdown = pool->shutdown_flag;\n ps_spin_unlock(&pool->lock);\n\n if (shutdown) {\n break;\n }\n\n PS_YIELD();\n continue;\n }\n\n // dequeue\n ps_job_t job = pool->queue[pool->hd];\n pool->hd = (pool->hd + 1) % PS_MAX_JOBS;\n\n pool->active_jobs++;\n pool->cnt--;\n ps_spin_unlock(&pool->lock);""", +) + +replace_once( + "tests/accuracy.c", + """ printf(\"[%s] Force mismatch at array index %zu (ID %u): \"\n \"ST(%.2f,%.2f,%.2f) MT(%.2f,%.2f,%.2f)\",\n phase, i, st->id[i], st->fx[i], st->fy[i], st->fz[i],\n mt->fx[i], mt->fy[i], mt->fz[i]);\n\n if (mismatches >= 5) {""", + """ printf(\"[%s] Force mismatch at array index %zu (ID %u): \"\n \"ST(%.2f,%.2f,%.2f) MT(%.2f,%.2f,%.2f)\",\n phase, i, st->id[i], st->fx[i], st->fy[i], st->fz[i],\n mt->fx[i], mt->fy[i], mt->fz[i]);\n mismatches++;\n\n if (mismatches >= 5) {""", +) + +replace_once( + "README.md", + """Include the header in one C file with `POLESITTER_IMPLEMENTATION` defined.\n\n```c\n#define POLESITTER_IMPLEMENTATION\n#include \"polesitter.h\"\n```""", + """Include the header in one C file with `POLESITTER_IMPLEMENTATION` defined. Define `PS_MULTITHREADING` as well when using more than one thread.\n\n```c\n#define PS_MULTITHREADING\n#define POLESITTER_IMPLEMENTATION\n#include \"polesitter.h\"\n```""", +) + +replace_once( + "README.md", + """#include \n#include """, + """#include \n#include \n#include """, +) + +replace_once( + "README.md", + """ // initialize the context\n ps_config_t cfg = { memory_block, MEMORY_SIZE };\n ps_context_t* ctx = NULL;\n ps_init(&ctx, &cfg);""", + """ // initialize the context\n ps_config_t cfg = {\n .buff = memory_block,\n .buff_size = MEMORY_SIZE,\n .max_particles = PARTICLE_CNT,\n .theta = 2.0F,\n .thrd_cnt = 4,\n };\n ps_context_t* ctx = NULL;\n ps_init(&ctx, &cfg);""", +) + +replace_once( + "README.md", + """ // reset arena and force accumulators for the new frame\n ps_arena_clear(&ctx->arena);\n for (int i = 0; i < PARTICLE_CNT; ++i) {""", + """ // reset IDs and force accumulators for the new frame\n for (int i = 0; i < PARTICLE_CNT; ++i) {""", +) + +replace_once( + "README.md", + """ // cleanup\n free(memory_block);\n return 0;""", + """ // cleanup\n ps_destroy(ctx);\n free(memory_block);\n return 0;""", +) From 588a5de1913196d3fc20dadc7ca85ac4473860f7 Mon Sep 17 00:00:00 2001 From: Relay Date: Fri, 7 Aug 2026 21:39:32 -0400 Subject: [PATCH 5/6] chore: execute threading correctness patch --- .../execute-threading-correctness-patch.yml | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/execute-threading-correctness-patch.yml diff --git a/.github/workflows/execute-threading-correctness-patch.yml b/.github/workflows/execute-threading-correctness-patch.yml new file mode 100644 index 0000000..9d49680 --- /dev/null +++ b/.github/workflows/execute-threading-correctness-patch.yml @@ -0,0 +1,30 @@ +name: Execute threading correctness patch + +on: + push: + branches: + - agent/threading-correctness-followup + +permissions: + contents: write + +jobs: + patch: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + ref: agent/threading-correctness-followup + fetch-depth: 0 + - run: python .github/apply_threading_correctness.py + - run: | + rm .github/apply_threading_correctness.py + rm .github/workflows/apply-threading-correctness.yml + rm .github/workflows/run-threading-correctness-patch.yml + rm .github/workflows/execute-threading-correctness-patch.yml + rm .github/workflows/probe.yml + git config user.name "project-relay" + git config user.email "csheldrick+relay@gmail.com" + git add README.md src/polesitter.h tests/accuracy.c .github + git commit -m "fix: tighten threading correctness and v2 docs" + git push origin HEAD:agent/threading-correctness-followup From 8347c7214c5158dd52722c76880db72e0348d0d1 Mon Sep 17 00:00:00 2001 From: project-relay Date: Sat, 8 Aug 2026 01:39:39 +0000 Subject: [PATCH 6/6] fix: tighten threading correctness and v2 docs --- .github/apply_threading_correctness.py | 85 ------------- .../workflows/apply-threading-correctness.yml | 117 ------------------ .../execute-threading-correctness-patch.yml | 30 ----- .github/workflows/probe.yml | 10 -- .../run-threading-correctness-patch.yml | 116 ----------------- README.md | 16 ++- src/polesitter.h | 71 ++++++----- tests/accuracy.c | 1 + 8 files changed, 54 insertions(+), 392 deletions(-) delete mode 100644 .github/apply_threading_correctness.py delete mode 100644 .github/workflows/apply-threading-correctness.yml delete mode 100644 .github/workflows/execute-threading-correctness-patch.yml delete mode 100644 .github/workflows/probe.yml delete mode 100644 .github/workflows/run-threading-correctness-patch.yml diff --git a/.github/apply_threading_correctness.py b/.github/apply_threading_correctness.py deleted file mode 100644 index 15f975c..0000000 --- a/.github/apply_threading_correctness.py +++ /dev/null @@ -1,85 +0,0 @@ -from pathlib import Path - - -def replace_once(path: str, old: str, new: str) -> None: - p = Path(path) - text = p.read_text() - count = text.count(old) - if count != 1: - raise SystemExit(f"expected exactly one match in {path}, found {count}") - p.write_text(text.replace(old, new, 1)) - - -replace_once( - "src/polesitter.h", - "#endif // POLESITTER_H\n\n#define POLESITTER_IMPLEMENTATION\n#ifdef POLESITTER_IMPLEMENTATION", - "#endif // POLESITTER_H\n\n#ifdef POLESITTER_IMPLEMENTATION", -) - -replace_once( - "src/polesitter.h", - """#if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || \\ - defined(_M_IX86)\n\n#define PS_YIELD() _mm_pause()\n#elif defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__)\n#define PS_YIELD() __asm__ volatile(\"yield\" ::: \"memory\")\n#else\n#define PS_YIELD()\n#endif""", - """#if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || \\ - defined(_M_IX86)\n\n#ifdef _WIN32\n#define PS_YIELD() YieldProcessor()\n#else\n#define PS_YIELD() __asm__ volatile(\"pause\" ::: \"memory\")\n#endif\n#elif defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__)\n#define PS_YIELD() __asm__ volatile(\"yield\" ::: \"memory\")\n#else\n#define PS_YIELD()\n#endif""", -) - -replace_once( - "src/polesitter.h", - """ ps_job_t queue[PS_MAX_JOBS];\n volatile int hd;\n volatile int tl;\n volatile int cnt;\n volatile int active_jobs;\n volatile int shutdown_flag;\n\n ps_spinlock_t lock;""", - """ ps_job_t queue[PS_MAX_JOBS];\n int hd;\n int tl;\n int cnt;\n int active_jobs;\n int shutdown_flag;\n\n ps_spinlock_t lock;""", -) - -replace_once( - "src/polesitter.h", - """ // block if buffer is full\n while (pool->cnt == PS_MAX_JOBS && !pool->shutdown_flag) {\n PS_YIELD();\n }\n\n // enqueue\n ps_spin_lock(&pool->lock);\n pool->queue[pool->tl] = job;\n pool->tl = (pool->tl + 1) % PS_MAX_JOBS;\n pool->cnt++;\n ps_spin_unlock(&pool->lock);\n return;""", - """ // Queue state is shared with workers; inspect and update it only\n // while holding the spinlock. volatile does not make these accesses\n // atomic or establish inter-thread ordering in C.\n while (1) {\n ps_spin_lock(&pool->lock);\n\n if (pool->shutdown_flag) {\n ps_spin_unlock(&pool->lock);\n return;\n }\n\n if (pool->cnt < PS_MAX_JOBS) {\n pool->queue[pool->tl] = job;\n pool->tl = (pool->tl + 1) % PS_MAX_JOBS;\n pool->cnt++;\n ps_spin_unlock(&pool->lock);\n return;\n }\n\n ps_spin_unlock(&pool->lock);\n PS_YIELD();\n }""", -) - -replace_once( - "src/polesitter.h", - """ while (1) {\n if (pool->cnt == 0 && pool->active_jobs == 0) {\n\n ps_spin_lock(&pool->lock);\n int done = (pool->cnt == 0 && pool->active_jobs == 0);\n ps_spin_unlock(&pool->lock);\n\n if (done)\n return;\n }\n PS_YIELD();\n }""", - """ while (1) {\n ps_spin_lock(&pool->lock);\n int done = (pool->cnt == 0 && pool->active_jobs == 0);\n ps_spin_unlock(&pool->lock);\n\n if (done) {\n return;\n }\n\n PS_YIELD();\n }""", -) - -replace_once( - "src/polesitter.h", - """ while (1) {\n while (pool->cnt == 0 && !pool->shutdown_flag) {\n PS_YIELD();\n }\n\n ps_spin_lock(&pool->lock);\n\n if (pool->cnt == 0) {\n ps_spin_unlock(&pool->lock);\n if (pool->shutdown_flag) {\n break;\n }\n\n continue;\n }\n\n // dequeue\n ps_job_t job = pool->queue[pool->hd];\n pool->hd = (pool->hd + 1) % PS_MAX_JOBS;\n\n pool->active_jobs++;\n pool->cnt--;\n ps_spin_unlock(&pool->lock);""", - """ while (1) {\n ps_spin_lock(&pool->lock);\n\n if (pool->cnt == 0) {\n int shutdown = pool->shutdown_flag;\n ps_spin_unlock(&pool->lock);\n\n if (shutdown) {\n break;\n }\n\n PS_YIELD();\n continue;\n }\n\n // dequeue\n ps_job_t job = pool->queue[pool->hd];\n pool->hd = (pool->hd + 1) % PS_MAX_JOBS;\n\n pool->active_jobs++;\n pool->cnt--;\n ps_spin_unlock(&pool->lock);""", -) - -replace_once( - "tests/accuracy.c", - """ printf(\"[%s] Force mismatch at array index %zu (ID %u): \"\n \"ST(%.2f,%.2f,%.2f) MT(%.2f,%.2f,%.2f)\",\n phase, i, st->id[i], st->fx[i], st->fy[i], st->fz[i],\n mt->fx[i], mt->fy[i], mt->fz[i]);\n\n if (mismatches >= 5) {""", - """ printf(\"[%s] Force mismatch at array index %zu (ID %u): \"\n \"ST(%.2f,%.2f,%.2f) MT(%.2f,%.2f,%.2f)\",\n phase, i, st->id[i], st->fx[i], st->fy[i], st->fz[i],\n mt->fx[i], mt->fy[i], mt->fz[i]);\n mismatches++;\n\n if (mismatches >= 5) {""", -) - -replace_once( - "README.md", - """Include the header in one C file with `POLESITTER_IMPLEMENTATION` defined.\n\n```c\n#define POLESITTER_IMPLEMENTATION\n#include \"polesitter.h\"\n```""", - """Include the header in one C file with `POLESITTER_IMPLEMENTATION` defined. Define `PS_MULTITHREADING` as well when using more than one thread.\n\n```c\n#define PS_MULTITHREADING\n#define POLESITTER_IMPLEMENTATION\n#include \"polesitter.h\"\n```""", -) - -replace_once( - "README.md", - """#include \n#include """, - """#include \n#include \n#include """, -) - -replace_once( - "README.md", - """ // initialize the context\n ps_config_t cfg = { memory_block, MEMORY_SIZE };\n ps_context_t* ctx = NULL;\n ps_init(&ctx, &cfg);""", - """ // initialize the context\n ps_config_t cfg = {\n .buff = memory_block,\n .buff_size = MEMORY_SIZE,\n .max_particles = PARTICLE_CNT,\n .theta = 2.0F,\n .thrd_cnt = 4,\n };\n ps_context_t* ctx = NULL;\n ps_init(&ctx, &cfg);""", -) - -replace_once( - "README.md", - """ // reset arena and force accumulators for the new frame\n ps_arena_clear(&ctx->arena);\n for (int i = 0; i < PARTICLE_CNT; ++i) {""", - """ // reset IDs and force accumulators for the new frame\n for (int i = 0; i < PARTICLE_CNT; ++i) {""", -) - -replace_once( - "README.md", - """ // cleanup\n free(memory_block);\n return 0;""", - """ // cleanup\n ps_destroy(ctx);\n free(memory_block);\n return 0;""", -) diff --git a/.github/workflows/apply-threading-correctness.yml b/.github/workflows/apply-threading-correctness.yml deleted file mode 100644 index 74671a0..0000000 --- a/.github/workflows/apply-threading-correctness.yml +++ /dev/null @@ -1,117 +0,0 @@ -name: Apply threading correctness follow-up - -on: - push: - branches: ["agent/threading-correctness-followup"] - -permissions: - contents: write - -jobs: - patch: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - ref: agent/threading-correctness-followup - fetch-depth: 0 - - - name: Apply focused fixes - shell: python - run: | - from pathlib import Path - - def replace_once(path, old, new): - p = Path(path) - text = p.read_text() - if text.count(old) != 1: - raise SystemExit(f"expected exactly one match in {path}, found {text.count(old)}") - p.write_text(text.replace(old, new, 1)) - - # Restore actual stb-style opt-in implementation semantics. - replace_once( - "src/polesitter.h", - "#endif // POLESITTER_H\n\n#define POLESITTER_IMPLEMENTATION\n#ifdef POLESITTER_IMPLEMENTATION", - "#endif // POLESITTER_H\n\n#ifdef POLESITTER_IMPLEMENTATION", - ) - - # Do not rely on AVX headers merely to issue a spin-wait hint. - replace_once( - "src/polesitter.h", - """#if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || \\ - defined(_M_IX86)\n\n#define PS_YIELD() _mm_pause()\n#elif defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__)\n#define PS_YIELD() __asm__ volatile(\"yield\" ::: \"memory\")\n#else\n#define PS_YIELD()\n#endif""", - """#if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || \\ - defined(_M_IX86)\n\n#ifdef _WIN32\n#define PS_YIELD() YieldProcessor()\n#else\n#define PS_YIELD() __asm__ volatile(\"pause\" ::: \"memory\")\n#endif\n#elif defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__)\n#define PS_YIELD() __asm__ volatile(\"yield\" ::: \"memory\")\n#else\n#define PS_YIELD()\n#endif""", - ) - - # Pool state is protected by the spinlock; volatile is not synchronization. - replace_once( - "src/polesitter.h", - """ ps_job_t queue[PS_MAX_JOBS];\n volatile int hd;\n volatile int tl;\n volatile int cnt;\n volatile int active_jobs;\n volatile int shutdown_flag;\n\n ps_spinlock_t lock;""", - """ ps_job_t queue[PS_MAX_JOBS];\n int hd;\n int tl;\n int cnt;\n int active_jobs;\n int shutdown_flag;\n\n ps_spinlock_t lock;""", - ) - - replace_once( - "src/polesitter.h", - """ // block if buffer is full\n while (pool->cnt == PS_MAX_JOBS && !pool->shutdown_flag) {\n PS_YIELD();\n }\n\n // enqueue\n ps_spin_lock(&pool->lock);\n pool->queue[pool->tl] = job;\n pool->tl = (pool->tl + 1) % PS_MAX_JOBS;\n pool->cnt++;\n ps_spin_unlock(&pool->lock);\n return;""", - """ // Queue state is shared with workers; inspect and update it only\n // while holding the spinlock. volatile does not make these accesses\n // atomic or establish inter-thread ordering in C.\n while (1) {\n ps_spin_lock(&pool->lock);\n\n if (pool->shutdown_flag) {\n ps_spin_unlock(&pool->lock);\n return;\n }\n\n if (pool->cnt < PS_MAX_JOBS) {\n pool->queue[pool->tl] = job;\n pool->tl = (pool->tl + 1) % PS_MAX_JOBS;\n pool->cnt++;\n ps_spin_unlock(&pool->lock);\n return;\n }\n\n ps_spin_unlock(&pool->lock);\n PS_YIELD();\n }""", - ) - - replace_once( - "src/polesitter.h", - """ while (1) {\n if (pool->cnt == 0 && pool->active_jobs == 0) {\n\n ps_spin_lock(&pool->lock);\n int done = (pool->cnt == 0 && pool->active_jobs == 0);\n ps_spin_unlock(&pool->lock);\n\n if (done)\n return;\n }\n PS_YIELD();\n }""", - """ while (1) {\n ps_spin_lock(&pool->lock);\n int done = (pool->cnt == 0 && pool->active_jobs == 0);\n ps_spin_unlock(&pool->lock);\n\n if (done) {\n return;\n }\n\n PS_YIELD();\n }""", - ) - - replace_once( - "src/polesitter.h", - """ while (1) {\n while (pool->cnt == 0 && !pool->shutdown_flag) {\n PS_YIELD();\n }\n\n ps_spin_lock(&pool->lock);\n\n if (pool->cnt == 0) {\n ps_spin_unlock(&pool->lock);\n if (pool->shutdown_flag) {\n break;\n }\n\n continue;\n }\n\n // dequeue\n ps_job_t job = pool->queue[pool->hd];\n pool->hd = (pool->hd + 1) % PS_MAX_JOBS;\n\n pool->active_jobs++;\n pool->cnt--;\n ps_spin_unlock(&pool->lock);""", - """ while (1) {\n ps_spin_lock(&pool->lock);\n\n if (pool->cnt == 0) {\n int shutdown = pool->shutdown_flag;\n ps_spin_unlock(&pool->lock);\n\n if (shutdown) {\n break;\n }\n\n PS_YIELD();\n continue;\n }\n\n // dequeue\n ps_job_t job = pool->queue[pool->hd];\n pool->hd = (pool->hd + 1) % PS_MAX_JOBS;\n\n pool->active_jobs++;\n pool->cnt--;\n ps_spin_unlock(&pool->lock);""", - ) - - # A force mismatch must actually fail the ST-vs-MT equivalence check. - replace_once( - "tests/accuracy.c", - """ printf(\"[%s] Force mismatch at array index %zu (ID %u): \"\n \"ST(%.2f,%.2f,%.2f) MT(%.2f,%.2f,%.2f)\",\n phase, i, st->id[i], st->fx[i], st->fy[i], st->fz[i],\n mt->fx[i], mt->fy[i], mt->fz[i]);\n\n if (mismatches >= 5) {""", - """ printf(\"[%s] Force mismatch at array index %zu (ID %u): \"\n \"ST(%.2f,%.2f,%.2f) MT(%.2f,%.2f,%.2f)\",\n phase, i, st->id[i], st->fx[i], st->fy[i], st->fz[i],\n mt->fx[i], mt->fy[i], mt->fz[i]);\n mismatches++;\n\n if (mismatches >= 5) {""", - ) - - # Bring the README quickstart up to the v2 API. - replace_once( - "README.md", - """Include the header in one C file with `POLESITTER_IMPLEMENTATION` defined.\n\n```c\n#define POLESITTER_IMPLEMENTATION\n#include \"polesitter.h\"\n```""", - """Include the header in one C file with `POLESITTER_IMPLEMENTATION` defined. Define `PS_MULTITHREADING` as well when using more than one thread.\n\n```c\n#define PS_MULTITHREADING\n#define POLESITTER_IMPLEMENTATION\n#include \"polesitter.h\"\n```""", - ) - - replace_once( - "README.md", - """#include \n#include """, - """#include \n#include \n#include """, - ) - - replace_once( - "README.md", - """ // initialize the context\n ps_config_t cfg = { memory_block, MEMORY_SIZE };\n ps_context_t* ctx = NULL;\n ps_init(&ctx, &cfg);""", - """ // initialize the context\n ps_config_t cfg = {\n .buff = memory_block,\n .buff_size = MEMORY_SIZE,\n .max_particles = PARTICLE_CNT,\n .theta = 2.0F,\n .thrd_cnt = 4,\n };\n ps_context_t* ctx = NULL;\n ps_init(&ctx, &cfg);""", - ) - - replace_once( - "README.md", - """ // reset arena and force accumulators for the new frame\n ps_arena_clear(&ctx->arena);\n for (int i = 0; i < PARTICLE_CNT; ++i) {""", - """ // reset IDs and force accumulators for the new frame\n for (int i = 0; i < PARTICLE_CNT; ++i) {""", - ) - - replace_once( - "README.md", - """ // cleanup\n free(memory_block);\n return 0;""", - """ // cleanup\n ps_destroy(ctx);\n free(memory_block);\n return 0;""", - ) - - - name: Commit focused fixes and remove helper workflow - run: | - rm .github/workflows/apply-threading-correctness.yml - git config user.name "project-relay" - git config user.email "csheldrick+relay@gmail.com" - git add README.md src/polesitter.h tests/accuracy.c .github/workflows/apply-threading-correctness.yml - git commit -m "fix: tighten threading correctness and v2 docs" - git push origin HEAD:agent/threading-correctness-followup diff --git a/.github/workflows/execute-threading-correctness-patch.yml b/.github/workflows/execute-threading-correctness-patch.yml deleted file mode 100644 index 9d49680..0000000 --- a/.github/workflows/execute-threading-correctness-patch.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Execute threading correctness patch - -on: - push: - branches: - - agent/threading-correctness-followup - -permissions: - contents: write - -jobs: - patch: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - ref: agent/threading-correctness-followup - fetch-depth: 0 - - run: python .github/apply_threading_correctness.py - - run: | - rm .github/apply_threading_correctness.py - rm .github/workflows/apply-threading-correctness.yml - rm .github/workflows/run-threading-correctness-patch.yml - rm .github/workflows/execute-threading-correctness-patch.yml - rm .github/workflows/probe.yml - git config user.name "project-relay" - git config user.email "csheldrick+relay@gmail.com" - git add README.md src/polesitter.h tests/accuracy.c .github - git commit -m "fix: tighten threading correctness and v2 docs" - git push origin HEAD:agent/threading-correctness-followup diff --git a/.github/workflows/probe.yml b/.github/workflows/probe.yml deleted file mode 100644 index 9fc63c5..0000000 --- a/.github/workflows/probe.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: Probe -on: - push: - branches: - - agent/threading-correctness-followup -jobs: - probe: - runs-on: ubuntu-latest - steps: - - run: echo hello diff --git a/.github/workflows/run-threading-correctness-patch.yml b/.github/workflows/run-threading-correctness-patch.yml deleted file mode 100644 index 728d870..0000000 --- a/.github/workflows/run-threading-correctness-patch.yml +++ /dev/null @@ -1,116 +0,0 @@ -name: Run threading correctness patch - -on: - push: - branches: - - agent/threading-correctness-followup - -permissions: - contents: write - -jobs: - patch: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - ref: agent/threading-correctness-followup - fetch-depth: 0 - - - name: Apply focused fixes - run: | - python - <<'PY' - from pathlib import Path - - def replace_once(path, old, new): - p = Path(path) - text = p.read_text() - count = text.count(old) - if count != 1: - raise SystemExit(f"expected exactly one match in {path}, found {count}") - p.write_text(text.replace(old, new, 1)) - - replace_once( - "src/polesitter.h", - "#endif // POLESITTER_H\n\n#define POLESITTER_IMPLEMENTATION\n#ifdef POLESITTER_IMPLEMENTATION", - "#endif // POLESITTER_H\n\n#ifdef POLESITTER_IMPLEMENTATION", - ) - - replace_once( - "src/polesitter.h", - """#if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || \\ - defined(_M_IX86)\n\n#define PS_YIELD() _mm_pause()\n#elif defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__)\n#define PS_YIELD() __asm__ volatile(\"yield\" ::: \"memory\")\n#else\n#define PS_YIELD()\n#endif""", - """#if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || \\ - defined(_M_IX86)\n\n#ifdef _WIN32\n#define PS_YIELD() YieldProcessor()\n#else\n#define PS_YIELD() __asm__ volatile(\"pause\" ::: \"memory\")\n#endif\n#elif defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__)\n#define PS_YIELD() __asm__ volatile(\"yield\" ::: \"memory\")\n#else\n#define PS_YIELD()\n#endif""", - ) - - replace_once( - "src/polesitter.h", - """ ps_job_t queue[PS_MAX_JOBS];\n volatile int hd;\n volatile int tl;\n volatile int cnt;\n volatile int active_jobs;\n volatile int shutdown_flag;\n\n ps_spinlock_t lock;""", - """ ps_job_t queue[PS_MAX_JOBS];\n int hd;\n int tl;\n int cnt;\n int active_jobs;\n int shutdown_flag;\n\n ps_spinlock_t lock;""", - ) - - replace_once( - "src/polesitter.h", - """ // block if buffer is full\n while (pool->cnt == PS_MAX_JOBS && !pool->shutdown_flag) {\n PS_YIELD();\n }\n\n // enqueue\n ps_spin_lock(&pool->lock);\n pool->queue[pool->tl] = job;\n pool->tl = (pool->tl + 1) % PS_MAX_JOBS;\n pool->cnt++;\n ps_spin_unlock(&pool->lock);\n return;""", - """ // Queue state is shared with workers; inspect and update it only\n // while holding the spinlock. volatile does not make these accesses\n // atomic or establish inter-thread ordering in C.\n while (1) {\n ps_spin_lock(&pool->lock);\n\n if (pool->shutdown_flag) {\n ps_spin_unlock(&pool->lock);\n return;\n }\n\n if (pool->cnt < PS_MAX_JOBS) {\n pool->queue[pool->tl] = job;\n pool->tl = (pool->tl + 1) % PS_MAX_JOBS;\n pool->cnt++;\n ps_spin_unlock(&pool->lock);\n return;\n }\n\n ps_spin_unlock(&pool->lock);\n PS_YIELD();\n }""", - ) - - replace_once( - "src/polesitter.h", - """ while (1) {\n if (pool->cnt == 0 && pool->active_jobs == 0) {\n\n ps_spin_lock(&pool->lock);\n int done = (pool->cnt == 0 && pool->active_jobs == 0);\n ps_spin_unlock(&pool->lock);\n\n if (done)\n return;\n }\n PS_YIELD();\n }""", - """ while (1) {\n ps_spin_lock(&pool->lock);\n int done = (pool->cnt == 0 && pool->active_jobs == 0);\n ps_spin_unlock(&pool->lock);\n\n if (done) {\n return;\n }\n\n PS_YIELD();\n }""", - ) - - replace_once( - "src/polesitter.h", - """ while (1) {\n while (pool->cnt == 0 && !pool->shutdown_flag) {\n PS_YIELD();\n }\n\n ps_spin_lock(&pool->lock);\n\n if (pool->cnt == 0) {\n ps_spin_unlock(&pool->lock);\n if (pool->shutdown_flag) {\n break;\n }\n\n continue;\n }\n\n // dequeue\n ps_job_t job = pool->queue[pool->hd];\n pool->hd = (pool->hd + 1) % PS_MAX_JOBS;\n\n pool->active_jobs++;\n pool->cnt--;\n ps_spin_unlock(&pool->lock);""", - """ while (1) {\n ps_spin_lock(&pool->lock);\n\n if (pool->cnt == 0) {\n int shutdown = pool->shutdown_flag;\n ps_spin_unlock(&pool->lock);\n\n if (shutdown) {\n break;\n }\n\n PS_YIELD();\n continue;\n }\n\n // dequeue\n ps_job_t job = pool->queue[pool->hd];\n pool->hd = (pool->hd + 1) % PS_MAX_JOBS;\n\n pool->active_jobs++;\n pool->cnt--;\n ps_spin_unlock(&pool->lock);""", - ) - - replace_once( - "tests/accuracy.c", - """ printf(\"[%s] Force mismatch at array index %zu (ID %u): \"\n \"ST(%.2f,%.2f,%.2f) MT(%.2f,%.2f,%.2f)\",\n phase, i, st->id[i], st->fx[i], st->fy[i], st->fz[i],\n mt->fx[i], mt->fy[i], mt->fz[i]);\n\n if (mismatches >= 5) {""", - """ printf(\"[%s] Force mismatch at array index %zu (ID %u): \"\n \"ST(%.2f,%.2f,%.2f) MT(%.2f,%.2f,%.2f)\",\n phase, i, st->id[i], st->fx[i], st->fy[i], st->fz[i],\n mt->fx[i], mt->fy[i], mt->fz[i]);\n mismatches++;\n\n if (mismatches >= 5) {""", - ) - - replace_once( - "README.md", - """Include the header in one C file with `POLESITTER_IMPLEMENTATION` defined.\n\n```c\n#define POLESITTER_IMPLEMENTATION\n#include \"polesitter.h\"\n```""", - """Include the header in one C file with `POLESITTER_IMPLEMENTATION` defined. Define `PS_MULTITHREADING` as well when using more than one thread.\n\n```c\n#define PS_MULTITHREADING\n#define POLESITTER_IMPLEMENTATION\n#include \"polesitter.h\"\n```""", - ) - - replace_once( - "README.md", - """#include \n#include """, - """#include \n#include \n#include """, - ) - - replace_once( - "README.md", - """ // initialize the context\n ps_config_t cfg = { memory_block, MEMORY_SIZE };\n ps_context_t* ctx = NULL;\n ps_init(&ctx, &cfg);""", - """ // initialize the context\n ps_config_t cfg = {\n .buff = memory_block,\n .buff_size = MEMORY_SIZE,\n .max_particles = PARTICLE_CNT,\n .theta = 2.0F,\n .thrd_cnt = 4,\n };\n ps_context_t* ctx = NULL;\n ps_init(&ctx, &cfg);""", - ) - - replace_once( - "README.md", - """ // reset arena and force accumulators for the new frame\n ps_arena_clear(&ctx->arena);\n for (int i = 0; i < PARTICLE_CNT; ++i) {""", - """ // reset IDs and force accumulators for the new frame\n for (int i = 0; i < PARTICLE_CNT; ++i) {""", - ) - - replace_once( - "README.md", - """ // cleanup\n free(memory_block);\n return 0;""", - """ // cleanup\n ps_destroy(ctx);\n free(memory_block);\n return 0;""", - ) - PY - - - name: Commit focused fixes and remove helper files - run: | - rm .github/workflows/apply-threading-correctness.yml - rm .github/workflows/run-threading-correctness-patch.yml - git config user.name "project-relay" - git config user.email "csheldrick+relay@gmail.com" - git add README.md src/polesitter.h tests/accuracy.c .github/workflows - git commit -m "fix: tighten threading correctness and v2 docs" - git push origin HEAD:agent/threading-correctness-followup diff --git a/README.md b/README.md index 209aa79..b2e342b 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,10 @@ ## Quickstart -Include the header in one C file with `POLESITTER_IMPLEMENTATION` defined. +Include the header in one C file with `POLESITTER_IMPLEMENTATION` defined. Define `PS_MULTITHREADING` as well when using more than one thread. ```c +#define PS_MULTITHREADING #define POLESITTER_IMPLEMENTATION #include "polesitter.h" ``` @@ -18,6 +19,7 @@ Include the header in one C file with `POLESITTER_IMPLEMENTATION` defined. ### Basic setup ```c +#include #include #include @@ -29,7 +31,13 @@ int main(void) { void* memory_block = malloc(MEMORY_SIZE); // initialize the context - ps_config_t cfg = { memory_block, MEMORY_SIZE }; + ps_config_t cfg = { + .buff = memory_block, + .buff_size = MEMORY_SIZE, + .max_particles = PARTICLE_CNT, + .theta = 2.0F, + .thrd_cnt = 4, + }; ps_context_t* ctx = NULL; ps_init(&ctx, &cfg); @@ -55,8 +63,7 @@ int main(void) { float dt = 0.016F; // 60FPS bool running = true; while (running) { - // reset arena and force accumulators for the new frame - ps_arena_clear(&ctx->arena); + // reset IDs and force accumulators for the new frame for (int i = 0; i < PARTICLE_CNT; ++i) { ids[i] = i; // reset ids before sorting fx[i] = 0.0F; fy[i] = 0.0F; fz[i] = 0.0F; @@ -105,6 +112,7 @@ int main(void) { } // cleanup + ps_destroy(ctx); free(memory_block); return 0; } diff --git a/src/polesitter.h b/src/polesitter.h index d9ade04..8177212 100644 --- a/src/polesitter.h +++ b/src/polesitter.h @@ -251,7 +251,11 @@ typedef void* (*ps_thrd_func_t)(void*); #if defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || \ defined(_M_IX86) -#define PS_YIELD() _mm_pause() +#ifdef _WIN32 +#define PS_YIELD() YieldProcessor() +#else +#define PS_YIELD() __asm__ volatile("pause" ::: "memory") +#endif #elif defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) #define PS_YIELD() __asm__ volatile("yield" ::: "memory") #else @@ -336,7 +340,6 @@ ps_result_t ps_destroy(ps_context_t* ctx); #endif // POLESITTER_H -#define POLESITTER_IMPLEMENTATION #ifdef POLESITTER_IMPLEMENTATION // ===================================================================== @@ -432,12 +435,12 @@ typedef struct { ps_thrd_t thrds[PS_MAX_THRDS]; uint32_t thrd_cnt; - ps_job_t queue[PS_MAX_JOBS]; - volatile int hd; - volatile int tl; - volatile int cnt; - volatile int active_jobs; - volatile int shutdown_flag; + ps_job_t queue[PS_MAX_JOBS]; + int hd; + int tl; + int cnt; + int active_jobs; + int shutdown_flag; ps_spinlock_t lock; } ps_thrd_pool_t; @@ -640,18 +643,28 @@ static void ps_impl_pool_submit(ps_context_t* ctx, ps_job_t job) { if (ctx->pool.thrd_cnt > 0) { ps_thrd_pool_t* pool = &ctx->pool; - // block if buffer is full - while (pool->cnt == PS_MAX_JOBS && !pool->shutdown_flag) { + // Queue state is shared with workers; inspect and update it only + // while holding the spinlock. volatile does not make these accesses + // atomic or establish inter-thread ordering in C. + while (1) { + ps_spin_lock(&pool->lock); + + if (pool->shutdown_flag) { + ps_spin_unlock(&pool->lock); + return; + } + + if (pool->cnt < PS_MAX_JOBS) { + pool->queue[pool->tl] = job; + pool->tl = (pool->tl + 1) % PS_MAX_JOBS; + pool->cnt++; + ps_spin_unlock(&pool->lock); + return; + } + + ps_spin_unlock(&pool->lock); PS_YIELD(); } - - // enqueue - ps_spin_lock(&pool->lock); - pool->queue[pool->tl] = job; - pool->tl = (pool->tl + 1) % PS_MAX_JOBS; - pool->cnt++; - ps_spin_unlock(&pool->lock); - return; } #endif @@ -668,15 +681,14 @@ static void ps_impl_pool_wait(ps_context_t* ctx) { ps_thrd_pool_t* pool = &ctx->pool; while (1) { - if (pool->cnt == 0 && pool->active_jobs == 0) { - - ps_spin_lock(&pool->lock); - int done = (pool->cnt == 0 && pool->active_jobs == 0); - ps_spin_unlock(&pool->lock); + ps_spin_lock(&pool->lock); + int done = (pool->cnt == 0 && pool->active_jobs == 0); + ps_spin_unlock(&pool->lock); - if (done) - return; + if (done) { + return; } + PS_YIELD(); } #endif @@ -689,18 +701,17 @@ static PS_THRD_RET_TYPE ps_impl_worker_loop(void* arg) { ps_thrd_pool_t* pool = w_arg->pool; while (1) { - while (pool->cnt == 0 && !pool->shutdown_flag) { - PS_YIELD(); - } - ps_spin_lock(&pool->lock); if (pool->cnt == 0) { + int shutdown = pool->shutdown_flag; ps_spin_unlock(&pool->lock); - if (pool->shutdown_flag) { + + if (shutdown) { break; } + PS_YIELD(); continue; } diff --git a/tests/accuracy.c b/tests/accuracy.c index 4ef0726..12b807b 100644 --- a/tests/accuracy.c +++ b/tests/accuracy.c @@ -154,6 +154,7 @@ static int comp_arrs(const char* phase, size_t cnt, ps_particle_arrs_t* st, "ST(%.2f,%.2f,%.2f) MT(%.2f,%.2f,%.2f)", phase, i, st->id[i], st->fx[i], st->fy[i], st->fz[i], mt->fx[i], mt->fy[i], mt->fz[i]); + mismatches++; if (mismatches >= 5) { break;