Skip to main content

logicaffeine_compile/codegen_tt/
mod.rs

1//! LOGOS → **Tenstorrent** (TT-Metalium) AOT codegen tier. Emits a complete
2//! single-Tensix-core TT-Metalium program — a **host** orchestrator plus a
3//! **data-movement compute kernel** that runs on one baby RISC-V core
4//! (`DataMovementProcessor::RISCV_0`) — for the scalar-compute fragment of the
5//! benchmark suite.
6//!
7//! ## Why the C backend is the compute source
8//! A Tensix baby core is a RISC-V CPU and a TT-Metalium kernel is C++; the
9//! canonical `add_2_integers_in_riscv` kernel is literally `dat0[0] = dat0[0] +
10//! dat1[0]`. So the kernel's compute body is the SAME self-contained C the
11//! [`crate::codegen_c`] backend already emits — which is byte-for-byte ≡ the VM
12//! (locked by `assert_c_output`). The Tenstorrent tier does not re-lower
13//! arithmetic; it *wraps* that proven compute in the TT-Metalium host/kernel
14//! split and routes `Show` to the host DPRINT stream. See `wrap`.
15//!
16//! ## What is verified, and when
17//! - **Pre-hardware (now, no card):** the compute is executably ≡ VM via the C
18//!   oracle ([`TtArtifact::oracle_c`], run through gcc exactly like the C tier),
19//!   and the emitted host/kernel/CMake carry the correct TT-Metalium API surface.
20//! - **On-hardware (Phase 5, gated on a Wormhole/Blackhole card + tt-metal SDK):**
21//!   the host+kernel build against tt-metal and run on the Tensix core, DPRINT
22//!   output compared to the VM. That path is `#[ignore]`d until `TT_METAL_HOME`
23//!   is present, so nothing here fabricates an on-silicon result.
24//!
25//! ## Totality discipline (mirrors the RISC-V / WASM tiers)
26//! Any program using an op outside the single-baby-core fragment — CONCURRENCY
27//! (channels/tasks/select, which need a `ucontext` scheduler + multiple contexts;
28//! the genuine multi-baby-core mesh tier in [`concurrency_mesh`] lowers those) —
29//! returns [`TtLowerError::Unsupported`]: a SOUND refusal, never a miscompile.
30//! OFFLINE networking ([`TT_NET_OPS`]) is served on-core through the newlib-clean
31//! C net runtime (wrapped verbatim). The `tenstorrent_op_coverage_lock` ratchet
32//! forces the off-core set onto a shrink-only ledger so nothing is silently dropped.
33
34pub mod concurrency_mesh;
35pub mod matmul_lower;
36mod runtime;
37mod wrap;
38
39pub use runtime::{TT_HOST_TEMPLATE, TT_KERNEL_PREAMBLE};
40pub use wrap::wrap_as_tt;
41
42use crate::vm::CompiledProgram;
43
44/// An op / shape outside the current phase's single-baby-core fragment. The
45/// `&str` names what is not yet lowered. A refusal — the backend declines to
46/// emit rather than emit wrong code (mirrors [`crate::codegen_riscv::RiscVLowerError`]).
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub enum TtLowerError {
49    Unsupported(&'static str),
50}
51
52impl std::fmt::Display for TtLowerError {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        match self {
55            TtLowerError::Unsupported(what) => {
56                write!(f, "tenstorrent AOT backend: unsupported {what}")
57            }
58        }
59    }
60}
61
62impl std::error::Error for TtLowerError {}
63
64/// A complete single-Tensix-core TT-Metalium program emitted from a LOGOS program.
65#[derive(Debug, Clone)]
66pub struct TtArtifact {
67    /// The TT-Metalium compute kernel (runs on `DataMovementProcessor::RISCV_0`).
68    pub kernel_cpp: String,
69    /// The host orchestrator: open device, create program + kernel, launch, wait, close.
70    pub host_cpp: String,
71    /// CMake glue so `host_cpp` builds against a tt-metal checkout (`$TT_METAL_HOME`).
72    pub cmake: String,
73    /// The pre-hardware ≡VM correctness oracle: the self-contained C program from
74    /// [`crate::codegen_c`], whose compute is what the kernel runs. Compiled with
75    /// gcc and run on the host, its output is byte-identical to the VM.
76    pub oracle_c: String,
77}
78
79/// The single canonical name of the generated kernel source (referenced by the
80/// host `CreateKernel` call and the CMake glue).
81pub const KERNEL_FILE_NAME: &str = "logos_compute_kernel.cpp";
82
83/// The OFFLINE NETWORKING ops the single Tensix baby core serves via the loopback transport
84/// ([`crate::codegen_c`]'s `C_NET_RUNTIME`, wrapped verbatim). `Connect`/`Listen`/`Sync` are
85/// no-ops, `MakePeer` is identity, `Send`/`Stream` enqueue a 64-bit message into a per-address
86/// FIFO, `Await` dequeues it. That runtime is newlib-clean (`strcmp`/`strdup` + static arrays —
87/// no `ucontext`, unlike the concurrency runtime), so it rides the SAME wrap the scalar body does
88/// and a single-node `Send X to peer; Await …` round-trips ≡ the VM's offline `NetInbox`. Every op
89/// here is [`crate::aot_unsupported::Offload::Net`]; excluded from the refusal below (device-served,
90/// not host-only). The CUDA analogue is `codegen_cuda::CUDA_NET_OPS`; bound by
91/// `tenstorrent_op_coverage_lock`.
92pub const TT_NET_OPS: &[&str] = &[
93    "NetConnect",
94    "NetListen",
95    "NetSend",
96    "NetStream",
97    "NetAwait",
98    "NetMakePeer",
99    "NetSync",
100];
101
102/// Returns `Some(reason)` if `program` uses an op that cannot run on a single
103/// Tensix baby core in this phase — concurrency (a Tensix mesh is SPMD, not a
104/// shared-memory thread pool with a scheduler; the concurrency runtime needs
105/// `ucontext`, which a baby core has no libc for). Everything else — including
106/// single-replica CRDT counters (in-memory maps in the C body) and the OFFLINE
107/// networking loopback ([`TT_NET_OPS`], the newlib-clean `C_NET_RUNTIME`) —
108/// lowers through the proven C compute body. `Sleep` is virtual-time (a
109/// scheduler primitive) and is refused with the concurrency set.
110pub fn unsupported_reason(program: &CompiledProgram) -> Option<&'static str> {
111    use crate::aot_unsupported::Offload;
112    // The offload categories (channel / task / virtual-time / net) are refused EXCEPT the offline
113    // networking loopback the C net runtime serves ([`TT_NET_OPS`]). So find the first offload op
114    // that is NOT net-served; a `Send`/`Await` on a peer passes through to the wrapped C body.
115    let op = program.code.iter().find(|op| {
116        crate::aot_unsupported::classify(op).is_some() && !TT_NET_OPS.contains(&op.name())
117    })?;
118    let o = crate::aot_unsupported::classify(op).expect("filtered to Some");
119    Some(match o {
120        Offload::Channel => "channel op (no shared-memory channels on a single Tensix core)",
121        Offload::Task => "task spawn/await (Tensix cores are SPMD, not a thread pool)",
122        Offload::VirtualTime => "virtual-time / select (needs a cooperative scheduler)",
123        Offload::Net => "networking op (host-only IO)",
124        Offload::Dom => "DOM / browser op (a Tensix baby core has no document to build)",
125    })
126}
127
128/// Emit `program` as a single-Tensix-core TT-Metalium program. `oracle_c` is the
129/// self-contained C translation of the SAME program (from [`crate::compile::compile_to_c`]);
130/// its compute body becomes the kernel. Returns [`TtLowerError::Unsupported`] if the
131/// program uses any op outside the single-baby-core fragment.
132pub fn codegen_program_tt(
133    program: &CompiledProgram,
134    oracle_c: &str,
135) -> Result<TtArtifact, TtLowerError> {
136    if let Some(reason) = unsupported_reason(program) {
137        return Err(TtLowerError::Unsupported(reason));
138    }
139    Ok(wrap_as_tt(oracle_c))
140}