1#![warn(missing_docs)]
27
28use camino::{Utf8Path, Utf8PathBuf};
29use parking_lot::RwLock;
30use rustc_hash::FxHashSet;
31use serde::{Deserialize, Serialize};
32use std::sync::Arc;
33
34#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
38pub enum Profile {
39 #[default]
41 Default,
42 Server,
44 Numeric,
46 Edge,
48 Realtime,
50 Embedded,
53}
54
55impl Profile {
56 #[must_use]
58 pub const fn is_strict_by_default(self) -> bool {
59 matches!(self, Self::Numeric | Self::Edge | Self::Embedded)
60 }
61
62 #[must_use]
64 pub const fn has_fusion_guarantees(self) -> bool {
65 matches!(self, Self::Numeric)
66 }
67
68 #[must_use]
73 pub const fn requires_escape_analysis(self) -> bool {
74 matches!(self, Self::Embedded)
75 }
76
77 #[must_use]
79 pub const fn is_gc_free(self) -> bool {
80 matches!(self, Self::Embedded)
81 }
82}
83
84#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
86pub enum OptLevel {
87 None,
89 #[default]
91 Less,
92 Default,
94 Aggressive,
96 Size,
98 SizeMin,
100}
101
102#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
104pub enum DebugInfo {
105 #[default]
107 None,
108 LineTablesOnly,
110 Full,
112}
113
114#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
116pub enum OutputType {
117 #[default]
119 Object,
120 StaticLib,
122 DynamicLib,
124 Executable,
126 LlvmIr,
128 LlvmBitcode,
130 Assembly,
132 Wasm,
134}
135
136#[derive(Clone, Debug, Serialize, Deserialize)]
138pub struct Options {
139 pub profile: Profile,
141 pub opt_level: OptLevel,
143 pub debug_info: DebugInfo,
145 pub output_type: OutputType,
147 pub target_triple: Option<String>,
149 pub output_path: Option<Utf8PathBuf>,
151 pub import_paths: Vec<Utf8PathBuf>,
153 pub library_paths: Vec<Utf8PathBuf>,
155 pub libraries: Vec<String>,
157 pub warn_all: bool,
159 pub deny_warnings: bool,
161 pub emit_kernel_report: bool,
163 pub dump_ir: IrDumpOptions,
165 pub stdlib_path: Option<Utf8PathBuf>,
168 pub hackage_packages: Vec<String>,
171}
172
173impl Default for Options {
174 fn default() -> Self {
175 Self {
176 profile: Profile::Default,
177 opt_level: OptLevel::Default,
178 debug_info: DebugInfo::None,
179 output_type: OutputType::Object,
180 target_triple: None,
181 output_path: None,
182 import_paths: Vec::new(),
183 library_paths: Vec::new(),
184 libraries: Vec::new(),
185 warn_all: false,
186 deny_warnings: false,
187 emit_kernel_report: false,
188 dump_ir: IrDumpOptions::default(),
189 stdlib_path: None,
190 hackage_packages: Vec::new(),
191 }
192 }
193}
194
195#[derive(Clone, Debug, Default, Serialize, Deserialize)]
197pub struct IrDumpOptions {
198 pub dump_ast: bool,
200 pub dump_core: bool,
202 pub dump_core_passes: Vec<String>,
204 pub dump_tensor_ir: bool,
206 pub dump_loop_ir: bool,
208 pub dump_llvm: bool,
210}
211
212#[derive(Clone, Debug, Default)]
214pub struct SearchPaths {
215 pub module_paths: Vec<Utf8PathBuf>,
217 pub interface_paths: Vec<Utf8PathBuf>,
219 pub library_paths: Vec<Utf8PathBuf>,
221}
222
223impl SearchPaths {
224 #[must_use]
226 pub fn new() -> Self {
227 Self::default()
228 }
229
230 pub fn add_module_path(&mut self, path: impl Into<Utf8PathBuf>) {
232 self.module_paths.push(path.into());
233 }
234
235 pub fn add_interface_path(&mut self, path: impl Into<Utf8PathBuf>) {
237 self.interface_paths.push(path.into());
238 }
239
240 pub fn add_library_path(&mut self, path: impl Into<Utf8PathBuf>) {
242 self.library_paths.push(path.into());
243 }
244}
245
246#[derive(Debug, thiserror::Error)]
248pub enum SessionError {
249 #[error("configuration file not found: {0}")]
251 ConfigNotFound(Utf8PathBuf),
252 #[error("invalid configuration: {0}")]
254 InvalidConfig(String),
255 #[error("unsupported target: {0}")]
257 UnsupportedTarget(String),
258 #[error("IO error: {0}")]
260 Io(#[from] std::io::Error),
261}
262
263pub struct Session {
268 pub options: Options,
270 pub search_paths: SearchPaths,
272 loaded_modules: RwLock<FxHashSet<String>>,
274 working_dir: Utf8PathBuf,
276}
277
278impl Session {
279 pub fn new(options: Options) -> Result<Self, SessionError> {
285 let working_dir = std::env::current_dir()
286 .map_err(SessionError::Io)?
287 .try_into()
288 .map_err(|e| SessionError::InvalidConfig(format!("invalid working dir: {e}")))?;
289
290 Ok(Self {
291 options,
292 search_paths: SearchPaths::default(),
293 loaded_modules: RwLock::new(FxHashSet::default()),
294 working_dir,
295 })
296 }
297
298 pub fn with_defaults() -> Result<Self, SessionError> {
304 Self::new(Options::default())
305 }
306
307 #[must_use]
309 pub fn working_dir(&self) -> &Utf8Path {
310 &self.working_dir
311 }
312
313 #[must_use]
315 pub fn profile(&self) -> Profile {
316 self.options.profile
317 }
318
319 #[must_use]
321 pub fn is_module_loaded(&self, name: &str) -> bool {
322 self.loaded_modules.read().contains(name)
323 }
324
325 pub fn mark_module_loaded(&self, name: String) {
327 self.loaded_modules.write().insert(name);
328 }
329
330 #[must_use]
332 pub fn output_path(&self, input_name: &str) -> Utf8PathBuf {
333 if let Some(ref path) = self.options.output_path {
334 path.clone()
335 } else {
336 let stem = Utf8Path::new(input_name).file_stem().unwrap_or(input_name);
337 let ext = match self.options.output_type {
338 OutputType::Object => "o",
339 OutputType::StaticLib => "a",
340 OutputType::DynamicLib => {
341 if cfg!(target_os = "macos") {
342 "dylib"
343 } else if cfg!(target_os = "windows") {
344 "dll"
345 } else {
346 "so"
347 }
348 }
349 OutputType::Executable => "",
350 OutputType::LlvmIr => "ll",
351 OutputType::LlvmBitcode => "bc",
352 OutputType::Assembly => "s",
353 OutputType::Wasm => "wasm",
354 };
355 if ext.is_empty() {
356 Utf8PathBuf::from(stem)
357 } else {
358 Utf8PathBuf::from(format!("{stem}.{ext}"))
359 }
360 }
361 }
362}
363
364pub type SessionRef = Arc<Session>;
366
367#[must_use]
369pub fn create_session(options: Options) -> Result<SessionRef, SessionError> {
370 Ok(Arc::new(Session::new(options)?))
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376
377 #[test]
378 fn test_profile_properties() {
379 assert!(!Profile::Default.is_strict_by_default());
380 assert!(!Profile::Server.is_strict_by_default());
381 assert!(Profile::Numeric.is_strict_by_default());
382 assert!(Profile::Edge.is_strict_by_default());
383
384 assert!(Profile::Numeric.has_fusion_guarantees());
385 assert!(!Profile::Default.has_fusion_guarantees());
386 }
387
388 #[test]
389 fn test_session_module_tracking() {
390 let session = Session::with_defaults().unwrap();
391 assert!(!session.is_module_loaded("Data.List"));
392 session.mark_module_loaded("Data.List".to_string());
393 assert!(session.is_module_loaded("Data.List"));
394 }
395}