This file provides guidance to Claude Code when working with the LLVM-MOS repository located at ~/git/llvm-mos.
LLVM-MOS is a fork of LLVM that adds support for the MOS 65xx series of microprocessors (6502 and variants). It extends LLVM's code generation infrastructure to target vintage 8-bit processors commonly found in retro computers and embedded systems. The project includes a complete backend implementation with specialized optimizations for the unique constraints of 6502-based systems.
CRITICAL: NEVER pipe build output through tail, head, or similar truncation commands.
All build commands MUST redirect output to a temporary file, then read the file afterwards:
# CORRECT - capture full output
cmake --build build --target check-lld 2>&1 | tee /tmp/build-output.log
# Then read the relevant parts:
cat /tmp/build-output.log # or tail -100 /tmp/build-output.log
# WRONG - loses important error context
cmake --build build --target check-lld 2>&1 | tail -60Long-running builds produce megabytes of output. Piping through tail loses:
- Early compilation errors that cause later failures
- Warning messages that explain the root cause
- Test names and line numbers needed for debugging
Always capture to a file first, then examine as needed.
# Configure with MOS cache (recommended)
cmake -C clang/cmake/caches/MOS.cmake -G Ninja -S llvm -B build
# Add additional projects if needed
cmake -C clang/cmake/caches/MOS.cmake -C .vscode/jbyrd-dev.cmake -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;lld;lldb" -S llvm -B build
# Debug build with assertions
cmake -C clang/cmake/caches/MOS.cmake -C .vscode/jbyrd-dev.cmake -DCMAKE_BUILD_TYPE=Debug -DLLVM_ENABLE_ASSERTIONS=On -S llvm -B buildCRITICAL: NEVER run ninja directly to build llvm-mos. Always use either:
cmake --build build(recommended)- The full build script:
~/git/llvm-mos/.vscode/build-all.sh
Running ninja lld or similar direct ninja invocations skips crucial steps:
- Cross-compilation of the MOS runtime libraries (builtins, compiler-rt)
- Installation of headers and libraries to the install directory
- Proper sequencing of dependent targets
This creates subtle, hard-to-debug issues where the installed toolchain uses stale components. Always do a full build!
# Build all components (CORRECT)
cmake --build build
# Build and install (primary build command - CORRECT)
cmake --build ~/git/llvm-mos/build --config Debug --target install --
# Full rebuild including picolibc and test binaries (RECOMMENDED)
~/git/llvm-mos/.vscode/build-all.sh
# Build specific target - AVOID unless you know what you're doing
# cmake --build build --target lld
# Run all tests
cmake --build build --target check-all
# Run LLVM-specific tests
cmake --build build --target check-llvm
# Run MOS-specific tests
llvm-lit build/test/CodeGen/MOS/
# Run specific test file
llvm-lit llvm/test/CodeGen/MOS/hello-world.ll# Format code (uses LLVM style)
clang-format -i path/to/file.cpp
# Format all modified files
find . -name "*.cpp" -o -name "*.h" | xargs clang-format -i
# Update test CHECK lines
python3 llvm/utils/update_llc_test_checks.py llvm/test/CodeGen/MOS/*.llCore Target Files (llvm/lib/Target/MOS/):
MOSTargetMachine.cpp/.h- Main target machine implementationMOSInstrInfo.td- Real 6502 instruction definitionsMOSInstrPseudos.td- Pseudo instructions for code generationMOSRegisterInfo.td- Register definitions including "imaginary registers"MOSDevices.td- Processor variant definitions (6502, 65C02, W65816, etc.)
Key Optimization Passes:
MOSZeroPageAlloc.cpp- Allocates frequently-used values to zero-page memoryMOSStaticStackAlloc.cpp- Converts dynamic to static stack allocationMOSCopyOpt.cpp- Optimizes register-to-register copiesMOSIndexIV.cpp- Optimizes induction variables for indexed addressingMOSNonReentrant.cpp- Optimizes non-reentrant functionsMOSLateOptimization.cpp- Post-register allocation optimizations
GlobalISel Support:
MOSCallLowering.cpp- Function call/return handlingMOSLegalizerInfo.cpp- Legal operation definitionsMOSRegisterBankInfo.cpp- Register bank selectionMOSInstructionSelector.cpp- Final instruction selection
The MOS backend uses an innovative approach to handle the 6502's limited register set:
Real Registers: A, X, Y (8-bit), S (stack pointer), P (processor status)
Imaginary Registers: Up to 256 8-bit registers (rc0-rc255) and 128 16-bit pointer registers (rs0-rs127) that map to zero-page memory locations and are treated as physical registers for allocation.
Two-layer instruction architecture:
- Machine Instructions - Models actual 6502 instruction set with all irregularities
- Pseudo Instructions - Regularized virtual instruction set for code generation, lowered during assembly printing
- MOS CodeGen Tests:
llvm/test/CodeGen/MOS/ - Machine IR Tests: Use
.mirfiles for testing specific passes - Assembly Tests: Test different assembler syntax variants (generic, ca65, xa65)
# All MOS tests
llvm-lit llvm/test/CodeGen/MOS/
# Specific optimization pass tests
llvm-lit llvm/test/CodeGen/MOS/zeropage.ll
llvm-lit llvm/test/CodeGen/MOS/static-stack-alloc.ll
# Run with specific FileCheck prefixes
llvm-lit --param=target_triple=mos-unknown-unknown test.ll- Use
llvm/utils/update_llc_test_checks.pyto automatically generate CHECK lines - Use
llvm/utils/update_test_checks.pyfor general test updates - FileCheck patterns should test both functionality and performance characteristics
- Define in
MOSInstrInfo.tdwith appropriate instruction format - Add pseudo version in
MOSInstrPseudos.tdif needed - Update
MOSInstrInfo.cppfor any special handling - Add tests in
llvm/test/CodeGen/MOS/
- Define new subtarget features in
MOSDevices.td - Update instruction predicates to use new features
- Add variant-specific tests
- Follow LLVM pass structure conventions
- Consider zero-page allocation implications
- Test with both optimized and unoptimized builds
- Verify performance on real 6502 code patterns
LLVM_EXPERIMENTAL_TARGETS_TO_BUILD="MOS"- Enables MOS targetLLVM_DEFAULT_TARGET_TRIPLE="mos-unknown-unknown"- Default targetLLDB_INCLUDE_TESTS=OFF- Disabled until stabilized- Uses
MinSizeRelbuild type by default for space optimization
- 6502 (original with BCD)
- 6502X (with "illegal" opcodes)
- 65C02, R65C02, W65C02 (CMOS variants)
- W65816 (16-bit capable)
- HUC6280 (PC Engine)
- SPC700 (Nintendo sound processor)
- 65CE02, 65DTV02, 4510, 45GS02
The llvm-mc tool includes a built-in emulator for running MOS programs directly.
# Assemble and run with instruction trace (discard object file)
llvm-mc --triple=mos -filetype=obj -o /dev/null --run --trace program.s
# Example output:
# 0 $0000 A=00 X=00 Y=00 S=FF P=20 lda #66
# 2 $0002 A=42 X=00 Y=00 S=FF P=20 ldx #16
# 4 $0004 A=42 X=10 Y=00 S=FF P=20 ldy #32
# 6 $0006 A=42 X=10 Y=20 S=FF P=20 brk--run # Execute after assembly
--trace # Print each instruction with register state
--run-max-cycles=<N> # Limit execution cycles (default 1M)
--semihost=<path> # Enable file I/O sandboxed to directoryThe trace output columns are: cycle count, PC, register state (A/X/Y/S/P), instruction.
BRK halts the emulator by default (exit code 0). See .vscode/emulation.md for full documentation.
The .vscode/ directory contains scripts for source-level debugging of MOS programs using LLDB and MAME.
Starting a debug session:
# Start with default test binary (float_qsort_O0)
.vscode/debug-start.sh
# Start with specific binary
.vscode/debug-start.sh /path/to/binary.elfThis starts:
- MAME with the
zbcm6502machine and GDB stub on port 23946 - LLDB with MCP server on port 59999
- Sets a breakpoint on
partitionand continues to it
Stopping a debug session:
.vscode/debug-stop.shAlways run debug-stop.sh before starting a new session. Stale MAME processes will cause connection failures.
Claude can send LLDB commands via the MCP server:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"command","arguments":{"command":"bt"}}}' | nc localhost 59999Common commands: bt (backtrace), frame variable, frame select N, register read
- MAME:
/tmp/mame-debug.log - LLDB:
/tmp/lldb-debug.log
Test programs are in ~/git/dwarf-torture/. Build with .vscode/build-all.sh which builds llvm-mos, picolibc, and dwarf-torture together.
CRITICAL: When creating branches from upstream/main, always use --no-track:
# CORRECT - prevents accidental pushes to upstream
git checkout -b feature/my-branch upstream/main --no-track
# WRONG - creates tracking to upstream, sync button will push to upstream!
git checkout -b feature/my-branch upstream/mainIf you forget --no-track, immediately fix the tracking:
git branch --set-upstream-to=origin/feature/my-branchThe VSCode sync button pushes to the tracked remote. If a branch tracks upstream/main, clicking sync will push directly to the main llvm-mos repo, which is almost never what you want.
- Build: Use MOS.cmake cache file for consistent configuration
- Test: Run MOS-specific tests frequently during development
- Format: Apply clang-format before committing
- Validate: Ensure changes work across multiple processor variants
- Document: Update tests and documentation for new features
The codebase follows LLVM coding standards strictly. All changes should include appropriate tests and maintain compatibility with the existing processor variant ecosystem.
When rendering change descriptions, DO NOT WRITE "Co-Authored By Claude" on ANYTHING. That is extremely offensive.