Skip to main content

logicaffeine_compile/codegen_riscv/
mod.rs

1//! LOGOS → **RISC-V** AOT codegen tier. Lowers the register bytecode
2//! [`CompiledProgram`] — the SAME IR the WASM backend and the Silicon ISA emitter
3//! consume — to **RV64GC** assembly text (SysV lp64d ABI, glibc), which a RISC-V
4//! GCC assembles+links and `qemu-user` runs, byte-for-byte identical to the VM.
5//!
6//! Structurally this mirrors [`crate::vm::isa::emit_isa`] (a `CompiledProgram → String`
7//! text emitter), and its totality discipline mirrors the WASM tier: every op the
8//! current phase does not lower returns [`RiscVLowerError::Unsupported`] — a SOUND
9//! refusal, never a miscompile — and the `riscv_coverage_lock` ratchet forces each
10//! such op onto a shrink-only KNOWN_GAPS list so nothing is silently wrong.
11//!
12//! Register model (v1): each VM virtual register is one 8-byte STACK SLOT at
13//! `(8*reg)(sp)`; every op loads operands into scratch (`t0..t2`), computes, and
14//! stores the result back. No values live in registers across instructions, so
15//! lowering is a local, context-free transliteration and calls are trivially
16//! correct (no caller-saved value is live across a `call`). A real register
17//! allocator is a later optimization.
18
19mod emit;
20mod rodata;
21mod runtime;
22
23pub use runtime::RISCV_RUNTIME_C;
24
25/// The symbol the assembly of a program the exact extension reaches defines ([`links_value_runtime`]), which the
26/// extension's adapter in the shim reads, so neither links without the other.
27pub(crate) const VALUE_RUNTIME_SYMBOL: &str = "logos_links_value_runtime";
28
29/// Whether the RV64GC assembly `asm` links the exact extension's value runtime: it defines
30/// `VALUE_RUNTIME_SYMBOL`.
31pub fn links_value_runtime(asm: &str) -> bool {
32    asm.lines().any(|line| line.strip_suffix(':') == Some(VALUE_RUNTIME_SYMBOL))
33}
34
35/// What a RISC-V program links beside its assembly: the C of its runtime shim, and what its link line names after
36/// the assembly and the shim.
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct Runtime {
39    pub c: String,
40    pub link_args: Vec<String>,
41}
42
43/// The runtime the assembly `asm` links. A program the exact extension never reaches links [`RISCV_RUNTIME_C`] and the
44/// math library. One it reaches links the shim with the extension's adapter after it (the extension's C core over the
45/// shim's collector, then the entry points the assembly calls), and `logicaffeine-data`'s value runtime built for
46/// RV64GC Linux with the system libraries it calls, statically.
47pub fn runtime(asm: &str) -> Result<Runtime, String> {
48    if !links_value_runtime(asm) {
49        return Ok(Runtime {
50            c: RISCV_RUNTIME_C.to_string(),
51            link_args: vec!["-lm".to_string()],
52        });
53    }
54    let mut link_args =
55        crate::codegen_c::value_runtime::archive_link_args(crate::codegen_c::value_runtime::Target::Riscv64)?;
56    link_args.push("-lm".to_string());
57    Ok(Runtime {
58        c: format!(
59            "{RISCV_RUNTIME_C}\n{}\n{}",
60            crate::codegen_c::tower::CORE,
61            include_str!("tower.c")
62        ),
63        link_args,
64    })
65}
66
67use crate::analysis::PolicyRegistry;
68use crate::intern::Interner;
69use crate::vm::CompiledProgram;
70
71/// An op / type / shape outside the current phase's supported fragment. The `&str` names what is not
72/// yet lowered (an op name, or a reason like `"Show of non-scalar value"`). A refusal — the backend
73/// declines to emit rather than emit wrong code (mirrors [`crate::vm::wasm::WasmLowerError`]).
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub enum RiscVLowerError {
76    Unsupported(&'static str),
77}
78
79impl std::fmt::Display for RiscVLowerError {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        match self {
82            RiscVLowerError::Unsupported(what) => {
83                write!(f, "riscv AOT backend: unsupported {what}")
84            }
85        }
86    }
87}
88
89impl std::error::Error for RiscVLowerError {}
90
91/// Whether this tier lowers `op` as a value-semantics mutation of a register's own collection or Text, or of
92/// a value in its own record's field: through an `_own` runtime helper that copies first only what the
93/// register's owned flag (and, for a field, its field flag) says may be held elsewhere, so the change never
94/// reaches an alias and a loop of them runs in place.
95pub fn mutates_own_collection(op: &crate::vm::Op) -> bool {
96    matches!(
97        emit::mutation(op),
98        Some(emit::Mutation::Owned(_) | emit::Mutation::Field(_))
99    ) || emit::text_append(op).is_some()
100}
101
102/// Emit `program` as RV64GC assembly text. Returns [`RiscVLowerError::Unsupported`] if the program
103/// uses any op/shape outside the currently-lowered fragment.
104pub fn codegen_program_riscv(
105    program: &CompiledProgram,
106    policies: &PolicyRegistry,
107    interner: &Interner,
108) -> Result<String, RiscVLowerError> {
109    emit::emit(program, policies, interner)
110}