Skip to main content

seqc/
config.rs

1//! Compiler configuration for extensibility
2//!
3//! This module provides configuration types that allow external projects
4//! to extend the Seq compiler with additional builtins without modifying
5//! the core compiler.
6//!
7//! # Example
8//!
9//! ```rust,ignore
10//! use seqc::{CompilerConfig, ExternalBuiltin};
11//!
12//! // Define builtins provided by your runtime extension
13//! let config = CompilerConfig::new()
14//!     .with_builtin(ExternalBuiltin::new(
15//!         "journal-append",
16//!         "my_runtime_journal_append",
17//!     ))
18//!     .with_builtin(ExternalBuiltin::new(
19//!         "actor-send",
20//!         "my_runtime_actor_send",
21//!     ));
22//!
23//! // Compile with extended builtins
24//! compile_file_with_config(source_path, output_path, false, &config)?;
25//! ```
26
27use crate::types::Effect;
28use std::path::PathBuf;
29
30/// Optimization level for clang compilation
31#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
32pub enum OptimizationLevel {
33    /// No optimization (fastest compile, for script mode)
34    O0,
35    /// Basic optimizations
36    O1,
37    /// Moderate optimizations
38    O2,
39    /// Aggressive optimizations (default for production builds)
40    #[default]
41    O3,
42}
43
44/// Definition of an external builtin function
45///
46/// External builtins are functions provided by a runtime extension
47/// (like an actor system) that should be callable from Seq code.
48///
49/// # Type Safety (v2.0)
50///
51/// All external builtins **must** specify their stack effect for type checking.
52/// The compiler will error if an external builtin is registered without an effect.
53///
54/// Use [`ExternalBuiltin::with_effect`] to create builtins with explicit effects.
55#[derive(Debug, Clone)]
56pub struct ExternalBuiltin {
57    /// The name used in Seq code (e.g., "journal-append")
58    pub seq_name: String,
59
60    /// The symbol name for linking (e.g., "seq_actors_journal_append")
61    ///
62    /// Must contain only alphanumeric characters, underscores, and periods.
63    /// This is validated at construction time to prevent LLVM IR injection.
64    pub symbol: String,
65
66    /// Stack effect for type checking (required as of v2.0).
67    ///
68    /// The type checker enforces this signature at all call sites.
69    /// The compiler will error if this is `None`.
70    pub effect: Option<Effect>,
71}
72
73impl ExternalBuiltin {
74    /// Validate that a symbol name is safe for LLVM IR
75    ///
76    /// Valid symbols contain only: alphanumeric characters, underscores, and periods.
77    /// This prevents injection of arbitrary LLVM IR directives.
78    fn validate_symbol(symbol: &str) -> Result<(), String> {
79        if symbol.is_empty() {
80            return Err("Symbol name cannot be empty".to_string());
81        }
82        for c in symbol.chars() {
83            if !c.is_alphanumeric() && c != '_' && c != '.' {
84                return Err(format!(
85                    "Invalid character '{}' in symbol '{}'. \
86                     Symbols may only contain alphanumeric characters, underscores, and periods.",
87                    c, symbol
88                ));
89            }
90        }
91        Ok(())
92    }
93
94    /// Create a new external builtin with just name and symbol (deprecated)
95    ///
96    /// # Deprecated
97    ///
98    /// As of v2.0, all external builtins must have explicit stack effects.
99    /// Use [`ExternalBuiltin::with_effect`] instead. Builtins created with
100    /// this method will cause a compiler error.
101    ///
102    /// # Panics
103    ///
104    /// Panics if the symbol contains invalid characters for LLVM IR.
105    /// Valid symbols contain only alphanumeric characters, underscores, and periods.
106    #[deprecated(
107        since = "2.0.0",
108        note = "Use with_effect instead - effects are now required"
109    )]
110    pub fn new(seq_name: impl Into<String>, symbol: impl Into<String>) -> Self {
111        let symbol = symbol.into();
112        Self::validate_symbol(&symbol).expect("Invalid symbol name");
113        ExternalBuiltin {
114            seq_name: seq_name.into(),
115            symbol,
116            effect: None,
117        }
118    }
119
120    /// Create a new external builtin with a stack effect
121    ///
122    /// # Panics
123    ///
124    /// Panics if the symbol contains invalid characters for LLVM IR.
125    pub fn with_effect(
126        seq_name: impl Into<String>,
127        symbol: impl Into<String>,
128        effect: Effect,
129    ) -> Self {
130        let symbol = symbol.into();
131        Self::validate_symbol(&symbol).expect("Invalid symbol name");
132        ExternalBuiltin {
133            seq_name: seq_name.into(),
134            symbol,
135            effect: Some(effect),
136        }
137    }
138}
139
140/// Configuration for the Seq compiler
141///
142/// Allows external projects to extend the compiler with additional
143/// builtins and configuration options.
144#[derive(Debug, Clone)]
145pub struct CompilerConfig {
146    /// External builtins to include in compilation
147    pub external_builtins: Vec<ExternalBuiltin>,
148
149    /// Additional library paths for linking
150    pub library_paths: Vec<String>,
151
152    /// Additional libraries to link
153    pub libraries: Vec<String>,
154
155    /// External FFI manifest paths to load
156    ///
157    /// These manifests are loaded in addition to any `include ffi:*` statements
158    /// in the source code. Use this to provide custom FFI bindings without
159    /// embedding them in the compiler.
160    pub ffi_manifest_paths: Vec<PathBuf>,
161
162    /// Pure inline test mode: bypass scheduler, return top of stack as exit code.
163    /// Only supports inline operations (integers, arithmetic, stack ops).
164    /// Used for testing and benchmarking pure computation without FFI overhead.
165    pub pure_inline_test: bool,
166
167    /// Optimization level for clang compilation
168    pub optimization_level: OptimizationLevel,
169
170    /// Bake per-word atomic call counters into the binary.
171    /// When true, each word entry point gets an `atomicrmw add` counter.
172    /// Use with `SEQ_REPORT=words` to see call counts at exit.
173    pub instrument: bool,
174
175    /// Lower self-tail-recursive words to native LLVM loops instead of
176    /// `musttail` calls. See `docs/design/LOOP_LOWERING.md`. Opt-in.
177    pub loop_opt: bool,
178
179    /// Iterations between cooperative yields inside a lowered loop.
180    /// Must be a power of two (used as an AND mask). Default 1024.
181    pub loop_yield_cadence: u32,
182}
183
184impl Default for CompilerConfig {
185    fn default() -> Self {
186        CompilerConfig {
187            external_builtins: Vec::new(),
188            library_paths: Vec::new(),
189            libraries: Vec::new(),
190            ffi_manifest_paths: Vec::new(),
191            pure_inline_test: false,
192            optimization_level: OptimizationLevel::default(),
193            instrument: false,
194            loop_opt: false,
195            loop_yield_cadence: 1024,
196        }
197    }
198}
199
200impl CompilerConfig {
201    /// Create a new empty configuration
202    pub fn new() -> Self {
203        CompilerConfig::default()
204    }
205
206    /// Add an external builtin (builder pattern)
207    pub fn with_builtin(mut self, builtin: ExternalBuiltin) -> Self {
208        self.external_builtins.push(builtin);
209        self
210    }
211
212    /// Add multiple external builtins
213    pub fn with_builtins(mut self, builtins: impl IntoIterator<Item = ExternalBuiltin>) -> Self {
214        self.external_builtins.extend(builtins);
215        self
216    }
217
218    /// Add a library path for linking
219    pub fn with_library_path(mut self, path: impl Into<String>) -> Self {
220        self.library_paths.push(path.into());
221        self
222    }
223
224    /// Add a library to link
225    pub fn with_library(mut self, lib: impl Into<String>) -> Self {
226        self.libraries.push(lib.into());
227        self
228    }
229
230    /// Add an external FFI manifest path
231    ///
232    /// The manifest will be loaded and its functions made available
233    /// during compilation, in addition to any `include ffi:*` statements.
234    pub fn with_ffi_manifest(mut self, path: impl Into<PathBuf>) -> Self {
235        self.ffi_manifest_paths.push(path.into());
236        self
237    }
238
239    /// Add multiple external FFI manifest paths
240    pub fn with_ffi_manifests(mut self, paths: impl IntoIterator<Item = PathBuf>) -> Self {
241        self.ffi_manifest_paths.extend(paths);
242        self
243    }
244
245    /// Set the optimization level for compilation
246    pub fn with_optimization_level(mut self, level: OptimizationLevel) -> Self {
247        self.optimization_level = level;
248        self
249    }
250
251    /// Enable loop lowering for self-tail-recursive words.
252    pub fn with_loop_opt(mut self) -> Self {
253        self.loop_opt = true;
254        self
255    }
256
257    /// Set the loop-opt yield cadence. Must be a power of two greater than 0.
258    ///
259    /// # Panics
260    ///
261    /// Panics if `cadence` is not a power of two.
262    pub fn with_loop_yield_cadence(mut self, cadence: u32) -> Self {
263        assert!(
264            cadence > 0 && cadence.is_power_of_two(),
265            "loop yield cadence must be a power of two, got {cadence}"
266        );
267        self.loop_yield_cadence = cadence;
268        self
269    }
270
271    /// Get seq names of all external builtins (for AST validation)
272    pub fn external_names(&self) -> Vec<&str> {
273        self.external_builtins
274            .iter()
275            .map(|b| b.seq_name.as_str())
276            .collect()
277    }
278}
279
280#[cfg(test)]
281mod tests;