Skip to main content

ir_lang/
lib.rs

1//! # ir_lang
2//!
3//! An intermediate representation and the lowering interface a compiler builds it
4//! through.
5//!
6//! ir-lang gives a front-end a place to put a program once it has been parsed and
7//! type-checked, in the flat, explicit form that optimization passes rewrite and
8//! backends read. A [`Function`] is a control-flow graph of basic blocks; each block
9//! is a straight-line run of value-producing [instructions](Inst) ended by one
10//! [`Terminator`]; every value is named by a small [`Value`] handle and defined
11//! exactly once, in SSA form. Values cross control-flow joins as block parameters
12//! rather than through phi nodes, which keeps the representation flat and the
13//! validation simple.
14//!
15//! ## Lowering
16//!
17//! There is no AST type here to lower *from* — a language brings its own syntax
18//! tree. Lowering is instead expressed through the [`Builder`]: a front-end walks
19//! its tree and, for each construct, calls a builder method that emits the matching
20//! IR and hands back the [`Value`] it defines. This is the same shape as Cranelift's
21//! `FunctionBuilder` or LLVM's `IRBuilder`. When the function is built, check it with
22//! [`Function::validate`].
23//!
24//! ## Example
25//!
26//! Lower and validate `fn abs(x: int) -> int { if x < 0 { -x } else { x } }`:
27//!
28//! ```
29//! use ir_lang::{Builder, BinOp, Type, UnOp};
30//!
31//! let mut b = Builder::new("abs", &[Type::Int], Type::Int);
32//! let x = b.block_params(b.entry())[0];
33//!
34//! // The merge point takes the result as a parameter.
35//! let join = b.create_block(&[Type::Int]);
36//! let neg_blk = b.create_block(&[]);
37//! let pos_blk = b.create_block(&[]);
38//!
39//! let zero = b.iconst(0);
40//! let is_neg = b.bin(BinOp::Lt, x, zero);
41//! b.branch(is_neg, neg_blk, &[], pos_blk, &[]);
42//!
43//! b.switch_to(neg_blk);
44//! let negated = b.un(UnOp::Neg, x);
45//! b.jump(join, &[negated]);
46//!
47//! b.switch_to(pos_blk);
48//! b.jump(join, &[x]);
49//!
50//! b.switch_to(join);
51//! let result = b.block_params(join)[0];
52//! b.ret(Some(result));
53//!
54//! let func = b.finish();
55//! func.validate().expect("the lowered function is well-formed");
56//! assert_eq!(func.name(), "abs");
57//! ```
58//!
59//! ## Features
60//!
61//! - `std` (default) — the standard library; without it the crate is `#![no_std]`
62//!   and needs only `alloc`.
63//! - `serde` — derives `serde::Serialize` / `Deserialize` for the IR types so a
64//!   function can be cached, inspected, or moved between tools.
65//!
66//! ## Stability
67//!
68//! The public surface is frozen and stable as of `1.0.0`: it follows Semantic
69//! Versioning, with no breaking changes before `2.0`. The full surface and the
70//! SemVer promise are catalogued in
71//! [`docs/API.md`](https://github.com/jamesgober/ir-lang/blob/main/docs/API.md#semver-promise).
72
73#![cfg_attr(not(feature = "std"), no_std)]
74#![cfg_attr(docsrs, feature(doc_cfg))]
75#![deny(missing_docs)]
76#![forbid(unsafe_code)]
77#![deny(
78    clippy::unwrap_used,
79    clippy::expect_used,
80    clippy::panic,
81    clippy::todo,
82    clippy::unimplemented,
83    clippy::unreachable,
84    clippy::dbg_macro,
85    clippy::print_stdout,
86    clippy::print_stderr
87)]
88
89extern crate alloc;
90
91mod builder;
92mod entity;
93mod function;
94mod inst;
95mod ty;
96mod validate;
97
98pub use builder::Builder;
99pub use entity::{Block, Value};
100pub use function::Function;
101pub use inst::{BinOp, Inst, Terminator, UnOp};
102pub use ty::Type;
103pub use validate::ValidationError;