This repository contains an LLVM backend for the Sampo 16-bit RISC CPU, enabling compilation of Rust, C, and other LLVM-based languages for custom Sampo hardware.
Sampo is a custom 16-bit RISC processor designed with clean RISC principles and Z80-inspired features. This LLVM backend provides:
- GlobalISel-based instruction selection - Modern, modular code generation
- Complete type legalization - Handles 8/16/32/64-bit operations on 16-bit hardware
- Rust support - Full
libcoreandliballoccompilation - Calling convention - R4-R7 for arguments, R1 for return address
Rust/C Source
↓
Frontend (rustc/clang)
↓
LLVM IR
↓
GlobalISel (this backend)
↓
Sampo Assembly
↓
sasm Assembler
↓
Binary → semu Emulator / FPGA
Install the required build tools:
macOS:
brew install cmake ninja python3 gitUbuntu/Debian:
sudo apt install cmake ninja-build python3 git build-essentialFedora:
sudo dnf install cmake ninja-build python3 git gcc gcc-c++The easiest way to build everything is with the bootstrap script:
git clone https://github.com/ajokela/llvm-sampo.git
cd llvm-sampo
./bootstrap.shThis will:
- Clone and build LLVM with Sampo target support
- Clone and build Rust with Sampo cross-compilation
- Create a sysroot for compiling Rust programs
The toolchain is installed to ~/sampo-toolchain by default. Customize with:
WORK_DIR=/opt/sampo JOBS=8 ./bootstrap.shIf you prefer manual control, follow these steps:
# Clone LLVM
git clone --depth 1 https://github.com/llvm/llvm-project.git
cd llvm-project
# Copy Sampo backend
git clone https://github.com/ajokela/llvm-sampo.git /tmp/llvm-sampo
cp -r /tmp/llvm-sampo/llvm/lib/Target/Sampo llvm/lib/Target/
# Register Sampo as an experimental target
sed -i.bak 's/set(LLVM_ALL_TARGETS/set(LLVM_ALL_TARGETS\n Sampo/' llvm/CMakeLists.txt
# Configure and build
mkdir build && cd build
cmake -G Ninja \
-DLLVM_ENABLE_PROJECTS="clang" \
-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD="Sampo" \
-DLLVM_TARGETS_TO_BUILD="X86" \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_ENABLE_ASSERTIONS=ON \
../llvm
ninja -j$(nproc)# Clone Rust
git clone --depth 1 https://github.com/rust-lang/rust.git rust-sampo
cd rust-sampo
# Copy target spec
cp /tmp/llvm-sampo/rust-target/sampo_unknown_none.rs \
compiler/rustc_target/src/spec/targets/
# Register the target (add to supported_targets! macro in mod.rs)
# Add: ("sampo-unknown-none", sampo_unknown_none),
# Create config.toml (adjust paths as needed)
cat > config.toml << 'EOF'
[llvm]
download-ci-llvm = false
[build]
target = ["x86_64-unknown-linux-gnu", "sampo-unknown-none"]
docs = false
[target.x86_64-unknown-linux-gnu]
llvm-config = "/path/to/llvm-project/build/bin/llvm-config"
EOF
# Build stage 1 with Sampo target
./x.py build --stage 1 library --target sampo-unknown-none# Create sysroot directory structure
mkdir -p ~/sampo-sysroot/lib/rustlib/sampo-unknown-none/lib
# Copy compiled libraries
cp rust-sampo/build/x86_64-unknown-linux-gnu/stage1/lib/rustlib/sampo-unknown-none/lib/*.rlib \
~/sampo-sysroot/lib/rustlib/sampo-unknown-none/lib/
cp rust-sampo/build/x86_64-unknown-linux-gnu/stage1/lib/rustlib/sampo-unknown-none/lib/*.rmeta \
~/sampo-sysroot/lib/rustlib/sampo-unknown-none/lib/Create hello.rs:
#![no_std]
#![no_main]
extern "C" {
fn putc(c: u8);
}
#[no_mangle]
pub extern "C" fn _start() {
unsafe {
putc(b'H');
putc(b'i');
putc(b'!');
putc(b'\n');
}
loop {}
}
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
loop {}
}Compile with (using bootstrap):
~/sampo-toolchain/rustc-sampo --emit=asm hello.rsOr manually:
rustc --target sampo-unknown-none \
--sysroot ~/sampo-sysroot \
-C opt-level=1 \
--emit=asm \
hello.rsOutput assembly:
_start:
LIX R4, 72 ; 'H'
JALX putc
LIX R4, 105 ; 'i'
JALX putc
LIX R4, 33 ; '!'
JALX putc
LIX R4, 10 ; '\n'
JALX putc
.loop:
J .loopUse the Sampo assembler and emulator:
# Clone Sampo tools
git clone https://github.com/ajokela/sampo.git
cd sampo
# Build assembler and emulator
cargo build --release
# Assemble
./target/release/sasm hello.s -o hello.bin
# Run on emulator
./target/release/semu hello.binllvm-sampo/
├── bootstrap.sh # Automated build script
├── README.md
├── llvm/lib/Target/Sampo/ # LLVM backend
│ ├── Sampo.td # Top-level TableGen
│ ├── SampoInstrInfo.td # Instruction definitions
│ ├── SampoRegisterInfo.td # Register definitions
│ ├── SampoCallingConv.td # Calling convention
│ ├── SampoCallLowering.cpp # GlobalISel call lowering
│ ├── SampoLegalizerInfo.cpp # Type legalization rules
│ ├── SampoInstructionSelector.cpp # Instruction selection
│ ├── SampoAsmPrinter.cpp # Assembly generation
│ ├── MCTargetDesc/ # MC layer (encoding, printing)
│ └── TargetInfo/ # Target registration
└── rust-target/
└── sampo_unknown_none.rs # Rust target specification
| Register | Alias | Purpose |
|---|---|---|
| R0 | ZERO | Always 0 |
| R1 | RA | Return address |
| R2 | SP | Stack pointer |
| R3 | GP | Global pointer |
| R4-R7 | A0-A3 | Arguments / Return values |
| R8-R11 | T0-T3 | Temporaries (caller-saved) |
| R12-R15 | S0-S3 | Saved (callee-saved) |
Ensure Sampo is registered as an experimental target:
grep -r "Sampo" llvm-project/llvm/CMakeLists.txtMake sure the target is registered in compiler/rustc_target/src/spec/mod.rs:
supported_targets! {
("sampo-unknown-none", sampo_unknown_none),
// ... other targets
}Both .rlib and .rmeta files must be in the sysroot:
ls ~/sampo-sysroot/lib/rustlib/sampo-unknown-none/lib/
# Should show: libcore-*.rlib, libcore-*.rmeta, liballoc-*.rlib, etc.Reduce parallel jobs:
JOBS=2 ./bootstrap.shOr limit parallel link jobs in cmake:
cmake ... -DLLVM_PARALLEL_LINK_JOBS=1- Sampo CPU - Architecture, assembler, emulator, FPGA RTL
- tinycomputers.io - Blog series on Sampo development
This project is licensed under the Apache License 2.0 with LLVM Exceptions, consistent with the LLVM project.
This backend was developed with assistance from Claude Code (Anthropic). The iterative development process demonstrates how AI-assisted development can tackle complex compiler engineering challenges.