Parallel to TY_STR/TY_SLICE branches at cgen.c:1939/1962. Word-copy from src slot to field+k*8 via AX, MOVL/MOVB ragged tail. N_IDENT rhs only — struct-call-result and struct-literal rhs are different code paths, filed as task #27. Symmetric in N_ASSIGN field case AND spine-walker terminal; mirrored in wwstage cgassign four sub-shapes (*struct base, direct local, global, spine terminal).
516 lines
18 KiB
C
516 lines
18 KiB
C
/*
|
|
* 681_arr_elem_field_write — `arr[i].field = v` (write-side counterpart
|
|
* of 680_arr_elem_field). Both stages had a silent store-drop:
|
|
*
|
|
* cstage's chained-pointer-field-write branch (cgen.c near 1986) caught
|
|
* `[N]*Struct` writes via its `!= N_IDENT` guard but skipped `[N]Struct`
|
|
* value-arrays (TY_STRUCT element fails the TY_PTR guard).
|
|
*
|
|
* wwstage had no N_DOT(N_INDEX,...) lhs branch at all in cgassign
|
|
* (cgenexpr.ww). Both shapes silently emitted no store.
|
|
*
|
|
* Closed in task #16 by mirroring task #8's read-side N_INDEX-lhs branch
|
|
* onto the write side (fldstoreop, str/float leaf coverage, deref-or-not
|
|
* for `*Struct` vs `Struct` element).
|
|
*
|
|
* Coverage — both array shapes, scalar/sub-word/str/float leaves, dyn
|
|
* idx, and read-modify-write compound (`arr[i].x += 5`). Cstage and
|
|
* wwstage on every fixture; wwstage gated on access(X_OK).
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
|
|
static int
|
|
runwait(const char *cmd)
|
|
{
|
|
int rc = system(cmd);
|
|
if (rc == -1) return -1;
|
|
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
|
return -1;
|
|
}
|
|
|
|
struct row { const char *label; const char *src; int want; };
|
|
|
|
static const struct row rows[] = {
|
|
/* [N]*Struct, i32 field write. Sole write into stk[0].x; read it
|
|
* back. Returns 42. */
|
|
{ "ptr_arr_i32_write",
|
|
"type nd = struct { name: str, x: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let v: nd; v.name = \"hi\"; v.x = 0;\n"
|
|
" let stk: [16]*nd; stk[0] = &v;\n"
|
|
" stk[0].x = 42;\n"
|
|
" return stk[0].x;\n"
|
|
"};\n",
|
|
42 },
|
|
/* [N]*Struct, u8 field write. Sub-word store via MOVB through
|
|
* fieldstoreop. Returns 9. */
|
|
{ "ptr_arr_u8_write",
|
|
"type nd = struct { tag: u8, pad: u8, x: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let v: nd; v.tag = 0u8; v.pad = 0u8; v.x = 0;\n"
|
|
" let stk: [4]*nd; stk[0] = &v;\n"
|
|
" stk[0].tag = 9u8;\n"
|
|
" return stk[0].tag: i32;\n"
|
|
"};\n",
|
|
9 },
|
|
/* [N]*Struct, str field write — 16B store of both ptr+len.
|
|
* Returns len("hello") = 5. */
|
|
{ "ptr_arr_str_write",
|
|
"type nd = struct { a: i32, b: i32, name: str };\n"
|
|
"fn main() i32 = {\n"
|
|
" let v: nd; v.a = 0; v.b = 0; v.name = \"old\";\n"
|
|
" let stk: [16]*nd; stk[0] = &v;\n"
|
|
" stk[0].name = \"hello\";\n"
|
|
" return stk[0].name.len: i32;\n"
|
|
"};\n",
|
|
5 },
|
|
/* [N]Struct, i32 field write (value-array). The cstage chained-
|
|
* pointer-field branch's TY_PTR guard skips this; without the new
|
|
* value-array path the store silently drops. Returns 50. */
|
|
{ "val_arr_i32_write",
|
|
"type nd = struct { name: str, x: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let arr: [4]nd;\n"
|
|
" arr[2].x = 50;\n"
|
|
" return arr[2].x;\n"
|
|
"};\n",
|
|
50 },
|
|
/* [N]Struct, u8 field write — value-array sub-word store. Returns 7. */
|
|
{ "val_arr_u8_write",
|
|
"type nd = struct { tag: u8, pad: u8, x: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let arr: [4]nd;\n"
|
|
" arr[1].tag = 7u8;\n"
|
|
" return arr[1].tag: i32;\n"
|
|
"};\n",
|
|
7 },
|
|
/* [N]Struct, str field write — value-array 16B store. Returns
|
|
* len("world") = 5. */
|
|
{ "val_arr_str_write",
|
|
"type nd = struct { name: str, x: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let arr: [4]nd;\n"
|
|
" arr[1].name = \"world\";\n"
|
|
" return arr[1].name.len: i32;\n"
|
|
"};\n",
|
|
5 },
|
|
/* Sub-word signed: write -3i8 then read back as i32. If MOVB
|
|
* stored the truncated low byte and the field's fldloadop sign-
|
|
* extends correctly, the i32 read returns -3. Returns 42 when
|
|
* the equality check passes, 0 otherwise. */
|
|
{ "ptr_arr_i8_signed_write",
|
|
"type nd = struct { tag: i8, pad: u8, x: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let v: nd; v.tag = 0i8; v.pad = 0u8; v.x = 0;\n"
|
|
" let stk: [4]*nd; stk[0] = &v;\n"
|
|
" stk[0].tag = -3i8;\n"
|
|
" let t: i32 = stk[0].tag: i32;\n"
|
|
" if (t == -3) { return 42; };\n"
|
|
" return 0;\n"
|
|
"};\n",
|
|
42 },
|
|
/* Value-array sub-word signed: distinct from ptr_arr_i8_signed —
|
|
* this path takes LEAQ &arr[i] (no MOVQ-to-deref) and stores the
|
|
* truncated byte via fldstoreop MOVB. Read back via fldloadop
|
|
* MOVSBQ sign-extends to i32; -3 round-trips. Returns 42. */
|
|
{ "val_arr_i8_signed_write",
|
|
"type nd = struct { tag: i8, pad: u8, x: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let arr: [4]nd;\n"
|
|
" arr[1].tag = -3i8;\n"
|
|
" let t: i32 = arr[1].tag: i32;\n"
|
|
" if (t == -3) { return 42; };\n"
|
|
" return 0;\n"
|
|
"};\n",
|
|
42 },
|
|
/* f64 field write through value-array — pins MOVSD from X0 into
|
|
* field offset of &arr[i]. Returns 23. */
|
|
{ "val_arr_f64_write",
|
|
"type nd = struct { x: i32, d: f64 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let arr: [4]nd;\n"
|
|
" arr[1].d = 23.0f64;\n"
|
|
" let v: f64 = arr[1].d;\n"
|
|
" return v: i32;\n"
|
|
"};\n",
|
|
23 },
|
|
/* Dyn idx through [N]*Struct write — index isn't a literal, so
|
|
* the IMULQ path fires. Returns 99. */
|
|
{ "ptr_arr_dyn_idx_write",
|
|
"type nd = struct { name: str, x: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let a: nd; a.name = \"a\"; a.x = 0;\n"
|
|
" let b: nd; b.name = \"b\"; b.x = 0;\n"
|
|
" let c: nd; c.name = \"c\"; c.x = 0;\n"
|
|
" let stk: [4]*nd;\n"
|
|
" stk[0] = &a; stk[1] = &b; stk[2] = &c;\n"
|
|
" let i: i32 = 2;\n"
|
|
" stk[i].x = 99;\n"
|
|
" return stk[2].x;\n"
|
|
"};\n",
|
|
99 },
|
|
/* Read-modify-write compound on [N]Struct — exercises BOTH the
|
|
* read (load old field) and the write (store combined). For
|
|
* `arr[1].x += 5` with arr[1].x = 37 → 42. */
|
|
{ "val_arr_compound_plus",
|
|
"type nd = struct { name: str, x: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let arr: [4]nd;\n"
|
|
" arr[1].x = 37;\n"
|
|
" arr[1].x += 5;\n"
|
|
" return arr[1].x;\n"
|
|
"};\n",
|
|
42 },
|
|
/* Non-PLUSEQ compound (CARETEQ) on [N]Struct — exercises the XORQ
|
|
* arm of the compound switch. Worker wired all six integer
|
|
* compound ops; this pins one of the non-PLUSEQ arms so a future
|
|
* regression in the switch table is caught. 0x2A ^ 0x14 = 0x3E
|
|
* (62), then return 62 - 20 = 42. */
|
|
{ "val_arr_compound_xor",
|
|
"type nd = struct { name: str, x: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let arr: [4]nd;\n"
|
|
" arr[2].x = 42;\n"
|
|
" arr[2].x ^= 20;\n"
|
|
" return arr[2].x - 20;\n"
|
|
"};\n",
|
|
42 },
|
|
/* Compound through [N]*Struct — same shape but viaptr is true,
|
|
* so the address compute deref-loads (BX), and the load_op picks
|
|
* MOVSXD for i32. 10 += 22 → 32. */
|
|
{ "ptr_arr_compound_plus",
|
|
"type nd = struct { name: str, x: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let v: nd; v.name = \"a\"; v.x = 10;\n"
|
|
" let stk: [4]*nd; stk[0] = &v;\n"
|
|
" stk[0].x += 22;\n"
|
|
" return stk[0].x;\n"
|
|
"};\n",
|
|
32 },
|
|
/* Whole-slice rhs to struct field, direct struct local (task #24).
|
|
* cgen N_ASSIGN had a TY_STR branch that stored ptr+len at +0/+8
|
|
* but no TY_SLICE branch; the fallthrough generic store wrote only
|
|
* AX (ptr), silently dropping .len and .cap. Fixed by a parallel
|
|
* TY_SLICE branch that stores AX/BX/CX at +0/+8/+16. The bug bit
|
|
* bufio.init's `b.rbuf = rbuf` (first stdlib struct with a []u8
|
|
* field). Returns 42 when len, cap, and a trailing scalar field
|
|
* all survived the store. */
|
|
{ "slice_field_value_write",
|
|
"type bs = struct { rbuf: []u8, mark: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let raw: [8]u8; raw[0] = 0u8;\n"
|
|
" let rbuf: []u8 = raw[0:5];\n"
|
|
" let b: bs;\n"
|
|
" b.mark = 7;\n"
|
|
" b.rbuf = rbuf;\n"
|
|
" if (b.rbuf.len != 5) { return 1; };\n"
|
|
" if (b.rbuf.cap != 5) { return 2; };\n"
|
|
" if (b.mark != 7) { return 3; };\n"
|
|
" return 42;\n"
|
|
"};\n",
|
|
42 },
|
|
/* Same bug via pointer to struct — the bufio scenario exactly
|
|
* (`init(b: *bstream, rbuf: []u8) { b.rbuf = rbuf; }`). The via_ptr
|
|
* arm of the new TY_SLICE branch stages the struct addr in DX to
|
|
* avoid clobbering CX (cap). */
|
|
{ "slice_field_ptr_write",
|
|
"type bs = struct { rbuf: []u8, mark: i32 };\n"
|
|
"fn init(b: *bs, rbuf: []u8) void = {\n"
|
|
" b.rbuf = rbuf;\n"
|
|
" b.mark = 9;\n"
|
|
"};\n"
|
|
"fn main() i32 = {\n"
|
|
" let raw: [8]u8; raw[0] = 0u8;\n"
|
|
" let b: bs;\n"
|
|
" init(&b, raw[0:5]);\n"
|
|
" if (b.rbuf.len != 5) { return 1; };\n"
|
|
" if (b.rbuf.cap != 5) { return 2; };\n"
|
|
" if (b.mark != 9) { return 3; };\n"
|
|
" return 42;\n"
|
|
"};\n",
|
|
42 },
|
|
/* Distinct byte-pattern check — independent verification that
|
|
* the AX/BX/CX MOVQs landed at +0/+8/+16 rather than passing by
|
|
* coincidence with neighbouring zero memory. Distinct byte
|
|
* patterns at raw[0] (0xAA) and raw[5] (0xFF) are read back
|
|
* through b.rbuf[i], which only succeeds if .ptr (AX) survived
|
|
* at +0. Trailing mark uses a distinctive value (0x33) so a
|
|
* stray CX store one slot too far is caught separately. .len
|
|
* and .cap are forced equal by ww's slice expression (both
|
|
* BX = hi-lo), so this row pins .ptr survival while the other
|
|
* rows pin .len/.cap distinctness from the zero default. */
|
|
{ "slice_field_distinct_bytes",
|
|
"type bs = struct { rbuf: []u8, mark: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let raw: [16]u8;\n"
|
|
" raw[0] = 0xAAu8; raw[1] = 0xBBu8; raw[2] = 0xCCu8;\n"
|
|
" raw[3] = 0xDDu8; raw[4] = 0xEEu8; raw[5] = 0xFFu8;\n"
|
|
" let b: bs;\n"
|
|
" b.mark = 0x33;\n"
|
|
" b.rbuf = raw[0:6];\n"
|
|
" if (b.rbuf.len != 6) { return 1; };\n"
|
|
" if (b.rbuf.cap != 6) { return 2; };\n"
|
|
" if (b.mark != 0x33) { return 3; };\n"
|
|
" if (b.rbuf[0] != 0xAAu8) { return 4; };\n"
|
|
" if (b.rbuf[5] != 0xFFu8) { return 5; };\n"
|
|
" return 42;\n"
|
|
"};\n",
|
|
42 },
|
|
/* Str-field regression for the parallel TY_STR branch right above
|
|
* the new TY_SLICE branch — pin that store path so a future refactor
|
|
* doesn't drop it. Direct struct local; `b.s = "hello"` stores
|
|
* AX/BX at +0/+8, and the trailing i32 mark survives. */
|
|
{ "str_field_value_write_regression",
|
|
"type bs = struct { s: str, mark: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let b: bs;\n"
|
|
" b.mark = 11;\n"
|
|
" b.s = \"hello\";\n"
|
|
" if (b.s.len != 5) { return 1; };\n"
|
|
" if (b.mark != 11) { return 2; };\n"
|
|
" return 42;\n"
|
|
"};\n",
|
|
42 },
|
|
/* Whole-struct rhs to struct field (task #25). cgen N_ASSIGN had
|
|
* special-cases for TY_STR (2 words) and TY_SLICE (3 words) but no
|
|
* TY_STRUCT branch — the fallthrough fldstoreop wrote only AX,
|
|
* dropping every trailing word. Fixed by a word-copy branch that
|
|
* runs MOVQ from rhs slot to dest field, plus a 4/1-byte ragged
|
|
* tail. The 16B row is the BORDER case: same total size as TY_STR
|
|
* but must NOT take the str branch (str field has 16B header; this
|
|
* is a 2-word struct value, structurally identical but type-
|
|
* dispatched). Distinct byte values per word so a stray store at
|
|
* the wrong offset surfaces as a misvalue, not coincidental zero. */
|
|
{ "struct_field_value_write_16B",
|
|
"type inner = struct { a: i64, b: i64 };\n"
|
|
"type outer = struct { i: inner, mark: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let o: inner;\n"
|
|
" o.a = 0x1122334455667788i64;\n"
|
|
" o.b = 0x99aabbccddeeff00i64;\n"
|
|
" let x: outer;\n"
|
|
" x.mark = 0x55;\n"
|
|
" x.i = o;\n"
|
|
" if (x.i.a != 0x1122334455667788i64) { return 1; };\n"
|
|
" if (x.i.b != 0x99aabbccddeeff00i64) { return 2; };\n"
|
|
" if (x.mark != 0x55) { return 3; };\n"
|
|
" return 42;\n"
|
|
"};\n",
|
|
42 },
|
|
/* 24B struct rhs (the original #25 probe case). Three words at +0/+8/+16
|
|
* exercise the word-copy loop with no ragged tail. Distinct byte
|
|
* patterns pin each word independently. */
|
|
{ "struct_field_value_write_24B",
|
|
"type inner = struct { a: i64, b: i64, c: i64 };\n"
|
|
"type outer = struct { i: inner, mark: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let o: inner;\n"
|
|
" o.a = 0x0a0a0a0a0a0a0a0ai64;\n"
|
|
" o.b = 0x1b1b1b1b1b1b1b1bi64;\n"
|
|
" o.c = 0x2c2c2c2c2c2c2c2ci64;\n"
|
|
" let x: outer;\n"
|
|
" x.mark = 7;\n"
|
|
" x.i = o;\n"
|
|
" if (x.i.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n"
|
|
" if (x.i.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n"
|
|
" if (x.i.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n"
|
|
" if (x.mark != 7) { return 4; };\n"
|
|
" return 42;\n"
|
|
"};\n",
|
|
42 },
|
|
/* 32B struct rhs — four-word copy, no ragged tail. Trailing mark in
|
|
* the outer struct catches a stray fifth-word store. */
|
|
{ "struct_field_value_write_32B",
|
|
"type inner = struct { a: i64, b: i64, c: i64, d: i64 };\n"
|
|
"type outer = struct { i: inner, mark: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let o: inner;\n"
|
|
" o.a = 0x0a0a0a0a0a0a0a0ai64;\n"
|
|
" o.b = 0x1b1b1b1b1b1b1b1bi64;\n"
|
|
" o.c = 0x2c2c2c2c2c2c2c2ci64;\n"
|
|
" o.d = 0x3d3d3d3d3d3d3d3di64;\n"
|
|
" let x: outer;\n"
|
|
" x.mark = 0x33;\n"
|
|
" x.i = o;\n"
|
|
" if (x.i.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n"
|
|
" if (x.i.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n"
|
|
" if (x.i.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n"
|
|
" if (x.i.d != 0x3d3d3d3d3d3d3d3di64) { return 4; };\n"
|
|
" if (x.mark != 0x33) { return 5; };\n"
|
|
" return 42;\n"
|
|
"};\n",
|
|
42 },
|
|
/* Nested struct field write through chained N_DOT spine
|
|
* (`b.inner.deep = other_deep`). Confirms #22's spine walker
|
|
* handles a TY_STRUCT terminal, not just scalar/str/slice. The
|
|
* 24B inner struct payload must survive both the spine walk and
|
|
* the word-copy. */
|
|
{ "struct_field_value_write_nested_chain",
|
|
"type deep = struct { a: i64, b: i64, c: i64 };\n"
|
|
"type mid = struct { d: deep, mark: i32 };\n"
|
|
"type outer = struct { m: mid, tag: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let other: deep;\n"
|
|
" other.a = 0x0a0a0a0a0a0a0a0ai64;\n"
|
|
" other.b = 0x1b1b1b1b1b1b1b1bi64;\n"
|
|
" other.c = 0x2c2c2c2c2c2c2c2ci64;\n"
|
|
" let v: outer;\n"
|
|
" v.m.mark = 5;\n"
|
|
" v.tag = 9;\n"
|
|
" v.m.d = other;\n"
|
|
" if (v.m.d.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n"
|
|
" if (v.m.d.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n"
|
|
" if (v.m.d.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n"
|
|
" if (v.m.mark != 5) { return 4; };\n"
|
|
" if (v.tag != 9) { return 5; };\n"
|
|
" return 42;\n"
|
|
"};\n",
|
|
42 },
|
|
/* Whole-struct rhs to a struct field via a *struct base — exercises
|
|
* the via_ptr arm of the new TY_STRUCT branch. Stages the dest addr
|
|
* in BX (MOVQ off(BP),BX) before iterating, then writes at fi.foff+k(BX). */
|
|
{ "struct_field_value_write_via_ptr",
|
|
"type inner = struct { a: i64, b: i64, c: i64 };\n"
|
|
"type outer = struct { i: inner, mark: i32 };\n"
|
|
"fn fill(x: *outer) void = {\n"
|
|
" let o: inner;\n"
|
|
" o.a = 0x0a0a0a0a0a0a0a0ai64;\n"
|
|
" o.b = 0x1b1b1b1b1b1b1b1bi64;\n"
|
|
" o.c = 0x2c2c2c2c2c2c2c2ci64;\n"
|
|
" x.mark = 7;\n"
|
|
" x.i = o;\n"
|
|
"};\n"
|
|
"fn main() i32 = {\n"
|
|
" let x: outer;\n"
|
|
" fill(&x);\n"
|
|
" if (x.i.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n"
|
|
" if (x.i.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n"
|
|
" if (x.i.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n"
|
|
" if (x.mark != 7) { return 4; };\n"
|
|
" return 42;\n"
|
|
"};\n",
|
|
42 },
|
|
/* Ragged tail — 12B inner struct (3 i32 fields, maxalign=4, no 8B
|
|
* round-up) lands the word-copy loop at k=8 with 4 bytes left and
|
|
* pins the MOVL tail branch. Tail-only structs are reachable for
|
|
* any TY_STRUCT whose maxalign < 8: this is the smallest such
|
|
* shape. The trailing i32 mark in the outer struct catches a stray
|
|
* MOVQ tail (which would overwrite +4 of the mark slot). The 5/3/2
|
|
* byte tails (struct of just i8 fields) fall back to MOVQ in cgen
|
|
* and would overwrite past the field boundary; the language permits
|
|
* align==1 structs but they're not exercised here — filed as a
|
|
* sub-followup if a real callsite surfaces. */
|
|
{ "struct_field_value_write_ragged_tail_12B",
|
|
"type inner = struct { a: i32, b: i32, c: i32 };\n"
|
|
"type outer = struct { i: inner, mark: i32 };\n"
|
|
"fn main() i32 = {\n"
|
|
" let o: inner;\n"
|
|
" o.a = 0x11223344;\n"
|
|
" o.b = 0x55667788;\n"
|
|
" o.c = 0x29aabbcc;\n"
|
|
" let x: outer;\n"
|
|
" x.mark = 0x33;\n"
|
|
" x.i = o;\n"
|
|
" if (x.i.a != 0x11223344) { return 1; };\n"
|
|
" if (x.i.b != 0x55667788) { return 2; };\n"
|
|
" if (x.i.c != 0x29aabbcc) { return 3; };\n"
|
|
" if (x.mark != 0x33) { return 4; };\n"
|
|
" return 42;\n"
|
|
"};\n",
|
|
42 },
|
|
};
|
|
|
|
static int
|
|
run_driver(const char *driver, const struct row *r, int i)
|
|
{
|
|
char src[64], tmpdir[64], cmd[1024];
|
|
snprintf(src, sizeof src, "/tmp/waew_%d_%d.ww", getpid(), i);
|
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/waew_%d_d_%d", getpid(), i);
|
|
|
|
FILE *f = fopen(src, "wb");
|
|
if (!f) return -1;
|
|
fputs(r->src, f);
|
|
fclose(f);
|
|
|
|
mkdir(tmpdir, 0755);
|
|
snprintf(cmd, sizeof cmd, "cd %s && %s build %s",
|
|
tmpdir, driver, src);
|
|
if (runwait(cmd) != 0) {
|
|
fprintf(stderr, "row[%s]: build via %s failed\n",
|
|
r->label, driver);
|
|
unlink(src); rmdir(tmpdir);
|
|
return -1;
|
|
}
|
|
|
|
const char *base = strrchr(src, '/');
|
|
base = base ? base + 1 : src;
|
|
char outbin[128];
|
|
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
|
char *dot = strrchr(outbin, '.');
|
|
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
|
int got = runwait(outbin);
|
|
|
|
unlink(src); unlink(outbin); rmdir(tmpdir);
|
|
return got;
|
|
}
|
|
|
|
int
|
|
main(void)
|
|
{
|
|
const char *bin = getenv("BIN");
|
|
if (!bin) bin = "out/bin";
|
|
char absbin[1024];
|
|
if (bin[0] != '/') {
|
|
char cwd[1024];
|
|
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
|
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
|
bin = absbin;
|
|
}
|
|
|
|
char cdrv[1024];
|
|
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
|
char wdrv[1024];
|
|
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
|
|
|
struct { const char *name; const char *path; int gated_on_existence; }
|
|
drivers[] = {
|
|
{ "cstage", cdrv, 0 },
|
|
{ "wwstage", wdrv, 1 },
|
|
{ NULL, NULL, 0 },
|
|
};
|
|
|
|
int n = (int)(sizeof rows / sizeof rows[0]);
|
|
int total = 0, fail = 0;
|
|
for (int d = 0; drivers[d].name; d++) {
|
|
if (drivers[d].gated_on_existence
|
|
&& access(drivers[d].path, X_OK) != 0) {
|
|
fprintf(stderr, "arr_elem_field_write: skip %s (no %s)\n",
|
|
drivers[d].name, drivers[d].path);
|
|
continue;
|
|
}
|
|
for (int i = 0; i < n; i++) {
|
|
int got = run_driver(drivers[d].path, &rows[i], i);
|
|
total++;
|
|
if (got != rows[i].want) {
|
|
fprintf(stderr,
|
|
"arr_elem_field_write[%s][%s]: exit=%d want=%d\n",
|
|
drivers[d].name, rows[i].label,
|
|
got, rows[i].want);
|
|
fail++;
|
|
}
|
|
}
|
|
}
|
|
if (fail) {
|
|
fprintf(stderr,
|
|
"arr_elem_field_write: %d/%d fixtures failed\n", fail, total);
|
|
return 1;
|
|
}
|
|
printf("arr_elem_field_write: %d/%d ok\n", total, total);
|
|
return 0;
|
|
}
|