1use std::sync::Arc;
4
5use std::ffi::c_void;
6
7use cudarc::driver::LaunchConfig;
8use xlog_core::{resolve_bool, Result, XlogError};
9use xlog_cuda::memory::TrackedCudaSlice;
10use xlog_cuda::provider::sat_kernels;
11use xlog_cuda::provider::SAT_MODULE;
12use xlog_cuda::{AsKernelParam, CudaKernelProvider, LaunchAsync};
13use xlog_solve::{GpuCdclConfig, GpuCdclSolver, GpuCnf};
14
15#[cfg(debug_assertions)]
16use crate::compilation::gpu_d4::validate_cnf_gpu;
17
18use crate::gpu::GpuXgcf;
19
20const MAX_GRID_X: u64 = 65_535;
21
22fn checked_launch_grid(elements: u32, block: u32, context: &str) -> Result<u32> {
23 if block == 0 {
24 return Err(XlogError::Kernel(format!(
25 "{context}: CUDA launch block size must be nonzero"
26 )));
27 }
28 let grid = if elements == 0 {
29 1
30 } else {
31 u64::from(elements).div_ceil(u64::from(block))
32 };
33 if grid > MAX_GRID_X {
34 return Err(XlogError::Kernel(format!(
35 "{context}: launch grid {grid} exceeds x-dimension limit {MAX_GRID_X} \
36 for {elements} elements with block size {block}"
37 )));
38 }
39 Ok(grid as u32)
40}
41
42fn checked_clause_offset_span(clause_cap: u32, context: &str) -> Result<u32> {
43 clause_cap
44 .checked_add(1)
45 .ok_or_else(|| XlogError::Kernel(format!("{context}: clause offset span overflow")))
46}
47
48#[derive(Debug, Clone, Copy, Default)]
55#[non_exhaustive]
56pub struct GpuEquivalenceConfig {
57 pub cdcl: GpuCdclConfig,
59 pub reuse_workspace: bool,
61}
62
63pub struct GpuEquivalenceQueries {
65 pub q1: GpuCnf,
67 pub q2: GpuCnf,
69 pub q2_unsat_var_base: TrackedCudaSlice<u32>,
71}
72
73struct CircuitCnf {
74 cnf: GpuCnf,
75 internal_prefix: TrackedCudaSlice<u32>,
78}
79
80fn build_circuit_cnf(
81 provider: &Arc<CudaKernelProvider>,
82 circuit: &GpuXgcf,
83 base_num_vars: &TrackedCudaSlice<u32>,
84 base_var_cap: u32,
85 compile_needed: &TrackedCudaSlice<u32>,
86) -> Result<CircuitCnf> {
87 if base_var_cap == 0 {
88 return Err(XlogError::Compilation(
89 "GPU equivalence verifier requires base_var_cap > 0".to_string(),
90 ));
91 }
92 if circuit.max_var() > base_var_cap {
93 return Err(XlogError::Compilation(format!(
94 "Circuit references var {} but base CNF has only {} vars",
95 circuit.max_var(),
96 base_var_cap
97 )));
98 }
99
100 let num_nodes = circuit.num_nodes();
101 if num_nodes == 0 {
102 return Err(XlogError::Compilation(
103 "GPU equivalence verifier requires circuit with num_nodes > 0".to_string(),
104 ));
105 }
106 if circuit.root() as usize >= num_nodes {
107 return Err(XlogError::Compilation(format!(
108 "GPU equivalence verifier: circuit root {} out of bounds (num_nodes={})",
109 circuit.root(),
110 num_nodes
111 )));
112 }
113
114 let num_nodes_u32 = u32::try_from(num_nodes).map_err(|_| {
115 XlogError::Compilation(format!(
116 "GPU equivalence verifier: circuit num_nodes {} exceeds u32::MAX",
117 num_nodes
118 ))
119 })?;
120
121 let num_edges = circuit.num_edges();
123 let n64 = num_nodes as u64;
124 let e64 = num_edges as u64;
125
126 let var_cap = u32::try_from((base_var_cap as u64).saturating_add(n64))
127 .map_err(|_| XlogError::Kernel("Circuit CNF var capacity exceeds u32::MAX".to_string()))?;
128 let clause_cap =
129 u32::try_from(e64.checked_add(4u64.saturating_mul(n64)).ok_or_else(|| {
130 XlogError::Kernel("Circuit CNF clause capacity overflow".to_string())
131 })?)
132 .map_err(|_| {
133 XlogError::Kernel("Circuit CNF clause capacity exceeds u32::MAX".to_string())
134 })?;
135 let lit_cap = u32::try_from(
136 (3u64.saturating_mul(e64))
137 .checked_add(12u64.saturating_mul(n64))
138 .ok_or_else(|| {
139 XlogError::Kernel("Circuit CNF literal capacity overflow".to_string())
140 })?,
141 )
142 .map_err(|_| XlogError::Kernel("Circuit CNF literal capacity exceeds u32::MAX".to_string()))?;
143
144 let memory = provider.memory();
145 let device = provider.device().inner();
146
147 let mut internal_prefix = memory.alloc::<u32>(num_nodes)?;
149 let mut clause_base = memory.alloc::<u32>(num_nodes)?;
150 let mut lit_base = memory.alloc::<u32>(num_nodes)?;
151
152 let counts_fn = device
153 .get_func(SAT_MODULE, sat_kernels::SAT_XGCF_CNF_COUNTS)
154 .ok_or_else(|| XlogError::Kernel("sat_xgcf_cnf_counts kernel not found".to_string()))?;
155
156 let block = 256u32;
157 let grid = checked_launch_grid(num_nodes_u32, block, "sat_xgcf_cnf_counts")?;
158
159 unsafe {
161 counts_fn.clone().launch(
162 LaunchConfig {
163 grid_dim: (grid, 1, 1),
164 block_dim: (block, 1, 1),
165 shared_mem_bytes: 0,
166 },
167 (
168 compile_needed,
169 circuit.node_type(),
170 circuit.child_offsets(),
171 num_nodes_u32,
172 &mut internal_prefix,
173 &mut clause_base,
174 &mut lit_base,
175 ),
176 )
177 }
178 .map_err(|e| XlogError::Kernel(format!("sat_xgcf_cnf_counts failed: {}", e)))?;
179
180 let mut internal_last = memory.alloc::<u32>(1)?;
182 let mut clause_last = memory.alloc::<u32>(1)?;
183 let mut lit_last = memory.alloc::<u32>(1)?;
184
185 let capture_last_fn = device
186 .get_func(SAT_MODULE, sat_kernels::SAT_XGCF_CNF_CAPTURE_LAST_COUNTS)
187 .ok_or_else(|| {
188 XlogError::Kernel("sat_xgcf_cnf_capture_last_counts kernel not found".to_string())
189 })?;
190 unsafe {
192 capture_last_fn.clone().launch(
193 LaunchConfig {
194 grid_dim: (1, 1, 1),
195 block_dim: (1, 1, 1),
196 shared_mem_bytes: 0,
197 },
198 (
199 &internal_prefix,
200 &clause_base,
201 &lit_base,
202 num_nodes_u32,
203 &mut internal_last,
204 &mut clause_last,
205 &mut lit_last,
206 ),
207 )
208 }
209 .map_err(|e| XlogError::Kernel(format!("sat_xgcf_cnf_capture_last_counts failed: {}", e)))?;
210
211 provider.exclusive_scan_u32_inplace(&mut internal_prefix, num_nodes_u32)?;
212 provider.exclusive_scan_u32_inplace(&mut clause_base, num_nodes_u32)?;
213 provider.exclusive_scan_u32_inplace(&mut lit_base, num_nodes_u32)?;
214 let d_num_vars = memory.alloc::<u32>(1)?;
218 let d_num_clauses = memory.alloc::<u32>(1)?;
219 let d_num_lits = memory.alloc::<u32>(1)?;
220 let mut d_offsets = memory.alloc::<u32>((clause_cap as usize) + 1)?;
221 let d_lits = memory.alloc::<i32>(lit_cap as usize)?;
222
223 let totals_fn = device
224 .get_func(SAT_MODULE, sat_kernels::SAT_XGCF_CNF_COMPUTE_TOTALS)
225 .ok_or_else(|| {
226 XlogError::Kernel("sat_xgcf_cnf_compute_totals kernel not found".to_string())
227 })?;
228 let mut totals_params: Vec<*mut c_void> = vec![
230 (&internal_prefix).as_kernel_param(),
231 (&clause_base).as_kernel_param(),
232 (&lit_base).as_kernel_param(),
233 (&internal_last).as_kernel_param(),
234 (&clause_last).as_kernel_param(),
235 (&lit_last).as_kernel_param(),
236 num_nodes_u32.as_kernel_param(),
237 (base_num_vars).as_kernel_param(),
238 clause_cap.as_kernel_param(),
239 lit_cap.as_kernel_param(),
240 (&d_num_vars).as_kernel_param(),
241 (&d_num_clauses).as_kernel_param(),
242 (&d_num_lits).as_kernel_param(),
243 ];
244 unsafe {
246 totals_fn.clone().launch(
247 LaunchConfig {
248 grid_dim: (1, 1, 1),
249 block_dim: (1, 1, 1),
250 shared_mem_bytes: 0,
251 },
252 &mut totals_params,
253 )
254 }
255 .map_err(|e| XlogError::Kernel(format!("sat_xgcf_cnf_compute_totals failed: {}", e)))?;
256
257 let emit_fn = device
258 .get_func(SAT_MODULE, sat_kernels::SAT_XGCF_CNF_EMIT)
259 .ok_or_else(|| XlogError::Kernel("sat_xgcf_cnf_emit kernel not found".to_string()))?;
260
261 let mut params: Vec<*mut c_void> = vec![
264 compile_needed.as_kernel_param(),
265 circuit.node_type().as_kernel_param(),
266 circuit.child_offsets().as_kernel_param(),
267 circuit.child_indices().as_kernel_param(),
268 circuit.lit().as_kernel_param(),
269 circuit.decision_var().as_kernel_param(),
270 circuit.decision_child_false().as_kernel_param(),
271 circuit.decision_child_true().as_kernel_param(),
272 (&internal_prefix).as_kernel_param(),
273 (&clause_base).as_kernel_param(),
274 (&lit_base).as_kernel_param(),
275 (base_num_vars).as_kernel_param(),
276 num_nodes_u32.as_kernel_param(),
277 (&d_offsets).as_kernel_param(),
278 (&d_lits).as_kernel_param(),
279 ];
280
281 unsafe {
283 emit_fn.clone().launch(
284 LaunchConfig {
285 grid_dim: (grid, 1, 1),
286 block_dim: (block, 1, 1),
287 shared_mem_bytes: 0,
288 },
289 &mut params,
290 )
291 }
292 .map_err(|e| XlogError::Kernel(format!("sat_xgcf_cnf_emit failed: {}", e)))?;
293
294 let term_fn = device
296 .get_func(SAT_MODULE, sat_kernels::SAT_CNF_WRITE_TERMINATOR)
297 .ok_or_else(|| {
298 XlogError::Kernel("sat_cnf_write_terminator kernel not found".to_string())
299 })?;
300 unsafe {
302 term_fn.clone().launch(
303 LaunchConfig {
304 grid_dim: (1, 1, 1),
305 block_dim: (1, 1, 1),
306 shared_mem_bytes: 0,
307 },
308 (&mut d_offsets, &d_num_clauses, &d_num_lits),
309 )
310 }
311 .map_err(|e| XlogError::Kernel(format!("sat_cnf_write_terminator failed: {}", e)))?;
312 Ok(CircuitCnf {
315 cnf: GpuCnf {
316 var_cap,
317 clause_cap,
318 lit_cap,
319 num_vars: d_num_vars,
320 num_clauses: d_num_clauses,
321 num_lits: d_num_lits,
322 clause_offsets: d_offsets,
323 literals: d_lits,
324 },
325 internal_prefix,
326 })
327}
328
329fn build_phi_and_not_c(
330 provider: &Arc<CudaKernelProvider>,
331 phi: &GpuCnf,
332 circuit: &GpuXgcf,
333 circuit_cnf: &CircuitCnf,
334 compile_needed: &TrackedCudaSlice<u32>,
335) -> Result<GpuCnf> {
336 let device = provider.device().inner();
337 let memory = provider.memory();
338
339 let phi_clause_cap = phi.clause_cap;
340 let phi_lit_cap = phi.lit_cap;
341
342 let clause_cap = u32::try_from(
343 (phi_clause_cap as u64)
344 .checked_add(circuit_cnf.cnf.clause_cap as u64)
345 .and_then(|v| v.checked_add(1))
346 .ok_or_else(|| XlogError::Kernel("phi ∧ ¬C clause capacity overflow".to_string()))?,
347 )
348 .map_err(|_| XlogError::Kernel("phi ∧ ¬C clause capacity exceeds u32::MAX".to_string()))?;
349 let lit_cap = u32::try_from(
350 (phi_lit_cap as u64)
351 .checked_add(circuit_cnf.cnf.lit_cap as u64)
352 .and_then(|v| v.checked_add(1))
353 .ok_or_else(|| XlogError::Kernel("phi ∧ ¬C literal capacity overflow".to_string()))?,
354 )
355 .map_err(|_| XlogError::Kernel("phi ∧ ¬C literal capacity exceeds u32::MAX".to_string()))?;
356
357 let var_cap = circuit_cnf.cnf.var_cap;
358
359 let out_num_vars = memory.alloc::<u32>(1)?;
360 let out_num_clauses = memory.alloc::<u32>(1)?;
361 let out_num_lits = memory.alloc::<u32>(1)?;
362 let d_unused0 = memory.alloc::<u32>(1)?;
363 let d_unused1 = memory.alloc::<u32>(1)?;
364 let d_unused2 = memory.alloc::<u32>(1)?;
365
366 let mut d_zero = memory.alloc::<u32>(1)?;
367 provider
368 .htod_launch_metadata_sync_copy_into(&[0u32], &mut d_zero)
369 .map_err(|e| XlogError::Kernel(format!("Failed to upload zero: {}", e)))?;
370
371 let mut out_offsets = memory.alloc::<u32>((clause_cap as usize) + 1)?;
372 let mut out_lits = memory.alloc::<i32>(lit_cap as usize)?;
373
374 let copy_fn = device
375 .get_func(SAT_MODULE, sat_kernels::SAT_CNF_COPY_INTO)
376 .ok_or_else(|| XlogError::Kernel("sat_cnf_copy_into kernel not found".to_string()))?;
377
378 let block = 256u32;
379 let phi_copy_elems =
380 checked_clause_offset_span(phi_clause_cap, "sat_cnf_copy_into(phi)")?.max(phi_lit_cap);
381 let grid = checked_launch_grid(phi_copy_elems, block, "sat_cnf_copy_into(phi)")?;
382
383 unsafe {
388 copy_fn.clone().launch(
389 LaunchConfig {
390 grid_dim: (grid, 1, 1),
391 block_dim: (block, 1, 1),
392 shared_mem_bytes: 0,
393 },
394 (
395 &phi.clause_offsets,
396 &phi.literals,
397 &phi.num_clauses,
398 &phi.num_lits,
399 phi.clause_cap,
400 phi.lit_cap,
401 &d_zero,
402 &d_zero,
403 clause_cap,
404 lit_cap,
405 &mut out_offsets,
406 &mut out_lits,
407 ),
408 )
409 }
410 .map_err(|e| XlogError::Kernel(format!("sat_cnf_copy_into(phi) failed: {}", e)))?;
411
412 let circuit_copy_elems =
414 checked_clause_offset_span(circuit_cnf.cnf.clause_cap, "sat_cnf_copy_into(circuit)")?
415 .max(circuit_cnf.cnf.lit_cap);
416 let grid_c = checked_launch_grid(circuit_copy_elems, block, "sat_cnf_copy_into(circuit)")?;
417 unsafe {
419 copy_fn.clone().launch(
420 LaunchConfig {
421 grid_dim: (grid_c, 1, 1),
422 block_dim: (block, 1, 1),
423 shared_mem_bytes: 0,
424 },
425 (
426 &circuit_cnf.cnf.clause_offsets,
427 &circuit_cnf.cnf.literals,
428 &circuit_cnf.cnf.num_clauses,
429 &circuit_cnf.cnf.num_lits,
430 circuit_cnf.cnf.clause_cap,
431 circuit_cnf.cnf.lit_cap,
432 &phi.num_clauses,
433 &phi.num_lits,
434 clause_cap,
435 lit_cap,
436 &mut out_offsets,
437 &mut out_lits,
438 ),
439 )
440 }
441 .map_err(|e| XlogError::Kernel(format!("sat_cnf_copy_into(C) failed: {}", e)))?;
442
443 let unit_fn = device
445 .get_func(SAT_MODULE, sat_kernels::SAT_XGCF_WRITE_ROOT_UNIT_CLAUSE)
446 .ok_or_else(|| {
447 XlogError::Kernel("sat_xgcf_write_root_unit_clause kernel not found".to_string())
448 })?;
449
450 let root = circuit.root();
454 let force_true: i32 = 0;
455 let out_var_cap = var_cap;
456 let out_clause_cap = clause_cap;
457 let out_lit_cap = lit_cap;
458
459 let mut params: Vec<*mut c_void> = vec![
460 compile_needed.as_kernel_param(),
461 circuit.node_type().as_kernel_param(),
462 circuit.lit().as_kernel_param(),
463 (&circuit_cnf.internal_prefix).as_kernel_param(),
464 (&phi.num_vars).as_kernel_param(),
465 root.as_kernel_param(),
466 force_true.as_kernel_param(), (&phi.num_clauses).as_kernel_param(),
468 (&phi.num_lits).as_kernel_param(),
469 (&circuit_cnf.cnf.num_vars).as_kernel_param(),
470 (&circuit_cnf.cnf.num_clauses).as_kernel_param(),
471 (&circuit_cnf.cnf.num_lits).as_kernel_param(),
472 (&d_zero).as_kernel_param(), (&d_zero).as_kernel_param(), (&d_zero).as_kernel_param(), out_var_cap.as_kernel_param(),
476 out_clause_cap.as_kernel_param(),
477 out_lit_cap.as_kernel_param(),
478 (&out_num_vars).as_kernel_param(),
479 (&out_num_clauses).as_kernel_param(),
480 (&out_num_lits).as_kernel_param(),
481 (&d_unused0).as_kernel_param(),
482 (&d_unused1).as_kernel_param(),
483 (&d_unused2).as_kernel_param(),
484 (&out_offsets).as_kernel_param(),
485 (&out_lits).as_kernel_param(),
486 ];
487
488 unsafe {
490 unit_fn.clone().launch(
491 LaunchConfig {
492 grid_dim: (1, 1, 1),
493 block_dim: (1, 1, 1),
494 shared_mem_bytes: 0,
495 },
496 &mut params,
497 )
498 }
499 .map_err(|e| XlogError::Kernel(format!("sat_xgcf_write_root_unit_clause failed: {}", e)))?;
500 Ok(GpuCnf {
503 var_cap,
504 clause_cap,
505 lit_cap,
506 num_vars: out_num_vars,
507 num_clauses: out_num_clauses,
508 num_lits: out_num_lits,
509 clause_offsets: out_offsets,
510 literals: out_lits,
511 })
512}
513
514fn build_c_and_not_phi(
515 provider: &Arc<CudaKernelProvider>,
516 phi: &GpuCnf,
517 circuit: &GpuXgcf,
518 circuit_cnf: &CircuitCnf,
519 compile_needed: &TrackedCudaSlice<u32>,
520) -> Result<(GpuCnf, TrackedCudaSlice<u32>)> {
521 let device = provider.device().inner();
522 let memory = provider.memory();
523
524 let phi_clause_cap = phi.clause_cap;
525 let phi_lit_cap = phi.lit_cap;
526
527 let notphi_clause_cap = u32::try_from(
531 (phi_lit_cap as u64)
532 .checked_add(phi_clause_cap as u64)
533 .and_then(|v| v.checked_add(1))
534 .ok_or_else(|| XlogError::Kernel("¬phi clause count overflow".to_string()))?,
535 )
536 .map_err(|_| XlogError::Kernel("¬phi clause count exceeds u32::MAX".to_string()))?;
537 let notphi_lit_cap = u32::try_from(
538 (phi_lit_cap as u64)
539 .checked_mul(3)
540 .and_then(|v| v.checked_add(2u64.saturating_mul(phi_clause_cap as u64)))
541 .ok_or_else(|| XlogError::Kernel("¬phi literal count overflow".to_string()))?,
542 )
543 .map_err(|_| XlogError::Kernel("¬phi literal count exceeds u32::MAX".to_string()))?;
544
545 let var_cap = circuit_cnf
546 .cnf
547 .var_cap
548 .checked_add(phi_clause_cap)
549 .ok_or_else(|| XlogError::Kernel("C ∧ ¬phi var capacity overflow".to_string()))?;
550 let clause_cap = u32::try_from(
551 (circuit_cnf.cnf.clause_cap as u64)
552 .checked_add(1)
553 .and_then(|v| v.checked_add(notphi_clause_cap as u64))
554 .ok_or_else(|| XlogError::Kernel("C ∧ ¬phi clause capacity overflow".to_string()))?,
555 )
556 .map_err(|_| XlogError::Kernel("C ∧ ¬phi clause capacity exceeds u32::MAX".to_string()))?;
557 let lit_cap = u32::try_from(
558 (circuit_cnf.cnf.lit_cap as u64)
559 .checked_add(1)
560 .and_then(|v| v.checked_add(notphi_lit_cap as u64))
561 .ok_or_else(|| XlogError::Kernel("C ∧ ¬phi literal capacity overflow".to_string()))?,
562 )
563 .map_err(|_| XlogError::Kernel("C ∧ ¬phi literal capacity exceeds u32::MAX".to_string()))?;
564
565 let out_num_vars = memory.alloc::<u32>(1)?;
566 let out_num_clauses = memory.alloc::<u32>(1)?;
567 let out_num_lits = memory.alloc::<u32>(1)?;
568
569 let mut d_zero = memory.alloc::<u32>(1)?;
570 provider
571 .htod_launch_metadata_sync_copy_into(&[0u32], &mut d_zero)
572 .map_err(|e| XlogError::Kernel(format!("Failed to upload zero: {}", e)))?;
573
574 let mut d_extra_num_vars = memory.alloc::<u32>(1)?;
576 let mut d_extra_num_clauses = memory.alloc::<u32>(1)?;
577 let mut d_extra_num_lits = memory.alloc::<u32>(1)?;
578
579 let d_unsat_var_base = memory.alloc::<u32>(1)?;
580 let d_notphi_clause_base = memory.alloc::<u32>(1)?;
581 let d_notphi_lit_base = memory.alloc::<u32>(1)?;
582
583 let mut out_offsets = memory.alloc::<u32>((clause_cap as usize) + 1)?;
584 let mut out_lits = memory.alloc::<i32>(lit_cap as usize)?;
585
586 let copy_fn = device
587 .get_func(SAT_MODULE, sat_kernels::SAT_CNF_COPY_INTO)
588 .ok_or_else(|| XlogError::Kernel("sat_cnf_copy_into kernel not found".to_string()))?;
589
590 let block = 256u32;
592 let circuit_copy_elems =
593 checked_clause_offset_span(circuit_cnf.cnf.clause_cap, "sat_cnf_copy_into(circuit)")?
594 .max(circuit_cnf.cnf.lit_cap);
595 let grid = checked_launch_grid(circuit_copy_elems, block, "sat_cnf_copy_into(circuit)")?;
596 unsafe {
599 copy_fn.clone().launch(
600 LaunchConfig {
601 grid_dim: (grid, 1, 1),
602 block_dim: (block, 1, 1),
603 shared_mem_bytes: 0,
604 },
605 (
606 &circuit_cnf.cnf.clause_offsets,
607 &circuit_cnf.cnf.literals,
608 &circuit_cnf.cnf.num_clauses,
609 &circuit_cnf.cnf.num_lits,
610 circuit_cnf.cnf.clause_cap,
611 circuit_cnf.cnf.lit_cap,
612 &d_zero,
613 &d_zero,
614 clause_cap,
615 lit_cap,
616 &mut out_offsets,
617 &mut out_lits,
618 ),
619 )
620 }
621 .map_err(|e| XlogError::Kernel(format!("sat_cnf_copy_into(C) failed: {}", e)))?;
622
623 let notphi_counts_fn = device
625 .get_func(SAT_MODULE, sat_kernels::SAT_NOT_PHI_COUNTS)
626 .ok_or_else(|| XlogError::Kernel("sat_not_phi_counts kernel not found".to_string()))?;
627 unsafe {
629 notphi_counts_fn.clone().launch(
630 LaunchConfig {
631 grid_dim: (1, 1, 1),
632 block_dim: (1, 1, 1),
633 shared_mem_bytes: 0,
634 },
635 (
636 compile_needed,
637 &phi.num_clauses,
638 &phi.num_lits,
639 &mut d_extra_num_vars,
640 &mut d_extra_num_clauses,
641 &mut d_extra_num_lits,
642 ),
643 )
644 }
645 .map_err(|e| XlogError::Kernel(format!("sat_not_phi_counts failed: {}", e)))?;
646
647 let unit_fn = device
649 .get_func(SAT_MODULE, sat_kernels::SAT_XGCF_WRITE_ROOT_UNIT_CLAUSE)
650 .ok_or_else(|| {
651 XlogError::Kernel("sat_xgcf_write_root_unit_clause kernel not found".to_string())
652 })?;
653
654 let root = circuit.root();
656 let force_true: i32 = 1;
657 let out_var_cap = var_cap;
658 let out_clause_cap = clause_cap;
659 let out_lit_cap = lit_cap;
660
661 let mut params: Vec<*mut c_void> = vec![
662 compile_needed.as_kernel_param(),
663 circuit.node_type().as_kernel_param(),
664 circuit.lit().as_kernel_param(),
665 (&circuit_cnf.internal_prefix).as_kernel_param(),
666 (&phi.num_vars).as_kernel_param(),
667 root.as_kernel_param(),
668 force_true.as_kernel_param(), (&d_zero).as_kernel_param(), (&d_zero).as_kernel_param(), (&circuit_cnf.cnf.num_vars).as_kernel_param(),
672 (&circuit_cnf.cnf.num_clauses).as_kernel_param(),
673 (&circuit_cnf.cnf.num_lits).as_kernel_param(),
674 (&d_extra_num_vars).as_kernel_param(), (&d_extra_num_clauses).as_kernel_param(), (&d_extra_num_lits).as_kernel_param(), out_var_cap.as_kernel_param(),
678 out_clause_cap.as_kernel_param(),
679 out_lit_cap.as_kernel_param(),
680 (&out_num_vars).as_kernel_param(),
681 (&out_num_clauses).as_kernel_param(),
682 (&out_num_lits).as_kernel_param(),
683 (&d_unsat_var_base).as_kernel_param(),
684 (&d_notphi_clause_base).as_kernel_param(),
685 (&d_notphi_lit_base).as_kernel_param(),
686 (&out_offsets).as_kernel_param(),
687 (&out_lits).as_kernel_param(),
688 ];
689
690 unsafe {
692 unit_fn.clone().launch(
693 LaunchConfig {
694 grid_dim: (1, 1, 1),
695 block_dim: (1, 1, 1),
696 shared_mem_bytes: 0,
697 },
698 &mut params,
699 )
700 }
701 .map_err(|e| XlogError::Kernel(format!("sat_xgcf_write_root_unit_clause failed: {}", e)))?;
702
703 let not_phi_fn = device
705 .get_func(SAT_MODULE, sat_kernels::SAT_EMIT_NOT_PHI)
706 .ok_or_else(|| XlogError::Kernel("sat_emit_not_phi kernel not found".to_string()))?;
707
708 let block = 256u32;
709 let grid = checked_launch_grid(phi_clause_cap, block, "sat_emit_not_phi")?;
710
711 unsafe {
713 not_phi_fn.clone().launch(
714 LaunchConfig {
715 grid_dim: (grid, 1, 1),
716 block_dim: (block, 1, 1),
717 shared_mem_bytes: 0,
718 },
719 (
720 compile_needed,
721 &phi.clause_offsets,
722 &phi.literals,
723 &phi.num_clauses,
724 &d_unsat_var_base,
725 &d_notphi_clause_base,
726 &d_notphi_lit_base,
727 &mut out_offsets,
728 &mut out_lits,
729 ),
730 )
731 }
732 .map_err(|e| XlogError::Kernel(format!("sat_emit_not_phi failed: {}", e)))?;
733 Ok((
736 GpuCnf {
737 var_cap,
738 clause_cap,
739 lit_cap,
740 num_vars: out_num_vars,
741 num_clauses: out_num_clauses,
742 num_lits: out_num_lits,
743 clause_offsets: out_offsets,
744 literals: out_lits,
745 },
746 d_unsat_var_base,
747 ))
748}
749
750pub(crate) fn check_equivalence_gpu(
751 phi: &GpuCnf,
752 phi_decision_var_limit: &TrackedCudaSlice<u32>,
753 circuit: &GpuXgcf,
754 provider: &Arc<CudaKernelProvider>,
755 config: GpuEquivalenceConfig,
756) -> Result<()> {
757 let queries = build_equivalence_queries_gpu(phi, circuit, provider)?;
758
759 #[cfg(debug_assertions)]
760 {
761 validate_cnf_gpu(&queries.q1, provider.as_ref())?;
763 validate_cnf_gpu(&queries.q2, provider.as_ref())?;
764 }
765
766 let solver = GpuCdclSolver::new(provider.clone(), config.cdcl);
767 if config.reuse_workspace {
768 let max_var_cap = std::cmp::max(queries.q1.var_cap, queries.q2.var_cap);
769 let max_clause_cap = std::cmp::max(queries.q1.clause_cap, queries.q2.clause_cap);
770 let mut ws = solver.new_workspace(max_var_cap, max_clause_cap)?;
771 solver.solve_expect_unsat_with_branch_limit_ws(
773 &mut ws,
774 &queries.q1,
775 phi_decision_var_limit,
776 )?;
777 solver.solve_expect_unsat_with_decision_ranges_ws(
779 &mut ws,
780 &queries.q2,
781 phi_decision_var_limit,
782 &queries.q2_unsat_var_base,
783 &phi.num_clauses,
784 )?;
785 } else {
786 solver.solve_expect_unsat_with_branch_limit(&queries.q1, phi_decision_var_limit)?;
788 solver.solve_expect_unsat_with_decision_ranges(
790 &queries.q2,
791 phi_decision_var_limit,
792 &queries.q2_unsat_var_base,
793 &phi.num_clauses,
794 )?;
795 }
796 Ok(())
797}
798
799pub fn build_equivalence_queries_gpu(
806 phi: &GpuCnf,
807 circuit: &GpuXgcf,
808 provider: &Arc<CudaKernelProvider>,
809) -> Result<GpuEquivalenceQueries> {
810 let memory = provider.memory();
812 let mut compile_needed = memory.alloc::<u32>(1)?;
813 provider
814 .htod_launch_metadata_sync_copy_into(&[1u32], &mut compile_needed)
815 .map_err(|e| XlogError::Kernel(format!("Failed to upload compile_needed=1: {}", e)))?;
816
817 let circuit_cnf = build_circuit_cnf(
818 provider,
819 circuit,
820 &phi.num_vars,
821 phi.var_cap,
822 &compile_needed,
823 )?;
824 let q1 = build_phi_and_not_c(provider, phi, circuit, &circuit_cnf, &compile_needed)?;
825 let (q2, q2_unsat_var_base) =
826 build_c_and_not_phi(provider, phi, circuit, &circuit_cnf, &compile_needed)?;
827 Ok(GpuEquivalenceQueries {
828 q1,
829 q2,
830 q2_unsat_var_base,
831 })
832}
833
834pub(crate) fn check_equivalence_gpu_gated(
835 phi: &GpuCnf,
836 phi_decision_var_limit: &TrackedCudaSlice<u32>,
837 circuit: &GpuXgcf,
838 provider: &Arc<CudaKernelProvider>,
839 config: GpuEquivalenceConfig,
840 compile_needed: &TrackedCudaSlice<u32>,
841) -> Result<()> {
842 #[cfg(debug_assertions)]
843 eprintln!("[xlog-prob] equivalence: build_circuit_cnf");
844 let circuit_cnf = build_circuit_cnf(
845 provider,
846 circuit,
847 &phi.num_vars,
848 phi.var_cap,
849 compile_needed,
850 )?;
851 #[cfg(debug_assertions)]
852 {
853 provider.device().synchronize().map_err(|e| {
854 XlogError::Kernel(format!("sync after build_circuit_cnf failed: {}", e))
855 })?;
856 eprintln!("[xlog-prob] equivalence: build_phi_and_not_c");
857 }
858
859 let q1 = build_phi_and_not_c(provider, phi, circuit, &circuit_cnf, compile_needed)?;
860 #[cfg(debug_assertions)]
861 {
862 provider.device().synchronize().map_err(|e| {
863 XlogError::Kernel(format!("sync after build_phi_and_not_c failed: {}", e))
864 })?;
865 eprintln!("[xlog-prob] equivalence: build_c_and_not_phi");
866 }
867 let (q2, q2_unsat_var_base) =
868 build_c_and_not_phi(provider, phi, circuit, &circuit_cnf, compile_needed)?;
869 #[cfg(debug_assertions)]
870 {
871 provider.device().synchronize().map_err(|e| {
872 XlogError::Kernel(format!("sync after build_c_and_not_phi failed: {}", e))
873 })?;
874 eprintln!(
875 "[xlog-prob] equivalence: caps: phi(v={} c={} l={}) circuit_cnf(v={} c={} l={}) q1(v={} c={} l={}) q2(v={} c={} l={})",
876 phi.var_cap,
877 phi.clause_cap,
878 phi.lit_cap,
879 circuit_cnf.cnf.var_cap,
880 circuit_cnf.cnf.clause_cap,
881 circuit_cnf.cnf.lit_cap,
882 q1.var_cap,
883 q1.clause_cap,
884 q1.lit_cap,
885 q2.var_cap,
886 q2.clause_cap,
887 q2.lit_cap,
888 );
889 eprintln!("[xlog-prob] equivalence: solve_expect_unsat q1");
890 }
891
892 #[cfg(debug_assertions)]
893 {
894 validate_cnf_gpu(&q1, provider.as_ref())?;
895 validate_cnf_gpu(&q2, provider.as_ref())?;
896 }
897
898 let solver = GpuCdclSolver::new(provider.clone(), config.cdcl);
899 if config.reuse_workspace {
900 let max_var_cap = std::cmp::max(q1.var_cap, q2.var_cap);
901 let max_clause_cap = std::cmp::max(q1.clause_cap, q2.clause_cap);
902 let mut ws = solver.new_workspace(max_var_cap, max_clause_cap)?;
903 solver.solve_expect_unsat_with_branch_limit_gated_ws(
904 &mut ws,
905 &q1,
906 compile_needed,
907 phi_decision_var_limit,
908 )?;
909 #[cfg(debug_assertions)]
910 {
911 provider.device().synchronize().map_err(|e| {
912 XlogError::Kernel(format!("sync after solve_expect_unsat(q1) failed: {}", e))
913 })?;
914 eprintln!("[xlog-prob] equivalence: solve_expect_unsat q2");
915 }
916 solver.solve_expect_unsat_with_decision_ranges_gated_ws(
917 &mut ws,
918 &q2,
919 compile_needed,
920 phi_decision_var_limit,
921 &q2_unsat_var_base,
922 &phi.num_clauses,
923 )?;
924 } else {
925 solver.solve_expect_unsat_with_branch_limit_gated(
926 &q1,
927 compile_needed,
928 phi_decision_var_limit,
929 )?;
930 #[cfg(debug_assertions)]
931 {
932 provider.device().synchronize().map_err(|e| {
933 XlogError::Kernel(format!("sync after solve_expect_unsat(q1) failed: {}", e))
934 })?;
935 eprintln!("[xlog-prob] equivalence: solve_expect_unsat q2");
936 }
937 solver.solve_expect_unsat_with_decision_ranges_gated(
938 &q2,
939 compile_needed,
940 phi_decision_var_limit,
941 &q2_unsat_var_base,
942 &phi.num_clauses,
943 )?;
944 }
945 #[cfg(debug_assertions)]
946 {
947 provider.device().synchronize().map_err(|e| {
948 XlogError::Kernel(format!("sync after solve_expect_unsat(q2) failed: {}", e))
949 })?;
950 eprintln!("[xlog-prob] equivalence: done");
951 }
952 Ok(())
953}
954
955fn verify_size_budget() -> (u32, u32) {
969 fn env_u32(key: &str) -> u32 {
970 std::env::var(key)
971 .ok()
972 .and_then(|v| v.trim().parse::<u32>().ok())
973 .unwrap_or(u32::MAX)
974 }
975 static BUDGET: std::sync::OnceLock<(u32, u32)> = std::sync::OnceLock::new();
976 *BUDGET.get_or_init(|| {
977 (
978 env_u32("XLOG_D4_VERIFY_MAX_VARS"),
979 env_u32("XLOG_D4_VERIFY_MAX_CLAUSES"),
980 )
981 })
982}
983
984fn enforce_verify_size_bound(
988 var_cap: u32,
989 clause_cap: u32,
990 var_budget: u32,
991 clause_budget: u32,
992 context: &str,
993) -> Result<()> {
994 if var_cap > var_budget || clause_cap > clause_budget {
995 return Err(XlogError::CompileCapacityExceeded {
998 context: context.to_string(),
999 detail: format!(
1000 "CNF {var_cap} vars / {clause_cap} clauses exceeds size bound \
1001 ({var_budget} vars / {clause_budget} clauses)"
1002 ),
1003 });
1004 }
1005 Ok(())
1006}
1007
1008pub(crate) fn check_verify_size_bound(phi: &GpuCnf, context: &str) -> Result<()> {
1016 if resolve_bool(None, "XLOG_DEBUG_VERIFY_SIZE", false)? {
1021 eprintln!(
1022 "[xlog-prob] verify-size {context}: var_cap={} clause_cap={} lit_cap={}",
1023 phi.var_cap, phi.clause_cap, phi.lit_cap
1024 );
1025 }
1026 let (var_budget, clause_budget) = verify_size_budget();
1027 enforce_verify_size_bound(
1028 phi.var_cap,
1029 phi.clause_cap,
1030 var_budget,
1031 clause_budget,
1032 context,
1033 )
1034}
1035
1036pub fn validate_equivalence_gpu(
1038 phi: &GpuCnf,
1039 phi_decision_var_limit: &TrackedCudaSlice<u32>,
1040 circuit: &GpuXgcf,
1041 provider: &Arc<CudaKernelProvider>,
1042 config: GpuEquivalenceConfig,
1043) -> Result<()> {
1044 check_verify_size_bound(phi, "validate_equivalence_gpu")?;
1045 check_equivalence_gpu(phi, phi_decision_var_limit, circuit, provider, config)
1046}
1047
1048pub fn validate_equivalence_gpu_gated(
1050 phi: &GpuCnf,
1051 phi_decision_var_limit: &TrackedCudaSlice<u32>,
1052 circuit: &GpuXgcf,
1053 provider: &Arc<CudaKernelProvider>,
1054 config: GpuEquivalenceConfig,
1055 compile_needed: &TrackedCudaSlice<u32>,
1056) -> Result<()> {
1057 check_verify_size_bound(phi, "validate_equivalence_gpu_gated")?;
1058 check_equivalence_gpu_gated(
1059 phi,
1060 phi_decision_var_limit,
1061 circuit,
1062 provider,
1063 config,
1064 compile_needed,
1065 )
1066}
1067
1068#[cfg(test)]
1069mod verify_size_bound_tests {
1070 use super::enforce_verify_size_bound;
1071 use xlog_core::XlogError;
1072
1073 #[test]
1077 fn over_var_budget_declines_typed() {
1078 let err = enforce_verify_size_bound(5000, 100, 4096, u32::MAX, "ctx")
1079 .expect_err("must decline over var budget");
1080 match err {
1081 XlogError::CompileCapacityExceeded { context, detail } => {
1083 assert_eq!(context, "ctx");
1084 assert!(detail.contains("5000 vars"), "detail: {detail}");
1086 assert!(detail.contains("size bound"), "detail: {detail}");
1087 }
1088 other => panic!("wrong error variant: {other:?}"),
1089 }
1090 }
1091
1092 #[test]
1093 fn over_clause_budget_declines_typed() {
1094 let err = enforce_verify_size_bound(10, 20_000, u32::MAX, 16_384, "ctx")
1095 .expect_err("must decline over clause budget");
1096 assert!(matches!(err, XlogError::CompileCapacityExceeded { .. }));
1097 }
1098
1099 #[test]
1100 fn within_budget_proceeds() {
1101 enforce_verify_size_bound(100, 200, 4096, 16_384, "ctx")
1102 .expect("within budget must pass the bound");
1103 }
1104
1105 #[test]
1108 fn unbounded_default_never_declines() {
1109 enforce_verify_size_bound(u32::MAX, u32::MAX, u32::MAX, u32::MAX, "ctx")
1110 .expect("unbounded default must not decline");
1111 }
1112}