backend_lang/compile.rs
1//! The Tier-1 path: an ir-lang 2.0 module to x86-64 machine code, with the
2//! symbols, relocations, data, and stack maps a linker or JIT needs.
3
4use alloc::collections::BTreeSet;
5use alloc::string::{String, ToString};
6use alloc::vec::Vec;
7use core::fmt;
8
9use ir_lang::{FuncId, Init, Linkage, Module, SymbolRef};
10
11use crate::isel::{self, IselError};
12use crate::mir;
13use crate::regalloc::{self, RegAllocError};
14use crate::target::abi::Abi;
15use crate::x86_64::{self, EmitError, EmittedFunction, Emitter, Program, Reloc, RelocKind};
16
17/// How to compile: the platform's C convention, frame and runtime choices,
18/// and selection and allocation settings.
19///
20/// The C convention (`abi`) serves ir-lang's `c` tag and its `fast` tag (the
21/// backend's own convention for code compiled together is the platform's C
22/// convention in this release). Runtime entry points are called by name and
23/// left as relocations unless the module defines them:
24///
25/// | Symbol | Called | With |
26/// |---|---|---|
27/// | `trap_symbol` | for `trap` and every trapping policy | the OPS code (first argument); never returns |
28/// | `gc_poll_symbol` | for a `safepoint` instruction | nothing; a safepoint with a stack map |
29/// | `resume_symbol` | for `resume` | the exception payload; never returns |
30///
31/// # Examples
32///
33/// ```
34/// use backend_lang::Options;
35///
36/// let o = Options::sysv().with_frame_pointer(false);
37/// assert_eq!(o.abi.name(), "sysv64");
38/// assert!(!o.frame_pointer);
39/// assert_eq!(Options::win64().abi.name(), "win64");
40/// ```
41#[derive(Clone, Copy, Debug)]
42#[non_exhaustive]
43pub struct Options {
44 /// The platform's C convention, used for the `c` and `fast` tags and for
45 /// runtime calls.
46 pub abi: &'static Abi,
47 /// Whether functions keep a frame pointer (on by default). Without one,
48 /// `rbp` is one more register for the allocator.
49 pub frame_pointer: bool,
50 /// The runtime's trap handler.
51 pub trap_symbol: &'static str,
52 /// The runtime's GC poll, called by `safepoint`.
53 pub gc_poll_symbol: &'static str,
54 /// The runtime's unwinder entry, called by `resume`.
55 pub resume_symbol: &'static str,
56 /// The fewest cases a switch needs for a jump table.
57 pub jump_table_min_cases: u32,
58 /// A jump table is used when the case values span at most this many
59 /// table entries per case (3: at least a third of the entries are
60 /// cases).
61 pub jump_table_max_spread: u32,
62 /// Register-allocation settings.
63 pub regalloc: regalloc::Options,
64}
65
66impl Options {
67 /// For System V AMD64 platforms (Linux, the BSDs, macOS).
68 ///
69 /// # Examples
70 ///
71 /// ```
72 /// use backend_lang::Options;
73 ///
74 /// assert_eq!(Options::sysv().trap_symbol, "__lexersketch_trap");
75 /// ```
76 #[must_use]
77 pub const fn sysv() -> Options {
78 Options::with_abi(&x86_64::SYSV)
79 }
80
81 /// For Windows x64.
82 ///
83 /// # Examples
84 ///
85 /// ```
86 /// use backend_lang::Options;
87 ///
88 /// assert_eq!(Options::win64().abi.name(), "win64");
89 /// ```
90 #[must_use]
91 pub const fn win64() -> Options {
92 Options::with_abi(&x86_64::WIN64)
93 }
94
95 /// For the platform this crate is compiled for: Win64 on Windows, System
96 /// V elsewhere.
97 ///
98 /// # Examples
99 ///
100 /// ```
101 /// use backend_lang::Options;
102 ///
103 /// let name = Options::host().abi.name();
104 /// assert!(name == "sysv64" || name == "win64");
105 /// ```
106 #[must_use]
107 pub const fn host() -> Options {
108 if cfg!(windows) {
109 Options::win64()
110 } else {
111 Options::sysv()
112 }
113 }
114
115 /// The defaults with `abi` as the C convention.
116 ///
117 /// # Examples
118 ///
119 /// ```
120 /// use backend_lang::Options;
121 /// use backend_lang::x86_64::WIN64;
122 ///
123 /// assert_eq!(Options::with_abi(&WIN64).abi.name(), "win64");
124 /// ```
125 #[must_use]
126 pub const fn with_abi(abi: &'static Abi) -> Options {
127 Options {
128 abi,
129 frame_pointer: true,
130 trap_symbol: "__lexersketch_trap",
131 gc_poll_symbol: "__lexersketch_gc_poll",
132 resume_symbol: "__lexersketch_resume",
133 jump_table_min_cases: 4,
134 jump_table_max_spread: 3,
135 regalloc: regalloc::Options::new(),
136 }
137 }
138
139 /// Keeps or omits the frame pointer.
140 ///
141 /// # Examples
142 ///
143 /// ```
144 /// use backend_lang::Options;
145 ///
146 /// assert!(!Options::sysv().with_frame_pointer(false).frame_pointer);
147 /// ```
148 #[must_use]
149 pub const fn with_frame_pointer(mut self, keep: bool) -> Options {
150 self.frame_pointer = keep;
151 self
152 }
153
154 /// Renames the runtime entry points.
155 ///
156 /// # Examples
157 ///
158 /// ```
159 /// use backend_lang::Options;
160 ///
161 /// let o = Options::sysv().with_runtime("my_trap", "my_poll", "my_resume");
162 /// assert_eq!(o.gc_poll_symbol, "my_poll");
163 /// ```
164 #[must_use]
165 pub const fn with_runtime(
166 mut self,
167 trap: &'static str,
168 gc_poll: &'static str,
169 resume: &'static str,
170 ) -> Options {
171 self.trap_symbol = trap;
172 self.gc_poll_symbol = gc_poll;
173 self.resume_symbol = resume;
174 self
175 }
176
177 /// Sets when a switch becomes a jump table.
178 ///
179 /// # Examples
180 ///
181 /// ```
182 /// use backend_lang::Options;
183 ///
184 /// // Never use a jump table.
185 /// let o = Options::sysv().with_jump_tables(u32::MAX, 1);
186 /// assert_eq!(o.jump_table_min_cases, u32::MAX);
187 /// ```
188 #[must_use]
189 pub const fn with_jump_tables(mut self, min_cases: u32, max_spread: u32) -> Options {
190 self.jump_table_min_cases = min_cases;
191 self.jump_table_max_spread = max_spread;
192 self
193 }
194
195 /// Sets the register-allocation settings.
196 ///
197 /// # Examples
198 ///
199 /// ```
200 /// use backend_lang::{regalloc, Options};
201 ///
202 /// let o = Options::sysv().with_regalloc(regalloc::Options::new().with_live_budget(1 << 20));
203 /// assert_eq!(o.regalloc.live_budget, 1 << 20);
204 /// ```
205 #[must_use]
206 pub const fn with_regalloc(mut self, regalloc: regalloc::Options) -> Options {
207 self.regalloc = regalloc;
208 self
209 }
210}
211
212impl Default for Options {
213 fn default() -> Options {
214 Options::host()
215 }
216}
217
218/// Why a module could not be compiled.
219///
220/// # Examples
221///
222/// ```
223/// use backend_lang::{CompileError, Options};
224/// use ir_lang::{Linkage, Module, Signature, Type};
225///
226/// let mut m = Module::new("m");
227/// let f = m.declare_function("f", &Signature::new(&[], &[Type::F32]), Linkage::Export)?;
228/// let mut b = m.build(f)?;
229/// let x = b.f32const(1.0)?;
230/// b.ret(&[x])?;
231/// let err = backend_lang::compile(&m, &Options::sysv()).unwrap_err();
232/// assert!(matches!(err, CompileError::Select { .. }));
233/// # Ok::<(), Box<dyn std::error::Error>>(())
234/// ```
235#[derive(Clone, Debug, PartialEq, Eq)]
236#[non_exhaustive]
237pub enum CompileError {
238 /// The module does not validate.
239 Invalid(ir_lang::ValidationError),
240 /// Instruction selection failed for a function.
241 Select {
242 /// The function.
243 function: String,
244 /// Why.
245 error: IselError,
246 },
247 /// Register allocation failed for a function.
248 Allocate {
249 /// The function.
250 function: String,
251 /// Why.
252 error: RegAllocError,
253 },
254 /// Emission failed (for a function, if one is named).
255 Emit {
256 /// The function, if the failure belongs to one.
257 function: Option<String>,
258 /// Why.
259 error: EmitError,
260 },
261}
262
263impl fmt::Display for CompileError {
264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265 match self {
266 CompileError::Invalid(e) => write!(f, "invalid module: {e}"),
267 CompileError::Select { function, error } => write!(f, "`{function}`: {error}"),
268 CompileError::Allocate { function, error } => write!(f, "`{function}`: {error}"),
269 CompileError::Emit {
270 function: Some(n),
271 error,
272 } => write!(f, "`{n}`: {error}"),
273 CompileError::Emit {
274 function: None,
275 error,
276 } => fmt::Display::fmt(error, f),
277 }
278 }
279}
280
281impl core::error::Error for CompileError {
282 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
283 match self {
284 CompileError::Invalid(e) => Some(e),
285 CompileError::Select { error, .. } => Some(error),
286 CompileError::Allocate { error, .. } => Some(error),
287 CompileError::Emit { error, .. } => Some(error),
288 }
289 }
290}
291
292/// A global's bytes and the addresses stored in them.
293///
294/// # Examples
295///
296/// ```
297/// use backend_lang::Options;
298/// use ir_lang::{Global, Module};
299///
300/// let mut m = Module::new("m");
301/// let _ = m.declare_global("answer", Global::bytes(&[42, 0, 0, 0], 4))?;
302/// let c = backend_lang::compile(&m, &Options::sysv())?;
303/// let d = &c.data()[0];
304/// assert_eq!((d.name(), d.size(), d.align()), ("answer", 4, 4));
305/// assert_eq!(d.bytes(), Some(&[42u8, 0, 0, 0][..]));
306/// # Ok::<(), Box<dyn std::error::Error>>(())
307/// ```
308#[derive(Clone, Debug, PartialEq, Eq)]
309pub struct DataObject {
310 name: String,
311 size: u64,
312 align: u32,
313 mutable: bool,
314 exported: bool,
315 bytes: Option<Vec<u8>>,
316 relocs: Vec<Reloc>,
317}
318
319impl DataObject {
320 /// The global's symbol name.
321 ///
322 /// # Examples
323 ///
324 /// See [`DataObject`].
325 #[inline]
326 #[must_use]
327 pub fn name(&self) -> &str {
328 &self.name
329 }
330
331 /// Its size in bytes.
332 ///
333 /// # Examples
334 ///
335 /// See [`DataObject`].
336 #[inline]
337 #[must_use]
338 pub fn size(&self) -> u64 {
339 self.size
340 }
341
342 /// Its alignment in bytes.
343 ///
344 /// # Examples
345 ///
346 /// See [`DataObject`].
347 #[inline]
348 #[must_use]
349 pub fn align(&self) -> u32 {
350 self.align
351 }
352
353 /// Whether the program may write it (else it belongs in read-only data).
354 ///
355 /// # Examples
356 ///
357 /// See [`DataObject`].
358 #[inline]
359 #[must_use]
360 pub fn mutable(&self) -> bool {
361 self.mutable
362 }
363
364 /// Whether it is visible outside the module.
365 ///
366 /// # Examples
367 ///
368 /// See [`DataObject`].
369 #[inline]
370 #[must_use]
371 pub fn exported(&self) -> bool {
372 self.exported
373 }
374
375 /// Its initial bytes, or `None` for zero-filled data.
376 ///
377 /// # Examples
378 ///
379 /// See [`DataObject`].
380 #[inline]
381 #[must_use]
382 pub fn bytes(&self) -> Option<&[u8]> {
383 self.bytes.as_deref()
384 }
385
386 /// The addresses stored in it ([`RelocKind::Abs64`], offsets from its
387 /// start).
388 ///
389 /// # Examples
390 ///
391 /// See [`DataObject`].
392 #[inline]
393 #[must_use]
394 pub fn relocations(&self) -> &[Reloc] {
395 &self.relocs
396 }
397}
398
399/// A compiled module: machine code, every function's place and call-site
400/// records, the relocations against symbols defined elsewhere, and the
401/// module's data.
402///
403/// The code refers to globals and imports through relocations
404/// ([`relocations`](Compiled::relocations)); calls between the module's own
405/// functions are already resolved. A linker places the code and data and
406/// patches the relocations (object-lang writes them into an ELF relocatable
407/// as they are).
408///
409/// # Examples
410///
411/// ```
412/// use backend_lang::Options;
413/// use ir_lang::{Linkage, Module, Overflow, Signature, Type};
414///
415/// let mut m = Module::new("m");
416/// let f = m.declare_function("add", &Signature::new(&[Type::I64, Type::I64], &[Type::I64]), Linkage::Export)?;
417/// let mut b = m.build(f)?;
418/// let (x, y) = (b.param(0)?, b.param(1)?);
419/// let s = b.add(x, y, Overflow::Wrap)?;
420/// b.ret(&[s])?;
421///
422/// let c = backend_lang::compile(&m, &Options::sysv().with_frame_pointer(false))?;
423/// assert_eq!(c.function("add").unwrap().offset(), 0);
424/// assert!(c.relocations().is_empty());
425/// assert_eq!(c.code().last(), Some(&0xC3)); // ret
426/// # Ok::<(), Box<dyn std::error::Error>>(())
427/// ```
428#[derive(Clone, Debug, PartialEq, Eq)]
429pub struct Compiled {
430 program: Program,
431 data: Vec<DataObject>,
432 imports: Vec<String>,
433 exports: Vec<String>,
434}
435
436impl Compiled {
437 /// The machine code of every function, back to back.
438 ///
439 /// # Examples
440 ///
441 /// See [`Compiled`].
442 #[inline]
443 #[must_use]
444 pub fn code(&self) -> &[u8] {
445 self.program.bytes()
446 }
447
448 /// The emitted program (code, functions, relocations).
449 ///
450 /// # Examples
451 ///
452 /// See [`Compiled`].
453 #[inline]
454 #[must_use]
455 pub fn program(&self) -> &Program {
456 &self.program
457 }
458
459 /// Every function, in module order.
460 ///
461 /// # Examples
462 ///
463 /// See [`Compiled`].
464 #[inline]
465 #[must_use]
466 pub fn functions(&self) -> &[EmittedFunction] {
467 self.program.functions()
468 }
469
470 /// The function named `name`.
471 ///
472 /// # Examples
473 ///
474 /// See [`Compiled`].
475 #[must_use]
476 pub fn function(&self, name: &str) -> Option<&EmittedFunction> {
477 // The symbol table is sorted by name, and functions sit in emission
478 // order at increasing offsets: two binary searches, not a scan.
479 let offset = self.program.symbol(name)?;
480 let functions = self.program.functions();
481 let i = functions.partition_point(|f| f.offset() < offset);
482 functions.get(i).filter(|f| f.name() == name)
483 }
484
485 /// The code's relocations against globals, imports, and runtime entry
486 /// points.
487 ///
488 /// # Examples
489 ///
490 /// See [`Compiled`].
491 #[inline]
492 #[must_use]
493 pub fn relocations(&self) -> &[Reloc] {
494 self.program.relocations()
495 }
496
497 /// The module's globals (imported globals excluded).
498 ///
499 /// # Examples
500 ///
501 /// See [`DataObject`].
502 #[inline]
503 #[must_use]
504 pub fn data(&self) -> &[DataObject] {
505 &self.data
506 }
507
508 /// The symbols the code and data refer to that the module does not
509 /// define (imports and runtime entry points), in name order.
510 ///
511 /// # Examples
512 ///
513 /// ```
514 /// use backend_lang::Options;
515 /// use ir_lang::{Linkage, Module, Signature};
516 ///
517 /// let mut m = Module::new("m");
518 /// let f = m.declare_function("f", &Signature::new(&[], &[]), Linkage::Export)?;
519 /// let mut b = m.build(f)?;
520 /// b.trap(1)?;
521 /// let c = backend_lang::compile(&m, &Options::sysv())?;
522 /// assert_eq!(c.imports(), ["__lexersketch_trap"]);
523 /// # Ok::<(), Box<dyn std::error::Error>>(())
524 /// ```
525 #[inline]
526 #[must_use]
527 pub fn imports(&self) -> &[String] {
528 &self.imports
529 }
530
531 /// The functions and globals with `export` linkage, in name order.
532 ///
533 /// # Examples
534 ///
535 /// See [`Compiled::imports`].
536 #[inline]
537 #[must_use]
538 pub fn exports(&self) -> &[String] {
539 &self.exports
540 }
541}
542
543/// Compiles every function of `module` to x86-64 machine code: instruction
544/// selection, register allocation, and emission.
545///
546/// # Errors
547///
548/// [`CompileError`], naming the function that failed.
549///
550/// # Examples
551///
552/// See [`Compiled`].
553pub fn compile(module: &Module, options: &Options) -> Result<Compiled, CompileError> {
554 module.validate().map_err(CompileError::Invalid)?;
555 let mut defined: BTreeSet<String> = BTreeSet::new();
556 let mut exports: Vec<String> = Vec::new();
557 for func in module.functions() {
558 if let Some(d) = module.func_decl(func) {
559 if d.linkage() != Linkage::Import {
560 let _ = defined.insert(d.name().to_string());
561 if d.linkage() == Linkage::Export {
562 exports.push(d.name().to_string());
563 }
564 }
565 }
566 }
567 for g in module.globals() {
568 if let (Some(name), Some(data)) = (module.global_name(g), module.global(g)) {
569 if data.linkage != Linkage::Import {
570 let _ = defined.insert(name.to_string());
571 if data.linkage == Linkage::Export {
572 exports.push(name.to_string());
573 }
574 }
575 }
576 }
577 exports.sort();
578
579 let mut emitter = Emitter::new();
580 let emit_err = |function: Option<&str>, error: EmitError| CompileError::Emit {
581 function: function.map(str::to_string),
582 error,
583 };
584 // Everything not defined here is reached through a relocation:
585 // imports, globals (data lives apart from code), runtime entry points.
586 let mut externals: BTreeSet<String> = BTreeSet::new();
587 for func in module.functions() {
588 if let Some(d) = module.func_decl(func) {
589 if d.linkage() == Linkage::Import {
590 let _ = externals.insert(d.name().to_string());
591 }
592 }
593 }
594 for g in module.globals() {
595 if let Some(name) = module.global_name(g) {
596 let _ = externals.insert(name.to_string());
597 }
598 }
599 for rt in [
600 options.trap_symbol,
601 options.gc_poll_symbol,
602 options.resume_symbol,
603 ] {
604 if !defined.contains(rt) {
605 let _ = externals.insert(rt.to_string());
606 }
607 }
608 for name in &externals {
609 emitter
610 .declare_external(name)
611 .map_err(|e| emit_err(Some(name), e))?;
612 }
613
614 for func in module.functions() {
615 let Some(decl) = module.func_decl(func) else {
616 continue;
617 };
618 if decl.linkage() == Linkage::Import {
619 continue;
620 }
621 let name = decl.name();
622 let mir = compile_validated(module, func, options)?;
623 let _ = emitter
624 .function(&mir)
625 .map_err(|e| emit_err(Some(name), e))?;
626 }
627 let program = emitter.finish().map_err(|e| emit_err(None, e))?;
628
629 let mut data = Vec::new();
630 for g in module.globals() {
631 let (Some(name), Some(gd)) = (module.global_name(g), module.global(g)) else {
632 continue;
633 };
634 if gd.linkage == Linkage::Import {
635 continue;
636 }
637 let mut relocs = Vec::with_capacity(gd.relocs.len());
638 for r in &gd.relocs {
639 let target = match r.target {
640 SymbolRef::Func(f) => module.func_decl(f).map(|d| d.name().to_string()),
641 SymbolRef::Global(t) => module.global_name(t).map(str::to_string),
642 _ => None,
643 };
644 if let Some(t) = target {
645 relocs.push(Reloc::new(
646 r.offset as usize,
647 RelocKind::Abs64,
648 &t,
649 r.addend,
650 ));
651 }
652 }
653 data.push(DataObject {
654 name: name.to_string(),
655 size: gd.size,
656 align: gd.align,
657 mutable: gd.mutable,
658 exported: gd.linkage == Linkage::Export,
659 bytes: match &gd.init {
660 Init::Bytes(b) => Some(b.clone()),
661 _ => None,
662 },
663 relocs,
664 });
665 }
666
667 let mut imports: BTreeSet<String> = BTreeSet::new();
668 for r in program.relocations() {
669 if !defined.contains(&r.symbol) {
670 let _ = imports.insert(r.symbol.clone());
671 }
672 }
673 for d in &data {
674 for r in d.relocations() {
675 if !defined.contains(&r.symbol) {
676 let _ = imports.insert(r.symbol.clone());
677 }
678 }
679 }
680 Ok(Compiled {
681 program,
682 data,
683 imports: imports.into_iter().collect(),
684 exports,
685 })
686}
687
688/// Selects and allocates one function: the MIR on physical registers that
689/// [`emit`](crate::emit) or an [`Emitter`] encodes.
690///
691/// # Errors
692///
693/// [`CompileError`].
694///
695/// # Examples
696///
697/// ```
698/// use backend_lang::Options;
699/// use ir_lang::{Linkage, Module, Signature, Type};
700///
701/// let mut m = Module::new("m");
702/// let f = m.declare_function("seven", &Signature::new(&[], &[Type::I32]), Linkage::Export)?;
703/// let mut b = m.build(f)?;
704/// let k = b.iconst(Type::I32, 7)?;
705/// b.ret(&[k])?;
706/// let mir = backend_lang::compile_function(&m, f, &Options::sysv())?;
707/// assert!(mir.to_string().contains("rax"));
708/// # Ok::<(), Box<dyn std::error::Error>>(())
709/// ```
710pub fn compile_function(
711 module: &Module,
712 func: FuncId,
713 options: &Options,
714) -> Result<mir::Function, CompileError> {
715 module.validate().map_err(CompileError::Invalid)?;
716 compile_validated(module, func, options)
717}
718
719fn compile_validated(
720 module: &Module,
721 func: FuncId,
722 options: &Options,
723) -> Result<mir::Function, CompileError> {
724 let name = module
725 .func_decl(func)
726 .map_or_else(|| func.to_string(), |d| d.name().to_string());
727 let mut mir =
728 isel::select_validated(module, func, options).map_err(|error| CompileError::Select {
729 function: name.clone(),
730 error,
731 })?;
732 let _ = regalloc::allocate_with(&mut mir, &options.regalloc).map_err(|error| {
733 CompileError::Allocate {
734 function: name.clone(),
735 error,
736 }
737 })?;
738 Ok(mir)
739}