Skip to main content

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