Skip to main content

xlog_core/
config.rs

1//! Configuration types for XLOG runtime
2
3/// GPU memory budget configuration.
4///
5/// Use [`MemoryBudget::default()`] or the builder methods ([`MemoryBudget::from_device_memory`],
6/// [`MemoryBudget::with_limit`], [`MemoryBudget::with_ooc`]) to construct.
7#[derive(Debug, Clone)]
8#[non_exhaustive]
9pub struct MemoryBudget {
10    /// Maximum device memory to use in bytes
11    pub device_bytes: u64,
12    /// Allow out-of-core execution (spill to host)
13    pub allow_ooc: bool,
14    /// Abort on memory budget exceeded (vs try to continue)
15    pub abort_on_exceed: bool,
16}
17
18impl Default for MemoryBudget {
19    fn default() -> Self {
20        Self {
21            device_bytes: 0, // Will be set from device query
22            allow_ooc: false,
23            abort_on_exceed: true,
24        }
25    }
26}
27
28impl MemoryBudget {
29    /// Create a budget using 80% of available device memory
30    pub fn from_device_memory(total_bytes: u64) -> Self {
31        Self {
32            device_bytes: (total_bytes as f64 * 0.8) as u64,
33            allow_ooc: false,
34            abort_on_exceed: true,
35        }
36    }
37
38    /// Create a budget with explicit byte limit
39    pub fn with_limit(device_bytes: u64) -> Self {
40        Self {
41            device_bytes,
42            allow_ooc: false,
43            abort_on_exceed: true,
44        }
45    }
46
47    /// Enable out-of-core mode
48    pub fn with_ooc(mut self) -> Self {
49        self.allow_ooc = true;
50        self
51    }
52}
53
54/// Runtime configuration for XLOG execution.
55///
56/// Use [`RuntimeConfig::default()`] and the builder methods to construct.
57#[derive(Debug, Clone)]
58#[non_exhaustive]
59pub struct RuntimeConfig {
60    /// Memory budget settings
61    pub memory: MemoryBudget,
62    /// Use deterministic execution (may be slower)
63    pub deterministic: bool,
64    /// Enable profiling (row counts, memory tracking)
65    pub profile: bool,
66    /// Maximum fixpoint iterations before abort
67    pub max_iterations: u32,
68    /// Opt-in: enforce the strict deterministic-Datalog D2H gate during
69    /// `Executor::execute_plan`. When `true`, any data-plane device-to-host
70    /// transfer (column downloads, internal `dtoh_sync_copy_into_tracked`
71    /// calls) returns `XlogError::Execution` and increments the provider's
72    /// `deterministic_d2h_violation_count`. Metadata reads via
73    /// `dtoh_scalar_untracked` remain allowed.
74    ///
75    /// Default `false`: the runtime still has known data-plane D2H paths in
76    /// relational set difference and binary-join count/materialize that are
77    /// scheduled for replacement before the default flips.
78    pub strict_deterministic_d2h: bool,
79    /// Override the env-driven WCOJ triangle dispatch gate
80    /// (`XLOG_USE_WCOJ_TRIANGLE_U32`). `None` (default) consults
81    /// the env var; `Some(true)` / `Some(false)` force the
82    /// runtime to ignore the env and use the explicit value.
83    /// Test-only knob — production callers should leave this
84    /// `None` and configure via the env var.
85    pub wcoj_triangle_dispatch: Option<bool>,
86    /// Override the stats-backed WCOJ triangle dispatch gate.
87    /// `None` uses the production default. `Some(true)` enables
88    /// the cardinality model; `Some(false)` disables this runtime's
89    /// default stats-backed decision.
90    pub wcoj_triangle_dispatch_adaptive: Option<bool>,
91    /// Runtime-local hard stop for WCOJ triangle dispatch.
92    /// `Some(true)` pins dispatch off across force and stats mode.
93    /// `Some(false)` leaves dispatch available for this runtime.
94    /// `None` uses the production default.
95    pub wcoj_triangle_dispatch_disabled: Option<bool>,
96
97    /// Force gate for the 4-cycle WCOJ dispatch.
98    /// `Some(true)` / env `XLOG_USE_WCOJ_4CYCLE=1` forces every
99    /// recognized 4-cycle to dispatch the GPU kernel. `Some(false)` is
100    /// explicit force-off. `None` (default) consults the env.
101    pub wcoj_4cycle_dispatch: Option<bool>,
102    /// Adaptive opt-in for 4-cycle WCOJ. **Unlike triangle, 4-cycle
103    /// adaptive is opt-in by default**, not default-on: `None` resolves
104    /// to `false`. Default-on for 4-cycle requires separate benchmark evidence.
105    pub wcoj_4cycle_dispatch_adaptive: Option<bool>,
106    /// Kill switch for 4-cycle WCOJ. Same shape as triangle's kill switch:
107    /// beats force + adaptive.
108    pub wcoj_4cycle_dispatch_disabled: Option<bool>,
109    /// Selects the runtime WCOJ cost model.
110    /// `None` resolves by env/default precedence; see
111    /// [`RuntimeConfig::with_wcoj_cost_model`].
112    pub wcoj_cost_model: Option<CostModelKind>,
113    /// Runtime common subexpression elimination.
114    ///
115    /// `Some(true)` enables structural CSE for safe deterministic subplans.
116    /// `Some(false)` disables it. `None` consults `XLOG_CSE`; unset defaults
117    /// to disabled so existing runtime behavior is preserved unless the caller
118    /// opts in.
119    pub common_subexpression_elimination: Option<bool>,
120    /// Runtime adaptive re-optimization adoption gate.
121    ///
122    /// `Some(true)` allows an executor to compare a baseline plan against a
123    /// compiler-supplied candidate plan using runtime telemetry, adopt the
124    /// candidate only when deterministic mis-plan thresholds trigger, and roll
125    /// back on adverse candidates. `Some(false)` disables the adoption path.
126    /// `None` consults `XLOG_ADAPTIVE_REOPT`; unset defaults to disabled.
127    pub adaptive_reoptimization: Option<bool>,
128    /// Minimum mis-plan ratio required before the executor attempts to adopt a
129    /// candidate re-optimized plan. `None` consults
130    /// `XLOG_ADAPTIVE_REOPT_MIN_RATIO`; unset or invalid values default to 1.2.
131    pub adaptive_reoptimization_min_misplan_ratio: Option<f64>,
132    /// Persistent hash index manager gate.
133    ///
134    /// `Some(true)` enables persistent build-side hash index reuse in the
135    /// existing executor join-index cache. `Some(false)` disables the manager.
136    /// `None` consults `XLOG_PERSISTENT_HASH_INDEXES`; unset defaults to
137    /// enabled to preserve the existing adaptive-indexing behavior.
138    pub persistent_hash_indexes: Option<bool>,
139    /// Record background-build mode for the persistent hash index manager.
140    /// The current runtime keeps builds on the existing provider path but
141    /// records background build requests/completions so the transition to
142    /// recorded asynchronous builds has stable telemetry.
143    pub persistent_hash_index_background_build: Option<bool>,
144}
145
146/// Cost-model selector for WCOJ dispatch.
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148pub enum CostModelKind {
149    /// Legacy skew-classifier opt-out selector.
150    ///
151    /// The GPU skew-classifier surface is absent, so this selector is
152    /// implemented as a conservative opt-out from stats/cardinality dispatch.
153    SkewClassifier,
154    /// Stats/cardinality-backed dispatch selector.
155    Cardinality,
156}
157
158impl Default for RuntimeConfig {
159    fn default() -> Self {
160        Self {
161            memory: MemoryBudget::default(),
162            deterministic: true,
163            profile: false,
164            max_iterations: 1_000_000,
165            strict_deterministic_d2h: false,
166            wcoj_triangle_dispatch: None,
167            wcoj_triangle_dispatch_adaptive: None,
168            wcoj_triangle_dispatch_disabled: None,
169            wcoj_4cycle_dispatch: None,
170            wcoj_4cycle_dispatch_adaptive: None,
171            wcoj_4cycle_dispatch_disabled: None,
172            wcoj_cost_model: None,
173            common_subexpression_elimination: None,
174            adaptive_reoptimization: None,
175            adaptive_reoptimization_min_misplan_ratio: None,
176            persistent_hash_indexes: None,
177            persistent_hash_index_background_build: None,
178        }
179    }
180}
181
182impl RuntimeConfig {
183    /// Enable profiling
184    pub fn with_profiling(mut self) -> Self {
185        self.profile = true;
186        self
187    }
188
189    /// Set memory budget
190    pub fn with_memory(mut self, memory: MemoryBudget) -> Self {
191        self.memory = memory;
192        self
193    }
194
195    /// Enable the strict deterministic-Datalog D2H gate for this runtime.
196    pub fn with_strict_deterministic_d2h(mut self) -> Self {
197        self.strict_deterministic_d2h = true;
198        self
199    }
200
201    /// Override the env-driven WCOJ triangle dispatch gate. Pass
202    /// `Some(true)` / `Some(false)` to force the runtime to ignore
203    /// `XLOG_USE_WCOJ_TRIANGLE_U32`; `None` to consult the env var
204    /// (the production default). Test-only knob.
205    pub fn with_wcoj_triangle_dispatch(mut self, override_value: Option<bool>) -> Self {
206        self.wcoj_triangle_dispatch = override_value;
207        self
208    }
209
210    /// Override the stats-backed WCOJ triangle dispatch gate.
211    /// Force-WCOJ (`with_wcoj_triangle_dispatch(Some(true))`)
212    /// takes precedence.
213    pub fn with_wcoj_triangle_dispatch_adaptive(mut self, override_value: Option<bool>) -> Self {
214        self.wcoj_triangle_dispatch_adaptive = override_value;
215        self
216    }
217
218    /// Engage / disengage the runtime-local WCOJ triangle
219    /// dispatch hard stop.
220    pub fn with_wcoj_triangle_dispatch_disabled(mut self, override_value: Option<bool>) -> Self {
221        self.wcoj_triangle_dispatch_disabled = override_value;
222        self
223    }
224
225    /// Override the 4-cycle force gate.
226    /// `Some(true)` forces the GPU kernel; `Some(false)` is
227    /// explicit force-off; `None` consults `XLOG_USE_WCOJ_4CYCLE`.
228    pub fn with_wcoj_4cycle_dispatch(mut self, override_value: Option<bool>) -> Self {
229        self.wcoj_4cycle_dispatch = override_value;
230        self
231    }
232
233    /// Override the 4-cycle stats opt-in.
234    /// `Some(true)` engages the cardinality model; `Some(false)` skips it.
235    /// `None` resolves to `false` (opt-in by default — 4-cycle
236    /// does NOT inherit triangle's default-on behavior).
237    pub fn with_wcoj_4cycle_dispatch_adaptive(mut self, override_value: Option<bool>) -> Self {
238        self.wcoj_4cycle_dispatch_adaptive = override_value;
239        self
240    }
241
242    /// Engage / disengage the 4-cycle kill switch.
243    /// Same shape as the triangle kill switch.
244    pub fn with_wcoj_4cycle_dispatch_disabled(mut self, override_value: Option<bool>) -> Self {
245        self.wcoj_4cycle_dispatch_disabled = override_value;
246        self
247    }
248
249    /// Select which WCOJ cost-model implementation the runtime consults.
250    ///
251    /// Precedence:
252    /// 1. Explicit config field set here.
253    /// 2. `XLOG_WCOJ_COST_MODEL=cardinality` or `skew`.
254    /// 3. Default `Cardinality`.
255    pub fn with_wcoj_cost_model(mut self, kind: Option<CostModelKind>) -> Self {
256        self.wcoj_cost_model = kind;
257        self
258    }
259
260    /// Enable or disable runtime common subexpression elimination.
261    pub fn with_common_subexpression_elimination(mut self, override_value: Option<bool>) -> Self {
262        self.common_subexpression_elimination = override_value;
263        self
264    }
265
266    /// Enable or disable adaptive runtime re-optimization adoption.
267    pub fn with_adaptive_reoptimization(mut self, override_value: Option<bool>) -> Self {
268        self.adaptive_reoptimization = override_value;
269        self
270    }
271
272    /// Set the minimum mis-plan ratio for adaptive runtime re-optimization.
273    pub fn with_adaptive_reoptimization_min_misplan_ratio(
274        mut self,
275        override_value: Option<f64>,
276    ) -> Self {
277        self.adaptive_reoptimization_min_misplan_ratio = override_value;
278        self
279    }
280
281    /// Enable or disable persistent build-side hash index reuse.
282    pub fn with_persistent_hash_indexes(mut self, override_value: Option<bool>) -> Self {
283        self.persistent_hash_indexes = override_value;
284        self
285    }
286
287    /// Enable or disable persistent hash-index background-build telemetry.
288    pub fn with_persistent_hash_index_background_build(
289        mut self,
290        override_value: Option<bool>,
291    ) -> Self {
292        self.persistent_hash_index_background_build = override_value;
293        self
294    }
295
296    /// Resolve runtime common subexpression elimination by config/env/default.
297    pub fn resolved_common_subexpression_elimination(&self) -> bool {
298        if let Some(enabled) = self.common_subexpression_elimination {
299            return enabled;
300        }
301
302        std::env::var("XLOG_CSE")
303            .map(|raw| {
304                matches!(
305                    raw.trim().to_ascii_lowercase().as_str(),
306                    "1" | "true" | "on" | "yes"
307                )
308            })
309            .unwrap_or(false)
310    }
311
312    /// Resolve adaptive runtime re-optimization by config/env/default.
313    pub fn resolved_adaptive_reoptimization(&self) -> bool {
314        if let Some(enabled) = self.adaptive_reoptimization {
315            return enabled;
316        }
317
318        std::env::var("XLOG_ADAPTIVE_REOPT")
319            .map(|raw| {
320                matches!(
321                    raw.trim().to_ascii_lowercase().as_str(),
322                    "1" | "true" | "on" | "yes"
323                )
324            })
325            .unwrap_or(false)
326    }
327
328    /// Resolve the deterministic mis-plan threshold for adaptive re-optimization.
329    pub fn resolved_adaptive_reoptimization_min_misplan_ratio(&self) -> f64 {
330        const DEFAULT_MIN_RATIO: f64 = 1.2;
331        if let Some(value) = self.adaptive_reoptimization_min_misplan_ratio {
332            return sanitize_adaptive_reoptimization_ratio(value, DEFAULT_MIN_RATIO);
333        }
334
335        std::env::var("XLOG_ADAPTIVE_REOPT_MIN_RATIO")
336            .ok()
337            .and_then(|raw| raw.trim().parse::<f64>().ok())
338            .map(|value| sanitize_adaptive_reoptimization_ratio(value, DEFAULT_MIN_RATIO))
339            .unwrap_or(DEFAULT_MIN_RATIO)
340    }
341
342    /// Resolve persistent hash-index reuse by config/env/default.
343    pub fn resolved_persistent_hash_indexes(&self) -> bool {
344        if let Some(enabled) = self.persistent_hash_indexes {
345            return enabled;
346        }
347
348        std::env::var("XLOG_PERSISTENT_HASH_INDEXES")
349            .map(|raw| {
350                !matches!(
351                    raw.trim().to_ascii_lowercase().as_str(),
352                    "0" | "false" | "off" | "no"
353                )
354            })
355            .unwrap_or(true)
356    }
357
358    /// Resolve background-build telemetry for persistent hash indexes.
359    pub fn resolved_persistent_hash_index_background_build(&self) -> bool {
360        if let Some(enabled) = self.persistent_hash_index_background_build {
361            return enabled;
362        }
363
364        std::env::var("XLOG_PERSISTENT_HASH_INDEX_BACKGROUND_BUILD")
365            .map(|raw| {
366                matches!(
367                    raw.trim().to_ascii_lowercase().as_str(),
368                    "1" | "true" | "on" | "yes"
369                )
370            })
371            .unwrap_or(false)
372    }
373
374    /// Resolve the effective WCOJ cost-model selector.
375    pub fn resolved_wcoj_cost_model(&self) -> CostModelKind {
376        if let Some(kind) = self.wcoj_cost_model {
377            return kind;
378        }
379        let raw = std::env::var("XLOG_WCOJ_COST_MODEL").ok();
380        let normalized = raw.as_deref().map(|s| s.trim().to_ascii_lowercase());
381        match normalized.as_deref() {
382            Some("cardinality") => CostModelKind::Cardinality,
383            Some("skew") | Some("skewclassifier") | Some(_) => CostModelKind::SkewClassifier,
384            None => CostModelKind::Cardinality,
385        }
386    }
387}
388
389fn sanitize_adaptive_reoptimization_ratio(value: f64, fallback: f64) -> f64 {
390    if value.is_finite() && value >= 1.0 {
391        value
392    } else {
393        fallback
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use super::*;
400
401    #[test]
402    fn test_memory_budget_default() {
403        let budget = MemoryBudget::default();
404        assert!(!budget.allow_ooc);
405        assert!(budget.abort_on_exceed);
406    }
407
408    #[test]
409    fn test_runtime_config_default() {
410        let config = RuntimeConfig::default();
411        assert!(config.deterministic);
412        assert!(!config.profile);
413    }
414
415    #[test]
416    fn test_memory_budget_from_device() {
417        let budget = MemoryBudget::from_device_memory(10_000_000_000);
418        assert_eq!(budget.device_bytes, 8_000_000_000);
419    }
420}