backend_lang/lib.rs
1//! # backend_lang
2//!
3//! The LexerSketch native backend: instruction selection from ir-lang 2.0,
4//! SSA register allocation with exact stack maps, and emission to x86-64
5//! machine code through [`isa_lang`], on top of a machine IR over virtual and
6//! physical registers, target descriptions that state everything the backend
7//! needs to know about an ISA as data, SysV and Win64 calling conventions and
8//! frame layout, and a parallel-move resolver. No Cranelift, no LLVM, no
9//! system assembler.
10//!
11//! This release (0.3) compiles the integer, control-flow, memory, call, and
12//! checked-arithmetic subset of ir-lang. Floating point waits on isa-lang's
13//! SSE forms, AArch64 on its AArch64 forms, and unwind tables on object
14//! files that carry them; each is refused with an error that says so, never
15//! miscompiled.
16//!
17//! ## Quick start
18//!
19//! [`compile`] is the one-call path: an ir-lang module in, machine code with
20//! symbols and relocations out.
21//!
22//! ```
23//! use backend_lang::Options;
24//! use ir_lang::{Linkage, Module, Overflow, Signature, Type};
25//!
26//! // fn add(x: i64, y: i64) -> i64 { x + y }
27//! let mut m = Module::new("demo");
28//! let sig = Signature::new(&[Type::I64, Type::I64], &[Type::I64]);
29//! let f = m.declare_function("add", &sig, Linkage::Export)?;
30//! let mut b = m.build(f)?;
31//! let (x, y) = (b.param(0)?, b.param(1)?);
32//! let s = b.add(x, y, Overflow::Wrap)?;
33//! b.ret(&[s])?;
34//!
35//! let c = backend_lang::compile(&m, &Options::sysv().with_frame_pointer(false))?;
36//! // SysV: x arrives in rdi, y in rsi; the sum leaves in rax.
37//! // add rdi, rsi; mov rax, rdi; ret
38//! assert_eq!(c.code(), [0x48, 0x01, 0xF7, 0x48, 0x89, 0xF8, 0xC3]);
39//! assert_eq!(c.function("add").map(|f| f.offset()), Some(0));
40//! assert_eq!(c.exports(), ["add"]);
41//! # Ok::<(), Box<dyn std::error::Error>>(())
42//! ```
43//!
44//! [`emit`] is the lower-level path: a MIR function on physical registers
45//! (written by hand, or after [`regalloc::allocate`]) in, machine code out.
46//!
47//! ```
48//! use backend_lang::mir::{Effects, Function};
49//! use backend_lang::x86_64::{op, SYSV, RAX, RDI};
50//!
51//! // fn double(x) -> x + x, SysV: x arrives in rdi, the result leaves in rax.
52//! let mut f = Function::new("double", &SYSV);
53//! let entry = f.block()?;
54//! f.add_param(entry, RDI.into())?;
55//! f.push(entry, op::MOV64, &[RAX.into(), RDI.into()])?;
56//! f.push(entry, op::ADD64, &[RAX.into(), RAX.into(), RDI.into()])?;
57//! f.push_with(entry, op::RET, &[], Effects::new().uses(&[RAX]))?;
58//! f.omit_frame_pointer();
59//!
60//! let code = backend_lang::emit(&f)?;
61//! // mov rax, rdi; add rax, rdi; ret
62//! assert_eq!(code, [0x48, 0x89, 0xF8, 0x48, 0x01, 0xF8, 0xC3]);
63//! # Ok::<(), Box<dyn std::error::Error>>(())
64//! ```
65//!
66//! ## The pieces
67//!
68//! - [`compile`], [`Options`], [`Compiled`]: the whole pipeline for a module.
69//! - [`isel`]: instruction selection, one ir-lang function to MIR on
70//! virtual registers.
71//! - [`regalloc`]: SSA register allocation, virtual registers to physical
72//! registers and stack slots.
73//! - [`mir`]: [`Function`](mir::Function)s of [`Block`](mir::Block)s of
74//! target instructions, with typed virtual registers, stack slots, symbols,
75//! explicit implicit operands, call-site records, and a deterministic text
76//! form.
77//! - [`target`]: the [`Target`](target::Target) description (registers,
78//! classes, instructions with their operand rules and implicit operands),
79//! [`abi`](target::abi) (argument and return registers, callee-saved sets,
80//! stack alignment, shadow space, red zone, probing), and
81//! [`frame`](target::frame) (frame layout and prologue/epilogue plans).
82//! - [`moves`]: the parallel-move resolver every parallel copy goes through.
83//! - [`x86_64`]: the x86-64 description, SysV and Win64, and the
84//! [`Emitter`](x86_64::Emitter).
85//!
86//! ## Block parameters, not phi nodes
87//!
88//! Values cross control-flow edges as block parameters: a branch passes
89//! arguments, and the target block's parameters receive them all at once.
90//! This is the same information as phi nodes, kept at the edge where it is
91//! used: the parallel copy a phi implies on each incoming edge is written
92//! down on that edge. The register allocator assigns parameters and
93//! arguments independently and resolves each edge with the parallel-move
94//! resolver; ir-lang uses block parameters too, so instruction selection
95//! maps them one to one; and there is no per-block phi list to keep in sync
96//! with predecessor order.
97//!
98//! ## Guarantees
99//!
100//! - **Compiled code computes what the IR says.** Random ir-lang programs
101//! (every integer operation under every policy, error edges, loops,
102//! switches, memory, calls, tail calls) are compiled and run against
103//! ir-lang's reference interpreter, in an x86-64 interpreter under SysV and
104//! Win64 and natively on x86-64 Linux and Windows.
105//! - **Stack maps are exact.** At every safepoint the recorded root slots are
106//! exactly its GC list (IR.md ยง9), and no reference is in a register while
107//! the collector runs; a moving collector in the tests walks real frames by
108//! these records.
109//! - **Parallel copies are exact.** Every edge, `copy`, and `pcopy` goes
110//! through [`moves::MoveResolver`], which handles any permutation, cycles
111//! of any length, fan-out, registers and stack slots mixed, and
112//! immediates, with the fewest moves under its rules.
113//! - **Unsupported is an error, not bad code.** What this release does not
114//! select is reported as [`isel::IselError::Unsupported`] naming what it
115//! waits on.
116//! - **No reachable panics, no recursion.** Every table is a dense vector,
117//! every walk a loop, every fallible path a `Result`. Passes are linear in
118//! the function (one sort here and there) except liveness, whose cost is
119//! the size of the live sets and has a budget
120//! ([`regalloc::Options::live_budget`]).
121//!
122//! ## Features
123//!
124//! - `std` (default): no effect beyond isa-lang's and ir-lang's; the crate is
125//! `no_std` and needs only `alloc`.
126
127#![cfg_attr(not(feature = "std"), no_std)]
128#![cfg_attr(docsrs, feature(doc_cfg))]
129#![forbid(unsafe_code)]
130#![deny(missing_docs)]
131#![deny(unused_must_use)]
132#![deny(unused_results)]
133#![deny(clippy::unwrap_used)]
134#![deny(clippy::expect_used)]
135#![deny(clippy::todo)]
136#![deny(clippy::unimplemented)]
137#![deny(clippy::print_stdout)]
138#![deny(clippy::print_stderr)]
139#![deny(clippy::dbg_macro)]
140#![deny(clippy::unreachable)]
141
142extern crate alloc;
143
144mod compile;
145pub mod isel;
146pub mod mir;
147pub mod moves;
148pub mod regalloc;
149mod regs;
150pub mod target;
151pub mod x86_64;
152
153pub use compile::{CompileError, Compiled, DataObject, Options, compile, compile_function};
154pub use ir_lang;
155pub use isa_lang;
156pub use regs::{MAX_PREGS, PReg, Reg, RegClass, RegSet, RegSetIter, VReg};
157
158use alloc::vec::Vec;
159
160/// Emits one function to machine code: the one-call path.
161///
162/// The function must be on physical registers (after register allocation,
163/// or written that way). It is verified, its frame is laid out under its
164/// calling convention, and every instruction is encoded. Calls to other
165/// functions need [`x86_64::Emitter`], which places several functions
166/// together; a function may call itself by its own name here.
167///
168/// # Errors
169///
170/// An [`x86_64::EmitError`] naming the problem.
171///
172/// # Examples
173///
174/// ```
175/// use backend_lang::mir::{Function, Operand};
176/// use backend_lang::x86_64::{op, SYSV, RAX};
177///
178/// let mut f = Function::new("one", &SYSV);
179/// let b = f.block()?;
180/// f.push(b, op::MOV32RI, &[RAX.into(), Operand::Imm(1)])?;
181/// f.push(b, op::RET, &[])?;
182/// let code = backend_lang::emit(&f)?;
183/// assert_eq!(code.last(), Some(&0xC3));
184/// # Ok::<(), Box<dyn std::error::Error>>(())
185/// ```
186pub fn emit(f: &mir::Function) -> Result<Vec<u8>, x86_64::EmitError> {
187 let mut e = x86_64::Emitter::new();
188 let _info = e.function(f)?;
189 Ok(e.finish()?.into_bytes())
190}
191
192/// Compiles and runs the `rust` code blocks in `README.md` and `docs/API.md` as
193/// part of `cargo test`, so the published examples cannot drift from the API.
194///
195/// Present only while collecting doctests (`#[cfg(doctest)]`); it is not part of
196/// the public surface and does not appear in the built library or its docs.
197#[cfg(doctest)]
198#[doc = include_str!("../README.md")]
199#[doc = include_str!("../docs/API.md")]
200pub struct MarkdownDocTests;