1use crate::data::Data;
11pub use crate::gc::GcState;
12pub use crate::gc::GcStats;
13pub use crate::gc::PoolStats;
14use crate::instr::Instr;
15use crate::vm::MapPool;
16use crate::vm::ObjectPool;
17use crate::vm::StringPool;
18#[cfg(not(target_arch = "wasm32"))]
19use libloading::Library;
20use serde::Deserialize;
21use serde::Serialize;
22use smol_strc::SmolStr;
23use std::cell::Cell;
24use std::cell::RefCell;
25use std::hint::unreachable_unchecked;
26use std::path::Path;
27use std::path::PathBuf;
28#[cfg(not(target_arch = "wasm32"))]
29use std::rc::Rc;
30
31#[cfg(not(target_arch = "wasm32"))]
32use libffi::middle::Type;
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
36pub struct Span {
37 pub start: u32,
38 pub end: u32,
39}
40
41impl Span {
42 #[inline(always)]
43 #[must_use]
44 pub const fn extend(self, span: Self) -> Self {
45 Self {
46 start: self.start,
47 end: span.end,
48 }
49 }
50}
51
52impl From<std::range::Range<usize>> for Span {
53 #[inline(always)]
54 fn from(value: std::range::Range<usize>) -> Self {
55 Self {
56 start: value.start as u32,
57 end: value.end as u32,
58 }
59 }
60}
61
62impl From<std::ops::Range<usize>> for Span {
63 #[inline(always)]
64 fn from(value: std::ops::Range<usize>) -> Self {
65 Self {
66 start: value.start as u32,
67 end: value.end as u32,
68 }
69 }
70}
71
72impl From<Span> for std::ops::Range<usize> {
73 #[inline(always)]
74 fn from(val: Span) -> Self {
75 val.start as usize..val.end as usize
76 }
77}
78
79impl From<(usize, usize)> for Span {
80 #[inline(always)]
81 fn from((start, end): (usize, usize)) -> Self {
82 Self {
83 start: start as u32,
84 end: end as u32,
85 }
86 }
87}
88
89impl From<(u32, u32)> for Span {
90 #[inline(always)]
91 fn from((start, end): (u32, u32)) -> Self {
92 Self { start, end }
93 }
94}
95
96#[allow(clippy::unsafe_derive_deserialize)]
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub enum DataType {
104 Array(Option<Box<Self>>),
106 Float,
107 Int,
108 Bool,
109 String,
110 Null,
111 Unknown,
112 Union(Box<[Self]>),
113 Fn(u16),
114 FnValue(Box<FnValue>),
119 Struct(u16),
120 Enum(u16),
121 Map(Box<(Option<Self>, Option<Self>)>),
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct FnValue {
127 pub sig: Option<(Box<[DataType]>, DataType)>,
131 pub candidates: Box<[u16]>,
136 pub set: Option<u32>,
143}
144
145impl FnValue {
146 #[must_use]
149 pub fn inferred(candidates: Box<[u16]>) -> Self {
150 Self {
151 sig: None,
152 candidates,
153 set: None,
154 }
155 }
156
157 #[must_use]
159 pub fn from_set(set: u32) -> Self {
160 Self {
161 sig: None,
162 candidates: Box::from([]),
163 set: Some(set),
164 }
165 }
166}
167
168impl std::fmt::Display for FnValue {
169 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170 let Some((args, return_type)) = &self.sig else {
171 return write!(f, "function");
172 };
173 write!(f, "fn(")?;
174 for (i, arg) in args.iter().enumerate() {
175 if i > 0 {
176 write!(f, ", ")?;
177 }
178 write!(f, "{arg}")?;
179 }
180 write!(f, ")")?;
181 if !matches!(return_type, DataType::Null) {
182 write!(f, " -> {return_type}")?;
183 }
184 Ok(())
185 }
186}
187
188impl std::fmt::Display for DataType {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 match self {
191 Self::Float => write!(f, "float"),
192 Self::Int => write!(f, "int"),
193 Self::Bool => write!(f, "bool"),
194 Self::String => write!(f, "string"),
195 Self::Array(array_type) => match array_type {
196 Some(array_type) => write!(f, "{array_type}[]"),
197 None => write!(f, "Unknown[]"),
198 },
199 Self::Null => write!(f, "null"),
200 Self::Unknown => write!(f, "Unknown"),
201 Self::Union(types) => write!(
202 f,
203 "{}",
204 types
205 .into_iter()
206 .map(|x| format!("{x}"))
207 .collect::<Vec<_>>()
208 .join("|")
209 ),
210 Self::Struct(_) => write!(f, "struct"),
211 Self::Enum(_) => write!(f, "enum"),
212 Self::Map(m) => write!(
213 f,
214 "{{{}: {}}}",
215 m.0.as_ref().unwrap_or(&Self::Unknown),
216 m.1.as_ref().unwrap_or(&Self::Unknown)
217 ),
218 Self::Fn(_) => write!(f, "function"),
219 Self::FnValue(sig) => write!(f, "{sig}"),
220 }
221 }
222}
223
224impl DataType {
225 #[cfg(not(target_arch = "wasm32"))]
226 #[must_use]
227 pub fn to_c_type(&self, structs: &[Struct]) -> Type {
228 match self {
229 Self::Int => libffi::middle::Type::i64(),
230 Self::Float => libffi::middle::Type::f64(),
231 Self::String | Self::Array(_) => libffi::middle::Type::pointer(),
232 Self::Null => libffi::middle::Type::void(),
233 Self::Struct(id) => libffi::middle::Type::structure(
234 structs[*id as usize]
235 .fields
236 .iter()
237 .map(|(_, field_type, _)| field_type.to_c_type(structs)),
238 ),
239 other => {
243 panic!("type {other} has no C representation and cannot cross the FFI boundary")
244 }
245 }
246 }
247}
248
249#[derive(Debug)]
250pub struct ErrorCatch {
251 pub catch_loc: u32,
252 pub error_reg: u16,
253 pub call_frames_len: u32,
254 pub args_len: u32,
255 pub recursion_len: u32,
259}
260
261#[derive(Debug, Clone)]
265pub struct HostFnSig {
266 pub types: Box<[DataType]>,
268 pub namespace: SmolStr,
271 pub name: SmolStr,
272 pub variadic: bool,
276}
277
278impl HostFnSig {
279 #[inline(always)]
280 #[must_use]
281 pub fn get_return_type(&self) -> &DataType {
282 unsafe { self.types.get_unchecked(0) }
283 }
284 #[inline(always)]
285 #[must_use]
286 pub fn get_arg(&self, idx: usize) -> &DataType {
287 unsafe { self.types.get_unchecked(1 + idx) }
288 }
289 #[inline(always)]
290 #[must_use]
291 pub fn arg_count(&self) -> usize {
292 self.types.len() - 1
293 }
294}
295
296#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
304pub enum LibraryOrigin {
305 Program,
307 StandardLibrary,
310}
311
312#[derive(Debug)]
313#[allow(clippy::pub_underscore_fields)]
314pub struct DynamicLibFn {
315 pub types: Box<[DataType]>,
317 pub library: SmolStr,
324 pub origin: LibraryOrigin,
326 pub symbol: SmolStr,
329 #[cfg(not(target_arch = "wasm32"))]
333 pub _lib: Rc<Library>,
334 #[cfg(not(target_arch = "wasm32"))]
335 pub ptr: libffi::middle::CodePtr,
336 #[cfg(not(target_arch = "wasm32"))]
337 pub cif: libffi::middle::Cif,
338 #[cfg(target_arch = "wasm32")]
341 pub intrinsic: crate::intrinsics::Intrinsic,
342}
343
344#[derive(Clone, Copy, Debug, PartialEq, Eq)]
350pub enum TargetOs {
351 Linux,
352 Macos,
353 Windows,
354}
355
356impl TargetOs {
357 pub const CURRENT: Self = if cfg!(target_os = "macos") {
360 Self::Macos
361 } else if cfg!(target_os = "windows") {
362 Self::Windows
363 } else {
364 Self::Linux
365 };
366
367 #[must_use]
369 pub const fn dynamic_lib_extension(self) -> &'static str {
370 match self {
371 Self::Linux => "so",
372 Self::Macos => "dylib",
373 Self::Windows => "dll",
374 }
375 }
376
377 #[must_use]
380 pub const fn dynamic_lib_prefix(self) -> &'static str {
381 match self {
382 Self::Linux | Self::Macos => "lib",
383 Self::Windows => "",
384 }
385 }
386}
387
388#[must_use]
402pub fn resolve_library_filename(spec: &str, os: TargetOs) -> String {
403 let has_separator = spec.contains('/') || spec.contains('\\');
404 let has_extension = Path::new(spec).extension().is_some();
405 if has_extension {
406 spec.to_owned()
407 } else if has_separator {
408 format!("{spec}.{}", os.dynamic_lib_extension())
409 } else {
410 format!(
411 "{}{spec}.{}",
412 os.dynamic_lib_prefix(),
413 os.dynamic_lib_extension()
414 )
415 }
416}
417
418thread_local! {
419 static DYLIB_DIRS: RefCell<Vec<PathBuf>> = const { RefCell::new(Vec::new()) };
420 static ARGV_SKIP: Cell<usize> = const { Cell::new(DEFAULT_ARGV_SKIP) };
421}
422
423pub fn set_dylib_dirs(dirs: Vec<PathBuf>) -> Vec<PathBuf> {
440 DYLIB_DIRS.replace(dirs)
441}
442
443#[must_use]
445pub fn dylib_dirs() -> Vec<PathBuf> {
446 DYLIB_DIRS.with(|dirs| dirs.borrow().clone())
447}
448
449const DEFAULT_ARGV_SKIP: usize = 2;
455
456pub fn set_argv_skip(count: usize) -> usize {
464 ARGV_SKIP.replace(count)
465}
466
467#[must_use]
469pub fn argv_skip() -> usize {
470 ARGV_SKIP.get()
471}
472
473#[must_use]
481pub fn std_lib_dir() -> Option<PathBuf> {
482 if let Some(base) = std::env::var_os("CANDELA_LIB_PATH") {
483 return Some(PathBuf::from(base));
484 }
485 std::env::current_exe().ok().map(|exe| {
486 exe.canonicalize()
487 .unwrap_or(exe)
488 .parent()
489 .unwrap_or_else(|| Path::new("."))
490 .join("libs")
491 })
492}
493
494#[cfg(not(target_arch = "wasm32"))]
501pub(crate) fn open_library(
502 filename: &str,
503 origin: LibraryOrigin,
504) -> Result<Library, libloading::Error> {
505 for dir in dylib_dirs() {
506 if let Ok(lib) = unsafe { Library::new(dir.join(filename)) } {
507 return Ok(lib);
508 }
509 }
510 if matches!(origin, LibraryOrigin::StandardLibrary)
511 && let Some(dir) = std_lib_dir()
512 && let Ok(lib) = unsafe { Library::new(dir.join(filename)) }
513 {
514 return Ok(lib);
515 }
516 unsafe { Library::new(filename) }
517}
518
519#[cfg(test)]
520mod dynlib_tests {
521 use super::TargetOs;
522 use super::resolve_library_filename;
523
524 #[test]
525 fn logical_name_maps_to_each_os_convention() {
526 assert_eq!(resolve_library_filename("m", TargetOs::Linux), "libm.so");
527 assert_eq!(resolve_library_filename("m", TargetOs::Macos), "libm.dylib");
528 assert_eq!(resolve_library_filename("m", TargetOs::Windows), "m.dll");
529
530 assert_eq!(
531 resolve_library_filename("sqlite3", TargetOs::Linux),
532 "libsqlite3.so"
533 );
534 assert_eq!(
535 resolve_library_filename("sqlite3", TargetOs::Macos),
536 "libsqlite3.dylib"
537 );
538 assert_eq!(
539 resolve_library_filename("sqlite3", TargetOs::Windows),
540 "sqlite3.dll"
541 );
542 }
543
544 #[test]
545 fn explicit_extension_is_honored_on_every_os() {
546 for os in [TargetOs::Linux, TargetOs::Macos, TargetOs::Windows] {
547 assert_eq!(resolve_library_filename("libfoo.so.6", os), "libfoo.so.6");
548 assert_eq!(
549 resolve_library_filename("/usr/lib/libz.so", os),
550 "/usr/lib/libz.so"
551 );
552 assert_eq!(resolve_library_filename("plugin.dll", os), "plugin.dll");
553 }
554 }
555
556 #[test]
557 fn path_without_extension_gets_platform_extension_and_no_prefix() {
558 assert_eq!(
559 resolve_library_filename("../std_src/math/math", TargetOs::Linux),
560 "../std_src/math/math.so"
561 );
562 assert_eq!(
563 resolve_library_filename("../std_src/math/math", TargetOs::Macos),
564 "../std_src/math/math.dylib"
565 );
566 assert_eq!(
567 resolve_library_filename("./plugins/foo", TargetOs::Windows),
568 "./plugins/foo.dll"
569 );
570 }
571}
572
573impl DynamicLibFn {
574 #[inline(always)]
575 #[must_use]
576 pub fn get_return_type(&self) -> &DataType {
577 unsafe { self.types.get_unchecked(0) }
578 }
579}
580
581#[derive(Debug)]
582pub struct Struct {
583 pub name: SmolStr,
584 pub fields: Box<[(SmolStr, DataType, Span)]>,
585 pub id: u16,
586 pub name_span: Span,
587}
588
589#[derive(Debug)]
593pub struct EnumType {
594 pub name: SmolStr,
595 pub variants: Box<[EnumVariant]>,
596 pub id: u16,
597 pub name_span: Span,
598}
599
600#[derive(Debug)]
601pub struct EnumVariant {
602 pub name: SmolStr,
603 pub payload: Box<[DataType]>,
605 pub name_span: Span,
606}
607
608pub struct Pools {
609 pub objs: ObjectPool,
610 pub maps: MapPool,
611 pub strings: StringPool,
612 pub gc: GcState,
613}
614
615impl Pools {
616 pub fn new(objs: ObjectPool, maps: MapPool, strings: StringPool) -> Self {
617 Self {
618 objs,
619 maps,
620 strings,
621 gc: GcState::default(),
622 }
623 }
624
625 pub fn collect(&mut self, registers: &[Data], budget: u32) -> bool {
629 self.gc.collect(
630 &mut self.objs,
631 &mut self.maps,
632 &mut self.strings,
633 registers,
634 budget,
635 )
636 }
637
638 #[must_use]
640 pub fn gc_stats(&self) -> GcStats {
641 self.gc.stats(&self.objs, &self.maps, &self.strings)
642 }
643}
644
645pub struct Source {
646 pub filename: SmolStr,
647 pub contents: String,
648}
649
650#[derive(Copy, Clone)]
651pub struct InstrSrc {
652 pub instr: Instr,
653 pub span: Span,
654 pub file_id: u16,
655}
656
657impl DataType {
658 #[inline(always)]
659 #[must_use]
660 pub const fn is_indexable(&self) -> bool {
661 matches!(self, Self::String | Self::Array(_) | Self::Unknown)
662 }
663
664 #[must_use]
667 pub fn check_poly(self) -> Self {
668 if let Self::Union(ref elems) = self {
669 if let Some(new) = reduce_null_struct(elems) {
670 return new;
671 }
672 let mut concrete = elems
673 .iter()
674 .filter(|elem_type| **elem_type != Self::Unknown);
675 if let Some(first_type) = concrete.next() {
676 if concrete.all(|x| x == first_type) {
677 first_type.clone()
678 } else {
679 self
680 }
681 } else if !elems.is_empty() {
682 Self::Unknown
683 } else {
684 unsafe { unreachable_unchecked() }
685 }
686 } else {
687 unsafe { unreachable_unchecked() }
688 }
689 }
690}
691
692fn reduce_null_struct(types: &[DataType]) -> Option<DataType> {
693 let mut struct_type = None;
694 for t in types {
695 match t {
696 DataType::Null | DataType::Unknown => {}
697 DataType::Struct(_) => {
698 if let Some(struct_type) = &struct_type {
699 if struct_type != t {
700 return None;
701 }
702 } else {
703 struct_type = Some(t.clone());
704 }
705 }
706 _ => return None,
707 }
708 }
709 struct_type
710}
711
712impl PartialEq for DataType {
713 fn eq(&self, other: &Self) -> bool {
714 match (self, other) {
715 (Self::Float, Self::Float)
717 | (Self::Int, Self::Int)
718 | (Self::Bool, Self::Bool)
719 | (Self::String, Self::String)
720 | (Self::Null, Self::Null)
721 | (Self::Unknown, Self::Unknown)
722 | (Self::Array(_), Self::Array(None))
723 | (Self::Array(None), Self::Array(_)) => true,
724 (Self::Array(Some(a)), Self::Array(Some(b))) => a == b,
725 (Self::Union(a), Self::Union(b)) => a == b,
726 (Self::Struct(a), Self::Struct(b)) => a == b,
727 (Self::Enum(a), Self::Enum(b)) => a == b,
728 (Self::Fn(_), Self::Fn(_)) => true,
729 (Self::FnValue(a), Self::FnValue(b)) => match (&a.sig, &b.sig) {
730 (Some(a), Some(b)) => a.0 == b.0 && a.1 == b.1,
731 _ => true,
732 },
733 (Self::Map(a), Self::Map(b)) => {
734 (a.0.is_none() || b.0.is_none() || a.0 == b.0)
735 && (a.1.is_none() || b.1.is_none() || a.1 == b.1)
736 }
737 (t, Self::Union(p)) | (Self::Union(p), t) => p.contains(t),
738 _ => false,
739 }
740 }
741}
742
743impl std::hash::Hash for DataType {
744 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
745 match self {
747 Self::Array(_) => 0u8.hash(state),
748 Self::Float => 1u8.hash(state),
749 Self::Int => 2u8.hash(state),
750 Self::Bool => 3u8.hash(state),
751 Self::String => 4u8.hash(state),
752 Self::Null => 6u8.hash(state),
753 Self::Unknown => 7u8.hash(state),
754 Self::Union(p) => {
755 8u8.hash(state);
756 p.hash(state);
757 }
758 Self::Fn(f) => {
759 9u8.hash(state);
760 f.hash(state);
761 }
762 Self::FnValue(_) => 13u8.hash(state),
765 Self::Struct(s) => {
766 10u8.hash(state);
767 s.hash(state);
768 }
769 Self::Enum(e) => {
770 12u8.hash(state);
771 e.hash(state);
772 }
773 Self::Map(m) => {
774 11u8.hash(state);
775 m.hash(state);
776 }
777 }
778 }
779}