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`: v0.5.5 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 /// v0.6.5 slice 2 — 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 explicit force-off. `None`
100 /// (default) consults the env.
101 pub wcoj_4cycle_dispatch: Option<bool>,
102 /// v0.6.5 slice 2 — adaptive opt-in for 4-cycle WCOJ. **Unlike
103 /// triangle, 4-cycle adaptive is opt-in by default**, not
104 /// default-on: `None` resolves to `false`. Default-on for
105 /// 4-cycle is a separate follow-up slice gated by bench evidence.
106 pub wcoj_4cycle_dispatch_adaptive: Option<bool>,
107 /// v0.6.5 slice 2 — kill switch for 4-cycle WCOJ. Same shape
108 /// as triangle's kill switch: beats force + adaptive.
109 pub wcoj_4cycle_dispatch_disabled: Option<bool>,
110 /// v0.6.5 W2.5 — selects the runtime WCOJ cost model.
111 /// `None` resolves by env/default precedence; see
112 /// [`RuntimeConfig::with_wcoj_cost_model`].
113 pub wcoj_cost_model: Option<CostModelKind>,
114 /// v0.8.6 G086_CSE — runtime common subexpression elimination.
115 ///
116 /// `Some(true)` enables structural CSE for safe deterministic subplans.
117 /// `Some(false)` disables it. `None` consults `XLOG_CSE`; unset defaults
118 /// to disabled so existing runtime behavior is preserved unless the caller
119 /// opts in.
120 pub common_subexpression_elimination: Option<bool>,
121 /// v0.8.6 G086_ADAPT — runtime adaptive re-optimization adoption gate.
122 ///
123 /// `Some(true)` allows an executor to compare a baseline plan against a
124 /// compiler-supplied candidate plan using runtime telemetry, adopt the
125 /// candidate only when deterministic mis-plan thresholds trigger, and roll
126 /// back on adverse candidates. `Some(false)` disables the adoption path.
127 /// `None` consults `XLOG_ADAPTIVE_REOPT`; unset defaults to disabled.
128 pub adaptive_reoptimization: Option<bool>,
129 /// Minimum mis-plan ratio required before the executor attempts to adopt a
130 /// candidate re-optimized plan. `None` consults
131 /// `XLOG_ADAPTIVE_REOPT_MIN_RATIO`; unset or invalid values default to 1.2.
132 pub adaptive_reoptimization_min_misplan_ratio: Option<f64>,
133 /// v0.8.6 G086_INDEX — persistent hash index manager gate.
134 ///
135 /// `Some(true)` enables persistent build-side hash index reuse in the
136 /// existing executor join-index cache. `Some(false)` disables the manager.
137 /// `None` consults `XLOG_PERSISTENT_HASH_INDEXES`; unset defaults to
138 /// enabled to preserve the existing adaptive-indexing behavior.
139 pub persistent_hash_indexes: Option<bool>,
140 /// v0.8.6 G086_INDEX — record background-build mode for the persistent
141 /// hash index manager. The current runtime keeps builds on the existing
142 /// provider path but records background build requests/completions so the
143 /// transition to recorded asynchronous builds has stable telemetry.
144 pub persistent_hash_index_background_build: Option<bool>,
145}
146
147/// v0.6.5 W2.5 cost-model selector for WCOJ dispatch.
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub enum CostModelKind {
150 /// Legacy skew-classifier opt-out selector.
151 ///
152 /// On current G38 integration code the GPU classifier surface is absent,
153 /// so this selector is implemented as a conservative opt-out from
154 /// stats/cardinality dispatch.
155 SkewClassifier,
156 /// Stats/cardinality-backed dispatch selector.
157 Cardinality,
158}
159
160impl Default for RuntimeConfig {
161 fn default() -> Self {
162 Self {
163 memory: MemoryBudget::default(),
164 deterministic: true,
165 profile: false,
166 max_iterations: 1_000_000,
167 strict_deterministic_d2h: false,
168 wcoj_triangle_dispatch: None,
169 wcoj_triangle_dispatch_adaptive: None,
170 wcoj_triangle_dispatch_disabled: None,
171 wcoj_4cycle_dispatch: None,
172 wcoj_4cycle_dispatch_adaptive: None,
173 wcoj_4cycle_dispatch_disabled: None,
174 wcoj_cost_model: None,
175 common_subexpression_elimination: None,
176 adaptive_reoptimization: None,
177 adaptive_reoptimization_min_misplan_ratio: None,
178 persistent_hash_indexes: None,
179 persistent_hash_index_background_build: None,
180 }
181 }
182}
183
184impl RuntimeConfig {
185 /// Enable profiling
186 pub fn with_profiling(mut self) -> Self {
187 self.profile = true;
188 self
189 }
190
191 /// Set memory budget
192 pub fn with_memory(mut self, memory: MemoryBudget) -> Self {
193 self.memory = memory;
194 self
195 }
196
197 /// Enable the strict deterministic-Datalog D2H gate for this runtime.
198 pub fn with_strict_deterministic_d2h(mut self) -> Self {
199 self.strict_deterministic_d2h = true;
200 self
201 }
202
203 /// Override the env-driven WCOJ triangle dispatch gate. Pass
204 /// `Some(true)` / `Some(false)` to force the runtime to ignore
205 /// `XLOG_USE_WCOJ_TRIANGLE_U32`; `None` to consult the env var
206 /// (the production default). Test-only knob.
207 pub fn with_wcoj_triangle_dispatch(mut self, override_value: Option<bool>) -> Self {
208 self.wcoj_triangle_dispatch = override_value;
209 self
210 }
211
212 /// Override the stats-backed WCOJ triangle dispatch gate.
213 /// Force-WCOJ (`with_wcoj_triangle_dispatch(Some(true))`)
214 /// takes precedence.
215 pub fn with_wcoj_triangle_dispatch_adaptive(mut self, override_value: Option<bool>) -> Self {
216 self.wcoj_triangle_dispatch_adaptive = override_value;
217 self
218 }
219
220 /// Engage / disengage the runtime-local WCOJ triangle
221 /// dispatch hard stop.
222 pub fn with_wcoj_triangle_dispatch_disabled(mut self, override_value: Option<bool>) -> Self {
223 self.wcoj_triangle_dispatch_disabled = override_value;
224 self
225 }
226
227 /// v0.6.5 slice 2 — override the 4-cycle force-gate.
228 /// `Some(true)` forces the GPU kernel; `Some(false)` is
229 /// explicit force-off; `None` consults `XLOG_USE_WCOJ_4CYCLE`.
230 pub fn with_wcoj_4cycle_dispatch(mut self, override_value: Option<bool>) -> Self {
231 self.wcoj_4cycle_dispatch = override_value;
232 self
233 }
234
235 /// v0.6.5 slice 2 — override the 4-cycle stats opt-in.
236 /// `Some(true)` engages the cardinality model; `Some(false)` skips it.
237 /// `None` resolves to `false` (opt-in by default — 4-cycle
238 /// does NOT inherit triangle's default-on behavior).
239 pub fn with_wcoj_4cycle_dispatch_adaptive(mut self, override_value: Option<bool>) -> Self {
240 self.wcoj_4cycle_dispatch_adaptive = override_value;
241 self
242 }
243
244 /// v0.6.5 slice 2 — engage / disengage the 4-cycle kill switch.
245 /// Same shape as the triangle kill switch.
246 pub fn with_wcoj_4cycle_dispatch_disabled(mut self, override_value: Option<bool>) -> Self {
247 self.wcoj_4cycle_dispatch_disabled = override_value;
248 self
249 }
250
251 /// Select which WCOJ cost-model implementation the runtime consults.
252 ///
253 /// Precedence:
254 /// 1. Explicit config field set here.
255 /// 2. `XLOG_WCOJ_COST_MODEL=cardinality` or `skew`.
256 /// 3. Default `Cardinality`.
257 pub fn with_wcoj_cost_model(mut self, kind: Option<CostModelKind>) -> Self {
258 self.wcoj_cost_model = kind;
259 self
260 }
261
262 /// Enable or disable runtime common subexpression elimination.
263 pub fn with_common_subexpression_elimination(mut self, override_value: Option<bool>) -> Self {
264 self.common_subexpression_elimination = override_value;
265 self
266 }
267
268 /// Enable or disable adaptive runtime re-optimization adoption.
269 pub fn with_adaptive_reoptimization(mut self, override_value: Option<bool>) -> Self {
270 self.adaptive_reoptimization = override_value;
271 self
272 }
273
274 /// Set the minimum mis-plan ratio for adaptive runtime re-optimization.
275 pub fn with_adaptive_reoptimization_min_misplan_ratio(
276 mut self,
277 override_value: Option<f64>,
278 ) -> Self {
279 self.adaptive_reoptimization_min_misplan_ratio = override_value;
280 self
281 }
282
283 /// Enable or disable persistent build-side hash index reuse.
284 pub fn with_persistent_hash_indexes(mut self, override_value: Option<bool>) -> Self {
285 self.persistent_hash_indexes = override_value;
286 self
287 }
288
289 /// Enable or disable persistent hash-index background-build telemetry.
290 pub fn with_persistent_hash_index_background_build(
291 mut self,
292 override_value: Option<bool>,
293 ) -> Self {
294 self.persistent_hash_index_background_build = override_value;
295 self
296 }
297
298 /// Resolve runtime common subexpression elimination by config/env/default.
299 pub fn resolved_common_subexpression_elimination(&self) -> bool {
300 if let Some(enabled) = self.common_subexpression_elimination {
301 return enabled;
302 }
303
304 std::env::var("XLOG_CSE")
305 .map(|raw| {
306 matches!(
307 raw.trim().to_ascii_lowercase().as_str(),
308 "1" | "true" | "on" | "yes"
309 )
310 })
311 .unwrap_or(false)
312 }
313
314 /// Resolve adaptive runtime re-optimization by config/env/default.
315 pub fn resolved_adaptive_reoptimization(&self) -> bool {
316 if let Some(enabled) = self.adaptive_reoptimization {
317 return enabled;
318 }
319
320 std::env::var("XLOG_ADAPTIVE_REOPT")
321 .map(|raw| {
322 matches!(
323 raw.trim().to_ascii_lowercase().as_str(),
324 "1" | "true" | "on" | "yes"
325 )
326 })
327 .unwrap_or(false)
328 }
329
330 /// Resolve the deterministic mis-plan threshold for adaptive re-optimization.
331 pub fn resolved_adaptive_reoptimization_min_misplan_ratio(&self) -> f64 {
332 const DEFAULT_MIN_RATIO: f64 = 1.2;
333 if let Some(value) = self.adaptive_reoptimization_min_misplan_ratio {
334 return sanitize_adaptive_reoptimization_ratio(value, DEFAULT_MIN_RATIO);
335 }
336
337 std::env::var("XLOG_ADAPTIVE_REOPT_MIN_RATIO")
338 .ok()
339 .and_then(|raw| raw.trim().parse::<f64>().ok())
340 .map(|value| sanitize_adaptive_reoptimization_ratio(value, DEFAULT_MIN_RATIO))
341 .unwrap_or(DEFAULT_MIN_RATIO)
342 }
343
344 /// Resolve persistent hash-index reuse by config/env/default.
345 pub fn resolved_persistent_hash_indexes(&self) -> bool {
346 if let Some(enabled) = self.persistent_hash_indexes {
347 return enabled;
348 }
349
350 std::env::var("XLOG_PERSISTENT_HASH_INDEXES")
351 .map(|raw| {
352 !matches!(
353 raw.trim().to_ascii_lowercase().as_str(),
354 "0" | "false" | "off" | "no"
355 )
356 })
357 .unwrap_or(true)
358 }
359
360 /// Resolve background-build telemetry for persistent hash indexes.
361 pub fn resolved_persistent_hash_index_background_build(&self) -> bool {
362 if let Some(enabled) = self.persistent_hash_index_background_build {
363 return enabled;
364 }
365
366 std::env::var("XLOG_PERSISTENT_HASH_INDEX_BACKGROUND_BUILD")
367 .map(|raw| {
368 matches!(
369 raw.trim().to_ascii_lowercase().as_str(),
370 "1" | "true" | "on" | "yes"
371 )
372 })
373 .unwrap_or(false)
374 }
375
376 /// Resolve the effective WCOJ cost-model selector.
377 pub fn resolved_wcoj_cost_model(&self) -> CostModelKind {
378 if let Some(kind) = self.wcoj_cost_model {
379 return kind;
380 }
381 let raw = std::env::var("XLOG_WCOJ_COST_MODEL").ok();
382 let normalized = raw.as_deref().map(|s| s.trim().to_ascii_lowercase());
383 match normalized.as_deref() {
384 Some("cardinality") => CostModelKind::Cardinality,
385 Some("skew") | Some("skewclassifier") | Some(_) => CostModelKind::SkewClassifier,
386 None => CostModelKind::Cardinality,
387 }
388 }
389}
390
391fn sanitize_adaptive_reoptimization_ratio(value: f64, fallback: f64) -> f64 {
392 if value.is_finite() && value >= 1.0 {
393 value
394 } else {
395 fallback
396 }
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402
403 #[test]
404 fn test_memory_budget_default() {
405 let budget = MemoryBudget::default();
406 assert!(!budget.allow_ooc);
407 assert!(budget.abort_on_exceed);
408 }
409
410 #[test]
411 fn test_runtime_config_default() {
412 let config = RuntimeConfig::default();
413 assert!(config.deterministic);
414 assert!(!config.profile);
415 }
416
417 #[test]
418 fn test_memory_budget_from_device() {
419 let budget = MemoryBudget::from_device_memory(10_000_000_000);
420 assert_eq!(budget.device_bytes, 8_000_000_000);
421 }
422}