Repository navigation
t27o: Zig front end step 2 -- --check on .zig, sweep lists .zig, imported consts, std.math.pow (Closes #10823) - #10918
Conversation
|
Review (orchestrator, Opus) -- stays a draft until its chain lands (#10883 -> #10819 -> this):
|
…racle says it does not apply (Closes #10823) --check on a .zig file used to run the Rust oracle and fail with "rustc refused the oracle harness". It now writes the converted spec to specs/t27o_check/<stem>.t27 and judges it as #10538 judges Rust specs (t27b test first, t27c test-report only when t27b's exit code is no verdict; t27b_outcome and needs_reference are reused, not copied). --oracle on a .zig file prints one line, that the file's own tests are the oracle, and exits 0. A spec with no converted test passes t27b test as 0 of 0, which is no verdict (#10497): has_evidence refuses it, exit 6 (EXIT_UNJUDGED, outside t27b's table). A Rust file keeps its oracle and is unchanged. ledger.t27 gains front_end (the extension rule `tri t27o` and the sweep both ask) and has_evidence, with tests and negative controls. zig_convert.t27 loses is_zig_name, which front_end replaces (one rule, not two). main.rs: +15 / -17 against the merge base (the own-language budget holds; rc=0). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…_convert.t27's seal too (Closes #10823) sweep_path (ledger.t27) takes a .zig file under the same rule as a .rs one (nothing under gen/ or target/). The glue picks the front end by extension through ledger.t27's front_end, the same rule `tri t27o file.zig` asks. A Zig row's converter key is the Zig seal's spec_hash joined to the Rust converter's (zig_convert.t27 uses rust_convert.t27's helpers), so a change to either makes every Zig row stale and no Rust row: checked by editing the Zig seal, 346 of 1105 trios rows went cold, 759 were reused. zig_convert.t27 numbers its five counters like rust_convert.t27's report (NFN, NOK, NTS, NTEST, NSTUB), so the sweep reads one set of slots for either front end; the output of all 2573 real .zig files is byte-identical to before the renumbering. Two tests pin the slots. kind_to now stops at a semicolon as well as a backtick: the cache row separates kinds with ';', and "generated; convert its source spec instead" was cut in two and counted as its second half. It is now one kind, "generated", with its own count. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…scalar literal when x.zig is in the same tree (Closes #10823) `pub const C = @import("x.zig").C;` made C another file's, so every use of C was refused ("uses external"). zig_convert.t27 now has two pure fns: `imports` lists the paths to read (each once), and `inline_imports` writes `pub const C: u32 = 5;` in place of the import when the imported file holds a `pub const C` that is a scalar literal (integer, float, bool, a string with no escape, at most 64 bytes, under a scalar type or none). The glue (zig_source in main.rs) only reads the files, from the importing file's own directory tree: a path with `..`, a leading `/`, a backslash or a colon is never listed, and a symlink out of the tree is not followed. Anything else is left exactly as it was and stays refused with its reason: a struct, a fn, a const that is not pub, an expression, a non-scalar type, an escaped string, a literal over 64 bytes, a declaration over several lines (no line is added or taken, so every "line N" of a refusal stays true), a missing file. Tests for each, negative controls for all of them, and the bound (output <= 4 x source + 64 bytes) the glue sizes its buffer by. The sweep keys a Zig row by the source with the values written in, so changing an imported literal makes the importing file's row stale and changing a comment in the imported file does not (checked on a two-file repo). Measured on trinity 0770abb99 src/ and trios 483dd9ee crates/: 23 imports of a scalar literal resolve in the tree; 12 are in two files that convert now (src/tri/constants.zig, src/tri/error.zig, consts only: no new fn converts), 11 are in files refused whole as generated. The other 2571 outputs are byte-identical to before. main.rs: +31 / -31 against the merge base. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…new zig_std.t27 (Closes #10823) 103 refusals, the largest left by step 1, were `std.math.pow`. t27 can produce Zig's result, so it does instead of keeping the refusal. Zig 0.16's pow is not libm's: it is Go's math.Pow, a port that squares the significand of x (frexp), counts the exponent apart, takes exp(yf * log x) for the fraction and scales once with ldexp. Plain repeated multiplication is not Zig's result (it differs on 69 of the 191 integer powers among the recorded vectors; libm's pow differs too), so zig_std.t27 (new, like rust_std.t27 beside rust_convert.t27) holds that algorithm step for step in operations t27b and t27c both have: @exp and @log (compiler_rt's, which t27b runs bit for bit), @sqrt, @floor, and Zig's ldexp ported as it is. The port is Zig's code and not the IEEE rule: Zig's ldexp tests an exact tie by `@ctz(repr)`, which counts the exponent field's own zeros, so 0.5 * 2^-1074 becomes 2^-1074 where musl's scalbn (and IEEE) gives 0. The first version used musl's scalbn and the recorded vectors caught it. Evidence: 433 pow vectors, 124 ldexp and 16 frexp vectors recorded with `zig run` (Zig 0.16.0, Debug, no libc) and tested by their bits under t27b AND t27c (the whole 1325-vector set of the lane passed under t27b and t27c before it was cut to these). Mutants of the port (the 0.5 test of the fraction, the +1 of yi, the sign of ae, the 2^63 threshold, the tie rule, the denormal exponent, the 1 / sqrt(x) branch, the 2^53 odd-integer limit, the overflow bound) are each killed. zig_convert.t27 writes `std.math.pow(f64, x, y)` as std_math_pow_f64(x, y) and a `use tools::t27o::zig_std::std_math_pow_f64;` after the header when a fn or test that stayed in the output calls it. Other types and the old two-operand form are refused as their own kind, "pow of a type other than f64"; a call in a file-level const (t27b cannot evaluate it; Zig does at compile time) as "pow in a file-level const". Tests and negative controls for each, and the use line. Measured on trinity 0770abb99 src/ and trios 483dd9ee crates/: converted fns 1837 -> 2022, translated tests 1766 -> 1887; 52 specs call the port, 45 pass under t27b, 5 fail the tests that fail under `zig test` too (phi_lerp, baryon_formulas, spectrum, in both repositories), 2 are not pow's (an `@as(f64, u64)` that Zig 0.16 refuses as well). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…n time, vacuous passes 0 of 61 (Closes #10823) The test that pins the five counters to five different slots compared constants, which Zig folds at compile time: `t27c test-report` counted it as a pass with 0 runtime asserts. It now writes a different number to each slot and reads each back, so two slots that collide fail it at run time. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
4c311b5 to
00cf90d
Compare
|
#10819 merged, so the hold is released: rebased onto master (5 commits); the Zig specs, gen files and seals are byte-identical to the reviewed head 4c311b5; |
PR DashboardGenerated at: 2026-10-19 08:48:08 UTC
Summary
Seal Status
|
Closes #10823. Step 2 of the Zig front end (#10597, option B of #10522), lane Z2.
Stacked on #10819 (branch
t27o/zig-front-10765, held as a draft until #10817 merges): this PR targets master, so its diff shows step 1's commits too. The five commits of this PR are4e7d54d50 .. 4c311b570. Order: #10817, then #10819 (re-sealzig_convert.t27), then this one, rebased and re-sealed.What changed, per item
tri t27o <file>.zig --check/--oracle.--checkwrites the converted spec tospecs/t27o_check/<stem>.t27and judges it as t27o --check: t27b test judges first, t27c test-report only when t27b cannot (Closes #10525) #10538 judges Rust specs:t27b testfirst,t27c test-reportonly when t27b's exit code is no verdict (t27b_outcome,needs_referencereused, not copied).--oracleprints one line (the file's own tests are the oracle) and exits 0. A spec none of whose tests converted passest27b testas 0 of 0, which is no verdict (tri test: a spec whose every test passed vacuously still passes -- report.t27 must read the "vacuous passes N of M" line #10497):has_evidencerefuses it, exit 6 (EXIT_UNJUDGED, outside t27b's table 0..5).ledger.t27gainsfront_end(the extension ruletri t27oand the sweep both ask),has_evidence, the two notes;zig_convert.t27losesis_zig_name(one rule, not two).tri t27o-sweeplists.zig.sweep_pathtakes.zigunder the same rule as.rs(nothing undergen/ortarget/). A Zig row's converter key is the Zig seal'sspec_hashjoined to the Rust converter's (zig_convert.t27usesrust_convert.t27's helpers): editing the Zig seal made 346 of 1105 trios rows go cold and 759 stay reused.zig_convert.t27numbers its five counters likerust_convert.t27's report, so the sweep reads one set of slots for both front ends (the output of all 2573 real.zigfiles was byte-identical before and after the renumbering; two tests pin the slots). Found on the way: the cache row separates kinds with;and the Zig reason "generated; convert its source spec instead" was cut in two and counted as its second half;kind_tonow stops at;as well as a backtick.pub const C = @import("x.zig").C;is written aspub const C: u32 = 5;when x.zig is in the importing file's own directory tree (no.., no leading/, no\, no:; a symlink out of the tree is not followed) and itspub const Cis a scalar literal (integer, float, bool, a string with no escape, at most 64 bytes, under a scalar type or none). Two pure fns inzig_convert.t27(importsnames the files,inline_importswrites the values); the glue only reads files. No line is added or taken, so every "line N" of a refusal stays true. Anything else is left as it was and stays refused with its reason ("uses externalC"): a struct, a fn, a non-pub const, an expression, a non-scalar type, a declaration over several lines, a missing file. The sweep keys a Zig row by the source with the values written in (an imported literal changed: the importing row is stale; a comment in the imported file changed: it is reused, checked on a two-file repo).std.math.pow(f64, x, y). Converted, bit for bit Zig's, through a newspecs/tools/t27o/zig_std.t27(besidezig_convert.t27asrust_std.t27is besiderust_convert.t27). Zig 0.16's pow is Go'smath.Pow, not libm's: the significand of x is squared (frexp), the exponent counted apart,exp(yf * log x)taken for the fraction, one ldexp at the end. Plain repeated multiplication is NOT Zig's result (69 of the 191 integer powers among the recorded vectors differ; libm'spowdiffers too), sozig_std.t27holds the algorithm step for step, in operations t27b and t27c both have (@exp/@logare compiler_rt's, which t27b runs bit for bit). Zig's ldexp is ported as it is, not as IEEE: it tests an exact tie by@ctz(repr), which counts the exponent field's own zeros, so0.5 * 2^-1074becomes2^-1074where musl's scalbn gives 0. My first version used musl's scalbn and the recorded vectors caught it.zig_convert.t27writesstd_math_pow_f64(x, y)and ause tools::t27o::zig_std::std_math_pow_f64;after the header when a fn or test that stayed in the output calls it. Other types and the old two-operand form are refused as "pow of a type other than f64"; a call in a file-level const (t27b cannot evaluate it) as "pow in a file-level const".Evidence
zig run(Zig 0.16.0, Debug, no libc; the program is in a comment inzig_std.t27): 433 pow, 124 ldexp, 16 frexp, compared by bits under t27b andt27c test-report. The lane first ran 1325 pow vectors (specials, the constants the corpus raises, results that overflow, underflow or land in the subnormals, random pairs): all equal under t27b and t27c. Mutants of the port (the 0.5 test of the fraction, the+1of yi, the sign of ae, the 2^63 threshold, the tie rule, the denormal exponent, the1/sqrtbranch, the 2^53 limit, the overflow bound) are each killed.t27b testzig_convert 61/61, ledger 38/38, zig_std 7/7;t27c test-reportonce at the end: vacuous 0 of 61, 0 of 38, 0 of 7 (a slot test of mine compared constants, which Zig folds: it was vacuous, fixed in4c311b570). All three seals--verifyMATCH;dupes --nameanddupe_scan --likefind nothing written elsewhere; devhome OK.Measured (trinity
0770abb99src/, clean working tree, so the sweep reads HEAD; trios483dd9eecrates/)tri t27oon each of the 2573.zigfiles(The lane review quoted 1,839 and 590 of 606; this run's own baseline is the first column.)
tri t27o-sweepnow lists 2562 Zig rows (346 trios, 2216 trinity; 11 files over the 300 KB cap are skipped, as for Rust): 30215 fn seen, 1803 -> 1987 converted at the sweep, tests 1733 -> 1852 of 14795. Wall time, load average about 37 on the bench Mac, minimum of three: trios cold 4.2 s, warm 0.7 s; trinity cold 8.6 s, warm 1.7 s (quiet-machine runs earlier in the lane: 2.6 s and 5.1 s, before items 3 and 4).--checkon five real files:src/gravity/quantum_gravity_full.zig26/26,src/cosmos/flatness_problem_solution.zig23/23 (calls the pow port),src/origin/abiogenesis.zig24/24,src/tri/tri_vacuum.zig17/17,src/time/temporal_constants.zig11/11, all exit 0;src/tri/constants.zig(no tests) exit 6.Imported consts found little: 23 imports of a scalar literal resolve in the two trees, 12 of them in two files that convert now (
src/tri/constants.zig,src/tri/error.zig, consts only: no new fn converts), 11 in files refused whole as generated. pow was the lever: 52 specs call the port, 45 pass under t27b.The pass/fail/blocked movement
zig testtoo (phi_lerp, baryon_formulas in both repositories; spectrum:zig testpanics on@intFromFloat, as the t27 test does). They are new in the failing column.compute_lucas.zig(both repositories) moves from failing to blocked: its newly converted test holds@as(f64, l_n)withl_n: u64, which Zig 0.16 refuses ("expected type 'f64', found 'u64'"), t27b too. I tried to make the converter refuse it and had to revert:@as(f32, N_LEVELS - 1)with a comptime-known typed const is valid Zig and converts today (5 fns would be lost), and the class tracker does not tell a comptime-knownusizefrom a run-time one.Not done
src/brain/benchmarks.zig(percentile_calculation,benchmark_accuracy_percentile_interpolation),src/kaggle/csv_parser.zig(detect_open_ended_vs_factual),src/phi-engine/cache/phi_lerp.zig(t^PHI_INV: 0.5^(1/phi) = 0.6517, the test expects 0.653 within 0.001),src/sacred/baryon_formulas.zig(baryon_photon_ratio_order_of_magnitude,neutron_proton_ratio),src/string_theory/spectrum.zig(bosonic critical dimension is 26,superstring critical dimension is 10),src/vibeec/golden_chain.zig(six export-constant tests); trios carries copies of brain_benchmarks, phi_lerp and baryon_formulas. brain/benchmarks.zig, kaggle/csv_parser.zig and vibeec/golden_chain.zig do not compile under Zig 0.16 at all (std.time.milliTimestamp,std.fs.cwd, missing modules): they may build with the Zig version those repositories use.ledger.t27of those findings (the issue asked for it); a ledger of upstream findings is data, not a rule, and I left it for the owner's decision on where it lives.Traps (for the next lane)
f32orf64is a float literal to t27b ("1137417019072311 is not exactly an f32"): the vector tables write those entries in decimal.&mut &ps[..], which does not compile; t27b rejects an array passed as a slice. Scratch forimports/inline_importslives in the state vector the glue passes.rust_convertdeclaration splices the module: a parameter calledpathtookzig_convert.rsfrom 126 KB to 269 KB ([bug] t27c gen-rust: the use splice matches words inside string literals and pulls in a whole module (13,432 vs 4,111 lines) #10824).@bitCastof a literal is refused: bind it to a typedconstfirst.git addrefusesgen/(ignored):git add -u, and-ffor a new file.🤖 Generated with Claude Code