kit

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

run.sh (12980B)


      1 #!/usr/bin/env bash
      2 # test/libc/musl/run.sh — drive kit ld against a real musl sysroot, on the
      3 # shared corpus harness (test/lib/kit_corpus.sh).
      4 #
      5 # Cases under test/libc/cases/*.c are compiled, linked, and run against a
      6 # podman-pinned Alpine/musl sysroot, in two link variants (two lanes):
      7 #
      8 #   S  static  — non-PIC object + libc.a, classic static-exe link
      9 #       kit ld -static -o case.exe                                          \
     10 #           $SYSROOT/usr/lib/crt1.o $SYSROOT/usr/lib/crti.o                    \
     11 #           case.o                                                             \
     12 #           $SYSROOT/usr/lib/libc.a $KIT_RT                                  \
     13 #           $SYSROOT/usr/lib/crtn.o
     14 #
     15 #   D  dynamic — PIE object + libc.so, expects PT_INTERP /lib/ld-musl-<arch>.so.1
     16 #       kit ld -pie -o case.exe                                             \
     17 #           $SYSROOT/usr/lib/Scrt1.o $SYSROOT/usr/lib/crti.o                   \
     18 #           case.o                                                             \
     19 #           $SYSROOT/usr/lib/libc.so $KIT_RT                                 \
     20 #           $SYSROOT/usr/lib/crtn.o
     21 #       (musl ships ld-musl-<arch>.so.1 *as* libc — same file. The harness
     22 #       intentionally has no -dynamic-linker flag yet because kit ld
     23 #       currently doesn't accept one; this is one of the gaps we expect the
     24 #       dynamic variant to surface.)
     25 #
     26 # Each case file may carry an `expected` companion (default 0) and an optional
     27 # `expected_stdout` (.stdout) file checked with substring match.
     28 #
     29 # The cross-exec is LANE-LOCAL: the lane runs the case under podman with musl's
     30 # own arch-pinned Alpine image, whose rootfs already carries the musl loader at
     31 # /lib/ld-musl-<arch>.so.1 — so NO QEMU_LD_PREFIX and no -dynamic-linker are
     32 # needed (unlike glibc). This is deliberately NOT routed through exec_target's
     33 # default-image queue; the image + loader provenance are musl-specific.
     34 #
     35 # Arch selection — KIT_LIBC_ARCHES (default "aa64"), space-separated; each
     36 # token becomes an "<arch>-elf" corpus tuple:
     37 #   aa64 -> $XDG_CACHE_HOME/kit/sysroots/linux-musl/  + build/rt/aarch64-linux/libkit_rt.a
     38 #                                       + --target=aarch64-linux-musl
     39 #   x64  -> $XDG_CACHE_HOME/kit/sysroots/linux-musl-x64/  + build/rt/x86_64-linux/libkit_rt.a
     40 #                                       + --target=x86_64-linux-musl
     41 #   rv64 -> $XDG_CACHE_HOME/kit/sysroots/linux-musl-rv64/ + build/rt/riscv64-linux/libkit_rt.a
     42 #                                       + --target=riscv64-linux-musl
     43 # A missing sysroot / rt / clang-target for an enabled arch is reported as a
     44 # non-gating SKIP-NA (never a failure); only test failures cause a nonzero exit.
     45 set -u
     46 
     47 ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
     48 _KIT_SYSROOTS="${XDG_CACHE_HOME:-$HOME/.cache}/kit/sysroots"
     49 export KIT_LIB_DIR="$ROOT/test/lib"
     50 . "$ROOT/test/lib/kit_corpus.sh"
     51 
     52 CASES_DIR="$ROOT/test/libc/cases"
     53 BUILD_DIR="$ROOT/build/musl"
     54 KIT="${KIT:-$ROOT/build/kit}"
     55 
     56 if [ ! -x "$KIT" ]; then
     57     echo "kit driver missing at $KIT — run 'make' first" >&2
     58     exit 2
     59 fi
     60 
     61 mkdir -p "$BUILD_DIR"
     62 
     63 # ---- arch lookup tables (LANE-LOCAL config) -------------------------------
     64 # Keyed on the arch token (aa64/x64/rv64), which is the tuple's arch half.
     65 
     66 arch_sysroot() {
     67     case "$1" in
     68         aa64) echo "$_KIT_SYSROOTS/linux-musl" ;;
     69         x64)  echo "$_KIT_SYSROOTS/linux-musl-x64" ;;
     70         rv64) echo "$_KIT_SYSROOTS/linux-musl-rv64" ;;
     71         *)    echo "" ;;
     72     esac
     73 }
     74 
     75 arch_rt() {
     76     case "$1" in
     77         aa64) echo "$ROOT/build/rt/aarch64-linux/libkit_rt.a" ;;
     78         x64)  echo "$ROOT/build/rt/x86_64-linux/libkit_rt.a" ;;
     79         rv64) echo "$ROOT/build/rt/riscv64-linux/libkit_rt.a" ;;
     80         *)    echo "" ;;
     81     esac
     82 }
     83 
     84 arch_target() {
     85     case "$1" in
     86         aa64) echo "aarch64-linux-musl" ;;
     87         x64)  echo "x86_64-linux-musl" ;;
     88         rv64) echo "riscv64-linux-musl" ;;
     89         *)    echo "" ;;
     90     esac
     91 }
     92 
     93 # Container image carrying the musl loader at /lib/ld-musl-<arch>.so.1, used by
     94 # the lane-local runner. Pinned to arch-specific repos (not multi-arch tags) to
     95 # dodge the cached-wrong-arch-manifest trap and to avoid --platform (which would
     96 # force a registry manifest lookup on every run).
     97 arch_image() {
     98     case "$1" in
     99         # arm64v8/alpine ships the musl loader at /lib/ld-musl-aarch64.so.1.
    100         aa64) echo "docker.io/arm64v8/alpine:latest" ;;
    101         # amd64v2/alpine isn't a thing; amd64/alpine is the canonical pin.
    102         # Ships /lib/ld-musl-x86_64.so.1.
    103         x64)  echo "docker.io/amd64/alpine:latest" ;;
    104         # alpine:edge currently carries the riscv64 musl loader.
    105         rv64) echo "docker.io/riscv64/alpine:edge" ;;
    106         *)    echo "" ;;
    107     esac
    108 }
    109 
    110 # Spelling extract.sh accepts for `-a`: aa64 -> aarch64; x64 -> x64.
    111 arch_extract_name() {
    112     case "$1" in
    113         aa64) echo "aarch64" ;;
    114         *)    echo "$1" ;;
    115     esac
    116 }
    117 
    118 # qemu-user fallback binary per arch (used when no native exec is possible).
    119 arch_qemu() {
    120     case "$1" in
    121         aa64) echo "$QEMU_AA64" ;;
    122         x64)  echo "$QEMU_X64" ;;
    123         rv64) echo "$QEMU_RV64" ;;
    124         *)    echo "" ;;
    125     esac
    126 }
    127 
    128 # ---- host capability detection --------------------------------------------
    129 #
    130 # Native linux/<arch> hosts can exec ELFs directly under podman without
    131 # binfmt; otherwise we fall back to qemu-<arch>-static.
    132 
    133 arch_raw="$(uname -m 2>/dev/null || true)"
    134 is_aarch64=0
    135 { [ "$arch_raw" = "aarch64" ] || [ "$arch_raw" = "arm64" ]; } && is_aarch64=1
    136 is_x86_64=0
    137 { [ "$arch_raw" = "x86_64" ] || [ "$arch_raw" = "amd64" ]; } && is_x86_64=1
    138 
    139 QEMU_AA64="$(command -v qemu-aarch64-static 2>/dev/null || command -v qemu-aarch64 2>/dev/null || true)"
    140 QEMU_X64="$(command -v qemu-x86_64-static 2>/dev/null || command -v qemu-x86_64 2>/dev/null || true)"
    141 QEMU_RV64="$(command -v qemu-riscv64-static 2>/dev/null || command -v qemu-riscv64 2>/dev/null || true)"
    142 have_podman=0; command -v podman >/dev/null 2>&1 && have_podman=1
    143 
    144 # ---- lane-local target runner ---------------------------------------------
    145 # run_target <arch> <exe> <out> <err>  -> sets RUN_RC
    146 # Musl: rootfs-baked loader, NO QEMU_LD_PREFIX.
    147 run_target() {
    148     local arch="$1" exe="$2" out="$3" err="$4"
    149     local qemu image
    150     qemu="$(arch_qemu "$arch")"
    151     image="$(arch_image "$arch")"
    152     if [ -n "$qemu" ]; then
    153         "$qemu" "$exe" >"$out" 2>"$err"; RUN_RC=$?; return
    154     fi
    155     if [ $have_podman -eq 1 ]; then
    156         local dir base
    157         dir="$(cd "$(dirname "$exe")" && pwd)"; base="$(basename "$exe")"
    158         podman run --rm --pull=never --net=none \
    159             -v "$dir":/work:Z -w /work \
    160             "$image" "./$base" \
    161             >"$out" 2>"$err"
    162         RUN_RC=$?; return
    163     fi
    164     RUN_RC=127
    165 }
    166 
    167 # ---- per-case marker reading (KIT_READ_CASE) --------------------------------
    168 # Reads the optional .stdout substring oracle and resolves this tuple's arch
    169 # config. The .expected sidecar (default 0) is read by the engine already
    170 # (KIT_EXPECTED_EXT=.expected). A tuple whose sysroot/rt/clang-target is
    171 # unavailable is marked SKIP-NA (non-gating), mirroring the original's
    172 # SKIP_ARCHES informational handling.
    173 libc_read_case() {
    174     LC_ARCH="$KIT_ARCH"
    175     LC_SYSROOT="$(arch_sysroot "$LC_ARCH")"
    176     LC_RT="$(arch_rt "$LC_ARCH")"
    177     LC_TARGET="$(arch_target "$LC_ARCH")"
    178 
    179     if [ -z "$LC_SYSROOT" ] || [ -z "$LC_RT" ] || [ -z "$LC_TARGET" ]; then
    180         KIT_SKIP_NA_CASE=1; return
    181     fi
    182     if [ ! -d "$LC_SYSROOT" ]; then
    183         KIT_SKIP_NA_CASE=1; return
    184     fi
    185     if [ ! -f "$LC_RT" ]; then
    186         KIT_SKIP_NA_CASE=1; return
    187     fi
    188     # clang must understand --target=<target>. Recent clang ships linux-musl as
    189     # a target alias of linux-gnu for our purposes (we override every system
    190     # path via --sysroot).
    191     if ! clang --target="$LC_TARGET" -c -x c - -o /dev/null < /dev/null 2>/dev/null; then
    192         KIT_SKIP_NA_CASE=1; return
    193     fi
    194 
    195     LC_EXPECT_STDOUT=""
    196     if [ -f "$CASES_DIR/${KIT_BASE}.stdout" ]; then
    197         LC_EXPECT_STDOUT="$(cat "$CASES_DIR/${KIT_BASE}.stdout")"
    198     fi
    199 }
    200 
    201 # ---- shared per-variant pipeline (compile -> link -> run -> stdout) --------
    202 # libc_run_variant <variant> <label>
    203 #   variant ∈ {static, dynamic}; emits exactly one kit_pass/kit_fail.
    204 #   All artifacts live under $KIT_WORK (parallel-safe).
    205 libc_run_variant() {
    206     local variant="$1" label="$2"
    207     local work="$KIT_WORK/$variant"
    208     mkdir -p "$work"
    209 
    210     # ---- compile ----
    211     # -nostdinc strips clang's default include path (resource dir +
    212     # /usr/include) so the sysroot's musl + linux-headers tree is the sole
    213     # source. -isystem $sysroot/usr/include picks it up.
    214     local cc_flags=(--target="$LC_TARGET" --sysroot="$LC_SYSROOT"
    215                     -nostdinc
    216                     -isystem "$LC_SYSROOT/usr/include"
    217                     -O0)
    218     case "$variant" in
    219         static)  cc_flags+=(-fno-PIC -fno-pie) ;;
    220         dynamic) cc_flags+=(-fPIE -fpic) ;;
    221     esac
    222 
    223     local obj="$work/${KIT_BASE}.o"
    224     if ! clang "${cc_flags[@]}" -c "$KIT_SRC" -o "$obj" 2>"$work/cc.err"; then
    225         kit_fail "$label (compile)"
    226         sed 's/^/    cc| /' "$work/cc.err"
    227         return
    228     fi
    229 
    230     # ---- link ----
    231     local exe="$work/${KIT_BASE}.exe"
    232     local link_cmd
    233     case "$variant" in
    234         static)
    235             # Link order mirrors a typical static-musl invocation:
    236             #   crt1.o crti.o  obj  libc.a libkit_rt.a  crtn.o
    237             # libkit_rt provides the TF / soft-float builtins (__addtf3,
    238             # __extenddftf2 etc.) that musl's libc.a calls from printf's
    239             # long-double formatting. Archive ingestion iterates demand-load
    240             # to a fixed point so one trailing libkit_rt.a is enough.
    241             link_cmd=("$KIT" "ld" -static -o "$exe"
    242                       "$LC_SYSROOT/usr/lib/crt1.o" "$LC_SYSROOT/usr/lib/crti.o"
    243                       "$obj"
    244                       "$LC_SYSROOT/usr/lib/libc.a" "$LC_RT"
    245                       "$LC_SYSROOT/usr/lib/crtn.o")
    246             ;;
    247         dynamic)
    248             # Dynamic-exe link: PIE start file, libc.so as a *shared* input
    249             # (not an archive), expects kit ld to:
    250             #   - accept ET_DYN ELF objects as input,
    251             #   - emit PT_INTERP "/lib/ld-musl-<arch>.so.1",
    252             #   - emit PT_DYNAMIC with DT_NEEDED libc.so,
    253             #   - emit a .dynsym/.dynstr/.gnu.hash + .rela.plt/.got.plt so the
    254             #     loader can bind imported symbols at runtime.
    255             # libkit_rt.a stays — soft-float TF helpers are still
    256             # static-bound from our side. crti/crtn are unchanged.
    257             link_cmd=("$KIT" "ld" -pie -o "$exe"
    258                       "$LC_SYSROOT/usr/lib/Scrt1.o" "$LC_SYSROOT/usr/lib/crti.o"
    259                       "$obj"
    260                       "$LC_SYSROOT/usr/lib/libc.so" "$LC_RT"
    261                       "$LC_SYSROOT/usr/lib/crtn.o")
    262             ;;
    263     esac
    264 
    265     if ! "${link_cmd[@]}" >"$work/link.out" 2>"$work/link.err"; then
    266         kit_fail "$label (link)"
    267         sed 's/^/    ld| /' "$work/link.err" | head -10
    268         return
    269     fi
    270 
    271     # ---- run (lane-local cross-exec) ----
    272     run_target "$LC_ARCH" "$exe" "$work/run.out" "$work/run.err"
    273     if [ "$RUN_RC" -ne "$KIT_EXPECTED" ]; then
    274         kit_fail "$label (run rc=$RUN_RC, want $KIT_EXPECTED)"
    275         [ -s "$work/run.err" ] && sed 's/^/    err| /' "$work/run.err" | head -5
    276         [ -s "$work/run.out" ] && sed 's/^/    out| /' "$work/run.out" | head -5
    277         return
    278     fi
    279 
    280     if [ -n "$LC_EXPECT_STDOUT" ]; then
    281         if ! grep -qF -- "$LC_EXPECT_STDOUT" "$work/run.out"; then
    282             kit_fail "$label (stdout mismatch)"
    283             printf '    expected substring: %s\n' "$LC_EXPECT_STDOUT"
    284             sed 's/^/    got| /' "$work/run.out" | head -5
    285             return
    286         fi
    287     fi
    288 
    289     kit_pass "$label"
    290 }
    291 
    292 # ---- lanes (static / dynamic variant axis) --------------------------------
    293 kit_lane_S() { libc_run_variant static  "$KIT_ARCH/$KIT_BASE [static]"; }
    294 kit_lane_D() { libc_run_variant dynamic "$KIT_ARCH/$KIT_BASE [dynamic]"; }
    295 
    296 # ---- drive the corpus ------------------------------------------------------
    297 # One kit_corpus_run per arch (single "<arch>-elf" tuple), so each arch gets a
    298 # distinct per-case $KIT_WORK ($KIT_BUILD_DIR/<arch>/<base>) and the static/
    299 # dynamic lanes for one arch never collide with another arch's under parallel
    300 # dispatch. Results accumulate into the shared counters; one summary at the end.
    301 printf 'test-libc-musl arches=%s\n' "${KIT_LIBC_ARCHES:-aa64}"
    302 
    303 PAR="${KIT_MUSL_PARALLEL:-1}"
    304 for arch in ${KIT_LIBC_ARCHES:-aa64}; do
    305     KIT_LABEL=test-libc-musl KIT_BUILD_DIR="$BUILD_DIR/$arch" \
    306       KIT_CORPUS_GLOBS="$CASES_DIR/*.c" KIT_CORPUS_EXT=c KIT_SIDECAR_DIR="$CASES_DIR" \
    307       KIT_LANES="S D" KIT_OPT_LEVELS="" KIT_TUPLES="$arch-elf" \
    308       KIT_EXPECTED_EXT=.expected KIT_TARGETS_EXT="" \
    309       KIT_READ_CASE=libc_read_case KIT_PARALLELIZABLE="$PAR" \
    310       kit_corpus_run
    311 done
    312 
    313 kit_summary test-libc-musl
    314 kit_exit