kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 7ef74a962f314f7d01416856d4484aff2ed07079
parent 22b8a80d7271c883acfddcfc5948b058c2c8e716
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri, 29 May 2026 22:57:37 -0700

test+aa64: asm<->disasm self-symmetry sweep (decode<->encode)

A new sweep checks the assembler and disassembler agree on the instruction
set, independent of codegen (the codegen round-trip only exercises the
disassembler on what the compiler emits):

  decode-side: aa64_sweep_gen emits a representative encoding per disasm-table
    row; the harness decodes -> re-assembles -> decodes and requires a text
    fixed point. Catches decode-only / disagreeing forms.
  encode-side: assemble every aa64 encode/*.s and disassemble; any .inst is a
    form the assembler emits that the disassembler can't decode.

Known asymmetries are snapshotted in test/asm/symmetry.baseline; the sweep
gates against *new* asymmetry while the baseline documents the disasm-
completeness backlog (test-asm-symmetry, opt-in, ~2s).

Closed in passing (found by the sweep): fmax/fmin/fnmul (decoded but the
assembler lacked them) and the non-acquire byte/half exclusives
ldxrb/ldxrh/stxrb/stxrh (encodable but undecoded). Baseline starts at 69
encode-only forms (LSE/CAS/writeback/signed-regoff/logical-imm/q/...), all
instructions codegen never emits.

Diffstat:
Mdoc/ASM_ROUNDTRIP_TESTING.md | 25+++++++++++++++++++++++++
Msrc/arch/aa64/asm.c | 6++++++
Msrc/arch/aa64/isa.c | 4++++
Msrc/arch/aa64/isa.h | 3+++
Atest/arch/aa64_sweep_gen.c | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/asm/symmetry.baseline | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/asm/symmetry.sh | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/test.mk | 17+++++++++++++++++
8 files changed, 306 insertions(+), 0 deletions(-)

diff --git a/doc/ASM_ROUNDTRIP_TESTING.md b/doc/ASM_ROUNDTRIP_TESTING.md @@ -45,6 +45,31 @@ follow-up below). L0+L1 are wired into the default `make test` via (atomics were the one core-op family the corpus fan-out missed); now `roundtrip/atomic_{rmw,cas,ops}`. +### asm⊗disasm self-symmetry sweep (`test-asm-symmetry`) + +The codegen round-trip only exercises the disassembler on instructions the +compiler emits. A complementary sweep checks the *tools' own* instruction set +for asm⊗disasm symmetry, independent of codegen (`test/asm/symmetry.sh`): + +- **decode-side** (`test/arch/aa64_sweep_gen.c`): synthesize one representative + encoding per row of `aa64_insn_table`, decode → re-assemble → decode, and + require the disassembly text to be a fixed point. Catches a form the + disassembler decodes but the assembler can't re-encode, or where they + disagree. Now clean (closed `fmax`/`fmin`/`fnmul`, missing from `as`). +- **encode-side**: assemble every aa64 `test/asm/encode/*.s` and disassemble; + any `.inst` is a form the assembler encodes but the disassembler can't decode. + +Known asymmetries live in a checked-in snapshot, `test/asm/symmetry.baseline`; +the sweep passes iff the current set equals it, so it **gates against new +asymmetry** (a regression) while the baseline is the disasm-completeness +backlog. The current 69 entries are all encode-only (the assembler accepts +these for completeness but codegen never emits them, so the disassembler never +had to decode them): LSE atomics (`ldadd`/`swp`/…), CAS, single-register +writeback ld/st (`ldr x,[x,#imm]!`), signed register-offset ld/st, logical- +immediate (`mov #bitmask`/`orr #imm`), the `bfm` bitfield + aliases, 128-bit +`q` ld/st, and a couple of `ldp`/`stp` variants. Closing any of these shrinks +the baseline (`bash test/asm/symmetry.sh --update`). + ### Earlier vertical-slice notes (aa64) - **L0 decode-completeness** — `cc -S` already emits the distinct, re-assemblable diff --git a/src/arch/aa64/asm.c b/src/arch/aa64/asm.c @@ -1712,6 +1712,9 @@ static void p_fadd(AsmDriver* d) { p_fp_dp2(d, AA64_FP_DP2_FADD); } static void p_fsub(AsmDriver* d) { p_fp_dp2(d, AA64_FP_DP2_FSUB); } static void p_fmul(AsmDriver* d) { p_fp_dp2(d, AA64_FP_DP2_FMUL); } static void p_fdiv(AsmDriver* d) { p_fp_dp2(d, AA64_FP_DP2_FDIV); } +static void p_fmax(AsmDriver* d) { p_fp_dp2(d, AA64_FP_DP2_FMAX); } +static void p_fmin(AsmDriver* d) { p_fp_dp2(d, AA64_FP_DP2_FMIN); } +static void p_fnmul(AsmDriver* d) { p_fp_dp2(d, AA64_FP_DP2_FNMUL); } static void p_fneg(AsmDriver* d) { p_fp_dp1(d, AA64_FP_DP1_FNEG); } static void p_fabs(AsmDriver* d) { p_fp_dp1(d, AA64_FP_DP1_FABS); } static void p_fsqrt(AsmDriver* d) { p_fp_dp1(d, AA64_FP_DP1_FSQRT); } @@ -2010,6 +2013,9 @@ static const AA64Mn kTable[] = { {"fsub", p_fsub, 0}, {"fmul", p_fmul, 0}, {"fdiv", p_fdiv, 0}, + {"fmax", p_fmax, 0}, + {"fmin", p_fmin, 0}, + {"fnmul", p_fnmul, 0}, {"fneg", p_fneg, 0}, {"fabs", p_fabs, 0}, {"fsqrt", p_fsqrt, 0}, diff --git a/src/arch/aa64/isa.c b/src/arch/aa64/isa.c @@ -322,6 +322,10 @@ const AA64InsnDesc aa64_insn_table[] = { {MN("stxr"), 0x88000000u, 0xBFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}}, {MN("stlxr"), 0x88008000u, 0xBFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}}, {MN("stlr"), 0x88808000u, 0xBFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}}, + {MN("ldxrb"), 0x08400000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}}, + {MN("ldxrh"), 0x48400000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}}, + {MN("stxrb"), 0x08000000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}}, + {MN("stxrh"), 0x48000000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}}, {MN("ldaxrb"), 0x08408000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}}, {MN("ldaxrh"), 0x48408000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}}, {MN("ldarb"), 0x08C08000u, 0xFFE08000u, AA64_FMT_LDST_EXCL, 0, {0, 0}}, diff --git a/src/arch/aa64/isa.h b/src/arch/aa64/isa.h @@ -801,6 +801,9 @@ static inline u32 aa64_str64_uimm12(u32 Rt, u32 Rn, u32 imm12_scaled) { #define AA64_FP_DP2_FDIV 0x1800u #define AA64_FP_DP2_FADD 0x2800u #define AA64_FP_DP2_FSUB 0x3800u +#define AA64_FP_DP2_FMAX 0x4800u +#define AA64_FP_DP2_FMIN 0x5800u +#define AA64_FP_DP2_FNMUL 0x8800u #define AA64_FP_DP1_FMOV 0x4000u #define AA64_FP_DP1_FABS 0xC000u #define AA64_FP_DP1_FNEG 0x14000u diff --git a/test/arch/aa64_sweep_gen.c b/test/arch/aa64_sweep_gen.c @@ -0,0 +1,62 @@ +/* aa64_sweep_gen — emit one representative encoding per row of aa64_insn_table + * (as little-endian hex words), for the asm<->disasm self-symmetry round-trip + * (test/asm/symmetry.sh). + * + * The harness decodes each word, re-assembles the disassembly, and decodes + * again, asserting the text is a fixed point. That catches the two decode-side + * asymmetries the codegen round-trip can't reach systematically: a form the + * disassembler decodes but the assembler can't re-encode (assemble error), and + * a form where decode and encode disagree on the bytes (text changes across the + * round-trip). All decode/encode goes through asm-runner, so the comparison is + * immune to any formatting difference between rendering paths. + * + * Variable (~mask) bits are filled with a fixed representative pattern that + * gives distinct, non-ZR/SP registers in the standard field positions; the + * text-fixed-point criterion tolerates the resulting don't-care bits (e.g. the + * RES Rt2 field of an exclusive load). Formats whose only operand is a + * PC-relative target or a relocation — branches and adr/adrp — are skipped: + * they aren't assemblable standalone without a label/symbol, and L1/L2 of the + * codegen round-trip already cover them. + * + * Builds against the internal arch/aa64/isa.h surface (test.mk passes -Isrc). */ + +#include <stdint.h> +#include <stdio.h> + +#include "arch/aa64/isa.h" + +static int needs_context(uint8_t fmt) { + switch (fmt) { + case AA64_FMT_BR_IMM: /* b/bl: numeric target needs a label */ + case AA64_FMT_BR_COND: /* b.cc */ + case AA64_FMT_CB: /* cbz/cbnz */ + case AA64_FMT_PCREL_ADR: /* adr/adrp: needs a symbol operand */ + return 1; + default: + return 0; + } +} + +int main(void) { + /* Rt/Rd[4:0]=1, Rn[9:5]=2, Rt2/Ra[14:10]=3, Rm/Rs[20:16]=4 — distinct, + * non-ZR/SP so ZR-keyed aliases don't fire; small immediates elsewhere. */ + const uint32_t PATTERN = + (1u << 0) | (2u << 5) | (3u << 10) | (4u << 16); + for (uint32_t i = 0; i < aa64_insn_table_n; ++i) { + const AA64InsnDesc* d = &aa64_insn_table[i]; + uint32_t word; + if (needs_context(d->fmt)) continue; + word = (d->match & d->mask) | (PATTERN & ~d->mask); + /* The register-offset form's option field [15:13] must name a valid + * extend; the bare fill pattern yields the invalid UXTB(000). Force + * LSL/UXTX(011) so the synthesized word is a real instruction. */ + if (d->fmt == AA64_FMT_LDST_REGOFF) + word = (word & ~(7u << 13)) | (3u << 13); + /* Only sweep words that decode cleanly to this kind of instruction. */ + if (!aa64_disasm_find(word)) continue; + printf("%02x%02x%02x%02x\n", (unsigned)(word & 0xff), + (unsigned)((word >> 8) & 0xff), (unsigned)((word >> 16) & 0xff), + (unsigned)((word >> 24) & 0xff)); + } + return 0; +} diff --git a/test/asm/symmetry.baseline b/test/asm/symmetry.baseline @@ -0,0 +1,69 @@ +encode-only: aa64_bitfield_dp1 0xb34840a4 +encode-only: aa64_compare_and_swap 0x48a07c41 +encode-only: aa64_compare_and_swap 0x48a0fc41 +encode-only: aa64_compare_and_swap 0x48e07c41 +encode-only: aa64_compare_and_swap 0x48e0fc41 +encode-only: aa64_compare_and_swap 0x88a07c41 +encode-only: aa64_compare_and_swap 0x88a0fc41 +encode-only: aa64_compare_and_swap 0x88e07c41 +encode-only: aa64_compare_and_swap 0x88e0fc41 +encode-only: aa64_compare_and_swap 0x8a07c41 +encode-only: aa64_compare_and_swap 0x8a0fc41 +encode-only: aa64_compare_and_swap 0x8e07c41 +encode-only: aa64_compare_and_swap 0x8e0fc41 +encode-only: aa64_compare_and_swap 0xc8a07c41 +encode-only: aa64_compare_and_swap 0xc8e07c41 +encode-only: aa64_compare_and_swap 0xc8e0fc41 +encode-only: aa64_fp_ldst 0x3d800540 +encode-only: aa64_fp_ldst 0x3dc00141 +encode-only: aa64_ldp_stp_index 0x28c153f3 +encode-only: aa64_ldp_stp_index 0x29bf53f3 +encode-only: aa64_ldst_pre_post_index 0x381ffc20 +encode-only: aa64_ldst_pre_post_index 0x38401420 +encode-only: aa64_ldst_pre_post_index 0x38401c20 +encode-only: aa64_ldst_pre_post_index 0x38801c20 +encode-only: aa64_ldst_pre_post_index 0x38c01420 +encode-only: aa64_ldst_pre_post_index 0x78002420 +encode-only: aa64_ldst_pre_post_index 0x78402c20 +encode-only: aa64_ldst_pre_post_index 0x78802c20 +encode-only: aa64_ldst_pre_post_index 0x78c02420 +encode-only: aa64_ldst_pre_post_index 0xb8004c20 +encode-only: aa64_ldst_pre_post_index 0xb8404c20 +encode-only: aa64_ldst_pre_post_index 0xb85fc420 +encode-only: aa64_ldst_pre_post_index 0xb8804420 +encode-only: aa64_ldst_pre_post_index 0xb8804c20 +encode-only: aa64_ldst_pre_post_index 0xf80ff420 +encode-only: aa64_ldst_pre_post_index 0xf8100c20 +encode-only: aa64_ldst_pre_post_index 0xf8408420 +encode-only: aa64_ldst_pre_post_index 0xf8408c20 +encode-only: aa64_ldst_regoff 0x38a26820 +encode-only: aa64_ldst_regoff 0x38e26820 +encode-only: aa64_ldst_regoff 0x78a27820 +encode-only: aa64_ldst_regoff 0x78e27820 +encode-only: aa64_ldst_regoff 0xb8a27820 +encode-only: aa64_ldst_regoff 0xb8a2d820 +encode-only: aa64_lse_atomics 0x38200041 +encode-only: aa64_lse_atomics 0x38203041 +encode-only: aa64_lse_atomics 0x38208041 +encode-only: aa64_lse_atomics 0x78200041 +encode-only: aa64_lse_atomics 0x78201041 +encode-only: aa64_lse_atomics 0x78208041 +encode-only: aa64_lse_atomics 0xb8200041 +encode-only: aa64_lse_atomics 0xb8201041 +encode-only: aa64_lse_atomics 0xb8202041 +encode-only: aa64_lse_atomics 0xb8203041 +encode-only: aa64_lse_atomics 0xb8208041 +encode-only: aa64_lse_atomics 0xb8600041 +encode-only: aa64_lse_atomics 0xb8601041 +encode-only: aa64_lse_atomics 0xb8608041 +encode-only: aa64_lse_atomics 0xb8a00041 +encode-only: aa64_lse_atomics 0xb8a08041 +encode-only: aa64_lse_atomics 0xb8e00041 +encode-only: aa64_lse_atomics 0xb8e02041 +encode-only: aa64_lse_atomics 0xb8e08041 +encode-only: aa64_lse_atomics 0xf8200041 +encode-only: aa64_lse_atomics 0xf8208041 +encode-only: aa64_lse_atomics 0xf8a03041 +encode-only: aa64_mov_orr_bitmask 0x3204cfe4 +encode-only: aa64_mov_orr_bitmask 0xb201f3e3 +encode-only: aa64_mov_orr_bitmask 0xb204c3e1 diff --git a/test/asm/symmetry.sh b/test/asm/symmetry.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +# test/asm/symmetry.sh — asm<->disasm self-symmetry sweep (aa64). +# +# Systematically checks that the assembler and disassembler agree on the +# instruction set, in both directions: +# +# decode-side (table sweep): aa64_sweep_gen emits one representative encoding +# per row of the disassembler's instruction table. Each is decoded, the +# disassembly re-assembled, and decoded again; the text must be a FIXED +# POINT. A form the assembler can't re-encode is "decode-only"; one that +# re-encodes to a different instruction is a "disagree". +# +# encode-side (corpus sweep): every test/asm/encode/*.s that applies to aa64 +# is assembled and disassembled; any `.inst` means the assembler emitted a +# byte the disassembler can't decode ("encode-only"). +# +# The two tools cover slightly different ISA subsets (forms the assembler +# accepts for completeness but codegen never emits, so the disassembler never +# had to decode them, and vice-versa). The known asymmetries live in a checked- +# in snapshot, test/asm/symmetry.baseline; the sweep PASSES iff the current set +# equals the baseline, so it gates against *new* asymmetry (a regression) while +# the baseline documents the disasm-completeness backlog. Closing a gap shrinks +# the baseline (regenerate with --update). See doc/ASM_ROUNDTRIP_TESTING.md. +# +# Opt-in; host-independent (no execution). Decode-side assembles line-by-line, +# so it is a few seconds. + +set -u + +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +AR="$ROOT/build/test/asm-runner" +GEN="$ROOT/build/test/aa64_sweep_gen" +ENCODE_DIR="$ROOT/test/asm/encode" +WORK="$ROOT/build/test/asm/symmetry" +BASELINE="$ROOT/test/asm/symmetry.baseline" +UPDATE=0 +[ "${1:-}" = "--update" ] && UPDATE=1 + +color_red() { printf '\033[31m%s\033[0m' "$1"; } +color_grn() { printf '\033[32m%s\033[0m' "$1"; } + +if [ ! -x "$AR" ] || [ ! -x "$GEN" ]; then + printf '%s asm-runner / aa64_sweep_gen missing — run the test target\n' \ + "$(color_red FATAL)" >&2 + exit 2 +fi + +export CFREE_TEST_ARCH=aa64 +mkdir -p "$WORK" +report="$WORK/report" +: > "$report" + +strip_off() { sed -E 's/^[0-9a-f]+:\t//'; } + +# ---- decode-side: table sweep --------------------------------------------- +# Fast path: re-assemble the whole disassembly at once; if it assembles, the +# round-trip is decode->encode->decode and we compare the text line-for-line. +# Slow path (only when whole-file assembly fails, i.e. some form is decode- +# only): re-assemble line-by-line to name each offending mnemonic. +"$GEN" > "$WORK/words.hex" +"$AR" --decode "$WORK/words.hex" "$WORK/t1.txt" 2>/dev/null +strip_off < "$WORK/t1.txt" > "$WORK/t1.norm" +{ printf '\t.text\n'; sed -E 's/^[0-9a-f]+:\t/\t/' "$WORK/t1.txt"; } > "$WORK/t1.s" +if "$AR" --encode "$WORK/t1.s" "$WORK/hex2.hex" 2>/dev/null; then + "$AR" --decode "$WORK/hex2.hex" "$WORK/t2.txt" 2>/dev/null + strip_off < "$WORK/t2.txt" > "$WORK/t2.norm" + awk 'NR==FNR { a[FNR] = $0; next } + a[FNR] != $0 { printf "disagree: %s => %s\n", a[FNR], $0 }' \ + "$WORK/t1.norm" "$WORK/t2.norm" >> "$report" +else + while IFS= read -r line; do + [ -z "$line" ] && continue + mnem=$(printf '%s' "$line" | sed -E 's/^\t?([a-z0-9.]+).*/\1/') + printf '\t.text\n%s\n' "$line" > "$WORK/one.s" + if ! "$AR" --encode "$WORK/one.s" "$WORK/one.hex" 2>/dev/null; then + printf 'decode-only: %s\n' "$mnem" >> "$report" + continue + fi + "$AR" --decode "$WORK/one.hex" "$WORK/one.t2" 2>/dev/null + t2=$(strip_off < "$WORK/one.t2") + norm_line=$(printf '%s' "$line" | sed -E 's/^\t//') + [ "$norm_line" != "$t2" ] && \ + printf 'disagree: %s => %s\n' "$norm_line" "$t2" >> "$report" + done < "$WORK/t1.norm" +fi + +# ---- encode-side: corpus sweep -------------------------------------------- +shopt -s nullglob +for s in "$ENCODE_DIR"/*.s; do + name="$(basename "$s" .s)" + tg="$ENCODE_DIR/$name.targets" + [ -f "$tg" ] && ! grep -qE 'aa64|aarch64|arm64' "$tg" && continue + # Skip cases that deliberately place data in .text (not instructions). + grep -qE '^\s*\.(uleb128|sleb128|byte|hword|short|word|long|quad|ascii|asciz|string|zero|fill|space|inst)\b' "$s" && continue + w="$WORK/$name" + "$AR" --encode "$s" "$w.hex" 2>/dev/null || continue + "$AR" --decode "$w.hex" "$w.dec" 2>/dev/null + awk -v n="$name" '/[[:space:]]\.inst[[:space:]]/{print "encode-only: " n " " $NF}' "$w.dec" >> "$report" +done +shopt -u nullglob + +sort -u "$report" -o "$report" + +# ---- compare against the baseline snapshot -------------------------------- +if [ $UPDATE -eq 1 ]; then + cp "$report" "$BASELINE" + printf 'symmetry: baseline updated (%d known asymmetries)\n' "$(wc -l < "$report" | tr -d ' ')" + exit 0 +fi +[ -f "$BASELINE" ] || : > "$BASELINE" +if diff -u "$BASELINE" "$report" > "$WORK/delta.diff" 2>&1; then + printf 'symmetry: %s (%d known asymmetries in baseline)\n' \ + "$(color_grn 'no new asm<->disasm asymmetry')" \ + "$(wc -l < "$BASELINE" | tr -d ' ')" + exit 0 +fi +printf 'symmetry: %s\n' "$(color_red 'asymmetry set changed vs baseline')" +printf ' (+ = new asymmetry / regression, - = fixed; regenerate with --update)\n' +grep -E '^[-+][^-+]' "$WORK/delta.diff" | head -30 | sed 's/^/ /' +exit 1 diff --git a/test/test.mk b/test/test.mk @@ -40,6 +40,7 @@ TEST_TARGETS = \ test-disasm-complete \ test-asm-roundtrip \ test-asm-roundtrip-exec \ + test-asm-symmetry \ test-bounce \ test-cbackend \ test-cg-api \ @@ -270,6 +271,14 @@ $(RV64_DECODE_TEST_BIN): test/arch/rv64_decode_test.c $(LIB_OBJS) @mkdir -p $(dir $@) $(CC) $(TEST_HOST_CFLAGS) -Isrc test/arch/rv64_decode_test.c $(LIB_OBJS) -o $@ +# aa64_sweep_gen: emits one representative encoding per disasm-table row for the +# asm<->disasm self-symmetry sweep (test/asm/symmetry.sh). Needs the internal +# arch/aa64/isa.h surface, so -Isrc + LIB_OBJS like the ISA unit test. +AA64_SWEEP_GEN = build/test/aa64_sweep_gen +$(AA64_SWEEP_GEN): test/arch/aa64_sweep_gen.c $(LIB_OBJS) + @mkdir -p $(dir $@) + $(CC) $(TEST_HOST_CFLAGS) -Isrc test/arch/aa64_sweep_gen.c $(LIB_OBJS) -o $@ + # test-emu: emulator unit tests. The rv64 lane builds a tiny in-memory rv64 # ELF and asserts the lifted/JIT path exits through the syscall handler with # the expected code. Internal arch/emu surface — needs -Isrc. @@ -646,6 +655,14 @@ test-asm-roundtrip-exec: bin $(JIT_RUNNER) @CFREE_TEST_ARCH=aa64 CFREE_TEST_OPTS="O0 O1" CFREE_TEST_PATHS=012 \ bash test/asm/roundtrip.sh +# test-asm-symmetry: asm<->disasm self-symmetry sweep (aa64). Decode-side sweeps +# every disasm-table form (decode->encode->decode fixed point); encode-side +# asserts every byte the assembler emits over the encode corpus is decodable. +# Catches encode/decode asymmetries the codegen round-trip can't reach (e.g. a +# form one tool handles and the other doesn't). Host-independent, no exec. +test-asm-symmetry: $(ASM_RUNNER) $(AA64_SWEEP_GEN) + @bash test/asm/symmetry.sh + test-wasm: test-wasm-front test-wasm-target test-wasm-toy test-wasm-front: bin $(WASM_TOOL) $(LINK_EXE_RUNNER) $(JIT_RUNNER)