kit

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

run.sh (17059B)


      1 #!/usr/bin/env bash
      2 # test/asm/run.sh — file-driven assembler / disassembler test harness, on the
      3 # shared corpus harness (test/lib/kit_corpus.sh).
      4 #
      5 # Three sub-corpora under test/asm/, one per asm-runner static-output mode.
      6 # Each is a SEQUENTIAL corpus with a disjoint case set (its own kit_corpus_run,
      7 # no opt axis, single arch), NOT a parallel lane within a case:
      8 #
      9 #   encode/   <name>.s      + <name>.expected.hex       golden hex bytes
     10 #                             (+ optional <name>.expected integer exit code)
     11 #   decode/   <name>.hex    + <name>.expected.txt       golden decoded text
     12 #   listing/  <name>.in.bin + <name>.expected.lst       golden objdump-style
     13 #
     14 # Path matrix (6 letters, default HTLDJE):
     15 #
     16 #   H  Hex encode      — encode/ only. asm-runner --encode IN.s → hex; diff
     17 #                        vs <name>.expected.hex. Missing golden => SKIP.
     18 #   T  Text decode     — decode/ only. asm-runner --decode IN.hex → text; diff
     19 #                        vs <name>.expected.txt. Missing golden => FAIL.
     20 #   L  Listing         — listing/ only. asm-runner --listing IN.in.bin → text;
     21 #                        diff vs <name>.expected.lst. Missing golden => FAIL.
     22 #   D  Direct JIT      — encode/ only, when <name>.expected (integer exit) is
     23 #                        present. asm-runner --jit IN.s → exit code matches.
     24 #                        Host arch must match the cross-target. Missing
     25 #                        .expected exit => SKIP.
     26 #   J  JIT via file    — encode/ only, when <name>.expected is present.
     27 #                        asm-runner --emit + jit-runner. Host arch must match.
     28 #   E  ELF exec        — encode/ only, when <name>.expected is present.
     29 #                        asm-runner --emit + start.o → link-exe-runner →
     30 #                        qemu/podman → exit code. Cross-host friendly (deferred
     31 #                        batched exec via kit_queue_e).
     32 #
     33 # Reuses the test/link harness binaries (link-exe-runner, jit-runner) plus
     34 # test/link/harness/start.c verbatim — same convention as test/parse/run.sh.
     35 #
     36 # Whole-case <name>.targets carries bare arch tokens (aa64/x64/rv64 + synonyms);
     37 # a case applies only when one of them matches KIT_TEST_ARCH (else SKIP-NA).
     38 # Per-case <name>.skip (+ .<arch>.skip / .<lane>.skip) sidecars skip cleanly.
     39 #
     40 # Phase 1 (doc/ASM.md §5): asm_parse and the disasm iterator are still stubs
     41 # in src/api/stubs.c. KIT_TEST_ALLOW_SKIP defaults to 1 here for the
     42 # duration of phase 1 — flip to 0 (matching the rest of the suite) once the
     43 # assembler / disasm iterator are real.
     44 #
     45 # Filtering:
     46 #   ./run.sh [name_filter] [paths]
     47 #     name_filter   substring match against case basename
     48 #     paths         subset of "HTLDJE" (default "HTLDJE")
     49 #   Equivalent env vars: KIT_TEST_FILTER, KIT_TEST_PATHS.
     50 #
     51 # Parallelism: every lane hook writes only under $KIT_WORK and records only via
     52 # kit_*, so the console summary is identical serial or parallel. KIT_ASM_PARALLEL
     53 # flips dispatch (default on).
     54 
     55 set -u
     56 
     57 ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
     58 TEST_DIR="$ROOT/test/asm"
     59 LINK_TEST_DIR="$ROOT/test/link"
     60 BUILD_DIR="$ROOT/build/test"
     61 LIB_AR="$ROOT/build/libkit.a"
     62 
     63 export KIT_LIB_DIR="$ROOT/test/lib"
     64 # shellcheck source=../lib/kit_corpus.sh
     65 . "$ROOT/test/lib/kit_corpus.sh"
     66 
     67 ASM_RUNNER="$BUILD_DIR/asm-runner"
     68 LINK_EXE_RUNNER="$BUILD_DIR/link-exe-runner"
     69 JIT_RUNNER="$BUILD_DIR/jit-runner"
     70 
     71 # Phase 1: ALLOW_SKIP defaults to 1 (smoke cases skip cleanly because
     72 # asm_parse / kit_disasm_iter_* are still stubs). The engine's kit_exit reads
     73 # KIT_TEST_ALLOW_SKIP (default 0), so export the phase-1 default of 1 unless
     74 # the caller overrides it.
     75 export KIT_TEST_ALLOW_SKIP="${KIT_TEST_ALLOW_SKIP:-1}"
     76 
     77 # KIT_TEST_ARCH selects the cross-target. Default aa64 preserves the
     78 # pre-multiarch behavior. The asm-runner reads the same env via
     79 # test/lib/kit_test_target.h.
     80 KIT_TEST_ARCH="${KIT_TEST_ARCH:-aa64}"
     81 case "$KIT_TEST_ARCH" in
     82     aa64|aarch64|arm64)   TEST_ARCH=aa64;   CLANG_TRIPLE=aarch64-linux-gnu;  EXEC_ARCH=aarch64 ;;
     83     x64|x86_64|amd64)     TEST_ARCH=x64;    CLANG_TRIPLE=x86_64-linux-gnu;   EXEC_ARCH=x64 ;;
     84     rv64|riscv64)         TEST_ARCH=rv64;   CLANG_TRIPLE=riscv64-linux-gnu;  EXEC_ARCH=rv64 ;;
     85     rv32|riscv32)         TEST_ARCH=rv32;   CLANG_TRIPLE=riscv32-unknown-elf; EXEC_ARCH=rv32 ;;
     86     *) printf 'unknown KIT_TEST_ARCH=%s\n' "$KIT_TEST_ARCH" >&2; exit 2 ;;
     87 esac
     88 export KIT_TEST_ARCH
     89 CLANG_TARGET="--target=$CLANG_TRIPLE"
     90 
     91 # Synthetic single tuple — there is no opt axis and this harness is single-arch.
     92 # Whole-case .targets applicability is computed by kit_read_case (bare arch
     93 # tokens), so the engine's tuple matcher is disabled (KIT_TARGETS_EXT="").
     94 CUR_TUPLE="$TEST_ARCH-asm"
     95 
     96 # Filtering: positional [name_filter] [paths] mirror KIT_TEST_FILTER / PATHS.
     97 # The engine's discovery honors KIT_TEST_FILTER, so export the positional one.
     98 FILTER="${1:-${KIT_TEST_FILTER:-}}"
     99 [ -n "$FILTER" ] && export KIT_TEST_FILTER="$FILTER"
    100 PATHS="${2:-${KIT_TEST_PATHS:-HTLDJE}}"
    101 case "$PATHS" in *H*) RUN_H=1;; *) RUN_H=0;; esac
    102 case "$PATHS" in *T*) RUN_T=1;; *) RUN_T=0;; esac
    103 case "$PATHS" in *L*) RUN_L=1;; *) RUN_L=0;; esac
    104 case "$PATHS" in *D*) RUN_D=1;; *) RUN_D=0;; esac
    105 case "$PATHS" in *J*) RUN_J=1;; *) RUN_J=0;; esac
    106 case "$PATHS" in *E*) RUN_E=1;; *) RUN_E=0;; esac
    107 
    108 PAR="${KIT_ASM_PARALLEL:-1}"
    109 
    110 # ---- tool detection (mirrors test/parse/run.sh) ----------------------------
    111 
    112 have_clang_cross=0
    113 have_exe_runner=0
    114 have_jit_runner=0
    115 have_qemu=0
    116 have_podman=0
    117 is_aarch64=0
    118 
    119 if clang $CLANG_TARGET -c -x c - -o /dev/null < /dev/null 2>/dev/null; then
    120     have_clang_cross=1
    121 fi
    122 
    123 QEMU_BIN="$(command -v qemu-aarch64-static 2>/dev/null || command -v qemu-aarch64 2>/dev/null || true)"
    124 [ -n "$QEMU_BIN" ] && have_qemu=1
    125 command -v podman >/dev/null 2>&1 && have_podman=1
    126 
    127 arch_raw="$(uname -m 2>/dev/null || true)"
    128 { [ "$arch_raw" = "aarch64" ] || [ "$arch_raw" = "arm64" ]; } && is_aarch64=1
    129 
    130 # is_native_target=1 when the cross-target arch matches the host arch.
    131 # Required for in-process JIT (path D) and the jit-runner (path J).
    132 is_native_target=0
    133 case "$TEST_ARCH" in
    134     aa64) [ $is_aarch64 -eq 1 ] && is_native_target=1 ;;
    135     x64)  { [ "$arch_raw" = "x86_64" ] || [ "$arch_raw" = "amd64" ]; } && is_native_target=1 ;;
    136     rv64) [ "$arch_raw" = "riscv64" ] && is_native_target=1 ;;
    137 esac
    138 
    139 # Shared per-arch exec helper — see test/lib/exec_target.sh. Path E queues bare
    140 # EXEC_ARCH tags (== <arch>-linux), so set the caller-contract knobs it reads.
    141 EXEC_TARGET_MOUNT_ROOT="$BUILD_DIR"
    142 export have_qemu have_podman is_aarch64 QEMU_BIN EXEC_TARGET_MOUNT_ROOT
    143 # shellcheck source=../lib/exec_target.sh
    144 . "$ROOT/test/lib/exec_target.sh"
    145 
    146 # ---- harness binaries ------------------------------------------------------
    147 
    148 printf 'Checking harness...\n'
    149 
    150 if [ ! -x "$ASM_RUNNER" ]; then
    151     printf '  %sFATAL%s asm-runner missing — run "make test-asm"\n' \
    152         "$_CF_RED" "$_CF_RST" >&2
    153     exit 1
    154 fi
    155 printf '  %sfound%s asm-runner\n' "$_CF_GRN" "$_CF_RST"
    156 
    157 # link-exe-runner — for path E.
    158 if [ -x "$LINK_EXE_RUNNER" ]; then
    159     have_exe_runner=1
    160     printf '  %sfound%s link-exe-runner\n' "$_CF_GRN" "$_CF_RST"
    161 else
    162     printf '  %swarn%s link-exe-runner missing; E path will skip\n' "$_CF_YEL" "$_CF_RST"
    163 fi
    164 
    165 # jit-runner — for path J. Only meaningful when host arch matches the cross-target.
    166 if [ $is_native_target -eq 1 ]; then
    167     if [ -x "$JIT_RUNNER" ]; then
    168         have_jit_runner=1
    169         printf '  %sfound%s jit-runner\n' "$_CF_GRN" "$_CF_RST"
    170     else
    171         printf '  %swarn%s jit-runner missing; J path will skip\n' "$_CF_YEL" "$_CF_RST"
    172     fi
    173 fi
    174 
    175 # Cached start.o — same trick as parse/cg harnesses; build once for the
    176 # whole run. Arch-qualified so concurrent per-arch lanes (the test-asm aggregate
    177 # under `make -j`) don't race on one path with arch-specific content.
    178 START_OBJ="$BUILD_DIR/asm_start.$TEST_ARCH.o"
    179 have_start_obj=0
    180 if [ $have_clang_cross -eq 1 ]; then
    181     if clang $CLANG_TARGET -O1 -ffreestanding -fno-stack-protector \
    182             -fno-PIC -fno-pie \
    183             -c "$LINK_TEST_DIR/harness/start.c" -o "$START_OBJ" 2>/dev/null; then
    184         have_start_obj=1
    185     fi
    186 fi
    187 
    188 printf 'Running cases...\n'
    189 
    190 # ---- whole-case .targets applicability (bare arch tokens) ------------------
    191 # The original .targets sidecars list bare arch names (aa64/x64/rv64 and
    192 # synonyms), NOT <arch>-<obj> tuples, so the engine's kit_tuple_applicable does
    193 # not apply. Read them here and emit SKIP-NA via KIT_SKIP_NA_CASE. The engine's
    194 # KIT_TARGETS_EXT matcher stays disabled (KIT_TARGETS_EXT="").
    195 kit_read_case() {
    196     # Reset per-case emit state (serial mode reuses this shell across cases).
    197     _asm_emit_done=0
    198     local targets="$KIT_SIDECAR_DIR/$KIT_BASE.targets"
    199     [ -f "$targets" ] || return 0
    200     local tuple
    201     for tuple in $(cat "$targets"); do
    202         case "$tuple:$TEST_ARCH" in
    203             aa64:aa64|aarch64:aa64|arm64:aa64) return 0 ;;
    204             x64:x64|x86_64:x64|amd64:x64) return 0 ;;
    205             rv64:rv64|riscv64:rv64) return 0 ;;
    206             rv32:rv32|riscv32:rv32) return 0 ;;
    207         esac
    208     done
    209     KIT_SKIP_NA_CASE=1
    210 }
    211 KIT_READ_CASE=kit_read_case
    212 
    213 # ---- golden-diff helper (KIT_WORK-confined) ---------------------------------
    214 # kit_golden_diff LABEL EXPECTED ACTUAL : PASS on byte-exact match, else FAIL.
    215 kit_golden_diff() {
    216     local label="$1" expected="$2" actual="$3"
    217     if diff -u "$expected" "$actual" >"$KIT_WORK/diff" 2>&1; then
    218         kit_pass "$label"
    219     else
    220         kit_fail "$label" "golden mismatch; see $KIT_WORK/diff"
    221     fi
    222 }
    223 
    224 # ---- decode corpus: lane T (text golden-diff) ------------------------------
    225 kit_lane_T() {
    226     local expected="$KIT_SIDECAR_DIR/$KIT_BASE.expected.txt"
    227     if [ ! -e "$expected" ]; then
    228         kit_fail "$KIT_NAME/T" "missing golden $(basename "$expected")"; return
    229     fi
    230     local out="$KIT_WORK/out.txt"
    231     if ! "$ASM_RUNNER" --decode "$KIT_SRC" "$out" >"$KIT_WORK/stdout" 2>"$KIT_WORK/stderr"; then
    232         kit_fail "$KIT_NAME/T" "asm-runner --decode failed; see $KIT_WORK/stderr"; return
    233     fi
    234     kit_golden_diff "$KIT_NAME/T" "$expected" "$out"
    235 }
    236 
    237 # ---- listing corpus: lane L (listing golden-diff) --------------------------
    238 kit_lane_L() {
    239     local expected="$KIT_SIDECAR_DIR/$KIT_BASE.expected.lst"
    240     if [ ! -e "$expected" ]; then
    241         kit_fail "$KIT_NAME/L" "missing golden $(basename "$expected")"; return
    242     fi
    243     local out="$KIT_WORK/out.lst"
    244     if ! "$ASM_RUNNER" --listing "$KIT_SRC" "$out" >"$KIT_WORK/stdout" 2>"$KIT_WORK/stderr"; then
    245         kit_fail "$KIT_NAME/L" "asm-runner --listing failed; see $KIT_WORK/stderr"; return
    246     fi
    247     kit_golden_diff "$KIT_NAME/L" "$expected" "$out"
    248 }
    249 
    250 # ---- encode corpus lanes: H / D / J / E ------------------------------------
    251 # KIT_EXPECTED carries the .expected integer exit code (0 when absent); a missing
    252 # .expected file means the exec lanes (D/J/E) SKIP. Detect "has exit" via the
    253 # sidecar's presence directly so a legitimate expected exit of 0 still runs.
    254 _asm_has_exit() { [ -f "$KIT_SIDECAR_DIR/$KIT_BASE.expected" ]; }
    255 _asm_exit_byte() { echo $(( KIT_EXPECTED & 0xff )); }
    256 
    257 # Path H: hex encode roundtrip. Missing .expected.hex golden => SKIP.
    258 kit_lane_H() {
    259     local expected_hex="$KIT_SIDECAR_DIR/$KIT_BASE.expected.hex"
    260     if [ ! -e "$expected_hex" ]; then
    261         kit_skip "$KIT_NAME/H" "no .expected.hex golden"; return
    262     fi
    263     local out="$KIT_WORK/out.hex"
    264     if ! "$ASM_RUNNER" --encode "$KIT_SRC" "$out" >"$KIT_WORK/h.out" 2>"$KIT_WORK/h.err"; then
    265         kit_fail "$KIT_NAME/H" "asm-runner --encode failed; see $KIT_WORK/h.err"; return
    266     fi
    267     kit_golden_diff "$KIT_NAME/H" "$expected_hex" "$out"
    268 }
    269 
    270 # Ensure the .o needed by J/E exists. Sets ASM_OBJ to its path and returns 0 on
    271 # success; on the first failure emits FAIL "$KIT_NAME/emit" (matching the
    272 # original standalone emit failure record) and returns 1. Must NOT be called in
    273 # a $(...) subshell — kit_fail mutates counters that a subshell would discard.
    274 # D does not need a .o — asm-runner --jit does the full parse+jit in process.
    275 _asm_emit_done=0
    276 _asm_emit() {
    277     ASM_OBJ="$KIT_WORK/$KIT_BASE.o"
    278     [ -f "$ASM_OBJ" ] && return 0
    279     if [ "$_asm_emit_done" -eq 1 ]; then return 1; fi
    280     _asm_emit_done=1
    281     if "$ASM_RUNNER" --emit "$KIT_SRC" "$ASM_OBJ" 2>"$KIT_WORK/emit.err"; then
    282         return 0
    283     fi
    284     kit_fail "$KIT_NAME/emit" "asm-runner --emit failed; see $KIT_WORK/emit.err"
    285     return 1
    286 }
    287 
    288 # Path D: in-process JIT. Missing .expected exit => SKIP.
    289 kit_lane_D() {
    290     if ! _asm_has_exit; then kit_skip "$KIT_NAME/D" "no .expected exit code"; return; fi
    291     if [ $is_native_target -eq 0 ]; then
    292         kit_skip "$KIT_NAME/D" "host arch != $TEST_ARCH (no native JIT)"; return
    293     fi
    294     local want; want=$(_asm_exit_byte)
    295     "$ASM_RUNNER" --jit "$KIT_SRC" >"$KIT_WORK/d.out" 2>"$KIT_WORK/d.err"
    296     local rc=$?
    297     if [ "$rc" -eq "$want" ]; then kit_pass "$KIT_NAME/D"
    298     else kit_fail "$KIT_NAME/D" "expected $want got $rc"; fi
    299 }
    300 
    301 # Path J: jit-via-file. Missing .expected exit => SKIP.
    302 kit_lane_J() {
    303     if ! _asm_has_exit; then kit_skip "$KIT_NAME/J" "no .expected exit code"; return; fi
    304     if [ $have_jit_runner -eq 0 ]; then
    305         kit_skip "$KIT_NAME/J" "no jit-runner (host arch != $TEST_ARCH)"; return
    306     fi
    307     _asm_emit || { kit_skip "$KIT_NAME/J" "no .o (--emit failed)"; return; }
    308     local want; want=$(_asm_exit_byte)
    309     "$JIT_RUNNER" "$ASM_OBJ" >"$KIT_WORK/jit.out" 2>"$KIT_WORK/jit.err"
    310     local rc=$?
    311     if [ "$rc" -eq "$want" ]; then kit_pass "$KIT_NAME/J"
    312     else kit_fail "$KIT_NAME/J" "expected $want got $rc"; fi
    313 }
    314 
    315 # Path E: link + deferred (batched) qemu/podman exec. Missing .expected => SKIP.
    316 kit_lane_E() {
    317     if ! _asm_has_exit; then kit_skip "$KIT_NAME/E" "no .expected exit code"; return; fi
    318     if [ $have_exe_runner -eq 0 ] || [ $have_clang_cross -eq 0 ] || [ $have_start_obj -eq 0 ]; then
    319         kit_skip "$KIT_NAME/E" "no link-exe-runner, $TEST_ARCH clang, or start.o"; return
    320     fi
    321     _asm_emit || { kit_skip "$KIT_NAME/E" "no .o (--emit failed)"; return; }
    322     local exe="$KIT_WORK/linked.exe"
    323     if ! "$LINK_EXE_RUNNER" -o "$exe" "$ASM_OBJ" "$START_OBJ" \
    324             >"$KIT_WORK/exec_link.out" 2>"$KIT_WORK/exec_link.err"; then
    325         kit_fail "$KIT_NAME/E" "link failed"; return
    326     fi
    327     if exec_target_supported "$EXEC_ARCH"; then
    328         kit_queue_e "$KIT_NAME/E" "$exe" \
    329             "$KIT_WORK/exec.out" "$KIT_WORK/exec.err" "$KIT_WORK/exec.rc" \
    330             "$(_asm_exit_byte)" "$EXEC_ARCH"
    331     else
    332         kit_skip "$KIT_NAME/E" "no runner for $EXEC_ARCH"
    333     fi
    334 }
    335 
    336 # ---- has-cases guard -------------------------------------------------------
    337 # kit_corpus_run exits 2 if a sub-corpus has zero matching cases (after the
    338 # KIT_TEST_FILTER), so only invoke it when at least one case is present.
    339 # _asm_have_cases GLOB EXT : mirrors the engine's discovery (basename minus
    340 # .EXT, KIT_TEST_FILTER substring) and returns 0 iff a case survives.
    341 _asm_have_cases() {
    342     local glob="$1" ext="$2" f base
    343     shopt -s nullglob
    344     for f in $glob; do
    345         base="$(basename "$f")"; base="${base%.$ext}"
    346         if [ -n "${KIT_TEST_FILTER:-}" ]; then
    347             case "$base" in *"$KIT_TEST_FILTER"*) ;; *) continue ;; esac
    348         fi
    349         return 0
    350     done
    351     return 1
    352 }
    353 
    354 # ---- drive the three sub-corpora -------------------------------------------
    355 
    356 # (1) encode/*.s — lanes H (golden hex), D (in-proc JIT), J (jit-runner),
    357 #     E (link + deferred exec). Active lanes in PATHS order.
    358 ENCODE_LANES=
    359 [ "$RUN_H" -eq 1 ] && ENCODE_LANES="$ENCODE_LANES H"
    360 [ "$RUN_D" -eq 1 ] && ENCODE_LANES="$ENCODE_LANES D"
    361 [ "$RUN_J" -eq 1 ] && ENCODE_LANES="$ENCODE_LANES J"
    362 [ "$RUN_E" -eq 1 ] && ENCODE_LANES="$ENCODE_LANES E"
    363 if [ -n "$ENCODE_LANES" ] && _asm_have_cases "$TEST_DIR/encode/*.s" s; then
    364     KIT_LABEL=test-asm KIT_BUILD_DIR="$BUILD_DIR/asm/encode" \
    365       KIT_CORPUS_GLOBS="$TEST_DIR/encode/*.s" KIT_CORPUS_EXT=s KIT_SIDECAR_DIR="$TEST_DIR/encode" \
    366       KIT_LANES="$ENCODE_LANES" KIT_OPT_LEVELS="" KIT_TUPLES="$CUR_TUPLE" \
    367       KIT_TARGETS_EXT="" KIT_PARALLELIZABLE="$PAR" kit_corpus_run
    368 fi
    369 
    370 # (2) decode/*.hex — lane T (golden decoded text).
    371 if [ "$RUN_T" -eq 1 ] && _asm_have_cases "$TEST_DIR/decode/*.hex" hex; then
    372     KIT_LABEL=test-asm KIT_BUILD_DIR="$BUILD_DIR/asm/decode" \
    373       KIT_CORPUS_GLOBS="$TEST_DIR/decode/*.hex" KIT_CORPUS_EXT=hex KIT_SIDECAR_DIR="$TEST_DIR/decode" \
    374       KIT_LANES="T" KIT_OPT_LEVELS="" KIT_TUPLES="$CUR_TUPLE" \
    375       KIT_TARGETS_EXT="" KIT_PARALLELIZABLE="$PAR" kit_corpus_run
    376 fi
    377 
    378 # (3) listing/*.in.bin — lane L (golden listing).
    379 if [ "$RUN_L" -eq 1 ] && _asm_have_cases "$TEST_DIR/listing/*.in.bin" in.bin; then
    380     KIT_LABEL=test-asm KIT_BUILD_DIR="$BUILD_DIR/asm/listing" \
    381       KIT_CORPUS_GLOBS="$TEST_DIR/listing/*.in.bin" KIT_CORPUS_EXT=in.bin KIT_SIDECAR_DIR="$TEST_DIR/listing" \
    382       KIT_LANES="L" KIT_OPT_LEVELS="" KIT_TUPLES="$CUR_TUPLE" \
    383       KIT_TARGETS_EXT="" KIT_PARALLELIZABLE="$PAR" kit_corpus_run
    384 fi
    385 
    386 # ---- summary ---------------------------------------------------------------
    387 
    388 kit_summary test-asm
    389 kit_exit