commit 9fb1e48ba4c1b8dcd5ab830faa2ccdc9ab6ab285
parent 784af4539fe0903af8fae52182337b712c366f7a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sat, 9 May 2026 16:15:58 -0700
test/cg: scaffold Group P (set_loc/debug) + W path
Group P drives CGTarget.set_loc and is the first group whose oracle is
metadata rather than test_main's exit code. The new W path reads the
emitted obj back through cfree_dwarf_open and asserts the registered
line/subprogram facts; cases without registered checks skip silently.
p01_line_one_inst returns 42 (so D/R/E/J still pass) and registers
"line p01.c 10" + "subprogram test_main" for W. W fails today because
debug_emit and the cfree_dwarf_* consumers are stubs — once those land
the same case starts passing W with no test changes.
Diffstat:
7 files changed, 455 insertions(+), 7 deletions(-)
diff --git a/test/cg/CORPUS.md b/test/cg/CORPUS.md
@@ -14,6 +14,11 @@ Test paths per case (run.sh):
`link-exe-runner` → run.
- **J** jit-via-file (aarch64 host only) — `cg-runner --emit` →
`jit-runner`.
+- **W** DWARF check (Group P only) — `cg-runner --emit` +
+ `cg-runner --dwarf-checks NAME | cg_check_dwarf OBJ`. Opens the obj
+ via `cfree_dwarf_open` and asserts the line program / subprograms
+ registered for the case. Cases without registered checks skip
+ silently.
`O` (opt-wrapped) lands once `opt_cgtarget` is implemented.
@@ -395,12 +400,30 @@ forward-declared helpers defined later in the TU.
| `q10_global_and_static_mix` | · | one SB_GLOBAL + two SB_LOCAL helpers; sum = 12+15+15 | 42 |
| `q11_addr_of_helper_through_global` | · | function ptr stored in `.data` (R_ABS64); indirect call | 42 |
+## Group P — set_loc / debug
+
+Drives `CGTarget.set_loc` (which forwards to `MCEmitter.set_loc` and, once
+wired, to `Debug` for the line program). The case body still returns 42
+so D/R/E/J keep working; the **W** path is the metadata oracle and reads
+the emitted obj back through `cfree_dwarf_open` /
+`cfree_dwarf_addr_to_line` / `cfree_dwarf_subprogram_at`.
+
+Today every W check fails by design: `debug_new`/`debug_emit` and the
+`cfree_dwarf_*` consumers are stubs (src/api/stubs.c), and
+`MCEmitter::set_loc` does not yet propagate to `Debug`. Once those land
+the same case bodies start producing real DWARF and the W path flips
+green. This matches the harness preamble's "fail at runtime until deps
+land" pattern.
+
+| Case | Status | Body | Expected (D/E/J / W) |
+|---|---|---|---|
+| `p01_line_one_inst` | · | `set_loc(p01.c:10)` before single `load_imm 42; ret`; W asserts addr↔line round-trip and `subprogram test_main` | 42 / line p01.c:10 + subprogram test_main |
+
## Deferred groups
| Group | Theme |
|---|---|
| M | inline asm |
-| P | set_loc / debug |
| R | opt-wrapped equivalence |
See `doc/cg_testing.md` for the strategy and group definitions.
diff --git a/test/cg/harness/cases.c b/test/cg/harness/cases.c
@@ -208,6 +208,8 @@ void build_o10_global_addend(CgTestCtx*);
void build_o11_text_section_named(CgTestCtx*);
void build_o12_global_across_call(CgTestCtx*);
+void build_p01_line_one_inst(CgTestCtx*);
+
void build_q01_three_helpers(CgTestCtx*);
void build_q02_static_internal_linkage(CgTestCtx*);
void build_q03_intra_tu_call_chain(CgTestCtx*);
@@ -464,6 +466,13 @@ const CgCase cg_cases[] = {
{"o12_global_across_call", build_o12_global_across_call, 42,
CG_CASE_DEFAULT},
+ /* Group P — set_loc / debug. Today these fail at runtime because the
+ * Debug subsystem and the cfree_dwarf_* consumers are stubbed; once
+ * those land, the same case bodies start producing real DWARF and
+ * path W flips green. The exit-code oracle (D/E/J) is 42; the W path
+ * checks the line program. See cases_p.c for the contract. */
+ {"p01_line_one_inst", build_p01_line_one_inst, 42, CG_CASE_DEFAULT},
+
/* Group Q — multi-function */
{"q01_three_helpers", build_q01_three_helpers, 42, CG_CASE_DEFAULT},
{"q02_static_internal_linkage", build_q02_static_internal_linkage, 42,
@@ -488,3 +497,15 @@ const CgCase cg_cases[] = {
};
const unsigned cg_cases_count = sizeof(cg_cases) / sizeof(cg_cases[0]);
+
+/* ---- DWARF check registry (path W) ----
+ * Only Group P has entries today. See cg_test.h for the directive
+ * vocabulary. */
+const CgDwarfCheck cg_dwarf_checks[] = {
+ {"p01_line_one_inst",
+ "subprogram test_main\n"
+ "line p01.c 10\n"},
+};
+
+const unsigned cg_dwarf_checks_count =
+ sizeof(cg_dwarf_checks) / sizeof(cg_dwarf_checks[0]);
diff --git a/test/cg/harness/cases_p.c b/test/cg/harness/cases_p.c
@@ -0,0 +1,38 @@
+/* Group P — set_loc / debug.
+ * See CORPUS.md for the case list and expected values.
+ *
+ * Group P's oracle is metadata, not exit code: the case still returns 42
+ * (so D/E/J keep passing once the line program is wired) but the *real*
+ * assertion runs through path W, which opens the emitted obj with
+ * cfree_dwarf_open and checks the line program against the
+ * (file, line) pairs the case set via cg_set_loc / target->set_loc.
+ *
+ * Today these cases fail by design: debug_new and debug_emit are stubs
+ * (src/api/stubs.c), MCEmitter::set_loc only stores the loc on the impl
+ * and does not propagate to Debug, and the cfree_dwarf_* consumers are
+ * stubbed. The W path will start passing once those land. The harness
+ * preamble in cg_test.h documents this "fail at runtime until deps land"
+ * pattern. */
+
+#include "cg_test.h"
+#include "core/core.h"
+
+/* p01_line_one_inst — one instruction at a known SrcLoc.
+ *
+ * Registers a synthetic source file "p01.c" with the SourceManager,
+ * stamps line 10 onto a single load_imm via target->set_loc, and returns
+ * 42. Path W asserts that the emitted obj's .debug_line maps some PC
+ * inside test_main back to (p01.c, 10). */
+void build_p01_line_one_inst(CgTestCtx* ctx) {
+ const Type* I32 = T_i32(ctx);
+
+ u32 file_id = source_add_memory(ctx->c->sources, "p01.c");
+ SrcLoc loc = {file_id, 10, 0};
+
+ CgTestFn* tf = cgtest_begin_main(ctx, I32);
+ ctx->target->set_loc(ctx->target, loc);
+ Reg r = ctx->target->alloc_reg(ctx->target, RC_INT, I32);
+ ctx->target->load_imm(ctx->target, REG_op(r, I32), 42);
+ cgtest_ret_reg(tf, r, I32);
+ cgtest_end(tf);
+}
diff --git a/test/cg/harness/cg_check_dwarf.c b/test/cg/harness/cg_check_dwarf.c
@@ -0,0 +1,279 @@
+/* cg_check_dwarf — path W oracle for the test/cg harness.
+ *
+ * cg_check_dwarf <obj_path> # reads directives from stdin, one per line
+ *
+ * Directives (see cg_test.h for the contract):
+ *
+ * line FILE LINE
+ * Some PC inside the object's text must map to (FILE, LINE) via
+ * cfree_dwarf_addr_to_line, and cfree_dwarf_line_to_addr(FILE, LINE)
+ * must return a PC that maps back to (FILE, LINE).
+ *
+ * subprogram NAME
+ * cfree_dwarf_subprogram_at must report a non-empty pc range whose
+ * name equals NAME.
+ *
+ * Exit code: 0 if every directive passes; 1 if any directive fails or the
+ * object cannot be opened. Blank lines and lines beginning with '#' are
+ * ignored. */
+
+#include <cfree.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/* ---- env ---- */
+
+static void* h_alloc(CfreeHeap* h, size_t n, size_t a) {
+ (void)h;
+ (void)a;
+ return n ? malloc(n) : NULL;
+}
+static void* h_realloc(CfreeHeap* h, void* p, size_t o, size_t n, size_t a) {
+ (void)h;
+ (void)o;
+ (void)a;
+ return realloc(p, n);
+}
+static void h_free(CfreeHeap* h, void* p, size_t n) {
+ (void)h;
+ (void)n;
+ free(p);
+}
+static CfreeHeap g_heap = {h_alloc, h_realloc, h_free, NULL};
+
+static void diag_emit(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc,
+ const char* fmt, va_list ap) {
+ static const char* names[] = {"note", "warning", "error", "fatal"};
+ (void)s;
+ (void)loc;
+ fprintf(stderr, "%s: ", names[k]);
+ vfprintf(stderr, fmt, ap);
+ fputc('\n', stderr);
+}
+static CfreeDiagSink g_diag = {diag_emit, NULL, 0, 0};
+
+/* ---- file slurp ---- */
+
+static int slurp(const char* path, uint8_t** out, size_t* n_out) {
+ int fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ perror(path);
+ return 1;
+ }
+ struct stat st;
+ if (fstat(fd, &st) != 0) {
+ perror("fstat");
+ close(fd);
+ return 1;
+ }
+ size_t n = (size_t)st.st_size;
+ uint8_t* buf = (uint8_t*)malloc(n);
+ if (!buf) {
+ close(fd);
+ return 1;
+ }
+ size_t off = 0;
+ while (off < n) {
+ ssize_t k = read(fd, buf + off, n - off);
+ if (k <= 0) {
+ perror("read");
+ close(fd);
+ free(buf);
+ return 1;
+ }
+ off += (size_t)k;
+ }
+ close(fd);
+ *out = buf;
+ *n_out = n;
+ return 0;
+}
+
+/* ---- directive checks ---- */
+
+typedef struct Ctx {
+ CfreeCompiler* cc;
+ CfreeDebugInfo* di;
+ int fails;
+} Ctx;
+
+static void fail(Ctx* c, const char* fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ fputs("FAIL ", stdout);
+ vfprintf(stdout, fmt, ap);
+ fputc('\n', stdout);
+ va_end(ap);
+ c->fails++;
+}
+
+static void pass(const char* fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ fputs("PASS ", stdout);
+ vfprintf(stdout, fmt, ap);
+ fputc('\n', stdout);
+ va_end(ap);
+}
+
+static void check_line(Ctx* c, const char* file, uint32_t line) {
+ uint64_t pc = 0;
+ if (cfree_dwarf_line_to_addr(c->di, file, line, &pc) != 0) {
+ fail(c, "line %s:%u — line_to_addr returned no PC", file, line);
+ return;
+ }
+ const char* got_file = NULL;
+ uint32_t got_line = 0, got_col = 0;
+ if (cfree_dwarf_addr_to_line(c->di, pc, &got_file, &got_line, &got_col) !=
+ 0) {
+ fail(c, "line %s:%u — addr_to_line(0x%llx) returned no entry", file, line,
+ (unsigned long long)pc);
+ return;
+ }
+ if (!got_file || strcmp(got_file, file) != 0 || got_line != line) {
+ fail(c, "line %s:%u — round-tripped to %s:%u (pc=0x%llx)", file, line,
+ got_file ? got_file : "(null)", got_line, (unsigned long long)pc);
+ return;
+ }
+ pass("line %s:%u (pc=0x%llx)", file, line, (unsigned long long)pc);
+}
+
+static void check_subprogram(Ctx* c, const char* name) {
+ /* No "find subprogram by name" entry exists in cfree_dwarf_*; we have
+ * subprogram_at(pc, ...). Walk a small probe range starting at 0 and
+ * accept the first hit whose name matches. This is a stopgap that
+ * keeps the directive vocabulary stable; once a name-keyed query
+ * lands we'll switch to it. */
+ CfreeDwarfSubprogram sp;
+ for (uint64_t pc = 0; pc < 0x10000ull; pc += 4) {
+ if (cfree_dwarf_subprogram_at(c->di, pc, &sp) != 0) continue;
+ if (sp.name && strcmp(sp.name, name) == 0) {
+ if (sp.high_pc <= sp.low_pc) {
+ fail(c, "subprogram %s — empty pc range [0x%llx, 0x%llx)", name,
+ (unsigned long long)sp.low_pc, (unsigned long long)sp.high_pc);
+ return;
+ }
+ pass("subprogram %s [0x%llx, 0x%llx)", name,
+ (unsigned long long)sp.low_pc, (unsigned long long)sp.high_pc);
+ return;
+ }
+ }
+ fail(c, "subprogram %s — not found in first 64KB of text", name);
+}
+
+static void run_directive(Ctx* c, char* line) {
+ while (*line == ' ' || *line == '\t') line++;
+ if (*line == '\0' || *line == '\n' || *line == '#') return;
+
+ /* strip trailing newline */
+ size_t n = strlen(line);
+ while (n > 0 && (line[n - 1] == '\n' || line[n - 1] == '\r')) line[--n] = 0;
+
+ char* sp = strchr(line, ' ');
+ if (!sp) {
+ fail(c, "bad directive: %s", line);
+ return;
+ }
+ *sp = 0;
+ const char* op = line;
+ char* rest = sp + 1;
+
+ if (strcmp(op, "line") == 0) {
+ char* sp2 = strchr(rest, ' ');
+ if (!sp2) {
+ fail(c, "line: expected FILE LINE");
+ return;
+ }
+ *sp2 = 0;
+ const char* file = rest;
+ long ln = strtol(sp2 + 1, NULL, 10);
+ if (ln <= 0) {
+ fail(c, "line: bad line number");
+ return;
+ }
+ check_line(c, file, (uint32_t)ln);
+ } else if (strcmp(op, "subprogram") == 0) {
+ check_subprogram(c, rest);
+ } else {
+ fail(c, "unknown directive: %s", op);
+ }
+}
+
+/* ---- main ---- */
+
+int main(int argc, char** argv) {
+ if (argc != 2) {
+ fprintf(stderr, "usage: cg_check_dwarf <obj_path>\n");
+ return 2;
+ }
+ const char* obj_path = argv[1];
+
+ /* Slurp the obj. */
+ uint8_t* bytes = NULL;
+ size_t nbytes = 0;
+ if (slurp(obj_path, &bytes, &nbytes) != 0) return 1;
+
+ CfreeTarget target;
+ memset(&target, 0, sizeof target);
+ target.arch = CFREE_ARCH_ARM_64;
+ target.os = CFREE_OS_LINUX;
+ target.obj = CFREE_OBJ_ELF;
+ target.ptr_size = 8;
+ target.ptr_align = 8;
+ CfreeEnv env;
+ memset(&env, 0, sizeof env);
+ env.heap = &g_heap;
+ env.diag = &g_diag;
+ env.now = -1;
+
+ CfreeCompiler* cc = cfree_compiler_new(target, &env);
+ if (!cc) {
+ fprintf(stderr, "cg_check_dwarf: compiler_new failed\n");
+ free(bytes);
+ return 1;
+ }
+
+ CfreeBytesInput in;
+ memset(&in, 0, sizeof in);
+ in.name = obj_path;
+ in.data = bytes;
+ in.len = nbytes;
+ CfreeObjFile* obj = cfree_obj_open(&env, &in);
+ if (!obj) {
+ fprintf(stderr, "cg_check_dwarf: cannot open %s as object\n", obj_path);
+ cfree_compiler_free(cc);
+ free(bytes);
+ return 1;
+ }
+
+ CfreeDebugInfo* di = cfree_dwarf_open(cc, obj);
+ if (!di) {
+ fprintf(stderr,
+ "cg_check_dwarf: %s has no DWARF (cfree_dwarf_open returned "
+ "NULL)\n",
+ obj_path);
+ cfree_obj_close(obj);
+ cfree_compiler_free(cc);
+ free(bytes);
+ return 1;
+ }
+
+ Ctx ctx = {cc, di, 0};
+
+ /* Stream directives from stdin. */
+ char buf[1024];
+ while (fgets(buf, sizeof buf, stdin)) {
+ run_directive(&ctx, buf);
+ }
+
+ cfree_dwarf_close(di);
+ cfree_obj_close(obj);
+ cfree_compiler_free(cc);
+ free(bytes);
+ return ctx.fails ? 1 : 0;
+}
diff --git a/test/cg/harness/cg_runner.c b/test/cg/harness/cg_runner.c
@@ -299,6 +299,19 @@ static int mode_expected(const char* name) {
return 0;
}
+/* --dwarf-checks NAME — print the W-path directive blob registered for
+ * NAME, or nothing if the case has no DWARF checks. The shell harness
+ * pipes this into cg_check_dwarf <obj>. */
+static int mode_dwarf_checks(const char* name) {
+ for (unsigned i = 0; i < cg_dwarf_checks_count; ++i) {
+ if (strcmp(cg_dwarf_checks[i].case_name, name) == 0) {
+ fputs(cg_dwarf_checks[i].directives, stdout);
+ return 0;
+ }
+ }
+ return 0; /* not registered → empty stdout, harness skips W */
+}
+
static int mode_emit(const char* name, const char* out_path) {
const CgCase* cc = find_case(name);
if (!cc) {
@@ -462,6 +475,7 @@ static int usage(void) {
fprintf(stderr,
"usage: cg-runner --list\n"
" cg-runner --expected NAME\n"
+ " cg-runner --dwarf-checks NAME\n"
" cg-runner --emit NAME OUT.o\n"
" cg-runner --jit NAME\n");
return 2;
@@ -477,6 +491,8 @@ int main(int argc, char** argv) {
return mode_list();
else if (!strcmp(argv[1], "--expected") && argc == 3)
return mode_expected(argv[2]);
+ else if (!strcmp(argv[1], "--dwarf-checks") && argc == 3)
+ return mode_dwarf_checks(argv[2]);
else if (!strcmp(argv[1], "--emit") && argc == 4)
return mode_emit(argv[2], argv[3]);
else if (!strcmp(argv[1], "--jit") && argc == 3)
diff --git a/test/cg/harness/cg_test.h b/test/cg/harness/cg_test.h
@@ -60,6 +60,30 @@ typedef struct CgCase {
extern const CgCase cg_cases[];
extern const unsigned cg_cases_count;
+/* ---- DWARF checks (path W) ----
+ * Optional per-case directives consumed by test/cg/harness/cg_check_dwarf
+ * after --emit. Each entry pairs a case name with a directive blob: one
+ * directive per line, blank lines ignored. Cases not listed here are
+ * skipped on path W. Supported directives:
+ *
+ * line FILE LINE
+ * Some PC inside the obj's text must map to (FILE, LINE) and the
+ * inverse line_to_addr must round-trip.
+ *
+ * subprogram NAME
+ * cfree_dwarf_subprogram_at must report a non-empty pc range for
+ * the named symbol.
+ *
+ * The cfree_dwarf_* consumers are stubbed today (src/api/stubs.c), so
+ * every directive currently fails — that's intentional. */
+typedef struct CgDwarfCheck {
+ const char* case_name;
+ const char* directives;
+} CgDwarfCheck;
+
+extern const CgDwarfCheck cg_dwarf_checks[];
+extern const unsigned cg_dwarf_checks_count;
+
/* ---- pre-interned type accessors ----
* Resolved once per ctx via type_prim/type_void/type_ptr against
* ctx->pool. Sugar so cases don't repeat the lookup. */
diff --git a/test/cg/run.sh b/test/cg/run.sh
@@ -10,6 +10,11 @@
# E exec via qemu — cg-runner --emit + start.o → link-exe-runner → qemu/
# podman → exit code. Cross-host friendly.
# J jit-via-file — cg-runner --emit + jit-runner. aarch64 host.
+# W DWARF check — cg-runner --emit + cg-runner --dwarf-checks NAME |
+# cg_check_dwarf OBJ. Group P only; cases that don't
+# register checks are silently skipped. Today every
+# check fails by design — debug_emit and the
+# cfree_dwarf_* consumers are stubs.
#
# Reuses the existing test/link harness binaries (link-exe-runner,
# jit-runner, cfree-roundtrip) verbatim.
@@ -20,7 +25,7 @@
# Filtering:
# ./run.sh [name_filter] [paths]
# name_filter substring match against case name (e.g. "a01", "add")
-# paths subset of "DREJ" (default "DREJ")
+# paths subset of "DREJW" (default "DREJW")
# Equivalent env vars: CFREE_TEST_FILTER, CFREE_TEST_PATHS.
set -u
@@ -35,6 +40,7 @@ CG_RUNNER="$BUILD_DIR/cg-runner"
ROUNDTRIP_BIN="$BUILD_DIR/cfree-roundtrip"
LINK_EXE_RUNNER="$BUILD_DIR/link-exe-runner"
JIT_RUNNER="$BUILD_DIR/jit-runner"
+DWARF_CHECK="$BUILD_DIR/cg-check-dwarf"
NORMALIZE="$ROOT/test/elf/normalize.py"
CLANG_TARGET="--target=aarch64-linux-gnu"
@@ -46,12 +52,13 @@ ALLOW_SKIP="${CFREE_TEST_ALLOW_SKIP:-0}"
# $1 / CFREE_TEST_FILTER — substring match against case name
# $2 / CFREE_TEST_PATHS — subset of "DREJ" (default "DREJ")
FILTER="${1:-${CFREE_TEST_FILTER:-}}"
-PATHS="${2:-${CFREE_TEST_PATHS:-DREJ}}"
+PATHS="${2:-${CFREE_TEST_PATHS:-DREJW}}"
case "$PATHS" in *D*) RUN_D=1;; *) RUN_D=0;; esac
case "$PATHS" in *R*) RUN_R=1;; *) RUN_R=0;; esac
case "$PATHS" in *E*) RUN_E=1;; *) RUN_E=0;; esac
case "$PATHS" in *J*) RUN_J=1;; *) RUN_J=0;; esac
-T_D=0; T_R=0; T_E=0; T_J=0 # accumulated wall-clock seconds per path
+case "$PATHS" in *W*) RUN_W=1;; *) RUN_W=0;; esac
+T_D=0; T_R=0; T_E=0; T_J=0; T_W=0 # accumulated wall-clock seconds per path
now_ms() { python3 -c 'import time;print(int(time.time()*1000))'; }
mkdir -p "$BUILD_DIR" "$BUILD_DIR/cg"
@@ -147,6 +154,7 @@ if $CC $CFREE_CFLAGS \
"$TEST_DIR/harness/cases_l.c" \
"$TEST_DIR/harness/cases_n.c" \
"$TEST_DIR/harness/cases_o.c" \
+ "$TEST_DIR/harness/cases_p.c" \
"$TEST_DIR/harness/cases_q.c" \
"$LIB_AR" -o "$CG_RUNNER" 2>"$BUILD_DIR/cg-runner.err"; then
printf ' %s cg-runner\n' "$(color_grn built)"
@@ -201,6 +209,18 @@ if [ $is_aarch64 -eq 1 ]; then
fi
fi
+# cg-check-dwarf — for path W. Always rebuild (small file, picks up
+# changes alongside the rest of the harness).
+have_dwarf_check=0
+if $CC -I"$ROOT/include" "$TEST_DIR/harness/cg_check_dwarf.c" \
+ "$LIB_AR" -o "$DWARF_CHECK" 2>"$BUILD_DIR/cg-check-dwarf.err"; then
+ have_dwarf_check=1
+ printf ' %s cg-check-dwarf\n' "$(color_grn built)"
+else
+ printf ' %s cg-check-dwarf (see %s)\n' \
+ "$(color_yel warn)" "$BUILD_DIR/cg-check-dwarf.err" >&2
+fi
+
printf 'Running cases...\n'
# ---- per-case loop ---------------------------------------------------------
@@ -235,9 +255,10 @@ for name in $CASES; do
fi
fi
- # ---- emit (needed by R/E/J) -------------------------------------------
+ # ---- emit (needed by R/E/J/W) -----------------------------------------
obj="$work/$name.o"
- if [ $RUN_R -eq 1 ] || [ $RUN_E -eq 1 ] || [ $RUN_J -eq 1 ]; then
+ if [ $RUN_R -eq 1 ] || [ $RUN_E -eq 1 ] || [ $RUN_J -eq 1 ] \
+ || [ $RUN_W -eq 1 ]; then
if ! "$CG_RUNNER" --emit "$name" "$obj" 2>"$work/emit.err"; then
note_fail "$name/emit (cg-runner --emit failed; see $work/emit.err)"
continue
@@ -311,12 +332,38 @@ for name in $CASES; do
note_skip "$name/J" "no jit-runner (not aarch64 host)"
fi
fi
+
+ # ---- Path W: DWARF check ----------------------------------------------
+ # Cases that don't register directives produce empty stdout from
+ # --dwarf-checks; we silently skip those (no SKIP entry, since W is
+ # opt-in per case rather than per host).
+ if [ $RUN_W -eq 1 ]; then
+ "$CG_RUNNER" --dwarf-checks "$name" >"$work/w.directives" \
+ 2>"$work/w.dc.err"
+ if [ -s "$work/w.directives" ]; then
+ if [ $have_dwarf_check -eq 1 ]; then
+ t0=$(now_ms)
+ "$DWARF_CHECK" "$obj" <"$work/w.directives" \
+ >"$work/w.out" 2>"$work/w.err"
+ w_rc=$?
+ dt=$(( $(now_ms) - t0 )); T_W=$(( T_W + dt ))
+ if [ "$w_rc" -eq 0 ]; then
+ note_pass "$name/W (${dt}ms)"
+ else
+ note_fail "$name/W (see $work/w.out, $work/w.err; ${dt}ms)"
+ fi
+ else
+ note_skip "$name/W" "no cg-check-dwarf"
+ fi
+ fi
+ fi
done
# ---- summary ---------------------------------------------------------------
printf '\nResults: %s pass, %s fail, %s skip\n' "$PASS" "$FAIL" "$SKIP"
-printf 'Time: D=%dms R=%dms E=%dms J=%dms\n' "$T_D" "$T_R" "$T_E" "$T_J"
+printf 'Time: D=%dms R=%dms E=%dms J=%dms W=%dms\n' \
+ "$T_D" "$T_R" "$T_E" "$T_J" "$T_W"
if [ ${#FAIL_NAMES[@]} -gt 0 ]; then
printf 'Failed:\n'