Skip to content

Build Integration

View as Markdown

Use this page when you need to compile a Rust program against the installed SDK and produce a deployable binary.

Start with Set Up Thru DevKit, then install the SDK:

Terminal window
thru dev toolchain install
thru dev sdk install rust

The CLI installs the Rust SDK under ~/.thru/sdk/rust. Programs are compiled by cargo for riscv64imac-unknown-none-elf, and the raw binary is produced by cargo objcopy from cargo-binutils:

Terminal window
rustup target add riscv64imac-unknown-none-elf
cargo install cargo-binutils
rustup component add llvm-tools

A downstream project needs four things copied or referenced from the SDK: the two crates as path dependencies, build.rs, link.x.in, and .cargo/config.toml.

my-thru-project/
├── .cargo/config.toml
├── Cargo.toml
├── build.rs
├── link.x.in
└── src/main.rs
Cargo.toml
[package]
name = "my-thru-project"
version = "0.1.0"
edition = "2021"
[dependencies]
thru-core = { path = "../thru-rust-sdk/core" }
thru-sdk-macros = { path = "../thru-rust-sdk/macros" }
[profile.release]
panic = "abort"
opt-level = "z"
lto = true
codegen-units = 1
debug = 2
strip = false

panic = "abort" is required: the SDK is no_std and provides its own panic handler. Keeping debug = 2 and strip = false preserves the DWARF info that thru debug resolve needs to map program counters back to source lines.

.cargo/config.toml
[build]
target = "riscv64imac-unknown-none-elf"
[target.riscv64imac-unknown-none-elf]
rustflags = [
"-C", "target-feature=+crt-static",
"-C", "target-feature=-a,+zba,+zbb,+zbc,+zbs",
"-C", "link-arg=-Tlink.x",
]

The target features disable atomics and enable the bit-manipulation extensions the Thru VM supports. -Tlink.x selects the SDK linker script.

Copy both from the SDK root unchanged. build.rs copies link.x.in into OUT_DIR as link.x and adds it to the link search path:

build.rs
use std::{env, fs, path::PathBuf};
fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let dest_path = out_dir.join("link.x");
let content = fs::read_to_string("link.x.in").unwrap();
fs::write(dest_path, content).unwrap();
println!("cargo:rustc-link-search={}", out_dir.display());
println!("cargo:rerun-if-changed=link.x.in");
}

link.x.in emits the 8-byte Thru VM program header, then places .text._start and .text.start — the sections the #[entry] macro generates — at the start of the image. Do not reorder or drop those KEEP entries.

Terminal window
cargo build --release --bin my-thru-project
cargo objcopy --release --bin my-thru-project -- -O binary my-thru-project.bin

The ELF lands in target/riscv64imac-unknown-none-elf/release/, and my-thru-project.bin is the raw image you deploy. Keep the ELF: it carries the debug info for symbolization.

The SDK also ships a make wrapper for projects that prefer make over raw cargo. thru_rust_sdk.mk includes the base configuration, the Thru VM machine configuration (RUST_TARGET, CARGO_BUILD_FLAGS, CARGO_PROFILE_DIR), and the rule generator:

GNUmakefile
THRU_RUST_SDK_DIR := $(HOME)/.thru/sdk/rust/thru-sdk
include $(THRU_RUST_SDK_DIR)/thru_rust_sdk.mk

Every Local.mk under the project is discovered automatically. Declare each program there, where the first argument is the output name and the second is the cargo example name — the make wrapper builds cargo examples, so program sources live in examples/:

Local.mk
$(call make-cargo-bin,my_thru_program,my_thru_program)

make then writes both build/thruvm/bin/my_thru_program.bin and .elf. make clean and make distclean also run cargo clean.

Deployment is the same as for C programs — create the program account from the .bin and execute against it. See Build a C Program for the CLI sequence and Program development lifecycle for the wider workflow.