275 lines
9.3 KiB
Bash
Executable file
275 lines
9.3 KiB
Bash
Executable file
#!/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"
|