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}