#!/usr/bin/env bash # W65816 backend smoke test. Run after any change to confirm the # scaffold still builds and llc still registers the target. Non-zero # exit on any failure. # # Usage: scripts/smokeTest.sh [--build] # --build Run ninja to (re)build LLVMW65816* + llc before testing. # Without this flag the script assumes tools/llvm-mos-build # is already up to date. set -euo pipefail source "$(dirname "$0")/common.sh" BUILD_DIR="$TOOLS_DIR/llvm-mos-build" LLC="$BUILD_DIR/bin/llc" LLVM_MC="$BUILD_DIR/bin/llvm-mc" doBuild=0 for arg in "$@"; do case "$arg" in --build) doBuild=1 ;; *) die "unknown flag: $arg" ;; esac done [ -x "$LLC" ] || die "llc not found at $LLC; run setup.sh and applyBackend.sh, or pass --build" if [ "$doBuild" -eq 1 ]; then log "ninja LLVMW65816* llc llvm-mc llvm-objdump" ninja -C "$BUILD_DIR" LLVMW65816Info LLVMW65816Desc LLVMW65816CodeGen \ LLVMW65816AsmParser LLVMW65816Disassembler llc llvm-mc llvm-objdump fi # 1. Target must be registered. log "check: llc --version lists w65816" if ! "$LLC" --version 2>/dev/null | grep -q "^[[:space:]]*w65816[[:space:]]"; then die "llc does not list the w65816 target" fi # 2. Empty IR must compile to nothing. log "check: llc -march=w65816 -filetype=null /dev/null exits 0" "$LLC" -march=w65816 -filetype=null /dev/null # 3. Trivial IR that shouldn't touch our (unimplemented) codegen paths. tmp="$(mktemp --suffix=.ll)" trap 'rm -f "$tmp"' EXIT cat > "$tmp" <<'EOF' ; ModuleID = 'smoke' target triple = "w65816-unknown-unknown" ; Empty module: exercises target initialization only. EOF log "check: llc accepts an empty module with w65816 triple" "$LLC" -filetype=null "$tmp" # 4. MC layer round-trip. Assemble a representative mix of addressing # modes and mode-switching instructions and grep for the expected # encoding bytes. Hex-byte strings are stable across llvm-mc # formatting changes, unlike full-line string matching. if [ -x "$LLVM_MC" ]; then log "check: llvm-mc -arch=w65816 emits expected encodings" # Only exercise instructions that round-trip cleanly: # - LDA/LDX/LDY immediates without explicit force use the _Imm16 # form (codegen-dominant path). A pure `lda #x` assembles to # LDA_Imm16 since the _Imm8 variant is isCodeGenOnly. mcInput=' nop rep #0x30 sep #0x20 lda #0x1234 sta 0x10 sta 0x1000 sta 0x010000 mvn 0x01, 0x02 jsl 0x012345' mcOut="$(printf '%s\n' "$mcInput" | "$LLVM_MC" -arch=w65816 -show-encoding 2>&1)" assertHas() { if ! printf '%s\n' "$mcOut" | grep -qF "$1"; then warn "missing expected encoding: $1" printf '%s\n' "$mcOut" >&2 die "llvm-mc did not produce expected encoding" fi } assertHas "[0xea]" assertHas "[0xc2,0x30]" assertHas "[0xe2,0x20]" assertHas "[0xa9,0x34,0x12]" assertHas "[0x85,0x10]" assertHas "[0x8d,0x00,0x10]" assertHas "[0x8f,0x00,0x00,0x01]" assertHas "[0x54,0x01,0x02]" assertHas "[0x22,0x45,0x23,0x01]" else warn "llvm-mc not built; skipping MC round-trip check" fi # 5. Disassembler round-trip. A raw byte stream fed to llvm-mc # --disassemble should produce the mnemonic we expect. if [ -x "$LLVM_MC" ]; then log "check: llvm-mc --disassemble decodes bytes back to mnemonics" disasmOut="$(printf '0xea 0xa9 0x34 0x12 0x85 0x10 0x8d 0x00 0x10 0x6b\n' \ | "$LLVM_MC" --disassemble --triple=w65816 2>&1)" for mnem in "nop" "lda #0x1234" "sta 0x10" "sta 0x1000" "rtl"; do if ! printf '%s\n' "$disasmOut" | grep -qF "$mnem"; then warn "disassembler missing: $mnem" printf '%s\n' "$disasmOut" >&2 die "disassembler round-trip failed" fi done fi # 6. End-to-end codegen: IR -> asm -> ELF -> disassembly. # This is the first real codegen test: verifies that our LowerReturn, # DAG pattern for the i16 constant pseudo, and prologue-emitting # frame lowering produce runnable 65816 machine code. OBJDUMP="$BUILD_DIR/bin/llvm-objdump" if [ -x "$LLC" ] && [ -x "$LLVM_MC" ] && [ -x "$OBJDUMP" ]; then log "check: end-to-end IR -> asm -> ELF -> disasm for a trivial function" irFile="$(mktemp --suffix=.ll)" sFile="$(mktemp --suffix=.s)" oFile="$(mktemp --suffix=.o)" trap 'rm -f "$irFile" "$sFile" "$oFile"' EXIT cat > "$irFile" <<'EOF' target triple = "w65816-unknown-unknown" define i16 @answer() { ret i16 42 } EOF "$LLC" -march=w65816 "$irFile" -o "$sFile" "$LLVM_MC" -arch=w65816 -filetype=obj "$sFile" -o "$oFile" disasm="$("$OBJDUMP" --triple=w65816 -d "$oFile" 2>&1)" for expect in "rep #0x30" "lda #0x2a" "rtl"; do if ! printf '%s\n' "$disasm" | grep -qF "$expect"; then warn "end-to-end pipeline missing: $expect" printf '%s\n' "$disasm" >&2 die "end-to-end pipeline failed" fi done fi # 7. Real codegen check: a non-trivial function exercising globals, # arithmetic, branches, bitwise. This tests our DAG selection # patterns and AsmPrinter pseudo expansions. if [ -x "$LLC" ]; then log "check: llc compiles a multi-pattern function" irFile="$(mktemp --suffix=.ll)" sFile="$(mktemp --suffix=.s)" trap 'rm -f "$irFile" "$sFile"' EXIT cat > "$irFile" <<'EOF' target triple = "w65816-unknown-unknown" @a = global i16 0 @b = global i16 0 define i16 @demo() { %x = load i16, ptr @a %y = load i16, ptr @b %s = add i16 %x, %y %m = and i16 %s, 4095 %c = icmp ult i16 %m, 100 br i1 %c, label %lo, label %hi lo: ret i16 0 hi: ret i16 %m } EOF "$LLC" -march=w65816 "$irFile" -o "$sFile" for expect in "rep #0x30" "lda a" "clc" "adc b" "and #0xfff" "cmp #0x64" "bcs" "rtl"; do if ! grep -qF "$expect" "$sFile"; then warn "multi-pattern test missing: $expect" cat "$sFile" >&2 die "multi-pattern test failed" fi done fi # 8. Function call check: caller passes i16 in A, callee adds, returns. if [ -x "$LLC" ]; then log "check: llc compiles a function call (single i16 arg in A)" irCallFile="$(mktemp --suffix=.ll)" sCallFile="$(mktemp --suffix=.s)" trap 'rm -f "$irFile" "$sFile" "$irCallFile" "$sCallFile"' EXIT cat > "$irCallFile" <<'EOF' target triple = "w65816-unknown-unknown" define i16 @inc(i16 %x) { %r = add i16 %x, 1 ret i16 %r } define i16 @caller() { %r = call i16 @inc(i16 41) ret i16 %r } EOF "$LLC" -march=w65816 "$irCallFile" -o "$sCallFile" # Caller passes 41 in A and JSL's inc. Inc is now an `inc a` # peephole (was clc; adc #1 before the INA_PSEUDO pattern). for expect in "lda #0x29" "jsl inc" "inc a"; do if ! grep -qF "$expect" "$sCallFile"; then warn "call test missing: $expect" cat "$sCallFile" >&2 die "call test failed" fi done fi # 9. Multi-arg sum: 3-arg function reads args 1 and 2 via stack-relative # addressing. if [ -x "$LLC" ]; then log "check: llc compiles a 3-arg function (stack-relative reads)" irMaFile="$(mktemp --suffix=.ll)" sMaFile="$(mktemp --suffix=.s)" trap 'rm -f "$irFile" "$sFile" "$irCallFile" "$sCallFile" "$irMaFile" "$sMaFile"' EXIT cat > "$irMaFile" <<'EOF' target triple = "w65816-unknown-unknown" define i16 @sum3(i16 %a, i16 %b, i16 %c) { %ab = add i16 %a, %b %r = add i16 %ab, %c ret i16 %r } EOF "$LLC" -march=w65816 "$irMaFile" -o "$sMaFile" for expect in "adc 0x4, s" "adc 0x6, s" "rtl"; do if ! grep -qF "$expect" "$sMaFile"; then warn "multi-arg test missing: $expect" cat "$sMaFile" >&2 die "multi-arg test failed" fi done fi # 10. i8 codegen: pure-i8 function uses SEP #$20 prologue and `inc a`. if [ -x "$LLC" ]; then log "check: llc compiles a pure-i8 function (SEP #\$20 prologue)" irI8File="$(mktemp --suffix=.ll)" sI8File="$(mktemp --suffix=.s)" trap 'rm -f "$irFile" "$sFile" "$irCallFile" "$sCallFile" "$irMaFile" "$sMaFile" "$irI8File" "$sI8File"' EXIT cat > "$irI8File" <<'EOF' target triple = "w65816-unknown-unknown" define i8 @i8_inc(i8 %x) { %r = add i8 %x, 1 ret i8 %r } EOF "$LLC" -march=w65816 "$irI8File" -o "$sI8File" for expect in "sep #0x20" "inc a" "rtl"; do if ! grep -qF "$expect" "$sI8File"; then warn "i8 test missing: $expect" cat "$sI8File" >&2 die "i8 test failed" fi done fi # 11. Real C through clang. Uses the clang front-end if it has been # built; skipped otherwise (clang takes 15-30 minutes to build the # first time; afterwards rebuilds are fast). CLANG="$BUILD_DIR/bin/clang" if [ -x "$CLANG" ] && [ -x "$OBJDUMP" ]; then log "check: clang -target w65816 -O2 compiles a tiny C function" cFile="$(mktemp --suffix=.c)" oFile2="$(mktemp --suffix=.o)" trap 'rm -f "$irFile" "$sFile" "$irCallFile" "$sCallFile" "$irMaFile" "$sMaFile" "$irI8File" "$sI8File" "$cFile" "$oFile2"' EXIT cat > "$cFile" <<'EOF' int answer(void) { return 42; } EOF "$CLANG" --target=w65816 -O2 -c "$cFile" -o "$oFile2" disasm="$("$OBJDUMP" --triple=w65816 -d "$oFile2" 2>&1)" for expect in "rep #0x30" "lda #0x2a" "rtl"; do if ! printf '%s\n' "$disasm" | grep -qF "$expect"; then warn "clang test missing: $expect" printf '%s\n' "$disasm" >&2 die "clang end-to-end test failed" fi done fi log "all smoke checks passed"