package freenoop_test; // free()-is-a-no-op byte-identity gate. Port of the retired native // carrier test/wcc/930_free_noop_run.c; every assertion preserved. // // ww has no free by design (rt/alloc.s:30 — drop-amalloc); post-#27 // both stages evaluate free's operand for side effects and emit // NOTHING else. Per row the inline source (prefixed exactly as the // carrier wrote it, `package main;` + blank line) compiles through // w6c AND w6c_ww with the asm on stdout; the stages must agree // byte-for-byte and the NEGATIVE gate holds: no substring 'free' // anywhere in the cstage output — no CALL free, no symbol reference // (byte identity extends the proof to wwstage, as in the carrier). // // The carrier captured stdout+stderr MERGED (`> cs.s 2>&1`) and // compared/grepped that one file; here the streams land separately // and EACH is byte-compared and 'free'-grepped — at least as strict // on both axes. // // Path discipline (the carrier's #8 note): no scratch path may carry // the substring 'free' or the whole-output grep false-fails. // testenv.fresh() is /tmp/<16 lowercase hex> — 'free' needs an 'r', // which the hex alphabet cannot produce — and the source basename // stays noopfr.ww, mirroring the carrier's naming. // // Dead data preserved as dead: the carrier's row table carried a // `want` exit-code field its main() never read (no run leg existed); // none is invented here. Runtime behavior lives in the r930_free_* // wwfixtures per the retired carrier's header. Dropped machinery, // not assertions: getpid()-keyed tmpdirs + rm/mkdir accounting // (testenv.fresh/clean own scratch and assert cleanup). import os; import os.exec; import strings; import testenv; import time; fn fail(label: str, why: str) void = { let m: str = strings.concat("freenoop FAIL: ", label, " -- ", why, "\n"); os.write(2, m.ptr, m.len: u64); assert(false); }; fn tmo() time.duration = { return (180i64 * (time.second: i64)): time.duration; }; // `drv noopfr.ww` with the asm on stdout — the carrier's exact shape // (no -o), so the captured stdout IS the .s under comparison. fn runstage(td: str, label: str, stage: str, drv: str, out: *testenv.commandout) void = { let av: []str = []; append(av, drv); append(av, "noopfr.ww"); testenv.runcommand(td, td, stage, av, tmo(), out); let ok: bool = out.termination == exec.termination.EXIT && out.code == 0; if (!ok) { fail(label, strings.concat(stage, " compile failed")); }; }; fn nooprow(label: str, src: str) void = { let td: str = testenv.fresh(); testenv.writefile(strings.concat(td, "/noopfr.ww"), strings.concat("package main;\n\n", src)); let cs: testenv.commandout; runstage(td, label, "cstage", testenv.driver("w6c"), &cs); let ws: testenv.commandout; runstage(td, label, "wwstage", testenv.driver("w6c_ww"), &ws); if (!testenv.same(cs.stdout, ws.stdout)) { fail(label, "cs != ww .s"); }; if (!testenv.same(cs.stderr, ws.stderr)) { fail(label, "cs != ww compile stderr"); }; if (testenv.has(cs.stdout, "free") || testenv.has(cs.stderr, "free")) { fail(label, "'free' survives in the .s -- lowering is not a no-op"); }; testenv.clean(td); }; // The five carrier rows: deref-after-free leak semantics, the N_DOT // operand shape, operand side effects run per call, no stack damage // across 1M no-op frees, and the verbatim Hare-port alloc/free // round-trip (i64 sidesteps the filed narrow-pointee alloc(value) // size divergence). @test fn asmrows() void = { nooprow("free_local_ptr_deref_after", strings.concat( "export fn main() i32 = {\n", " let x: i32 = 5;\n", " let p: *i32 = &x;\n", " free(p);\n", " if (*p != 5) { return 1; };\n", " return 0;\n", "};\n")); nooprow("free_struct_field", strings.concat( "type holder = struct { p: *i32, n: i32 };\n", "export fn main() i32 = {\n", " let x: i32 = 3;\n", " let h: holder = holder { p = &x, n = 4 };\n", " free(h.p);\n", " if (h.n != 4) { return 2; };\n", " return 0;\n", "};\n")); nooprow("free_call_operand_effects", strings.concat( "let g: i32 = 0;\n", "fn bump(p: *i32) *i32 = {\n", " g = g + 2;\n", " return p;\n", "};\n", "export fn main() i32 = {\n", " let x: i32 = 1;\n", " free(bump(&x));\n", " free(bump(&x));\n", " if (g != 4) { return 3; };\n", " return 0;\n", "};\n")); nooprow("free_loop_no_stack_damage", strings.concat( "let g: i32 = 0;\n", "fn bump(p: *i32) *i32 = {\n", " g = g + 1;\n", " return p;\n", "};\n", "export fn main() i32 = {\n", " let x: i32 = 1;\n", " let i: i32 = 0;\n", " for (i < 1000000) {\n", " free(bump(&x));\n", " i += 1;\n", " };\n", " if (g != 1000000) { return 5; };\n", " return 0;\n", "};\n")); nooprow("free_alloc_roundtrip", strings.concat( "import rt;\n", "export fn main() i32 = {\n", " let p: *i64 = alloc(11i64)!;\n", " free(p);\n", " if (*p != 11i64) { return 4; };\n", " return 0;\n", "};\n")); };