Skip to main content

backend_lang/
lib.rs

1//! # backend_lang
2//!
3//! The core of the LexerSketch native backend: a machine IR over virtual and
4//! physical registers, target descriptions that say everything the backend
5//! needs to know about an ISA as data, calling conventions and frame layout,
6//! a parallel-move resolver, and emission to machine code through
7//! [`isa_lang`]. No Cranelift, no LLVM, no system assembler.
8//!
9//! This release (0.2) is the foundation. Instruction selection from ir-lang
10//! and SSA register allocation build on it in 0.5; until then MIR is built
11//! directly, and emission takes functions whose registers are already
12//! physical.
13//!
14//! ## Quick start
15//!
16//! [`emit`] is the one-call path: a MIR function on physical registers in,
17//! x86-64 machine code out.
18//!
19//! ```
20//! use backend_lang::mir::{Effects, Function, Operand};
21//! use backend_lang::x86_64::{op, SYSV, RAX, RDI};
22//!
23//! // fn double(x) -> x + x, SysV: x arrives in rdi, the result leaves in rax.
24//! let mut f = Function::new("double", &SYSV);
25//! let entry = f.block()?;
26//! f.add_param(entry, RDI.into())?;
27//! f.push(entry, op::MOV64, &[RAX.into(), RDI.into()])?;
28//! f.push(entry, op::ADD64, &[RAX.into(), RAX.into(), RDI.into()])?;
29//! f.push_with(entry, op::RET, &[], Effects::new().uses(&[RAX]))?;
30//! f.omit_frame_pointer();
31//!
32//! let code = backend_lang::emit(&f)?;
33//! // mov rax, rdi; add rax, rdi; ret
34//! assert_eq!(code, [0x48, 0x89, 0xF8, 0x48, 0x01, 0xF8, 0xC3]);
35//! # Ok::<(), Box<dyn std::error::Error>>(())
36//! ```
37//!
38//! ## The pieces
39//!
40//! - [`mir`]: [`Function`](mir::Function)s of [`Block`](mir::Block)s of
41//!   target instructions, with typed virtual registers, stack slots, symbols,
42//!   explicit implicit operands, and a deterministic text form.
43//! - [`target`]: the [`Target`](target::Target) description (registers,
44//!   classes, instructions with their operand rules and implicit operands),
45//!   [`abi`](target::abi) (argument and return registers, callee-saved sets,
46//!   stack alignment, shadow space, red zone, probing), and
47//!   [`frame`](target::frame) (frame layout and prologue/epilogue plans).
48//! - [`moves`]: the parallel-move resolver every parallel copy goes through.
49//! - [`x86_64`]: the x86-64 description, SysV and Win64, and the
50//!   [`Emitter`](x86_64::Emitter).
51//!
52//! ## Block parameters, not phi nodes
53//!
54//! Values cross control-flow edges as block parameters: a branch passes
55//! arguments, and the target block's parameters receive them all at once.
56//! This is the same information as phi nodes, kept at the edge where it is
57//! used: the parallel copy a phi implies on each incoming edge is written
58//! down on that edge. An SSA register allocator (0.5) assigns parameters and
59//! arguments independently and resolves each edge with the parallel-move
60//! resolver; ir-lang already uses block parameters, so instruction selection
61//! maps them one to one; and there is no per-block phi list to keep in sync
62//! with predecessor order.
63//!
64//! ## Guarantees
65//!
66//! - **Parallel copies are exact.** Every edge, `copy`, and `pcopy` goes
67//!   through [`moves::MoveResolver`], which handles any permutation, cycles
68//!   of any length, fan-out, registers and stack slots mixed, and
69//!   immediates, with the fewest moves under its rules. Property tests run
70//!   it against a parallel-assignment reference, and emitted edges against
71//!   an interpreter of the emitted machine code.
72//! - **Emission is checked.** A function is verified before emission;
73//!   virtual registers, mismatched tied operands, and writes to the stack or
74//!   frame pointer are errors, not bad code.
75//! - **No reachable panics, no recursion.** Every table is a dense vector,
76//!   every walk a loop, every fallible path a `Result`.
77//!
78//! ## Features
79//!
80//! - `std` (default): no effect beyond isa-lang's; the crate is `no_std`
81//!   and needs only `alloc`.
82
83#![cfg_attr(not(feature = "std"), no_std)]
84#![cfg_attr(docsrs, feature(doc_cfg))]
85#![forbid(unsafe_code)]
86#![deny(missing_docs)]
87#![deny(unused_must_use)]
88#![deny(unused_results)]
89#![deny(clippy::unwrap_used)]
90#![deny(clippy::expect_used)]
91#![deny(clippy::todo)]
92#![deny(clippy::unimplemented)]
93#![deny(clippy::print_stdout)]
94#![deny(clippy::print_stderr)]
95#![deny(clippy::dbg_macro)]
96#![deny(clippy::unreachable)]
97
98extern crate alloc;
99
100pub mod mir;
101pub mod moves;
102mod regs;
103pub mod target;
104pub mod x86_64;
105
106pub use isa_lang;
107pub use regs::{MAX_PREGS, PReg, Reg, RegClass, RegSet, RegSetIter, VReg};
108
109use alloc::vec::Vec;
110
111/// Emits one function to machine code: the one-call path.
112///
113/// The function must be on physical registers (after register allocation,
114/// or written that way). It is verified, its frame is laid out under its
115/// calling convention, and every instruction is encoded. Calls to other
116/// functions need [`x86_64::Emitter`], which places several functions
117/// together; a function may call itself by its own name here.
118///
119/// # Errors
120///
121/// An [`x86_64::EmitError`] naming the problem.
122///
123/// # Examples
124///
125/// ```
126/// use backend_lang::mir::{Function, Operand};
127/// use backend_lang::x86_64::{op, SYSV, RAX};
128///
129/// let mut f = Function::new("one", &SYSV);
130/// let b = f.block()?;
131/// f.push(b, op::MOV32RI, &[RAX.into(), Operand::Imm(1)])?;
132/// f.push(b, op::RET, &[])?;
133/// let code = backend_lang::emit(&f)?;
134/// assert_eq!(code.last(), Some(&0xC3));
135/// # Ok::<(), Box<dyn std::error::Error>>(())
136/// ```
137pub fn emit(f: &mir::Function) -> Result<Vec<u8>, x86_64::EmitError> {
138    let mut e = x86_64::Emitter::new();
139    let _info = e.function(f)?;
140    Ok(e.finish()?.into_bytes())
141}
142
143/// Compiles and runs the `rust` code blocks in `README.md` and `docs/API.md` as
144/// part of `cargo test`, so the published examples cannot drift from the API.
145///
146/// Present only while collecting doctests (`#[cfg(doctest)]`); it is not part of
147/// the public surface and does not appear in the built library or its docs.
148#[cfg(doctest)]
149#[doc = include_str!("../README.md")]
150#[doc = include_str!("../docs/API.md")]
151pub struct MarkdownDocTests;