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