144 lines
4.9 KiB
Plaintext
144 lines
4.9 KiB
Plaintext
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(
|
|
"export fn main() i32 = {\n",
|
|
" let p: *i64 = alloc(11i64)!;\n",
|
|
" free(p);\n",
|
|
" if (*p != 11i64) { return 4; };\n",
|
|
" return 0;\n",
|
|
"};\n"));
|
|
};
|