Skip to main content

lamina_codegen/
abi.rs

1//! Common ABI trait and utilities for code generation backends.
2//!
3//! A trait that all ABI implementations must follow,
4//! reducing code duplication and giving a consistent interface.
5//!
6//! # Application Binary Interface (ABI) Overview
7//!
8//! Lamina uses platform C ABIs for function calls. The ABI implementation is
9//! abstracted through the `Abi` trait, which gives a consistent interface
10//! across all backends.
11//!
12//! ## Common ABI Contract
13//!
14//! ### Function Name Mangling
15//!
16//! All backends apply platform-specific symbol mangling:
17//! - **macOS**: Functions are prefixed with underscore (e.g., `main` → `_main`)
18//! - **Linux/Windows**: Functions use their original names (e.g., `main` → `main`)
19//!
20//! The `mangle_function_name()` method handles this automatically.
21//!
22//! ### Argument Passing
23//!
24//! - Arguments are passed using the platform C ABI register order
25//! - Overflow arguments (beyond register capacity) are spilled to the stack
26//! - Integer arguments use 64-bit slots, even for narrower integer types (i8, i16, i32)
27//! - Floating-point arguments follow platform-specific rules
28//!
29//! **Example: Function call with 3 arguments (RISC-V)**
30//! ```text
31//! # Arguments: a0=arg1, a1=arg2, a2=arg3
32//! mv a0, t0        # Load arg1 to a0
33//! mv a1, t1        # Load arg2 to a1
34//! mv a2, t2        # Load arg3 to a2
35//! call my_function # Call function
36//! ```
37//!
38//! **Example: Function call with 10 arguments (x86_64 System V)**
39//! ```text
40//! # First 6 args in registers: rdi, rsi, rdx, rcx, r8, r9
41//! # Remaining 4 args on stack (16-byte aligned)
42//! mov %rdi, %rax   # arg1
43//! mov %rsi, %rbx   # arg2
44//! # ... args 3-6 in rdx, rcx, r8, r9
45//! push %r10        # arg7 on stack
46//! push %r11        # arg8 on stack
47//! push %r12        # arg9 on stack
48//! push %r13        # arg10 on stack
49//! call my_function
50//! add $32, %rsp    # Clean up stack
51//! ```
52//!
53//! ### Return Values
54//!
55//! - Integer return values use the C ABI integer return register
56//! - Floating-point return values use the FP return register (if applicable)
57//! - Void functions return nothing
58//!
59//! **Example: Return value handling**
60//! ```text
61//! # RISC-V: return value in a0 (integer) or fa0 (float)
62//! call my_function
63//! mv t0, a0        # Save integer return value
64//!
65//! # x86_64: return value in rax (integer) or xmm0 (float)
66//! call my_function
67//! mov %rax, %rbx   # Save integer return value
68//! ```
69//!
70//! ### Builtin Functions
71//!
72//! Builtin functions can be overridden via the `LAMINA_BUILTINS` environment variable:
73//!
74//! ```bash
75//! LAMINA_BUILTINS="malloc=jemalloc_malloc,dealloc=jemalloc_free"
76//! ```
77//!
78//! Every runtime helper the compiler calls is a [`RuntimeSymbol`]. Keys are the
79//! C names (`malloc`, `free`, `exit`, `memcpy`, `memmove`, `memset`), plus the
80//! legacy alias `dealloc` (free).
81//! The compiler-rt routines for 128-bit integers (`__divti3`, `__floattidf`, ...)
82//! are keys too, so an embedder can supply them. Platform decoration (`_free`
83//! on macOS) is applied to the override as well.
84//!
85//! ## Backend-Specific ABIs
86//!
87//! See the documentation in each backend's ABI module:
88//! - `x86_64::abi` - System V AMD64 and Microsoft x64
89//! - `aarch64::abi` - AAPCS64
90//! - `riscv::abi` - RISC-V calling convention
91//! - `wasm::abi` - WebAssembly stack-based calling
92
93use crate::target_support::os_uses_macho;
94use lamina_platform::TargetOperatingSystem;
95use std::{collections::HashMap, env, error::Error, fmt};
96
97/// Runtime helpers the compiler calls by name.
98///
99/// Each variant names a C symbol. Emission sites use the variant, never a string
100/// literal, so `LAMINA_BUILTINS` can retarget every one of them.
101#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
102pub enum RuntimeSymbol {
103    Malloc,
104    Free,
105    Exit,
106    Memcpy,
107    Memmove,
108    Memset,
109    /// compiler-rt `__divti3`: 128-bit integer arithmetic and conversion, called by i128 lowering.
110    Divti3,
111    /// compiler-rt `__udivti3`: 128-bit integer arithmetic and conversion, called by i128 lowering.
112    Udivti3,
113    /// compiler-rt `__modti3`: 128-bit integer arithmetic and conversion, called by i128 lowering.
114    Modti3,
115    /// compiler-rt `__umodti3`: 128-bit integer arithmetic and conversion, called by i128 lowering.
116    Umodti3,
117    /// compiler-rt `__divdi3`: 64-bit signed division, called when a 32-bit target splits an i64.
118    Divdi3,
119    /// compiler-rt `__udivdi3`: 64-bit unsigned division, called when a 32-bit target splits an i64.
120    Udivdi3,
121    /// compiler-rt `__moddi3`: 64-bit signed remainder, called when a 32-bit target splits an i64.
122    Moddi3,
123    /// compiler-rt `__umoddi3`: 64-bit unsigned remainder, called when a 32-bit target splits an i64.
124    Umoddi3,
125    /// compiler-rt `__floatdidf`: 64-bit integer to double, called when a 32-bit target splits an i64.
126    Floatdidf,
127    /// compiler-rt `__floatundidf`: 64-bit unsigned to double, called when a 32-bit target splits an i64.
128    Floatundidf,
129    /// compiler-rt `__floatdisf`: 64-bit integer to float, called when a 32-bit target splits an i64.
130    Floatdisf,
131    /// compiler-rt `__floatundisf`: 64-bit unsigned to float, called when a 32-bit target splits an i64.
132    Floatundisf,
133    /// compiler-rt `__fixdfdi`: double to 64-bit integer, called when a 32-bit target splits an i64.
134    Fixdfdi,
135    /// compiler-rt `__fixunsdfdi`: double to 64-bit unsigned, called when a 32-bit target splits an i64.
136    Fixunsdfdi,
137    /// compiler-rt `__fixsfdi`: float to 64-bit integer, called when a 32-bit target splits an i64.
138    Fixsfdi,
139    /// compiler-rt `__fixunssfdi`: float to 64-bit unsigned, called when a 32-bit target splits an i64.
140    Fixunssfdi,
141    /// compiler-rt `__floattidf`: 128-bit integer arithmetic and conversion, called by i128 lowering.
142    Floattidf,
143    /// compiler-rt `__floatuntidf`: 128-bit integer arithmetic and conversion, called by i128 lowering.
144    Floatuntidf,
145    /// compiler-rt `__fixdfti`: 128-bit integer arithmetic and conversion, called by i128 lowering.
146    Fixdfti,
147    /// compiler-rt `__fixunsdfti`: 128-bit integer arithmetic and conversion, called by i128 lowering.
148    Fixunsdfti,
149    /// compiler-rt `__floattisf`: 128-bit integer arithmetic and conversion, called by i128 lowering.
150    Floattisf,
151    /// compiler-rt `__floatuntisf`: 128-bit integer arithmetic and conversion, called by i128 lowering.
152    Floatuntisf,
153    /// compiler-rt `__fixsfti`: 128-bit integer arithmetic and conversion, called by i128 lowering.
154    Fixsfti,
155    /// compiler-rt `__fixunssfti`: 128-bit integer arithmetic and conversion, called by i128 lowering.
156    Fixunssfti,
157}
158
159impl RuntimeSymbol {
160    pub const ALL: [RuntimeSymbol; 30] = [
161        RuntimeSymbol::Malloc,
162        RuntimeSymbol::Free,
163        RuntimeSymbol::Exit,
164        RuntimeSymbol::Memcpy,
165        RuntimeSymbol::Memmove,
166        RuntimeSymbol::Memset,
167        RuntimeSymbol::Divti3,
168        RuntimeSymbol::Udivti3,
169        RuntimeSymbol::Modti3,
170        RuntimeSymbol::Umodti3,
171        RuntimeSymbol::Divdi3,
172        RuntimeSymbol::Udivdi3,
173        RuntimeSymbol::Moddi3,
174        RuntimeSymbol::Umoddi3,
175        RuntimeSymbol::Floatdidf,
176        RuntimeSymbol::Floatundidf,
177        RuntimeSymbol::Floatdisf,
178        RuntimeSymbol::Floatundisf,
179        RuntimeSymbol::Fixdfdi,
180        RuntimeSymbol::Fixunsdfdi,
181        RuntimeSymbol::Fixsfdi,
182        RuntimeSymbol::Fixunssfdi,
183        RuntimeSymbol::Floattidf,
184        RuntimeSymbol::Floatuntidf,
185        RuntimeSymbol::Fixdfti,
186        RuntimeSymbol::Fixunsdfti,
187        RuntimeSymbol::Floattisf,
188        RuntimeSymbol::Floatuntisf,
189        RuntimeSymbol::Fixsfti,
190        RuntimeSymbol::Fixunssfti,
191    ];
192
193    /// The undecorated C symbol name.
194    pub const fn c_name(self) -> &'static str {
195        match self {
196            RuntimeSymbol::Malloc => "malloc",
197            RuntimeSymbol::Free => "free",
198            RuntimeSymbol::Exit => "exit",
199            RuntimeSymbol::Memcpy => "memcpy",
200            RuntimeSymbol::Memmove => "memmove",
201            RuntimeSymbol::Memset => "memset",
202            RuntimeSymbol::Divti3 => "__divti3",
203            RuntimeSymbol::Udivti3 => "__udivti3",
204            RuntimeSymbol::Modti3 => "__modti3",
205            RuntimeSymbol::Umodti3 => "__umodti3",
206            RuntimeSymbol::Divdi3 => "__divdi3",
207            RuntimeSymbol::Udivdi3 => "__udivdi3",
208            RuntimeSymbol::Moddi3 => "__moddi3",
209            RuntimeSymbol::Umoddi3 => "__umoddi3",
210            RuntimeSymbol::Floatdidf => "__floatdidf",
211            RuntimeSymbol::Floatundidf => "__floatundidf",
212            RuntimeSymbol::Floatdisf => "__floatdisf",
213            RuntimeSymbol::Floatundisf => "__floatundisf",
214            RuntimeSymbol::Fixdfdi => "__fixdfdi",
215            RuntimeSymbol::Fixunsdfdi => "__fixunsdfdi",
216            RuntimeSymbol::Fixsfdi => "__fixsfdi",
217            RuntimeSymbol::Fixunssfdi => "__fixunssfdi",
218            RuntimeSymbol::Floattidf => "__floattidf",
219            RuntimeSymbol::Floatuntidf => "__floatuntidf",
220            RuntimeSymbol::Fixdfti => "__fixdfti",
221            RuntimeSymbol::Fixunsdfti => "__fixunsdfti",
222            RuntimeSymbol::Floattisf => "__floattisf",
223            RuntimeSymbol::Floatuntisf => "__floatuntisf",
224            RuntimeSymbol::Fixsfti => "__fixsfti",
225            RuntimeSymbol::Fixunssfti => "__fixunssfti",
226        }
227    }
228
229    /// Looks up a symbol by its C name, as MIR call sites spell it.
230    pub fn from_c_name(name: &str) -> Option<Self> {
231        Self::ALL.into_iter().find(|symbol| symbol.c_name() == name)
232    }
233
234    /// Looks up a symbol by a `LAMINA_BUILTINS` key or builtin name.
235    ///
236    /// Accepts the C name plus the legacy key `dealloc` (free).
237    pub fn from_builtin_name(name: &str) -> Option<Self> {
238        match name {
239            "dealloc" => Some(RuntimeSymbol::Free),
240            _ => Self::from_c_name(name),
241        }
242    }
243}
244
245/// Resolves runtime helper names to the symbols the emitted code must reference.
246///
247/// Overrides name the C symbol. Platform decoration (the Mach-O leading
248/// underscore) is applied on top, the same way it is for every other C symbol.
249pub struct BuiltinLibrary {
250    target_os: TargetOperatingSystem,
251    overrides: HashMap<RuntimeSymbol, String>,
252}
253
254impl BuiltinLibrary {
255    pub fn new(
256        target_os: TargetOperatingSystem,
257        overrides: HashMap<RuntimeSymbol, String>,
258    ) -> Self {
259        Self {
260            target_os,
261            overrides,
262        }
263    }
264
265    /// The library `LAMINA_BUILTINS` describes, or why that value could not be read.
266    pub fn from_env(target_os: TargetOperatingSystem) -> Result<Self, BuiltinOverrideError> {
267        Ok(Self::new(target_os, builtin_overrides_from_env()?))
268    }
269
270    /// [`Self::from_env`] for callers with nowhere to put the error.
271    ///
272    /// A `LAMINA_BUILTINS` the compiler cannot read is refused by the codegen entry
273    /// point before any backend runs, so reaching this with an unreadable value means
274    /// the caller skipped that entry point; it then behaves as if nothing was set.
275    pub fn from_env_or_unset(target_os: TargetOperatingSystem) -> Self {
276        Self::new(target_os, builtin_overrides_from_env().unwrap_or_default())
277    }
278
279    pub fn has_overrides(&self) -> bool {
280        !self.overrides.is_empty()
281    }
282
283    /// The undecorated C name for a symbol. MIR call sites carry this name and the
284    /// backend's function mangling adds any platform prefix.
285    pub fn c_name(&self, symbol: RuntimeSymbol) -> &str {
286        self.overrides
287            .get(&symbol)
288            .map_or(symbol.c_name(), String::as_str)
289    }
290
291    /// The final symbol to reference from directly emitted code.
292    pub fn symbol(&self, symbol: RuntimeSymbol) -> String {
293        decorate_symbol(self.target_os, self.c_name(symbol))
294    }
295
296    pub fn resolve(&self, name: &str) -> Option<String> {
297        RuntimeSymbol::from_builtin_name(name).map(|symbol| self.symbol(symbol))
298    }
299}
300
301fn decorate_symbol(target_os: TargetOperatingSystem, name: &str) -> String {
302    if os_uses_macho(target_os) {
303        format!("_{name}")
304    } else {
305        name.to_string()
306    }
307}
308
309/// Why a `LAMINA_BUILTINS` value could not be read.
310///
311/// An override that cannot be understood is reported rather than dropped: a typo in
312/// the variable would otherwise leave the default symbol in the output, with nothing
313/// said about it.
314#[derive(Debug, Clone, PartialEq, Eq)]
315#[non_exhaustive]
316pub enum BuiltinOverrideError {
317    /// An entry with no `=`, so it names neither a builtin nor a replacement.
318    MalformedEntry(String),
319    /// An entry whose left side is not a runtime helper this compiler calls.
320    UnknownBuiltin(String),
321    /// An entry with an empty builtin name or an empty replacement symbol.
322    EmptySide(String),
323    /// Two entries naming the same builtin, with different replacements.
324    Conflict {
325        builtin: &'static str,
326        first: String,
327        second: String,
328    },
329}
330
331impl fmt::Display for BuiltinOverrideError {
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333        match self {
334            BuiltinOverrideError::MalformedEntry(entry) => {
335                write!(f, "LAMINA_BUILTINS entry `{entry}` is not `name=symbol`")
336            }
337            BuiltinOverrideError::UnknownBuiltin(name) => write!(
338                f,
339                "LAMINA_BUILTINS names `{name}`, which is not a runtime helper this compiler calls; the names are {}",
340                builtin_name_list()
341            ),
342            BuiltinOverrideError::EmptySide(entry) => {
343                write!(f, "LAMINA_BUILTINS entry `{entry}` leaves one side empty")
344            }
345            BuiltinOverrideError::Conflict {
346                builtin,
347                first,
348                second,
349            } => write!(
350                f,
351                "LAMINA_BUILTINS sets `{builtin}` twice, to `{first}` and to `{second}`"
352            ),
353        }
354    }
355}
356
357impl Error for BuiltinOverrideError {}
358
359fn builtin_name_list() -> String {
360    let mut names: Vec<&str> = RuntimeSymbol::ALL
361        .into_iter()
362        .map(RuntimeSymbol::c_name)
363        .collect();
364    names.push("dealloc");
365    names.sort_unstable();
366    names.join(", ")
367}
368
369/// Parses `name=symbol,name=symbol`.
370///
371/// Every entry must name a runtime helper and a replacement symbol. An entry that does
372/// not is an error, not a silent omission: it reaches the real codegen path, and a
373/// dropped override would change the emitted symbols without saying so.
374pub fn parse_builtin_overrides(
375    value: &str,
376) -> Result<HashMap<RuntimeSymbol, String>, BuiltinOverrideError> {
377    let mut overrides: HashMap<RuntimeSymbol, String> = HashMap::new();
378    for entry in value.split(',') {
379        if entry.trim().is_empty() {
380            continue;
381        }
382        let Some((name, symbol)) = entry.split_once('=') else {
383            return Err(BuiltinOverrideError::MalformedEntry(
384                entry.trim().to_owned(),
385            ));
386        };
387        let name = name.trim();
388        let symbol = symbol.trim();
389        if name.is_empty() || symbol.is_empty() {
390            return Err(BuiltinOverrideError::EmptySide(entry.trim().to_owned()));
391        }
392        let Some(runtime) = RuntimeSymbol::from_builtin_name(name) else {
393            return Err(BuiltinOverrideError::UnknownBuiltin(name.to_owned()));
394        };
395        if let Some(first) = overrides.get(&runtime)
396            && first != symbol
397        {
398            return Err(BuiltinOverrideError::Conflict {
399                builtin: runtime.c_name(),
400                first: first.clone(),
401                second: symbol.to_owned(),
402            });
403        }
404        overrides.insert(runtime, symbol.to_owned());
405    }
406    Ok(overrides)
407}
408
409fn builtin_overrides_from_env() -> Result<HashMap<RuntimeSymbol, String>, BuiltinOverrideError> {
410    match env::var("LAMINA_BUILTINS") {
411        Ok(value) => parse_builtin_overrides(&value),
412        Err(_) => Ok(HashMap::new()),
413    }
414}
415
416/// Trait defining the common interface for all ABI implementations.
417///
418/// This contract covers name mangling, builtin symbol mapping, and the
419/// minimal calling convention expectations shared by MIR backends.
420///
421/// ## Calling Convention Assumptions
422/// - Arguments are passed using the platform C ABI register order, with
423///   overflow arguments spilled to the stack.
424/// - Integer arguments use 64-bit slots, even for narrower integer types.
425/// - Return values use the C ABI integer return register when present.
426///
427/// ## Builtins
428/// - Builtins are resolved by `call_stub`, which can be overridden with
429///   the `LAMINA_BUILTINS` environment variable.
430/// - The builtin map recognizes every [`RuntimeSymbol`].
431///
432/// ## Naming
433/// - Backends must apply platform-specific symbol mangling, such as
434///   the underscore prefix on macOS.
435pub trait Abi {
436    /// Returns the target operating system for this ABI instance.
437    fn target_os(&self) -> TargetOperatingSystem;
438
439    /// Returns the mangled function name with platform-specific prefix.
440    ///
441    /// This method handles platform-specific symbol naming conventions,
442    /// such as the underscore prefix on macOS.
443    fn mangle_function_name(&self, name: &str) -> String;
444
445    fn builtin_library(&self) -> BuiltinLibrary {
446        BuiltinLibrary::from_env_or_unset(self.target_os())
447    }
448
449    /// Maps well-known intrinsic/runtime names to platform symbol stubs.
450    ///
451    /// Returns `None` if the name is not a known intrinsic that needs
452    /// special handling. This lets backends map internal names
453    /// (like "dealloc") to platform-specific symbols (like "_free" on macOS).
454    ///
455    /// Default implementation returns `None` for all names, meaning
456    /// no special mapping is needed.
457    fn call_stub(&self, name: &str) -> Option<String> {
458        self.builtin_library().resolve(name)
459    }
460}
461
462/// Helper function to mangle function names for macOS (adds underscore prefix).
463///
464/// This is a common pattern used by multiple backends, so we provide
465/// it as a shared utility function.
466pub fn mangle_macos_name(name: &str) -> String {
467    if name == "main" {
468        "_main".to_string()
469    } else {
470        format!("_{name}")
471    }
472}
473
474/// Helper function to get the platform-specific printf symbol name.
475///
476/// Returns "_printf" on macOS, "printf" on other platforms.
477pub fn get_printf_symbol(target_os: TargetOperatingSystem) -> &'static str {
478    match target_os {
479        os if os_uses_macho(os) => "_printf",
480        _ => "printf",
481    }
482}
483
484/// Helper function to get the platform-specific malloc symbol name.
485///
486/// Returns "_malloc" on macOS, "malloc" on other platforms.
487pub fn get_malloc_symbol(target_os: TargetOperatingSystem) -> &'static str {
488    match target_os {
489        os if os_uses_macho(os) => "_malloc",
490        _ => "malloc",
491    }
492}
493
494/// Helper function to get the platform-specific free symbol name.
495///
496/// Returns "_free" on macOS, "free" on other platforms.
497pub fn get_free_symbol(target_os: TargetOperatingSystem) -> &'static str {
498    match target_os {
499        os if os_uses_macho(os) => "_free",
500        _ => "free",
501    }
502}
503
504/// Helper function to create a common call_stub implementation.
505///
506/// Resolves a builtin name through the environment-configured [`BuiltinLibrary`].
507/// Backends can use this to reduce duplication in their `call_stub` implementations.
508pub fn common_call_stub(name: &str, target_os: TargetOperatingSystem) -> Option<String> {
509    BuiltinLibrary::from_env_or_unset(target_os).resolve(name)
510}
511
512#[cfg(test)]
513mod tests {
514    use super::*;
515
516    #[test]
517    fn test_mangle_macos_name() {
518        assert_eq!(mangle_macos_name("main"), "_main");
519        assert_eq!(mangle_macos_name("foo"), "_foo");
520        assert_eq!(mangle_macos_name("my_function"), "_my_function");
521    }
522
523    #[test]
524    fn test_get_printf_symbol() {
525        assert_eq!(get_printf_symbol(TargetOperatingSystem::MacOS), "_printf");
526        assert_eq!(get_printf_symbol(TargetOperatingSystem::IOS), "_printf");
527        assert_eq!(
528            get_printf_symbol(TargetOperatingSystem::VisionOS),
529            "_printf"
530        );
531        assert_eq!(get_printf_symbol(TargetOperatingSystem::Linux), "printf");
532        assert_eq!(get_printf_symbol(TargetOperatingSystem::Windows), "printf");
533    }
534
535    #[test]
536    fn test_get_malloc_symbol() {
537        assert_eq!(get_malloc_symbol(TargetOperatingSystem::MacOS), "_malloc");
538        assert_eq!(get_malloc_symbol(TargetOperatingSystem::IOS), "_malloc");
539        assert_eq!(get_malloc_symbol(TargetOperatingSystem::Linux), "malloc");
540        assert_eq!(get_malloc_symbol(TargetOperatingSystem::Windows), "malloc");
541    }
542
543    #[test]
544    fn test_get_free_symbol() {
545        assert_eq!(get_free_symbol(TargetOperatingSystem::MacOS), "_free");
546        assert_eq!(get_free_symbol(TargetOperatingSystem::TvOS), "_free");
547        assert_eq!(get_free_symbol(TargetOperatingSystem::Linux), "free");
548        assert_eq!(get_free_symbol(TargetOperatingSystem::Windows), "free");
549    }
550
551    #[test]
552    fn test_builtin_library_default_symbols() {
553        let lib_macos = BuiltinLibrary::from_env_or_unset(TargetOperatingSystem::MacOS);
554        assert_eq!(lib_macos.resolve("malloc"), Some("_malloc".to_string()));
555        assert_eq!(lib_macos.resolve("dealloc"), Some("_free".to_string()));
556
557        let lib_linux = BuiltinLibrary::from_env_or_unset(TargetOperatingSystem::Linux);
558        assert_eq!(lib_linux.resolve("malloc"), Some("malloc".to_string()));
559        assert_eq!(lib_linux.resolve("dealloc"), Some("free".to_string()));
560    }
561
562    #[test]
563    fn test_builtin_library_unknown_symbol() {
564        let lib = BuiltinLibrary::from_env_or_unset(TargetOperatingSystem::Linux);
565        assert_eq!(lib.resolve("unknown_function"), None);
566    }
567
568    #[test]
569    fn test_common_call_stub() {
570        assert_eq!(
571            common_call_stub("malloc", TargetOperatingSystem::MacOS),
572            Some("_malloc".to_string())
573        );
574        assert_eq!(
575            common_call_stub("dealloc", TargetOperatingSystem::Linux),
576            Some("free".to_string())
577        );
578        assert_eq!(
579            common_call_stub("unknown", TargetOperatingSystem::Linux),
580            None
581        );
582    }
583}
584
585#[cfg(test)]
586mod runtime_symbol_tests {
587    use super::*;
588
589    fn all_overridden(os: TargetOperatingSystem) -> BuiltinLibrary {
590        let overrides = RuntimeSymbol::ALL
591            .into_iter()
592            .map(|symbol| (symbol, format!("k_{}", symbol.c_name())))
593            .collect();
594        BuiltinLibrary::new(os, overrides)
595    }
596
597    #[test]
598    fn default_symbols_keep_c_names_and_platform_decoration() {
599        let linux = BuiltinLibrary::new(TargetOperatingSystem::Linux, HashMap::new());
600        let macos = BuiltinLibrary::new(TargetOperatingSystem::MacOS, HashMap::new());
601        assert!(!linux.has_overrides());
602        for symbol in RuntimeSymbol::ALL {
603            assert_eq!(linux.c_name(symbol), symbol.c_name());
604            assert_eq!(linux.symbol(symbol), symbol.c_name());
605            assert_eq!(macos.c_name(symbol), symbol.c_name());
606            assert_eq!(macos.symbol(symbol), format!("_{}", symbol.c_name()));
607        }
608    }
609
610    #[test]
611    fn overrides_replace_every_symbol() {
612        for os in [TargetOperatingSystem::Linux, TargetOperatingSystem::MacOS] {
613            let lib = all_overridden(os);
614            assert!(lib.has_overrides());
615            for symbol in RuntimeSymbol::ALL {
616                let expected_c = format!("k_{}", symbol.c_name());
617                assert_eq!(lib.c_name(symbol), expected_c);
618                assert_eq!(
619                    lib.resolve(symbol.c_name()),
620                    Some(decorate_symbol(os, &expected_c))
621                );
622            }
623        }
624    }
625
626    #[test]
627    fn resolve_maps_legacy_builtin_keys() {
628        let lib = BuiltinLibrary::new(
629            TargetOperatingSystem::Linux,
630            parse_builtin_overrides("dealloc=k_free").expect("a known builtin parses"),
631        );
632        assert_eq!(lib.resolve("dealloc"), Some("k_free".to_string()));
633        assert_eq!(lib.resolve("free"), Some("k_free".to_string()));
634        assert_eq!(lib.resolve("write"), None);
635    }
636
637    #[test]
638    fn parse_builtin_overrides_accepts_padded_known_entries() {
639        let parsed =
640            parse_builtin_overrides(" malloc = k_alloc ,exit=k_exit").expect("both are builtins");
641        assert_eq!(parsed.len(), 2);
642        assert_eq!(
643            parsed.get(&RuntimeSymbol::Malloc).map(String::as_str),
644            Some("k_alloc")
645        );
646        assert_eq!(
647            parsed.get(&RuntimeSymbol::Exit).map(String::as_str),
648            Some("k_exit")
649        );
650    }
651
652    #[test]
653    fn parse_builtin_overrides_refuses_what_it_cannot_apply() {
654        assert_eq!(
655            parse_builtin_overrides("malloc=k_alloc,bogus=x"),
656            Err(BuiltinOverrideError::UnknownBuiltin("bogus".to_owned()))
657        );
658        assert_eq!(
659            parse_builtin_overrides("memset="),
660            Err(BuiltinOverrideError::EmptySide("memset=".to_owned()))
661        );
662        assert_eq!(
663            parse_builtin_overrides("=y"),
664            Err(BuiltinOverrideError::EmptySide("=y".to_owned()))
665        );
666        assert_eq!(
667            parse_builtin_overrides("malloc"),
668            Err(BuiltinOverrideError::MalformedEntry("malloc".to_owned()))
669        );
670        assert_eq!(
671            parse_builtin_overrides("free=a,dealloc=b"),
672            Err(BuiltinOverrideError::Conflict {
673                builtin: "free",
674                first: "a".to_owned(),
675                second: "b".to_owned(),
676            })
677        );
678        assert!(parse_builtin_overrides("free=a,dealloc=a").is_ok());
679        assert!(parse_builtin_overrides("").is_ok());
680    }
681
682    #[test]
683    fn unknown_builtin_message_lists_the_names() {
684        let message = BuiltinOverrideError::UnknownBuiltin("bogus".to_owned()).to_string();
685        assert!(message.contains("bogus"), "{message}");
686        assert!(message.contains("malloc"), "{message}");
687        assert!(message.contains("__divti3"), "{message}");
688    }
689
690    #[test]
691    fn from_c_name_round_trips_every_symbol() {
692        for symbol in RuntimeSymbol::ALL {
693            assert_eq!(RuntimeSymbol::from_c_name(symbol.c_name()), Some(symbol));
694            assert_eq!(
695                RuntimeSymbol::from_builtin_name(symbol.c_name()),
696                Some(symbol)
697            );
698        }
699        assert_eq!(RuntimeSymbol::from_c_name("print"), None);
700    }
701}