1use crate::target_support::os_uses_macho;
94use lamina_platform::TargetOperatingSystem;
95use std::{collections::HashMap, env, error::Error, fmt};
96
97#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
102pub enum RuntimeSymbol {
103 Malloc,
104 Free,
105 Exit,
106 Memcpy,
107 Memmove,
108 Memset,
109 Divti3,
111 Udivti3,
113 Modti3,
115 Umodti3,
117 Divdi3,
119 Udivdi3,
121 Moddi3,
123 Umoddi3,
125 Floatdidf,
127 Floatundidf,
129 Floatdisf,
131 Floatundisf,
133 Fixdfdi,
135 Fixunsdfdi,
137 Fixsfdi,
139 Fixunssfdi,
141 Floattidf,
143 Floatuntidf,
145 Fixdfti,
147 Fixunsdfti,
149 Floattisf,
151 Floatuntisf,
153 Fixsfti,
155 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 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 pub fn from_c_name(name: &str) -> Option<Self> {
231 Self::ALL.into_iter().find(|symbol| symbol.c_name() == name)
232 }
233
234 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
245pub 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 pub fn from_env(target_os: TargetOperatingSystem) -> Result<Self, BuiltinOverrideError> {
267 Ok(Self::new(target_os, builtin_overrides_from_env()?))
268 }
269
270 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 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 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#[derive(Debug, Clone, PartialEq, Eq)]
315#[non_exhaustive]
316pub enum BuiltinOverrideError {
317 MalformedEntry(String),
319 UnknownBuiltin(String),
321 EmptySide(String),
323 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
369pub 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
416pub trait Abi {
436 fn target_os(&self) -> TargetOperatingSystem;
438
439 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 fn call_stub(&self, name: &str) -> Option<String> {
458 self.builtin_library().resolve(name)
459 }
460}
461
462pub fn mangle_macos_name(name: &str) -> String {
467 if name == "main" {
468 "_main".to_string()
469 } else {
470 format!("_{name}")
471 }
472}
473
474pub 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
484pub 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
494pub 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
504pub 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}