A Rust library providing a Cranelift-like SSABuilder API for building RISC-V object files, including custom sections, instructions, relocations, and more.
Emphasizes performance: cache-friendly memory layout and parsing accelerated via SSE2, AVX2, AVX512BW on x86-64 and Neon on AArch64.
Runs in QEMU emulator.
.include "std"
.text
.extern printf
.global main
main:
prologue
; t0 = 69
li t0, 69
printf1 fmt1, t0 ; prints: "t0 = 69"
; t1 = -t0
li t0, 69
neg t1, t0
printf1 fmt2, t1 ; prints: "neg(t0) = -69"
; t2 = ~t0
li t0, 69
not t2, t0
printf1 fmt3, t2 ; prints: "not(t0) = -70"
j done
skip:
printf1 fmt_skip, t0
epilogue
reti 0
done:
epilogue
reti 0
.rodata
fmt1:
.stringz "t0 = %ld\n"
fmt2:
.stringz "neg(t0) = %ld\n"
fmt3:
.stringz "not(t0) = %ld\n"
fmt_skip:
.stringz "this should not print, t0=%ld\n"
Quick example of using `brik` to build a factorial-computing program
fn produce_factorial_obj<'a>() -> Object<'a> {
let mut asm = Assembler::new(
BinaryFormat::Elf,
Arch::Riscv64,
Endianness::Little,
"rv64gc"
);
asm.set_object_flags(FileFlags::Elf {
os_abi: 0,
abi_version: 0,
e_flags: 0x4,
});
// .rodata section for format strings
let _rodata = asm.add_rodata_section_at_end();
let input_fmt_sym = asm.define_data(b"input_fmt", b"enter a number: \0");
let scanf_fmt_sym = asm.define_data(b"scanf_fmt", b"%ld\0");
let result_fmt_sym = asm.define_data(b"result_fmt", b"factorial: %ld\n\0");
// =================
// external symbols
// =================
let printf_sym = asm.add_symbol_extern(
b"printf",
SymbolKind::Text,
SymbolScope::Dynamic
);
let scanf_sym = asm.add_symbol_extern(
b"scanf",
SymbolKind::Text,
SymbolScope::Dynamic
);
let text_section = asm.add_text_section_at_end();
asm.emit_function_prologue();
// allocate space on stack for input number (8 bytes)
asm.emit_addi(SP, SP, -8);
// print input prompt
asm.emit_pcrel_load_addr(A0, input_fmt_sym, 0);
asm.emit_call_plt(printf_sym);
// read input number
asm.emit_pcrel_load_addr(A0, scanf_fmt_sym, 0);
asm.emit_addi(A1, SP, 0);
asm.emit_call_plt(scanf_sym);
// load input number into s1
asm.emit_ld(S1, SP, 0);
// init factorial result in s2 (result = 1)
asm.emit_addi(S2, ZERO, 1);
// init counter in s3 (i = 1)
asm.emit_addi(S3, ZERO, 1);
let loop_lbl = asm.add_label_here(
b".fact_loop",
SymbolKind::Text,
SymbolScope::Compilation
);
let done_lbl = asm.declare_label(
b".fact_done",
SymbolKind::Text,
SymbolScope::Compilation
);
// loop condition: if i > n, exit
asm.emit_branch_to(
done_lbl,
// if s1 < s3 (n < i)
BLT { s1: S1, s2: S3, im: 0 },
);
// result *= i
asm.emit_bytes(MUL { d: S2, s1: S2, s2: S3 });
// i++
asm.emit_addi(S3, S3, 1);
// jump back to loop
asm.emit_branch_to(
loop_lbl,
JAL { d: ZERO, im: 0 },
);
...
asm.add_symbol(
b"main",
0,
asm.section_size(text_section),
SymbolKind::Text,
SymbolScope::Dynamic,
false,
SymbolFlags::None
);
asm.finish().unwrap()
}
fn main() -> Result<(), Box> {
let args = env::args().collect::>();
let Some(output_path) = args.get(1) else {
println!{
"usage: {prog} ",
prog = args[0]
};
return Ok(())
};
let obj = produce_factorial_obj();
let file = fs::File::create(output_path)?;
obj.write_stream(&file)?;
println!("[wrote object file to {output_path}]");
Ok(())
}
Demo video/GIF will go here soon