Skip to main content

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}