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;