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