kit

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

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 }