exec_rv32_bare.sh (9636B)
1 #!/usr/bin/env bash 2 # test/lib/exec_rv32_bare.sh — shared bare-metal execution helper for the rv32 3 # cross-test lane (path "V") of the corpus harnesses (test/toy/run.sh and 4 # test/parse/run.sh). 5 # 6 # rv32 is a freestanding `-none-elf` target with no qemu-user / podman path 7 # (unlike the aa64/x64/rv64 Linux cross lanes that go through exec_target.sh). 8 # A corpus object — whose `main` returns an exit code — is instead linked with a 9 # bare-metal startup that sets the stack, enables the FPU (ilp32f), calls main, 10 # and reports its return through a SiFive test finisher, then run under 11 # qemu-system-riscv32 -machine virt. The qemu exit code equals main's return 12 # (0 -> 0x5555 -> qemu exit 0; N -> 0x3333|(N<<16) -> qemu exit N), so the 13 # corpus's existing `rc == expected` oracle applies unchanged. 14 # 15 # The link uses `kit ld` (a freestanding rv32 target defaults to no-PIE and 16 # auto-links no runtime, so the corpus + the kit runtime archive are supplied 17 # explicitly), exercising the full kit toolchain end to end. The startup stub is 18 # kit-assembled (`kit as`) too — kit's assembler now emits the `csrs` CSR pseudo 19 # used to enable the FPU — so the whole image is built by kit. 20 # 21 # Public API (after sourcing): 22 # rv32_bare_setup <workdir> populate RV32_BARE_OK (0/1) and cache 23 # the startup/wrapper/linkscript + rt. 24 # rv32_bare_run <obj> <work> <rcfile> link <obj> into a bootable image and 25 # run it; write the qemu exit code to 26 # <rcfile>. Echoes a one-line reason and 27 # returns: 0 ran (rc in <rcfile>), 28 # 2 link/build failure (caller decides). 29 30 # shellcheck disable=SC2034 # RV32_BARE_* are consumed by the sourcing harness. 31 32 _rv32_bare_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" 33 # shellcheck source=check_rv32_env.sh 34 . "$_rv32_bare_root/test/lib/check_rv32_env.sh" 35 36 RV32_BARE_OK=0 37 RV32_BARE_KIT="${KIT:-$_rv32_bare_root/build/kit}" 38 # Entry symbol the wrapper calls and reports the exit code of. Toy cases define 39 # `main`; the C parse corpus defines `test_main`. Set before rv32_bare_setup. 40 RV32_BARE_ENTRY="${RV32_BARE_ENTRY:-main}" 41 RV32_BARE_QEMU="" 42 RV32_BARE_RT="" 43 RV32_BARE_START="" 44 RV32_BARE_WRAP="" 45 RV32_BARE_LDS="" 46 # ilp32f: hardware single float + soft double + i64 — the verified rv32 profile. 47 RV32_BARE_MARCH="rv32imafc_zicsr_zifencei" 48 RV32_BARE_MABI="ilp32f" 49 50 rv32_bare_setup() { 51 local work="$1" 52 RV32_BARE_OK=0 53 check_rv32_env >/dev/null 2>&1 54 # The whole image (corpus, startup stub, wrapper, runtime archive, link) is 55 # built by kit — `kit cc` / `kit as` / `kit ld` — and run under 56 # qemu-system-riscv32, so the only external prerequisite is qemu. (clang is 57 # not invoked anywhere on this path; it remains a gate only for the smoke 58 # lane's ld.lld doctor in check_rv32_env.) 59 if [ "${RV32_HAVE_QEMU_SYSTEM:-0}" -ne 1 ]; then 60 return 0 61 fi 62 RV32_BARE_QEMU="${RV32_QEMU_SYSTEM_BIN:-qemu-system-riscv32}" 63 [ -x "$RV32_BARE_KIT" ] || return 0 64 65 # The freestanding runtime (i64 mul/div/shift + soft double helpers). Build it 66 # on demand; without it, link of any corpus case touching those would fail. 67 RV32_BARE_RT="$_rv32_bare_root/build/rt/riscv32-elf-hardfloat/libkit_rt.a" 68 if [ ! -f "$RV32_BARE_RT" ]; then 69 make -C "$_rv32_bare_root" rt-riscv32-elf-hardfloat >/dev/null 2>&1 || true 70 fi 71 [ -f "$RV32_BARE_RT" ] || return 0 72 73 mkdir -p "$work" 74 RV32_BARE_START="$work/_rv32_start.o" 75 RV32_BARE_WRAP="$work/_rv32_wrap.o" 76 RV32_BARE_LDS="$work/_rv32.lds" 77 78 # Bare-metal reset entry: stack at the top of `virt` RAM, enable the FPU 79 # (mstatus.FS=Initial) for ilp32f, then call the C wrapper. 80 cat > "$work/_rv32_start.S" <<'EOF' 81 .section .text.start,"ax",@progbits 82 .globl _start 83 _start: 84 li sp, 0x80100000 85 li t0, 0x2000 86 csrs mstatus, t0 # mstatus.FS = Initial (enable the FPU for ilp32f) 87 88 // ---- static thread-local storage setup ----------------------------------- 89 // Build the per-thread TLS image [TCB(16) | .tdata | .tbss] in RAM and point 90 // tp at it. The corpus's TPREL relocs were resolved by kit-ld's scripted path 91 // (which leaves img->tls_vaddr == 0) to tgt->vaddr + 16, so we must set 92 // tp = __rv32_tls_block - __rv32_tdata_lma 93 // making tp + (tgt->vaddr + 16) = __rv32_tls_block + 16 + off — the address 94 // inside the copy where we place each variable. See the linker-script comment. 95 la t0, __rv32_tls_block # t0 = block base (TCB at +0) 96 addi t1, t0, 16 # t1 = dst = block + 16 (.tdata copy start) 97 la t2, __rv32_tdata_lma # t2 = src = .tdata load image 98 la t3, __rv32_tdata_size # t3 = .tdata byte count (abs symbol: la yields value) 99 .Lcopy: # copy .tdata init image, byte at a time 100 beqz t3, .Lcopy_done 101 lbu t4, 0(t2) 102 sb t4, 0(t1) 103 addi t1, t1, 1 104 addi t2, t2, 1 105 addi t3, t3, -1 106 j .Lcopy 107 .Lcopy_done: 108 la t3, __rv32_tbss_size # t3 = .tbss byte count 109 .Lzero: # zero-fill .tbss (t1 already at end of .tdata copy) 110 beqz t3, .Lzero_done 111 sb zero, 0(t1) 112 addi t1, t1, 1 113 addi t3, t3, -1 114 j .Lzero 115 .Lzero_done: 116 la t2, __rv32_tdata_lma 117 sub tp, t0, t2 # tp = block - __rv32_tdata_lma 118 // --------------------------------------------------------------------------- 119 120 call _rv32_cmain 121 .Lhang: j .Lhang 122 123 // Per-thread TLS image scratch (single-threaded harness). Sized generously; 124 // the corpus TLS cases use only a handful of bytes. 125 .section .bss.rv32tls,"aw",@nobits 126 .balign 16 127 __rv32_tls_block: 128 .zero 4096 129 EOF 130 # The wrapper calls the corpus's main() and maps its return onto the SiFive 131 # test finisher at 0x100000. Compiled by kit (exercises rv32 codegen for the 132 # finisher store + the call); main may return i32 or i64 — the low word is the 133 # exit code. 134 cat > "$work/_rv32_wrap.c" <<EOF 135 #define FINISHER ((volatile unsigned int*)0x100000) 136 extern int ${RV32_BARE_ENTRY}(void); 137 __attribute__((noreturn)) void _rv32_cmain(void) { 138 int code = ${RV32_BARE_ENTRY}(); 139 *FINISHER = code ? (0x3333u | ((unsigned)code << 16)) : 0x5555u; 140 for (;;) {} 141 } 142 EOF 143 # The TLS output sections below give the bare stub a static-TLS image to 144 # seed from. kit-ld's *scripted* layout does NOT populate img->tls_vaddr (that 145 # only happens in the bucketed, scriptless path), so the linker's own 146 # __tdata_start/__tbss_size boundary symbols and PT_TLS are emitted as zero 147 # here — and the corpus's TPREL relocs resolve to (tgt->vaddr - 0) + 16 = 148 # tgt->vaddr + 16, i.e. the *placed vaddr* of the variable plus the 16-byte TCB 149 # bias (src/obj/elf/link.c:475). We therefore can't lean on the linker's TLS 150 # symbols; instead we define our own (__rv32_tdata_lma/_tdata_size/_tbss_size) 151 # that the linker never clobbers, and the stub computes the thread pointer so 152 # that tp + (tgt->vaddr + 16) lands inside a live RAM copy of the image. 153 # 154 # .tdata (PROGBITS, loaded at its vaddr) supplies the init image; .tbss 155 # (NOBITS) only contributes a size. They are placed contiguously so a single 156 # tp bias works for both: with the copy laid out as [TCB(16) | .tdata | .tbss] 157 # and .tdata copied to block+16, set tp = block - __rv32_tdata_lma. Then for 158 # any var v: tp + (v.vaddr + 16) = (block - tdata_lma) + (tdata_lma + off) + 16 159 # = block + 16 + off — exactly where we copied/zeroed v. The 160 # linker's +16 and our +16 TCB reservation cancel, mirroring start.c's 161 # [TCB | tdata | tbss] convention (test/link/harness/start.c:146-149). 162 # 163 # __x = . assignments inside a section body are all applied *before* that 164 # section's inputs (link_layout.c:775), so the post-.tdata dot is captured in a 165 # following input-less marker section; sizes are then derived at top level. 166 cat > "$RV32_BARE_LDS" <<'EOF' 167 ENTRY(_start) 168 SECTIONS { 169 . = 0x80000000; 170 .text : { *(.text.start) *(.text*) } 171 .rodata : { *(.rodata*) } 172 .data : { *(.data*) } 173 .tdata : { . = ALIGN(16); __rv32_tdata_lma = .; *(.tdata .tdata.*) } 174 .tdata_end : { __rv32_tdata_end = .; } 175 .tbss : { __rv32_tbss_start = .; *(.tbss .tbss.*) } 176 .tbss_end : { __rv32_tbss_end = .; } 177 .bss : { *(.bss*) *(COMMON) } 178 __rv32_tdata_size = __rv32_tdata_end - __rv32_tdata_lma; 179 __rv32_tbss_size = __rv32_tbss_end - __rv32_tbss_start; 180 /DISCARD/ : { *(.riscv.attributes) *(.comment) } 181 } 182 EOF 183 if ! "$RV32_BARE_KIT" as -target riscv32-none-elf -march="$RV32_BARE_MARCH" \ 184 -mabi="$RV32_BARE_MABI" -o "$RV32_BARE_START" "$work/_rv32_start.S" \ 185 >/dev/null 2>&1; then 186 return 0 187 fi 188 if ! "$RV32_BARE_KIT" cc -target riscv32-none-elf -march="$RV32_BARE_MARCH" \ 189 -mabi="$RV32_BARE_MABI" -O1 -ffreestanding -c "$work/_rv32_wrap.c" \ 190 -o "$RV32_BARE_WRAP" >/dev/null 2>&1; then 191 return 0 192 fi 193 RV32_BARE_OK=1 194 } 195 196 rv32_bare_run() { # <obj> <work> <rcfile> 197 local obj="$1" work="$2" rcfile="$3" 198 local elf="$work/$(basename "$obj").elf" 199 local lderr="$work/$(basename "$obj").rv32ld.err" 200 if [ "$RV32_BARE_OK" -ne 1 ]; then 201 echo "rv32 bare-metal toolchain unavailable"; return 2 202 fi 203 if ! "$RV32_BARE_KIT" ld -T "$RV32_BARE_LDS" -e _start \ 204 "$RV32_BARE_START" "$RV32_BARE_WRAP" "$obj" "$RV32_BARE_RT" \ 205 -o "$elf" 2>"$lderr"; then 206 echo "kit ld (rv32) failed: $(head -n1 "$lderr" 2>/dev/null)"; return 2 207 fi 208 local rc=0 209 timeout 20 "$RV32_BARE_QEMU" -machine virt -bios none -kernel "$elf" \ 210 -nographic -no-reboot >/dev/null 2>&1 || rc=$? 211 printf '%s' "$rc" > "$rcfile" 212 return 0 213 }