From 0e61db1857f2a0864a90a444c2a166929a1c0e95 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 5 Jun 2026 00:36:33 +0900 Subject: [PATCH] =?UTF-8?q?regex:=20fold=205a=20=E2=80=94=20add=5Fthread?= =?UTF-8?q?=20dups=20parent=20captures/rep=5Fcounters=20(ha:568-573)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The loud bound (capture dup not yet portable, #35/#34/#7) flips to the real dup now that #35's spread place-chain sources landed: fresh slice header + spread-append, the D3 spelling of Hare's alloc-dup. The ok/defer-if frees drop (free() is the documented no-op, #27). Dup-independence rows drive add_thread directly: values carried, backing independent both directions, empty parent → empty dup. --- lib/regex/regex.ww | 22 +++++---------- lib/regex/regex_test.ww | 60 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/lib/regex/regex.ww b/lib/regex/regex.ww index 72699613..86c79d0b 100644 --- a/lib/regex/regex.ww +++ b/lib/regex/regex.ww @@ -626,23 +626,15 @@ fn add_thread(threads: *[]thread, parent_idx: size, new_pc: size) (void | nomem) }; }; - // Hare dups the parent's captures/rep_counters here - // (`alloc(threads[parent_idx].captures...)?`, ha:569/572). Every - // ww route into that dup is blocked today (re-probed post-C3, - // PB7): the deref-spine spread SOURCE is loud-rejected (#35), - // the per-element append is loud-rejected (#34 struct element - // source), and the whole-element let-copy drops bytes (#7/F5). - // The abort is sound, not a semantic hole: fold-2a's compile() - // cannot emit inst_groupstart/inst_repeat, so both slices are - // provably empty in every program this fold can run; the empty - // case appends honest zeroed headers below. The verbatim dup - // lands with the group/repeat fold (ww-core #3). - if ((*threads)[parent_idx].captures.len != 0 - || (*threads)[parent_idx].rep_counters.len != 0) { - abort("regex: capture dup not yet portable (#35/#34/#7)"); - }; + // Hare dups via `alloc(threads[parent_idx].captures...)?` + // (ha:568-573); the alloc-dup form has no ww spelling (D3) — + // fresh slice header + spread-append, the #35-landed deref-spine + // source. The ok/defer-if frees (ha:570/573) drop: free() is the + // documented no-op (#27; see finish()). let captures: []capture; + append(captures, (*threads)[parent_idx].captures...); let rep_counters: []size; + append(rep_counters, (*threads)[parent_idx].rep_counters...); append(*threads, thread { pc = new_pc, diff --git a/lib/regex/regex_test.ww b/lib/regex/regex_test.ww index 99557b0d..b27b1c5b 100644 --- a/lib/regex/regex_test.ww +++ b/lib/regex/regex_test.ww @@ -504,9 +504,9 @@ fn ic_one(v: regex.inst, want: bool) void = { // add_thread (regex.ha:557-587): same-pc dedup suppression fires only // when the existing thread is unmatched AND started strictly earlier // than the parent (ha:561-565); otherwise the child appends, -// inheriting the parent's start/matched/failed with fresh empty -// capture/rep_counter headers and a zeroed root_capture. The -// capture-dup loud bound must NOT fire on these empty-caps parents. +// inheriting the parent's start/matched/failed with DUPLICATED +// capture/rep_counter slices (empty parent → empty dup, ha:569/572) +// and a zeroed root_capture. @test fn add_thread_dedup_inherit() void = { let ts: []thread = []; append(ts, thread { pc = 0, start_idx = 5, start_bytesize = 4, @@ -546,6 +546,59 @@ fn ic_one(v: regex.inst, want: bool) void = { if (ts3[2].start_idx != (5: size)) { fail(); }; }; +// add_thread dup (regex.ha:568-573): the child carries a COPY of the +// parent's captures/rep_counters — values equal, backing independent +// in both directions (mutate parent → child unchanged, mutate child → +// parent unchanged). Empty parent → empty dup (the pre-flip rows above +// stay byte-for-byte). Driven directly, the dedup-test precedent. +@test fn add_thread_dup_independence() void = { + let caps: []capture = []; + append(caps, capture { + content = "ab", start = 1, start_bytesize = 1, + end = 2, end_bytesize = 2, + }); + append(caps, capture { + content = "c", start = 3, start_bytesize = 3, + end = 4, end_bytesize = 4, + }); + let reps: []size = []; + append(reps, (5: size)); + append(reps, (6: size)); + let ts: []thread = []; + append(ts, thread { pc = 0, start_idx = 1, captures = caps, + rep_counters = reps, ... }); + let r: (void | nomem) = add_thread(&ts, 0, 9); + if (!(r is void)) { fail(); }; + if (len(ts) != 2) { fail(); }; + // dup carried the parent's values + if (ts[1].captures.len != 2) { fail(); }; + if (strings.compare(ts[1].captures[0].content, "ab") != 0) { fail(); }; + if (ts[1].captures[0].start != (1: size)) { fail(); }; + if (ts[1].captures[1].end != (4: size)) { fail(); }; + if (ts[1].rep_counters.len != 2) { fail(); }; + if (ts[1].rep_counters[0] != (5: size)) { fail(); }; + if (ts[1].rep_counters[1] != (6: size)) { fail(); }; + // independence, parent → child: mutate the parent post-add + ts[0].captures[0].start = 100; + ts[0].captures[0].content = "zz"; + ts[0].rep_counters[0] = 77; + if (ts[1].captures[0].start != (1: size)) { fail(); }; + if (strings.compare(ts[1].captures[0].content, "ab") != 0) { fail(); }; + if (ts[1].rep_counters[0] != (5: size)) { fail(); }; + // independence, child → parent + ts[1].captures[1].end = 200; + ts[1].rep_counters[1] = 88; + if (ts[0].captures[1].end != (4: size)) { fail(); }; + if (ts[0].rep_counters[1] != (6: size)) { fail(); }; + // empty parent → empty dup + let ts2: []thread = []; + append(ts2, thread { pc = 0, ... }); + let r2: (void | nomem) = add_thread(&ts2, 0, 3); + if (!(r2 is void)) { fail(); }; + if (ts2[1].captures.len != 0) { fail(); }; + if (ts2[1].rep_counters.len != 0) { fail(); }; +}; + // run_thread (regex.ha:589-742) driven directly over compile("ab")'s // real program [skip, lit 'a', lit 'b', match(false)] — the arms // fold-2a can emit. Phases: parked-skip spawn (len 1→2, parent pc @@ -1744,5 +1797,6 @@ export fn main() i32 = { signalled = 34; fold4_compile_errors(); signalled = 35; fold4_find_cases(); signalled = 36; fold4_findall(); + signalled = 37; add_thread_dup_independence(); return 0; };