Skip to main content

lamina_codegen/aarch64/
abi.rs

1//! AArch64 ABI utilities for symbol naming and calling conventions.
2
3use crate::abi::{Abi, common_call_stub, mangle_macos_name};
4use crate::target_support::os_uses_macho;
5use lamina_platform::TargetOperatingSystem;
6
7/// Platform-specific ABI utilities for AArch64 code generation.
8pub struct AArch64ABI {
9    target_os: TargetOperatingSystem,
10}
11
12impl AArch64ABI {
13    /// Creates a new ABI instance for the specified target OS.
14    pub fn new(target_os: TargetOperatingSystem) -> Self {
15        Self { target_os }
16    }
17
18    /// Returns the mangled function name with platform-specific prefix.
19    pub fn mangle_function_name(&self, name: &str) -> String {
20        match self.target_os {
21            os if os_uses_macho(os) => mangle_macos_name(name),
22            _ => name.to_string(),
23        }
24    }
25
26    /// Returns the global declaration directive for a function.
27    pub fn get_global_directive(&self, func_name: &str) -> Option<String> {
28        match self.target_os {
29            os if os_uses_macho(os) => Some(format!(".globl _{func_name}")),
30            _ => Some(format!(".globl {func_name}")),
31        }
32    }
33
34    /// Maps well-known intrinsic/runtime names to platform symbol stubs.
35    pub fn call_stub(&self, name: &str) -> Option<String> {
36        common_call_stub(name, self.target_os)
37    }
38
39    /// AArch64 AAPCS64 calling convention argument registers (first 8 arguments).
40    pub const ARG_REGISTERS: &'static [&'static str] =
41        &["x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7"];
42
43    /// Caller-saved registers that must be preserved by the caller if live across function calls.
44    pub const CALLER_SAVED_REGISTERS: &'static [&'static str] = &[
45        "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13",
46        "x14", "x15", "x16", "x17",
47    ];
48
49    /// Callee-saved registers that are preserved by called functions.
50    pub const CALLEE_SAVED_REGISTERS: &'static [&'static str] = &[
51        "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "x29", "x30",
52    ];
53
54    /// Returns the target operating system for this ABI instance.
55    pub fn target_os(&self) -> TargetOperatingSystem {
56        self.target_os
57    }
58}
59
60impl Abi for AArch64ABI {
61    fn target_os(&self) -> TargetOperatingSystem {
62        self.target_os
63    }
64
65    fn mangle_function_name(&self, name: &str) -> String {
66        AArch64ABI::mangle_function_name(self, name)
67    }
68
69    fn call_stub(&self, name: &str) -> Option<String> {
70        AArch64ABI::call_stub(self, name)
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_aarch64_mangle_function_name() {
80        let abi_linux = AArch64ABI::new(TargetOperatingSystem::Linux);
81        assert_eq!(abi_linux.mangle_function_name("main"), "main");
82        assert_eq!(abi_linux.mangle_function_name("foo"), "foo");
83        assert_eq!(abi_linux.mangle_function_name("my_func"), "my_func");
84
85        let abi_macos = AArch64ABI::new(TargetOperatingSystem::MacOS);
86        assert_eq!(abi_macos.mangle_function_name("main"), "_main");
87        assert_eq!(abi_macos.mangle_function_name("foo"), "_foo");
88        assert_eq!(abi_macos.mangle_function_name("my_func"), "_my_func");
89    }
90
91    #[test]
92    fn test_aarch64_call_stub() {
93        let abi_linux = AArch64ABI::new(TargetOperatingSystem::Linux);
94        assert_eq!(abi_linux.call_stub("malloc"), Some("malloc".to_string()));
95        assert_eq!(abi_linux.call_stub("dealloc"), Some("free".to_string()));
96        assert_eq!(abi_linux.call_stub("unknown"), None);
97
98        let abi_macos = AArch64ABI::new(TargetOperatingSystem::MacOS);
99        assert_eq!(abi_macos.call_stub("malloc"), Some("_malloc".to_string()));
100    }
101
102    #[test]
103    fn test_aarch64_arg_registers() {
104        assert_eq!(AArch64ABI::ARG_REGISTERS.len(), 8);
105        assert_eq!(AArch64ABI::ARG_REGISTERS[0], "x0");
106        assert_eq!(AArch64ABI::ARG_REGISTERS[7], "x7");
107    }
108
109    #[test]
110    fn test_aarch64_caller_saved_registers() {
111        assert_eq!(AArch64ABI::CALLER_SAVED_REGISTERS.len(), 18);
112        assert!(AArch64ABI::CALLER_SAVED_REGISTERS.contains(&"x0"));
113        assert!(AArch64ABI::CALLER_SAVED_REGISTERS.contains(&"x15"));
114    }
115
116    #[test]
117    fn test_aarch64_callee_saved_registers() {
118        assert_eq!(AArch64ABI::CALLEE_SAVED_REGISTERS.len(), 12);
119        assert!(AArch64ABI::CALLEE_SAVED_REGISTERS.contains(&"x19"));
120        assert!(AArch64ABI::CALLEE_SAVED_REGISTERS.contains(&"x30"));
121    }
122
123    #[test]
124    fn test_aarch64_get_global_directive() {
125        let abi_linux = AArch64ABI::new(TargetOperatingSystem::Linux);
126        assert_eq!(
127            abi_linux.get_global_directive("main"),
128            Some(".globl main".to_string())
129        );
130        assert_eq!(
131            abi_linux.get_global_directive("foo"),
132            Some(".globl foo".to_string())
133        );
134
135        let abi_macos = AArch64ABI::new(TargetOperatingSystem::MacOS);
136        assert_eq!(
137            abi_macos.get_global_directive("main"),
138            Some(".globl _main".to_string())
139        );
140        assert_eq!(
141            abi_macos.get_global_directive("foo"),
142            Some(".globl _foo".to_string())
143        );
144
145        let abi_ios = AArch64ABI::new(TargetOperatingSystem::IOS);
146        assert_eq!(abi_ios.mangle_function_name("main"), "_main");
147        assert_eq!(
148            abi_ios.get_global_directive("main"),
149            Some(".globl _main".to_string())
150        );
151    }
152}