kit

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

rv32.sh (10368B)


      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 # Negative control: a deliberately wrong result must produce a nonzero exit.
    215 sed 's/if (acc != 45) return 1;/if (acc != 45) return 1; return 99;/' "$BUILD_DIR/app.c" > "$BUILD_DIR/bad.c"
    216 if "$KIT" cc -target riscv32-none-elf -march=rv32imac_zicsr_zifencei -mabi=ilp32 -O1 \
    217      -ffreestanding -c "$BUILD_DIR/bad.c" -o "$BUILD_DIR/bad.o" 2>/dev/null \
    218    && ld.lld -T "$BUILD_DIR/link.ld" "$BUILD_DIR/ilp32.start.o" "$BUILD_DIR/bad.o" "$RT_SF" \
    219         -o "$BUILD_DIR/bad.elf" 2>/dev/null; then
    220   rc=0; timeout 20 "$QEMU" -machine virt -bios none -kernel "$BUILD_DIR/bad.elf" -nographic -no-reboot >/dev/null 2>&1 || rc=$?
    221   if [ "$rc" -eq 99 ]; then ok "negative-control (qemu rc=99)";
    222   else not_ok "negative-control" "expected exit 99, got $rc"; fi
    223 fi
    224 
    225 kit_summary test-smoke-rv32
    226 kit_exit