kit

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

arm32.sh (13145B)


      1 #!/usr/bin/env bash
      2 # test/smoke/arm32.sh — behavioral oracle for kit's arm-none-eabi (Cortex-M3,
      3 # Thumb-2) codegen.
      4 #
      5 # Like rv32 (freestanding `-none-elf`), arm32 is bare-metal: kit compiles the
      6 # app, we link a Cortex-M image with FLASH (code) at 0x00000000 and SRAM (data)
      7 # at 0x20000000, fronted by a startup stub whose M-profile vector table at 0x0
      8 # carries the initial MSP (top of SRAM) + the reset handler. The reset handler
      9 # copies .data flash->SRAM, zeroes .bss, calls the app, and reports the result
     10 # through the ARM semihosting SYS_EXIT_EXTENDED oracle (r0=0x20,
     11 # r1=&{ADP_Stopped_ApplicationExit=0x20026, code}, BKPT #0xAB) — qemu's own
     12 # process exit then == the program's exit code (no stdout parsing). Run under
     13 # qemu-system-arm -machine mps2-an385 -cpu cortex-m3 -semihosting.
     14 #
     15 # Scope: the verified codegen surface — 32-bit integer + pointer, control flow,
     16 # a ≤4-int-arg call to a second function, a switch, AND 64-bit-value
     17 # legalization: long long carry/borrow/bitwise/compare/convert inline as GPR
     18 # pairs, i64 mul/div/shift via __*di3 / __aeabi_* runtime calls, and soft
     19 # `double` arith/compare/convert via __*df3 / __aeabi_* calls. arm32 v1 is a
     20 # single soft-float lane (Cortex-M3 has no FPU): -mfloat-abi=soft routes both
     21 # float and double through the AEABI helpers. The i64-mul/div/shift and double
     22 # cases link kit's freestanding runtime (libkit_rt.a, the arm-eabi-thumb2
     23 # variant) for the helpers; the inline i64 cases need no runtime.
     24 #
     25 # Phase 1 bring-up: kit compiles app.c (the code under test); the startup stub
     26 # uses M-profile system mnemonics the kit assembler does not yet name, so it is
     27 # clang-assembled. The final bare-metal link uses ld.lld; a separate kit-ld
     28 # lane (kitld_lane) re-links with `kit ld` to exercise kit's own static ELF
     29 # base-addr control + runtime auto-resolution. The .eh_frame/.ARM.exidx kit
     30 # emits is discarded by the bare-metal link script (no unwinder in a
     31 # freestanding image).
     32 #
     33 # Skipped (per the shared kit_exit convention) if clang lacks the arm-none-eabi
     34 # target, ld.lld is missing, or qemu-system-arm is unavailable.
     35 
     36 set -u
     37 ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
     38 BUILD_DIR="$ROOT/build/test/smoke-arm32"
     39 mkdir -p "$BUILD_DIR"
     40 
     41 KIT_KIT_DIR="$ROOT/test/lib"
     42 # shellcheck source=../lib/kit_sh_kit.sh
     43 . "$ROOT/test/lib/kit_sh_kit.sh"
     44 kit_report_init
     45 KIT_SKIP_IS_FAILURE=1
     46 
     47 # ---- prerequisites (the arm32 doctor) -------------------------------------
     48 # shellcheck source=../lib/check_arm32_env.sh
     49 . "$ROOT/test/lib/check_arm32_env.sh"
     50 check_arm32_env
     51 if [ "${ARM32_HAVE_CLANG_TARGET:-0}" -eq 0 ]; then
     52   skip_test "smoke-arm32" "clang arm-none-eabi target unavailable"; kit_summary test-smoke-arm32; kit_exit
     53 fi
     54 if [ "${ARM32_HAVE_LLD:-0}" -eq 0 ]; then
     55   skip_test "smoke-arm32" "ld.lld unavailable"; kit_summary test-smoke-arm32; kit_exit
     56 fi
     57 if [ "${ARM32_HAVE_QEMU_SYSTEM:-0}" -eq 0 ]; then
     58   skip_test "smoke-arm32" "qemu-system-arm unavailable"; kit_summary test-smoke-arm32; kit_exit
     59 fi
     60 
     61 KIT="$ROOT/build/kit"
     62 QEMU="${ARM32_QEMU_SYSTEM_BIN:-qemu-system-arm}"
     63 CLANG="${ARM32_CLANG:-clang}"
     64 
     65 # Cortex-M startup stub: M-profile vector table at 0x0 (word[0]=initial MSP,
     66 # word[1]=reset handler), reset handler copies .data flash->SRAM, zeroes .bss,
     67 # calls cmain. The semihosting exit lives in app.c (__semihost_exit, an
     68 # assembler-op call), so the stub just hands control to cmain. The stub is
     69 # clang-assembled (uses M-profile mnemonics kit-as does not yet name).
     70 cat > "$BUILD_DIR/start.S" <<'EOF'
     71     .syntax unified
     72     .thumb
     73     .cpu cortex-m3
     74 
     75     .section .vectors, "a"
     76     .align 2
     77     .global _vectors
     78 _vectors:
     79     .word   _estack          /* word[0]: initial MSP = top of SRAM */
     80     .word   Reset_Handler    /* word[1]: reset handler (Thumb bit auto-set) */
     81 
     82     .section .text.start, "ax"
     83     .thumb_func
     84     .align 2
     85     .global Reset_Handler
     86     .global _start
     87 Reset_Handler:
     88 _start:
     89     ldr     r0, =_estack
     90     mov     sp, r0
     91 
     92     /* copy .data flash LMA -> SRAM VMA */
     93     ldr     r0, =_sidata
     94     ldr     r1, =_sdata
     95     ldr     r2, =_edata
     96 1:  cmp     r1, r2
     97     bcs     2f
     98     ldr     r3, [r0], #4
     99     str     r3, [r1], #4
    100     b       1b
    101 2:
    102     /* zero .bss */
    103     ldr     r0, =_sbss
    104     ldr     r1, =_ebss
    105     movs    r2, #0
    106 3:  cmp     r0, r1
    107     bcs     4f
    108     str     r2, [r0], #4
    109     b       3b
    110 4:
    111     bl      cmain
    112 5:  b       5b
    113 EOF
    114 
    115 cat > "$BUILD_DIR/link.ld" <<'EOF'
    116 /* mps2-an385 (Cortex-M3): FLASH (code) @ 0x0, SRAM (data) @ 0x20000000. */
    117 ENTRY(_start)
    118 
    119 MEMORY
    120 {
    121     FLASH (rx)  : ORIGIN = 0x00000000, LENGTH = 4M
    122     SRAM  (rwx) : ORIGIN = 0x20000000, LENGTH = 4M
    123 }
    124 
    125 SECTIONS
    126 {
    127     /* _estack is assigned inside SECTIONS (not at top level) so BOTH ld.lld and
    128      * kit ld accept it — kit ld's linker-script front end has no top-level
    129      * `sym = expr` directive. */
    130     _estack = ORIGIN(SRAM) + LENGTH(SRAM);
    131 
    132     .text :
    133     {
    134         KEEP(*(.vectors))
    135         *(.text.start)
    136         *(.text*)
    137         *(.rodata*)
    138         . = ALIGN(4);
    139         _etext = .;
    140     } > FLASH
    141 
    142     _sidata = LOADADDR(.data);
    143 
    144     .data : ALIGN(4)
    145     {
    146         _sdata = .;
    147         *(.data*)
    148         . = ALIGN(4);
    149         _edata = .;
    150     } > SRAM AT> FLASH
    151 
    152     .bss (NOLOAD) : ALIGN(4)
    153     {
    154         _sbss = .;
    155         *(.bss*)
    156         *(COMMON)
    157         . = ALIGN(4);
    158         _ebss = .;
    159     } > SRAM
    160 
    161     /DISCARD/ : { *(.eh_frame) *(.eh_frame_hdr)
    162                   *(.ARM.exidx*) *(.ARM.extab*) *(.ARM.attributes)
    163                   *(.comment) *(.note*) }
    164 }
    165 EOF
    166 
    167 # The app under test. Every check returns a distinct nonzero code on failure so
    168 # a qemu exit pinpoints the broken case. __semihost_exit reports the result via
    169 # ARM semihosting SYS_EXIT_EXTENDED (BKPT #0xAB).
    170 cat > "$BUILD_DIR/app.c" <<'EOF'
    171 __attribute__((noreturn)) static void __semihost_exit(int code){
    172   /* SYS_EXIT_EXTENDED (0x20): r1 -> {ADP_Stopped_ApplicationExit, code}. */
    173   volatile unsigned block[2];
    174   block[0] = 0x20026u;            /* ADP_Stopped_ApplicationExit */
    175   block[1] = (unsigned)code;
    176   register unsigned r0 __asm__("r0") = 0x20u;
    177   register const volatile unsigned *r1 __asm__("r1") = block;
    178   __asm__ volatile ("bkpt 0xAB" :: "r"(r0), "r"(r1) : "memory");
    179   for(;;){}
    180 }
    181 /* A second function with <=4 int args, called from compute(). */
    182 static int sum4(int a, int b, int c, int d){ return a + b + c + d; }
    183 static int classify(int n){
    184   switch (n){           /* switch -> TBB/TBH or branch table */
    185     case 0: return 100;
    186     case 1: return 101;
    187     case 2: return 102;
    188     case 3: return 103;
    189     default: return 199;
    190   }
    191 }
    192 static int compute(void){
    193   /* return-const / 32-bit integer / control flow */
    194   int acc = 0; for (int i = 0; i < 10; i++) acc += i;
    195   if (acc != 45) return 1;
    196   /* basic ALU */
    197   volatile unsigned u = 0x12345678u; u ^= 0x0F0F0F0Fu;
    198   if (u != (0x12345678u ^ 0x0F0F0F0Fu)) return 2;
    199   int p = 6, q = 7;
    200   if (p * q + (p << 2) - (q >> 1) != 42 + 24 - 3) return 3;
    201   /* if/else */
    202   int sel = (acc > 40) ? 1 : 0;
    203   if (sel) { if (u == 0) return 4; } else return 5;
    204   /* call to a second function with <=4 int args */
    205   if (sum4(1, 2, 3, 4) != 10) return 6;
    206   if (sum4(acc, p, q, sel) != 45 + 6 + 7 + 1) return 7;
    207   /* switch */
    208   if (classify(2) != 102) return 8;
    209   if (classify(9) != 199) return 9;
    210   /* i64 inline: carry / borrow / bitwise / compare / convert (no runtime) */
    211   volatile unsigned long long a = 0xFFFFFFFFull; a += 1;
    212   if (a != 0x100000000ull) return 10;
    213   volatile unsigned long long b = 0x100000000ull; b -= 1;
    214   if (b != 0x0FFFFFFFFull) return 11;
    215   volatile unsigned long long c = 0x1122334455667788ull;
    216   if ((c ^ 0xFFFFFFFFFFFFFFFFull) != 0xEEDDCCBBAA998877ull) return 12;
    217   volatile long long x = 0x1234567800000000ll, y = 0x1234567700000001ll;
    218   if (!(x > y) || !(y < x) || (x == y)) return 13;
    219   volatile int s32 = -7; volatile long long s64 = s32;
    220   if (s64 != -7) return 14;
    221   volatile long long big = 0x00000000FAFAFAFAll;
    222   if ((unsigned)(int)big != 0xFAFAFAFAu) return 15;
    223   if (a) { } else return 16;                        /* truthiness (hi word set) */
    224   /* i64 runtime: mul / udiv / umod / shifts (__muldi3/__udivdi3/__aeabi_*) */
    225   volatile unsigned long long m = 0x0000000100000001ull;
    226   if (m * 3ull != 0x0000000300000003ull) return 17;
    227   volatile unsigned long long d = 0xFFFFFFFFFFFFFFFFull;
    228   if (d / 0xFFFFFFFFull != 0x0000000100000001ull) return 18;
    229   if (d % 7ull != (0xFFFFFFFFFFFFFFFFull % 7ull)) return 19;
    230   volatile unsigned long long sh = 1ull;
    231   if ((sh << 40) != 0x0000010000000000ull) return 20;
    232   volatile long long sar = -0x4000000000000000ll;
    233   if ((sar >> 36) != (-0x4000000000000000ll >> 36)) return 21;
    234   /* soft double: arith / compare / convert (__adddf3/__muldf3/__aeabi_*) */
    235   volatile double dp = 1.5, dq = 2.25;
    236   if (dp + dq != 3.75) return 22;
    237   if (dp * dq != 3.375) return 23;
    238   if (!(dp < dq) || (dp >= dq)) return 24;
    239   volatile int iv = 7; volatile double dv = iv;
    240   if (dv != 7.0) return 25;
    241   volatile double dd = 3.75; if ((int)dd != 3) return 26;
    242   volatile long long L = 5000000000ll; volatile double dL = L;
    243   if (dL != 5000000000.0) return 27;
    244   volatile double dbig = 5000000000.0;
    245   if ((long long)dbig != 5000000000ll) return 28;
    246   return 0;
    247 }
    248 void cmain(void){ __semihost_exit(compute()); }
    249 EOF
    250 
    251 # Build kit's freestanding runtime archive for the helpers (i64 mul/div/shift +
    252 # soft double). Built on demand; skip the runtime-dependent lanes if it cannot
    253 # be produced (e.g. a partial checkout) rather than failing spuriously.
    254 RT="$ROOT/build/rt/arm-eabi-thumb2/libkit_rt.a"
    255 make -C "$ROOT" rt-arm-eabi-thumb2 >/dev/null 2>&1 || true
    256 
    257 # Assemble the startup stub once (clang; M-profile mnemonics).
    258 START_O="$BUILD_DIR/start.o"
    259 "$CLANG" --target=arm-none-eabi -mcpu=cortex-m3 -mthumb -ffreestanding \
    260       -nostdlib -fno-pic -fno-pie -c "$BUILD_DIR/start.S" -o "$START_O" 2>/dev/null
    261 
    262 run_lane() {  # <name>
    263   local name="$1"
    264   if [ ! -f "$RT" ]; then
    265     skip_test "$name" "runtime archive $RT missing"; return; fi
    266   local O
    267   for O in -O0 -O1; do
    268     local o="$BUILD_DIR/$name$O.o" elf="$BUILD_DIR/$name$O.elf"
    269     if ! "$KIT" cc -target arm-none-eabi -mcpu=cortex-m3 -mfloat-abi=soft $O \
    270          -ffreestanding -c "$BUILD_DIR/app.c" -o "$o" 2>"$BUILD_DIR/$name$O.cc.err"; then
    271       not_ok "$name $O (kit cc)" "$BUILD_DIR/$name$O.cc.err"; continue; fi
    272     if ! ld.lld -T "$BUILD_DIR/link.ld" --gc-sections -static -no-pie \
    273          "$START_O" "$o" "$RT" -o "$elf" 2>"$BUILD_DIR/$name$O.ld.err"; then
    274       not_ok "$name $O (link)" "$BUILD_DIR/$name$O.ld.err"; continue; fi
    275     local rc=0
    276     timeout 20 "$QEMU" -machine mps2-an385 -cpu cortex-m3 -kernel "$elf" \
    277       -nographic -monitor none -semihosting-config enable=on,target=native \
    278       -no-reboot >/dev/null 2>&1 || rc=$?
    279     if [ "$rc" -eq 0 ]; then ok "$name $O (qemu rc=0)";
    280     else not_ok "$name $O" "expected exit 0, got $rc (failed check #$rc)"; fi
    281   done
    282 }
    283 
    284 # Single soft-float lane (no FPU on Cortex-M3): float + double + i64 all via the
    285 # AEABI helpers.
    286 run_lane "soft"
    287 
    288 # Full kit toolchain, runtime auto-linked: re-link the -O1 object with `kit ld`
    289 # (not ld.lld) and WITHOUT naming a runtime archive. kit ld resolves the
    290 # float-ABI / arch axis from the object's ELF EABI header, builds the matching
    291 # libkit_rt.a on demand (build/rt/arm-eabi-thumb2), and links it in. A
    292 # freestanding arm32 target defaults to non-PIE; -T places .text at the
    293 # Cortex-M flash base (0x0). The i64-mul/div/shift and soft-double helpers come
    294 # from the auto-linked runtime, so a clean qemu exit also proves runtime
    295 # resolution.
    296 kitld_lane() {  # <name>
    297   local name="$1"
    298   local appo="$BUILD_DIR/$name-O1.o"
    299   local elf="$BUILD_DIR/kitld-$name.elf"
    300   [ -f "$START_O" ] && [ -f "$appo" ] || return
    301   if "$KIT" ld -T "$BUILD_DIR/link.ld" -e _start "$START_O" "$appo" \
    302        -o "$elf" 2>"$BUILD_DIR/kitld-$name.ld.err"; then
    303     local rc=0
    304     timeout 20 "$QEMU" -machine mps2-an385 -cpu cortex-m3 -kernel "$elf" \
    305       -nographic -monitor none -semihosting-config enable=on,target=native \
    306       -no-reboot >/dev/null 2>&1 || rc=$?
    307     if [ "$rc" -eq 0 ]; then ok "kit-ld $name -O1 auto-rt (qemu rc=0)";
    308     else not_ok "kit-ld $name -O1 auto-rt" "expected exit 0, got $rc"; fi
    309   else
    310     not_ok "kit-ld $name -O1 auto-rt (link)" "$BUILD_DIR/kitld-$name.ld.err"
    311   fi
    312 }
    313 kitld_lane "soft"
    314 
    315 # Negative control: a deliberately wrong result must produce a nonzero exit.
    316 sed 's/if (acc != 45) return 1;/if (acc != 45) return 1; return 99;/' "$BUILD_DIR/app.c" > "$BUILD_DIR/bad.c"
    317 if "$KIT" cc -target arm-none-eabi -mcpu=cortex-m3 -mfloat-abi=soft -O1 \
    318      -ffreestanding -c "$BUILD_DIR/bad.c" -o "$BUILD_DIR/bad.o" 2>/dev/null \
    319    && ld.lld -T "$BUILD_DIR/link.ld" --gc-sections -static -no-pie \
    320         "$START_O" "$BUILD_DIR/bad.o" "$RT" -o "$BUILD_DIR/bad.elf" 2>/dev/null; then
    321   rc=0; timeout 20 "$QEMU" -machine mps2-an385 -cpu cortex-m3 -kernel "$BUILD_DIR/bad.elf" \
    322     -nographic -monitor none -semihosting-config enable=on,target=native -no-reboot \
    323     >/dev/null 2>&1 || rc=$?
    324   if [ "$rc" -eq 99 ]; then ok "negative-control (qemu rc=99)";
    325   else not_ok "negative-control" "expected exit 99, got $rc"; fi
    326 fi
    327 
    328 kit_summary test-smoke-arm32
    329 kit_exit