#![warn(missing_docs)]
use camino::{Utf8Path, Utf8PathBuf};
use parking_lot::RwLock;
use rustc_hash::FxHashSet;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Profile {
#[default]
Default,
Server,
Numeric,
Edge,
Realtime,
Embedded,
}
impl Profile {
#[must_use]
pub const fn is_strict_by_default(self) -> bool {
matches!(self, Self::Numeric | Self::Edge | Self::Embedded)
}
#[must_use]
pub const fn has_fusion_guarantees(self) -> bool {
matches!(self, Self::Numeric)
}
#[must_use]
pub const fn requires_escape_analysis(self) -> bool {
matches!(self, Self::Embedded)
}
#[must_use]
pub const fn is_gc_free(self) -> bool {
matches!(self, Self::Embedded)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum OptLevel {
None,
#[default]
Less,
Default,
Aggressive,
Size,
SizeMin,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum DebugInfo {
#[default]
None,
LineTablesOnly,
Full,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum OutputType {
#[default]
Object,
StaticLib,
DynamicLib,
Executable,
LlvmIr,
LlvmBitcode,
Assembly,
Wasm,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Options {
pub profile: Profile,
pub opt_level: OptLevel,
pub debug_info: DebugInfo,
pub output_type: OutputType,
pub target_triple: Option<String>,
pub output_path: Option<Utf8PathBuf>,
pub import_paths: Vec<Utf8PathBuf>,
pub library_paths: Vec<Utf8PathBuf>,
pub libraries: Vec<String>,
pub warn_all: bool,
pub deny_warnings: bool,
pub emit_kernel_report: bool,
pub tensor_fusion: bool,
pub dump_ir: IrDumpOptions,
pub stdlib_path: Option<Utf8PathBuf>,
pub hackage_packages: Vec<String>,
pub compile_only: bool,
pub output_object_dir: Option<Utf8PathBuf>,
pub output_interface_dir: Option<Utf8PathBuf>,
pub package_dbs: Vec<Utf8PathBuf>,
pub package_ids: Vec<String>,
pub extensions: Vec<String>,
pub cpp_defines: Vec<String>,
}
impl Default for Options {
fn default() -> Self {
Self {
profile: Profile::Default,
opt_level: OptLevel::Default,
debug_info: DebugInfo::None,
output_type: OutputType::Object,
target_triple: None,
output_path: None,
import_paths: Vec::new(),
library_paths: Vec::new(),
libraries: Vec::new(),
warn_all: false,
deny_warnings: false,
emit_kernel_report: false,
tensor_fusion: false,
dump_ir: IrDumpOptions::default(),
stdlib_path: None,
hackage_packages: Vec::new(),
compile_only: false,
output_object_dir: None,
output_interface_dir: None,
package_dbs: Vec::new(),
package_ids: Vec::new(),
extensions: Vec::new(),
cpp_defines: Vec::new(),
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct IrDumpOptions {
pub dump_ast: bool,
pub dump_core: bool,
pub dump_core_passes: Vec<String>,
pub dump_tensor_ir: bool,
pub dump_loop_ir: bool,
pub dump_llvm: bool,
}
#[derive(Clone, Debug, Default)]
pub struct SearchPaths {
pub module_paths: Vec<Utf8PathBuf>,
pub interface_paths: Vec<Utf8PathBuf>,
pub library_paths: Vec<Utf8PathBuf>,
}
impl SearchPaths {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_module_path(&mut self, path: impl Into<Utf8PathBuf>) {
self.module_paths.push(path.into());
}
pub fn add_interface_path(&mut self, path: impl Into<Utf8PathBuf>) {
self.interface_paths.push(path.into());
}
pub fn add_library_path(&mut self, path: impl Into<Utf8PathBuf>) {
self.library_paths.push(path.into());
}
}
#[derive(Debug, thiserror::Error)]
pub enum SessionError {
#[error("configuration file not found: {0}")]
ConfigNotFound(Utf8PathBuf),
#[error("invalid configuration: {0}")]
InvalidConfig(String),
#[error("unsupported target: {0}")]
UnsupportedTarget(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
pub struct Session {
pub options: Options,
pub search_paths: SearchPaths,
loaded_modules: RwLock<FxHashSet<String>>,
working_dir: Utf8PathBuf,
}
impl Session {
pub fn new(options: Options) -> Result<Self, SessionError> {
let working_dir = std::env::current_dir()
.map_err(SessionError::Io)?
.try_into()
.map_err(|e| SessionError::InvalidConfig(format!("invalid working dir: {e}")))?;
Ok(Self {
options,
search_paths: SearchPaths::default(),
loaded_modules: RwLock::new(FxHashSet::default()),
working_dir,
})
}
pub fn with_defaults() -> Result<Self, SessionError> {
Self::new(Options::default())
}
#[must_use]
pub fn working_dir(&self) -> &Utf8Path {
&self.working_dir
}
#[must_use]
pub fn profile(&self) -> Profile {
self.options.profile
}
#[must_use]
pub fn is_module_loaded(&self, name: &str) -> bool {
self.loaded_modules.read().contains(name)
}
pub fn mark_module_loaded(&self, name: String) {
self.loaded_modules.write().insert(name);
}
#[must_use]
pub fn output_path(&self, input_name: &str) -> Utf8PathBuf {
if let Some(ref path) = self.options.output_path {
path.clone()
} else {
let stem = Utf8Path::new(input_name).file_stem().unwrap_or(input_name);
let ext = match self.options.output_type {
OutputType::Object => "o",
OutputType::StaticLib => "a",
OutputType::DynamicLib => {
if cfg!(target_os = "macos") {
"dylib"
} else if cfg!(target_os = "windows") {
"dll"
} else {
"so"
}
}
OutputType::Executable => "",
OutputType::LlvmIr => "ll",
OutputType::LlvmBitcode => "bc",
OutputType::Assembly => "s",
OutputType::Wasm => "wasm",
};
if ext.is_empty() {
Utf8PathBuf::from(stem)
} else {
Utf8PathBuf::from(format!("{stem}.{ext}"))
}
}
}
}
pub type SessionRef = Arc<Session>;
pub fn create_session(options: Options) -> Result<SessionRef, SessionError> {
Ok(Arc::new(Session::new(options)?))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_profile_properties() {
assert!(!Profile::Default.is_strict_by_default());
assert!(!Profile::Server.is_strict_by_default());
assert!(Profile::Numeric.is_strict_by_default());
assert!(Profile::Edge.is_strict_by_default());
assert!(Profile::Numeric.has_fusion_guarantees());
assert!(!Profile::Default.has_fusion_guarantees());
}
#[test]
fn test_session_module_tracking() {
let session = Session::with_defaults().unwrap();
assert!(!session.is_module_loaded("Data.List"));
session.mark_module_loaded("Data.List".to_string());
assert!(session.is_module_loaded("Data.List"));
}
}