kit

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

rv32.sh (14525B)


      1 #!/usr/bin/env bash
      2 # test/smoke/rv32.sh — behavioral oracle for kit's riscv32-none-elf codegen.
      3 #
      4 # Unlike rv64 (qemu-user / podman, a riscv64-linux ELF), rv32 is freestanding
      5 # `-none-elf`: kit compiles the app, we link a bare-metal image at the `virt`
      6 # machine's RAM base (0x80000000) with a startup stub that enables the FPU
      7 # (mstatus.FS, required before any fadd.s under ilp32f) and a SiFive test
      8 # finisher at 0x100000 that turns a program result into a qemu exit code
      9 # (0x5555 -> poweroff/exit 0; 0x3333|(code<<16) -> exit code). Run under
     10 # qemu-system-riscv32 -machine virt -bios none -kernel.
     11 #
     12 # Scope: the full verified codegen surface — 32-bit integer + pointer, hardware
     13 # single-float (ilp32f), control flow, AND (WS6) 64-bit-value legalization:
     14 # long long carry/borrow/bitwise/compare/convert inline as GPR pairs, i64
     15 # mul/div/shift via __*di3 runtime calls, and soft `double` arith/compare/
     16 # convert via __*df3 calls. Two ABI lanes are exercised: ilp32f (hardware single
     17 # float, soft double) and ilp32 (pure soft float). The i64-mul/div/shift and
     18 # double cases link kit's freestanding runtime (libkit_rt.a) for the helpers;
     19 # the inline i64 cases need no runtime.
     20 #
     21 # kit compiles app.c (the code under test) and assembles the startup stub with
     22 # `kit as` (kit's assembler now emits the `csrs` CSR pseudo that enables the
     23 # FPU); the final bare-metal link uses ld.lld (kit's static ELF base-addr
     24 # control for `virt` is exercised separately). The .eh_frame kit emits is
     25 # discarded by the bare-metal link script (no unwinder in a freestanding image).
     26 #
     27 # Skipped (per the shared kit_exit convention) if clang lacks the riscv32
     28 # target, ld.lld is missing, or qemu-system-riscv32 is unavailable.
     29 
     30 set -u
     31 ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
     32 BUILD_DIR="$ROOT/build/test/smoke-rv32"
     33 mkdir -p "$BUILD_DIR"
     34 
     35 KIT_KIT_DIR="$ROOT/test/lib"
     36 # shellcheck source=../lib/kit_sh_kit.sh
     37 . "$ROOT/test/lib/kit_sh_kit.sh"
     38 kit_report_init
     39 KIT_SKIP_IS_FAILURE=1
     40 
     41 # ---- prerequisites (the rv32 doctor) --------------------------------------
     42 # shellcheck source=../lib/check_rv32_env.sh
     43 . "$ROOT/test/lib/check_rv32_env.sh"
     44 check_rv32_env
     45 if [ "${RV32_HAVE_CLANG_TARGET:-0}" -eq 0 ]; then
     46   skip_test "smoke-rv32" "clang riscv32 target unavailable"; kit_summary test-smoke-rv32; kit_exit
     47 fi
     48 if [ "${RV32_HAVE_LLD:-0}" -eq 0 ]; then
     49   skip_test "smoke-rv32" "ld.lld unavailable"; kit_summary test-smoke-rv32; kit_exit
     50 fi
     51 if [ "${RV32_HAVE_QEMU_SYSTEM:-0}" -eq 0 ]; then
     52   skip_test "smoke-rv32" "qemu-system-riscv32 unavailable"; kit_summary test-smoke-rv32; kit_exit
     53 fi
     54 
     55 KIT="$ROOT/build/kit"
     56 QEMU="${RV32_QEMU_SYSTEM_BIN:-qemu-system-riscv32}"
     57 
     58 # Per-ABI startup stubs. ilp32f must enable the FPU (mstatus.FS) before any
     59 # fadd.s; pure-soft ilp32 has no FPU and skips it.
     60 cat > "$BUILD_DIR/start_hf.S" <<'EOF'
     61 .section .text.start,"ax",@progbits
     62 .globl _start
     63 _start:
     64   li sp, 0x80100000
     65   li t0, 0x2000          /* mstatus.FS = Initial: enable the FPU */
     66   csrs mstatus, t0
     67   call cmain
     68 .Lhang: j .Lhang
     69 EOF
     70 cat > "$BUILD_DIR/start_soft.S" <<'EOF'
     71 .section .text.start,"ax",@progbits
     72 .globl _start
     73 _start:
     74   li sp, 0x80100000
     75   call cmain
     76 .Lhang: j .Lhang
     77 EOF
     78 cat > "$BUILD_DIR/link.ld" <<'EOF'
     79 ENTRY(_start)
     80 SECTIONS {
     81   . = 0x80000000;
     82   .text   : { *(.text.start) *(.text*) }
     83   .rodata : { *(.rodata*) }
     84   .data   : { *(.data*) }
     85   .bss    : { *(.bss*) *(COMMON) }
     86   /DISCARD/ : { *(.eh_frame) *(.eh_frame_hdr) *(.riscv.attributes) *(.comment) }
     87 }
     88 EOF
     89 
     90 # The app under test. `hw_float` is gated so the pure-soft ilp32 lane (no FPU,
     91 # rv32imac) does not emit fadd.s. Every check returns a distinct nonzero code on
     92 # failure so a qemu exit pinpoints the broken case.
     93 cat > "$BUILD_DIR/app.c" <<'EOF'
     94 #define FINISHER ((volatile unsigned int*)0x100000)
     95 __attribute__((noreturn)) static void finish(int code){
     96   *FINISHER = code ? (0x3333u | ((unsigned)code << 16)) : 0x5555u;
     97   for(;;){}
     98 }
     99 static int compute(void){
    100   /* 32-bit integer / control flow */
    101   int acc = 0; for (int i = 0; i < 10; i++) acc += i;
    102   if (acc != 45) return 1;
    103   volatile unsigned u = 0x12345678u; u ^= 0x0F0F0F0Fu;
    104   if (u != (0x12345678u ^ 0x0F0F0F0Fu)) return 2;
    105 #ifdef HW_FLOAT
    106   volatile float f = 1.5f; f = f + 2.5f;          /* HW fadd.s -> 4.0 */
    107   if (f != 4.0f) return 3;
    108 #endif
    109   /* i64 inline: carry / borrow / bitwise / compare / convert (no runtime) */
    110   volatile unsigned long long a = 0xFFFFFFFFull; a += 1;
    111   if (a != 0x100000000ull) return 4;
    112   volatile unsigned long long b = 0x100000000ull; b -= 1;
    113   if (b != 0x0FFFFFFFFull) return 5;
    114   volatile unsigned long long c = 0x1122334455667788ull;
    115   if ((c ^ 0xFFFFFFFFFFFFFFFFull) != 0xEEDDCCBBAA998877ull) return 6;
    116   volatile long long x = 0x1234567800000000ll, y = 0x1234567700000001ll;
    117   if (!(x > y) || !(y < x) || (x == y)) return 7;
    118   volatile int s32 = -7; volatile long long s64 = s32;
    119   if (s64 != -7) return 8;
    120   volatile long long big = 0x00000000FAFAFAFAll;
    121   if ((unsigned)(int)big != 0xFAFAFAFAu) return 9;
    122   if (a) { } else return 10;                        /* truthiness (hi word set) */
    123   /* i64 runtime: mul / udiv / umod / shifts (__muldi3/__udivdi3/...) */
    124   volatile unsigned long long m = 0x0000000100000001ull;
    125   if (m * 3ull != 0x0000000300000003ull) return 11;
    126   volatile unsigned long long d = 0xFFFFFFFFFFFFFFFFull;
    127   if (d / 0xFFFFFFFFull != 0x0000000100000001ull) return 12;
    128   if (d % 7ull != (0xFFFFFFFFFFFFFFFFull % 7ull)) return 13;
    129   volatile unsigned long long sh = 1ull;
    130   if ((sh << 40) != 0x0000010000000000ull) return 14;
    131   volatile long long sar = -0x4000000000000000ll;
    132   if ((sar >> 36) != (-0x4000000000000000ll >> 36)) return 15;
    133   /* soft double: arith / compare / convert (__adddf3/__muldf3/__floatsidf/...) */
    134   volatile double p = 1.5, q = 2.25;
    135   if (p + q != 3.75) return 16;
    136   if (p * q != 3.375) return 17;
    137   if (!(p < q) || (p >= q)) return 18;
    138   volatile int iv = 7; volatile double dv = iv;
    139   if (dv != 7.0) return 19;
    140   volatile double dd = 3.75; if ((int)dd != 3) return 20;
    141   volatile long long L = 5000000000ll; volatile double dL = L;
    142   if (dL != 5000000000.0) return 21;
    143   volatile double dbig = 5000000000.0;
    144   if ((long long)dbig != 5000000000ll) return 22;
    145   return 0;
    146 }
    147 void cmain(void){ finish(compute()); }
    148 EOF
    149 
    150 # Build kit's freestanding runtime archive for the helpers (i64 mul/div/shift +
    151 # soft double). Built on demand; skip the runtime-dependent checks if it cannot
    152 # be produced (e.g. a partial checkout) rather than failing spuriously.
    153 RT_HF="$ROOT/build/rt/riscv32-elf-hardfloat/libkit_rt.a"
    154 RT_SF="$ROOT/build/rt/riscv32-elf/libkit_rt.a"
    155 make -C "$ROOT" rt-riscv32-elf-hardfloat rt-riscv32-elf >/dev/null 2>&1 || true
    156 
    157 run_lane() {  # <name> <march> <mabi> <startsrc> <rtarchive> <cppdef>
    158   local name="$1" march="$2" mabi="$3" startsrc="$4" rt="$5" def="$6"
    159   if [ ! -f "$rt" ]; then
    160     skip_test "$name" "runtime archive $rt missing"; return; fi
    161   local so="$BUILD_DIR/$name.start.o"
    162   "$KIT" as -target riscv32-none-elf -march="$march" -mabi="$mabi" \
    163         -o "$so" "$startsrc" 2>/dev/null
    164   local O
    165   for O in -O0 -O1; do
    166     local o="$BUILD_DIR/$name$O.o" elf="$BUILD_DIR/$name$O.elf"
    167     if ! "$KIT" cc -target riscv32-none-elf -march="$march" -mabi="$mabi" $O $def \
    168          -ffreestanding -c "$BUILD_DIR/app.c" -o "$o" 2>"$BUILD_DIR/$name$O.cc.err"; then
    169       not_ok "$name $O (kit cc)" "$BUILD_DIR/$name$O.cc.err"; continue; fi
    170     if ! ld.lld -T "$BUILD_DIR/link.ld" "$so" "$o" "$rt" -o "$elf" \
    171          2>"$BUILD_DIR/$name$O.ld.err"; then
    172       not_ok "$name $O (link)" "$BUILD_DIR/$name$O.ld.err"; continue; fi
    173     local rc=0
    174     timeout 20 "$QEMU" -machine virt -bios none -kernel "$elf" -nographic -no-reboot \
    175       >/dev/null 2>&1 || rc=$?
    176     if [ "$rc" -eq 0 ]; then ok "$name $O (qemu rc=0)";
    177     else not_ok "$name $O" "expected exit 0, got $rc (failed check #$rc)"; fi
    178   done
    179 }
    180 
    181 # ilp32f: hardware single float + soft double + i64.
    182 run_lane "ilp32f" "rv32imafc_zicsr_zifencei" "ilp32f" "$BUILD_DIR/start_hf.S"   "$RT_HF" "-DHW_FLOAT"
    183 # ilp32: pure soft float (no FPU) + i64.
    184 run_lane "ilp32"  "rv32imac_zicsr_zifencei"  "ilp32"  "$BUILD_DIR/start_soft.S" "$RT_SF" ""
    185 
    186 # Full kit toolchain, runtime auto-linked: re-link the per-ABI objects with
    187 # `kit ld` (not ld.lld) and WITHOUT naming a runtime archive. rv32 is no longer
    188 # special-cased — kit ld resolves the float ABI from the objects' ELF e_flags
    189 # (ilp32f -> single, ilp32 -> soft), builds the matching libkit_rt.a on demand
    190 # (build/rt/riscv32-elf-hardfloat / riscv32-elf), and links it in, exactly like
    191 # any other target. A freestanding rv32 target defaults to non-PIE, so no
    192 # -no-pie / -nostdlib is needed; -T places .text.start at the qemu `virt` RAM
    193 # base (0x80000000). The i64-mul/div/shift and soft-double helpers come from the
    194 # auto-linked runtime, so a clean qemu exit also proves runtime resolution.
    195 kitld_lane() {  # <name>
    196   local name="$1"
    197   local starto="$BUILD_DIR/$name.start.o" appo="$BUILD_DIR/$name-O1.o"
    198   local elf="$BUILD_DIR/kitld-$name.elf"
    199   [ -f "$starto" ] && [ -f "$appo" ] || return
    200   if "$KIT" ld -T "$BUILD_DIR/link.ld" -e _start "$starto" "$appo" \
    201        -o "$elf" 2>"$BUILD_DIR/kitld-$name.ld.err"; then
    202     local rc=0
    203     timeout 20 "$QEMU" -machine virt -bios none -kernel "$elf" \
    204       -nographic -no-reboot >/dev/null 2>&1 || rc=$?
    205     if [ "$rc" -eq 0 ]; then ok "kit-ld $name -O1 auto-rt (qemu rc=0)";
    206     else not_ok "kit-ld $name -O1 auto-rt" "expected exit 0, got $rc"; fi
    207   else
    208     not_ok "kit-ld $name -O1 auto-rt (link)" "$BUILD_DIR/kitld-$name.ld.err"
    209   fi
    210 }
    211 kitld_lane "ilp32f"
    212 kitld_lane "ilp32"
    213 
    214 # Relocated, read-only distribution + empty user-cache regression. Two links
    215 # per ABI race the same cache key: the driver must build entirely with Kit,
    216 # publish one complete archive atomically, and keep soft/ilp32f variants
    217 # separate. The linked images and an intermediate -r object retain the merged
    218 # psABI e_flags and execute under qemu.
    219 DIST="$BUILD_DIR/Runtime Cache Distribution"
    220 CACHE="$BUILD_DIR/empty user cache"
    221 chmod -R u+w "$DIST" 2>/dev/null || true
    222 rm -rf "$DIST" "$CACHE"
    223 mkdir -p "$DIST/bin" "$DIST/support"
    224 cp "$KIT" "$DIST/bin/kit"
    225 cp -R "$ROOT/rt" "$DIST/support/rt"
    226 chmod -R a-w "$DIST"
    227 
    228 cache_link_lane() { # <name> <suffix>
    229   local name="$1" suffix="$2"
    230   local starto="$BUILD_DIR/$name.start.o" appo="$BUILD_DIR/$name-O1.o"
    231   local elf="$BUILD_DIR/cache-$name-$suffix.elf"
    232   env HOME="$BUILD_DIR/cache-home" XDG_CACHE_HOME="$CACHE" \
    233     "$DIST/bin/kit" ld -T "$BUILD_DIR/link.ld" -e _start "$starto" "$appo" \
    234       -o "$elf" >"$BUILD_DIR/cache-$name-$suffix.out" \
    235       2>"$BUILD_DIR/cache-$name-$suffix.err"
    236 }
    237 
    238 pids= names=
    239 for lane in ilp32f ilp32; do
    240   for suffix in a b; do
    241     cache_link_lane "$lane" "$suffix" &
    242     pids="$pids $!"
    243     names="$names $lane-$suffix"
    244   done
    245 done
    246 set -- $pids
    247 set -- "$@" # keep shellcheck from treating the expansion as an array
    248 i=1
    249 for name in $names; do
    250   eval pid='${'$i'}'
    251   if wait "$pid"; then ok "runtime-cache concurrent $name";
    252   else not_ok "runtime-cache concurrent $name" "$BUILD_DIR/cache-$name.err"; fi
    253   i=$((i + 1))
    254 done
    255 
    256 for variant in riscv32-elf-hardfloat riscv32-elf; do
    257   archive="$CACHE/kit/$variant/libkit_rt.a"
    258   if [ -s "$archive" ] && "$DIST/bin/kit" ar t "$archive" \
    259        >"$BUILD_DIR/cache-$variant.members" 2>"$BUILD_DIR/cache-$variant.ar.err" \
    260        && [ -s "$BUILD_DIR/cache-$variant.members" ]; then
    261     ok "runtime-cache $variant atomically published archive"
    262   else
    263     not_ok "runtime-cache $variant archive" "$BUILD_DIR/cache-$variant.ar.err"
    264   fi
    265 done
    266 if [ ! -e "$DIST/support/build" ]; then
    267   ok "runtime-cache read-only distribution stayed unmodified"
    268 else
    269   not_ok "runtime-cache read-only distribution" "support/build was created"
    270 fi
    271 
    272 READELF="$(command -v llvm-readelf 2>/dev/null || command -v readelf 2>/dev/null || true)"
    273 check_flags() { # <label> <file> <hex>
    274   local label="$1" file="$2" want="$3" flags
    275   if [ -z "$READELF" ]; then
    276     skip_test "$label" "readelf unavailable"; return
    277   fi
    278   flags="$($READELF -h "$file" 2>"$file.readelf.err" | \
    279            sed -n 's/^[[:space:]]*Flags:[[:space:]]*//p')"
    280   if printf '%s\n' "$flags" | grep -q "^$want\(,\|$\)"; then
    281     ok "$label ($flags)"
    282   else
    283     not_ok "$label" "expected flags $want, got: $flags"
    284   fi
    285 }
    286 
    287 if "$DIST/bin/kit" ld -r "$BUILD_DIR/ilp32f.start.o" \
    288      "$BUILD_DIR/ilp32f-O1.o" -o "$BUILD_DIR/merged-ilp32f.o" \
    289      >"$BUILD_DIR/merged-ilp32f.out" 2>"$BUILD_DIR/merged-ilp32f.err"; then
    290   check_flags "rv32 ilp32f relocatable e_flags" "$BUILD_DIR/merged-ilp32f.o" "0x3"
    291 else
    292   not_ok "rv32 ilp32f relocatable link" "$BUILD_DIR/merged-ilp32f.err"
    293 fi
    294 check_flags "rv32 ilp32f final e_flags" "$BUILD_DIR/cache-ilp32f-a.elf" "0x3"
    295 check_flags "rv32 ilp32 final e_flags" "$BUILD_DIR/cache-ilp32-a.elf" "0x1"
    296 
    297 if env HOME="$BUILD_DIR/cache-home" XDG_CACHE_HOME="$CACHE" \
    298    "$DIST/bin/kit" ld -T "$BUILD_DIR/link.ld" -e _start \
    299      "$BUILD_DIR/ilp32f.start.o" "$BUILD_DIR/ilp32-O1.o" \
    300      -o "$BUILD_DIR/mixed-abi.elf" >"$BUILD_DIR/mixed-abi.out" \
    301      2>"$BUILD_DIR/mixed-abi.err"; then
    302   not_ok "rv32 mixed float ABI rejection" "link unexpectedly succeeded"
    303 elif grep -q 'ilp32-O1.o.*float ABI\|float ABI.*ilp32-O1.o' \
    304        "$BUILD_DIR/mixed-abi.err"; then
    305   ok "rv32 mixed float ABI rejection names input"
    306 else
    307   not_ok "rv32 mixed float ABI diagnostic" "$BUILD_DIR/mixed-abi.err"
    308 fi
    309 
    310 for lane in ilp32f ilp32; do
    311   rc=0
    312   timeout 20 "$QEMU" -machine virt -bios none \
    313     -kernel "$BUILD_DIR/cache-$lane-a.elf" -nographic -no-reboot \
    314     >"$BUILD_DIR/cache-$lane.qemu.out" 2>"$BUILD_DIR/cache-$lane.qemu.err" || rc=$?
    315   if [ "$rc" -eq 0 ]; then ok "runtime-cache $lane qemu rc=0";
    316   else not_ok "runtime-cache $lane qemu" "expected exit 0, got $rc"; fi
    317 done
    318 
    319 # Negative control: a deliberately wrong result must produce a nonzero exit.
    320 sed 's/if (acc != 45) return 1;/if (acc != 45) return 1; return 99;/' "$BUILD_DIR/app.c" > "$BUILD_DIR/bad.c"
    321 if "$KIT" cc -target riscv32-none-elf -march=rv32imac_zicsr_zifencei -mabi=ilp32 -O1 \
    322      -ffreestanding -c "$BUILD_DIR/bad.c" -o "$BUILD_DIR/bad.o" 2>/dev/null \
    323    && ld.lld -T "$BUILD_DIR/link.ld" "$BUILD_DIR/ilp32.start.o" "$BUILD_DIR/bad.o" "$RT_SF" \
    324         -o "$BUILD_DIR/bad.elf" 2>/dev/null; then
    325   rc=0; timeout 20 "$QEMU" -machine virt -bios none -kernel "$BUILD_DIR/bad.elf" -nographic -no-reboot >/dev/null 2>&1 || rc=$?
    326   if [ "$rc" -eq 99 ]; then ok "negative-control (qemu rc=99)";
    327   else not_ok "negative-control" "expected exit 99, got $rc"; fi
    328 fi
    329 
    330 kit_summary test-smoke-rv32
    331 kit_exit