test: group examples into per-category folders

Move examples/*.sx and their expected/ snapshots into per-category
subfolders (examples/<category>/...). Folder = leading filename token,
with ffi-objc/ffi-jni kept whole; filenames are unchanged. The corpus
runner and LSP sweep now discover each category's expected/ dir, while
issues/ stays flat. Example 1058's repo-root-relative companion import
is made file-relative. Path strings embedded in 164 snapshots were
regenerated (path-only changes). Test-layout docs in CLAUDE.md updated.
This commit is contained in:
agra
2026-06-21 14:41:34 +03:00
parent 6d1409bc1f
commit 66bdc70bf1
3357 changed files with 456 additions and 363 deletions

View File

@@ -0,0 +1,17 @@
// Atomic($T) load/store with explicit memory orderings, single-thread.
// Stream A (atomics) A.0 + A.0.5 — the ordering is a comptime `$o: Ordering`
// param (explicit, Rust-style): a.load(.acquire) emits `load atomic … acquire`.
// An invalid combination (a.load(.release)) is a compile error (see 1131).
#import "modules/std.sx";
#import "modules/std/atomic.sx";
main :: () {
a := Atomic(i64).init(7);
print("init: {}\n", a.load(.seq_cst));
a.store(42, .release);
print("after store: {}\n", a.load(.acquire));
a.store(a.load(.relaxed) + 1, .seq_cst);
print("incremented: {}\n", a.load(.seq_cst));
}

View File

@@ -0,0 +1,39 @@
// Atomic($T) read-modify-write: fetch_add/sub/and/or/xor/min/max → LLVM atomicrmw.
// Each returns the OLD value. Stream A (atomics) A.1. Single-thread.
// Also covers signed min/max with NEGATIVES at BOTH comptime (#run) and runtime —
// they must agree (regression: comptime once did an unsigned compare).
#import "modules/std.sx";
#import "modules/std/atomic.sx";
// comptime (#run) signed min/max with a negative — must match runtime.
c_max :: () -> i64 { a := Atomic(i64).init(-5); _ := a.fetch_max(3, .seq_cst); return a.load(.seq_cst); }
c_min :: () -> i64 { a := Atomic(i64).init(-5); _ := a.fetch_min(3, .seq_cst); return a.load(.seq_cst); }
G_MAX :: #run c_max();
G_MIN :: #run c_min();
main :: () {
a := Atomic(i64).init(10);
print("old add: {}\n", a.fetch_add(5, .seq_cst)); // returns 10, now 15
print("old sub: {}\n", a.fetch_sub(3, .acq_rel)); // returns 15, now 12
print("now: {}\n", a.load(.acquire)); // 12
b := Atomic(i64).init(0xF0);
print("old and: {}\n", b.fetch_and(0x3C, .relaxed));// returns 0xF0(240), now 0x30(48)
print("old or: {}\n", b.fetch_or(0x03, .relaxed)); // returns 0x30(48), now 0x33(51)
print("old xor: {}\n", b.fetch_xor(0x0F, .relaxed));// returns 0x33(51), now 0x3C(60)
print("now: {}\n", b.load(.relaxed)); // 60
m := Atomic(i64).init(20);
print("old min: {}\n", m.fetch_min(8, .seq_cst)); // returns 20, now 8
print("old max: {}\n", m.fetch_max(15, .seq_cst)); // returns 8, now 15
print("now: {}\n", m.load(.seq_cst)); // 15
// signed min/max with a negative — comptime (#run) and runtime must agree.
s := Atomic(i64).init(-5);
_ := s.fetch_max(3, .seq_cst);
print("runtime signed max(-5,3): {}\n", s.load(.seq_cst)); // 3
s.store(-5, .seq_cst);
_ := s.fetch_min(3, .seq_cst);
print("runtime signed min(-5,3): {}\n", s.load(.seq_cst)); // -5
print("comptime signed max(-5,3)={} min(-5,3)={}\n", G_MAX, G_MIN); // 3 / -5
}

View File

@@ -0,0 +1,34 @@
// Atomic($T) compare-exchange: compare_exchange / compare_exchange_weak → LLVM
// cmpxchg. Result is `?T` — null = SUCCESS; a present value is the ACTUAL current
// value on failure (for a retry loop). Stream A (atomics) A.2. Single-thread.
#import "modules/std.sx";
#import "modules/std/atomic.sx";
main :: () {
// Successful CAS: 10 == 10 → store 20, returns null.
a := Atomic(i64).init(10);
got := a.compare_exchange(10, 20, .acq_rel, .acquire);
if got == null {
print("cas ok, now: {}\n", a.load(.acquire)); // 20
} else {
print("cas unexpected fail: {}\n", got!);
}
// Failing CAS: 99 != 20 → no store, returns the actual value (20), unchanged.
got2 := a.compare_exchange(99, 0, .seq_cst, .seq_cst);
if got2 == null {
print("cas unexpected ok\n");
} else {
print("cas failed, actual: {}, still: {}\n", got2!, a.load(.seq_cst)); // 20, 20
}
// Retry loop with the weak variant: increment a counter by 5.
counter := Atomic(i64).init(100);
cur := counter.load(.relaxed);
while true {
r := counter.compare_exchange_weak(cur, cur + 5, .acq_rel, .acquire);
if r == null { break; }
cur = r!; // retry with the observed value
}
print("after loop: {}\n", counter.load(.seq_cst)); // 105
}

View File

@@ -0,0 +1,16 @@
// Atomic($T).swap — atomic exchange (LLVM atomicrmw xchg): store the new value,
// return the OLD one. Stream A (atomics) A.3. Single-thread.
// Covers swap at BOTH comptime (#run) and runtime — they must agree.
#import "modules/std.sx";
#import "modules/std/atomic.sx";
c_swap :: () -> i64 { a := Atomic(i64).init(7); old := a.swap(42, .seq_cst); return old * 100 + a.load(.seq_cst); }
G_SWAP :: #run c_swap(); // 742 (old 7, now 42)
main :: () {
a := Atomic(i64).init(7);
old := a.swap(42, .acq_rel);
print("swap old: {}\n", old); // 7
print("swap now: {}\n", a.load(.acquire)); // 42
print("comptime swap: {}\n", G_SWAP); // 742 (matches runtime)
}

View File

@@ -0,0 +1,15 @@
// Standalone memory fence — fence(.ordering) → LLVM fence. Stream A (atomics) A.3.
// (.relaxed is rejected; see 1187.) Single-thread: a fence is observable only as
// "compiled + ran without error" here.
#import "modules/std.sx";
#import "modules/std/atomic.sx";
main :: () {
a := Atomic(i64).init(1);
a.store(2, .relaxed);
fence(.release);
a.store(3, .relaxed);
fence(.acquire);
fence(.seq_cst);
print("after fences: {}\n", a.load(.relaxed)); // 3
}

View File

@@ -0,0 +1,20 @@
// Atomic(bool) — a sub-byte (i1) element atomically loaded/stored. LLVM
// rejects a sub-byte atomic ("atomic memory access' size must be byte-
// sized"), so codegen performs the access in the byte storage type (i8)
// and trunc/zext's the value at the boundary. (rmw/cmpxchg on a bool is
// rejected at the sx level — integer-only — so only load/store apply.)
// Regression (issue 0152): Atomic(bool) emitted an i1 atomic that failed
// LLVM verification; Future.canceled: Atomic(bool) in the async layer hit it.
#import "modules/std.sx";
#import "modules/std/atomic.sx";
main :: () {
a := Atomic(bool).init(false);
print("init: {}\n", a.load(.acquire)); // false
a.store(true, .release);
print("after store: {}\n", a.load(.acquire)); // true
a.store(false, .seq_cst);
print("after reset: {}\n", a.load(.seq_cst)); // false
}

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,3 @@
init: 7
after store: 42
incremented: 43

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,13 @@
old add: 10
old sub: 15
now: 12
old and: 240
old or: 48
old xor: 51
now: 60
old min: 20
old max: 8
now: 15
runtime signed max(-5,3): 3
runtime signed min(-5,3): -5
comptime signed max(-5,3)=3 min(-5,3)=-5

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,3 @@
cas ok, now: 20
cas failed, actual: 20, still: 20
after loop: 105

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,3 @@
swap old: 7
swap now: 42
comptime swap: 742

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
after fences: 3

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,3 @@
init: false
after store: true
after reset: false