1use std::ffi::c_void;
2use std::sync::Arc;
3
4use cudarc::driver::LaunchConfig;
5#[cfg(debug_assertions)]
6use xlog_core::resolve_bool;
7use xlog_core::{Result, XlogError};
8use xlog_cuda::memory::TrackedCudaSlice;
9use xlog_cuda::provider::{sat_kernels, SAT_MODULE};
10use xlog_cuda::{AsKernelParam, CudaKernelProvider, LaunchAsync};
11
12use crate::gpu_cnf::GpuCnf;
13
14const SAT_STATUS_UNSAT: i32 = 0;
16const SAT_STATUS_SAT: i32 = 1;
17const SAT_STATUS_BUDGET_EXHAUSTED: i32 = 2;
21
22struct GpuCdclRun {
23 assignment: TrackedCudaSlice<i8>,
24 _level: TrackedCudaSlice<u32>,
27 _reason: TrackedCudaSlice<i32>,
28 _var_activity: TrackedCudaSlice<u32>,
29 _var_phase: TrackedCudaSlice<i8>,
30 _decision_heap: TrackedCudaSlice<u32>,
31 _decision_heap_pos: TrackedCudaSlice<u32>,
32 _trail: TrackedCudaSlice<i32>,
33 _trail_lim: TrackedCudaSlice<u32>,
34 _seen: TrackedCudaSlice<u8>,
35 _learnt_tmp: TrackedCudaSlice<i32>,
36 _proof_vars_tmp: TrackedCudaSlice<u32>,
37 _proof_reason_tmp: TrackedCudaSlice<u32>,
38 _watch0_pos: TrackedCudaSlice<u32>,
39 _watch1_pos: TrackedCudaSlice<u32>,
40 _watch_head: TrackedCudaSlice<i32>,
41 _watch_next: TrackedCudaSlice<i32>,
42 _watch_prev: TrackedCudaSlice<i32>,
43
44 learned_offsets: TrackedCudaSlice<u32>,
45 learned_lits: TrackedCudaSlice<i32>,
46 _learned_deleted: TrackedCudaSlice<u8>,
47 _learned_lbd: TrackedCudaSlice<u32>,
48 _learned_activity: TrackedCudaSlice<u32>,
49 _learned_locked: TrackedCudaSlice<u8>,
50 proof_offsets: TrackedCudaSlice<u32>,
51 proof_data: TrackedCudaSlice<u32>,
52
53 out_status: TrackedCudaSlice<i32>,
54 out_error: TrackedCudaSlice<i32>,
55 out_learned_count: TrackedCudaSlice<u32>,
56}
57
58#[derive(Debug, Clone, Copy)]
65#[non_exhaustive]
66pub struct GpuCdclConfig {
67 pub max_learned_clauses: u32,
69 pub max_learned_lits: u32,
71 pub max_proof_u32: u32,
73 pub restart_base: u32,
75 pub reduce_interval: u32,
77 pub max_conflicts: u32,
83}
84
85impl Default for GpuCdclConfig {
86 fn default() -> Self {
87 Self {
88 max_learned_clauses: 32_768,
89 max_learned_lits: 262_144,
90 max_proof_u32: 1_048_576,
91 restart_base: 100,
92 reduce_interval: 2000,
93 max_conflicts: 0,
94 }
95 }
96}
97
98pub struct GpuCdclSolver {
100 provider: Arc<CudaKernelProvider>,
101 config: GpuCdclConfig,
102}
103
104pub struct GpuCdclWorkspace {
115 pub(crate) var_cap: usize,
117 pub(crate) clause_total_cap: usize,
118
119 pub(crate) assign: TrackedCudaSlice<i8>,
121 pub(crate) level: TrackedCudaSlice<u32>,
122 pub(crate) reason: TrackedCudaSlice<i32>,
123 pub(crate) var_activity: TrackedCudaSlice<u32>,
124 pub(crate) var_phase: TrackedCudaSlice<i8>,
125 pub(crate) decision_heap: TrackedCudaSlice<u32>,
126 pub(crate) decision_heap_pos: TrackedCudaSlice<u32>,
127
128 pub(crate) trail: TrackedCudaSlice<i32>,
130 pub(crate) trail_lim: TrackedCudaSlice<u32>,
131
132 pub(crate) seen: TrackedCudaSlice<u8>,
134 pub(crate) learnt_tmp: TrackedCudaSlice<i32>,
135 pub(crate) proof_vars_tmp: TrackedCudaSlice<u32>,
136 pub(crate) proof_reason_tmp: TrackedCudaSlice<u32>,
137
138 pub(crate) watch0_pos: TrackedCudaSlice<u32>, pub(crate) watch1_pos: TrackedCudaSlice<u32>, pub(crate) watch_head: TrackedCudaSlice<i32>, pub(crate) watch_next: TrackedCudaSlice<i32>, pub(crate) watch_prev: TrackedCudaSlice<i32>, pub(crate) learned_offsets: TrackedCudaSlice<u32>, pub(crate) learned_lits: TrackedCudaSlice<i32>, pub(crate) learned_deleted: TrackedCudaSlice<u8>, pub(crate) learned_lbd: TrackedCudaSlice<u32>, pub(crate) learned_activity: TrackedCudaSlice<u32>, pub(crate) learned_locked: TrackedCudaSlice<u8>, pub(crate) proof_offsets: TrackedCudaSlice<u32>, pub(crate) proof_data: TrackedCudaSlice<u32>, pub(crate) out_status: TrackedCudaSlice<i32>, pub(crate) out_error: TrackedCudaSlice<i32>, pub(crate) out_learned_count: TrackedCudaSlice<u32>, }
162
163impl GpuCdclWorkspace {
164 #[inline]
166 pub(crate) fn reset_for_solve(&mut self) {
167 }
169
170 #[inline]
172 pub fn assign_device_ptr(&self) -> cudarc::driver::sys::CUdeviceptr {
173 self.assign.device_ptr_value()
174 }
175}
176
177pub struct GpuCdclRawOutput {
182 pub assignment: TrackedCudaSlice<i8>,
184 pub out_status: TrackedCudaSlice<i32>,
186 pub out_error: TrackedCudaSlice<i32>,
188 pub out_learned_count: TrackedCudaSlice<u32>,
190}
191
192fn checked_solver_len_add_one(context: &str, value: usize) -> Result<usize> {
193 value
194 .checked_add(1)
195 .ok_or_else(|| XlogError::Kernel(format!("{context} length overflow")))
196}
197
198fn checked_solver_len_double(context: &str, value: usize) -> Result<usize> {
199 value
200 .checked_mul(2)
201 .ok_or_else(|| XlogError::Kernel(format!("{context} length overflow")))
202}
203
204impl GpuCdclSolver {
205 fn require_expected_status(
206 &self,
207 out_status: &TrackedCudaSlice<i32>,
208 out_error: &TrackedCudaSlice<i32>,
209 expected_status: i32,
210 context: &'static str,
211 ) -> Result<()> {
212 let actual_status = self
213 .provider
214 .dtoh_scalar_untracked(out_status, 0)
215 .map_err(|e| XlogError::Kernel(format!("Failed to read {context} status: {e}")))?;
216 let actual_error = self
217 .provider
218 .dtoh_scalar_untracked(out_error, 0)
219 .map_err(|e| XlogError::Kernel(format!("Failed to read {context} error: {e}")))?;
220 if actual_error == 0 && actual_status == SAT_STATUS_BUDGET_EXHAUSTED {
225 return Err(XlogError::VerifyBudgetExceeded {
226 context: context.to_string(),
227 detail: "CDCL conflict budget exhausted before the search \
228 terminated (indeterminate; verify declined fail-closed)"
229 .to_string(),
230 });
231 }
232 if actual_error != 0 || actual_status != expected_status {
233 return Err(XlogError::Kernel(format!(
234 "{context} expected status {expected_status}, got status {actual_status} error {actual_error}"
235 )));
236 }
237 Ok(())
238 }
239
240 fn provider_memory_ptr(&self) -> usize {
241 Arc::as_ptr(self.provider.memory()) as usize
242 }
243
244 fn require_slice_on_provider<T: cudarc::driver::DeviceRepr>(
245 &self,
246 name: &'static str,
247 slice: &TrackedCudaSlice<T>,
248 ) -> Result<()> {
249 let expected_memory = self.provider_memory_ptr();
250 let actual_memory = slice.memory_manager_ptr_value();
251 if actual_memory != expected_memory {
252 return Err(XlogError::UnsupportedEpistemicConstruct {
253 construct: "GPU CDCL solver provider boundary".to_string(),
254 context: format!(
255 "{name} belongs to memory manager {actual_memory}, expected {expected_memory}"
256 ),
257 });
258 }
259 Ok(())
260 }
261
262 pub(crate) fn require_workspace_on_provider(&self, ws: &GpuCdclWorkspace) -> Result<()> {
263 macro_rules! require_workspace_slice {
264 ($field:ident) => {
265 self.require_slice_on_provider(
266 concat!("workspace.", stringify!($field)),
267 &ws.$field,
268 )?
269 };
270 }
271
272 require_workspace_slice!(assign);
273 require_workspace_slice!(level);
274 require_workspace_slice!(reason);
275 require_workspace_slice!(var_activity);
276 require_workspace_slice!(var_phase);
277 require_workspace_slice!(decision_heap);
278 require_workspace_slice!(decision_heap_pos);
279 require_workspace_slice!(trail);
280 require_workspace_slice!(trail_lim);
281 require_workspace_slice!(seen);
282 require_workspace_slice!(learnt_tmp);
283 require_workspace_slice!(proof_vars_tmp);
284 require_workspace_slice!(proof_reason_tmp);
285 require_workspace_slice!(watch0_pos);
286 require_workspace_slice!(watch1_pos);
287 require_workspace_slice!(watch_head);
288 require_workspace_slice!(watch_next);
289 require_workspace_slice!(watch_prev);
290 require_workspace_slice!(learned_offsets);
291 require_workspace_slice!(learned_lits);
292 require_workspace_slice!(learned_deleted);
293 require_workspace_slice!(learned_lbd);
294 require_workspace_slice!(learned_activity);
295 require_workspace_slice!(learned_locked);
296 require_workspace_slice!(proof_offsets);
297 require_workspace_slice!(proof_data);
298 require_workspace_slice!(out_status);
299 require_workspace_slice!(out_error);
300 require_workspace_slice!(out_learned_count);
301 Ok(())
302 }
303
304 pub(crate) fn require_workspace_capacity_for_cnf(
305 &self,
306 ws: &GpuCdclWorkspace,
307 var_cap: u32,
308 clause_cap: u32,
309 ) -> Result<()> {
310 let num_vars_cap = var_cap as usize;
311 if num_vars_cap > ws.var_cap {
312 return Err(XlogError::Kernel(format!(
313 "CNF var_cap {} exceeds workspace var_cap {}",
314 num_vars_cap, ws.var_cap
315 )));
316 }
317
318 let max_learned_clauses = self.config.max_learned_clauses as usize;
319 let max_total_clauses = (clause_cap as usize)
320 .checked_add(max_learned_clauses)
321 .ok_or_else(|| XlogError::Kernel("SAT clause capacity overflow".to_string()))?;
322 if max_total_clauses > ws.clause_total_cap {
323 return Err(XlogError::Kernel(format!(
324 "CNF clause_total {} exceeds workspace clause_total_cap {}",
325 max_total_clauses, ws.clause_total_cap
326 )));
327 }
328
329 Ok(())
330 }
331
332 pub fn new(provider: Arc<CudaKernelProvider>, config: GpuCdclConfig) -> Self {
334 Self { provider, config }
335 }
336
337 pub fn new_workspace(&self, max_var_cap: u32, max_clause_cap: u32) -> Result<GpuCdclWorkspace> {
343 let num_vars_cap = max_var_cap as usize;
344 let num_clauses_cap = max_clause_cap as usize;
345 let max_learned_clauses = self.config.max_learned_clauses as usize;
346 let max_learned_lits = self.config.max_learned_lits as usize;
347 let max_proof_u32 = self.config.max_proof_u32 as usize;
348
349 if max_var_cap == 0 {
350 return Err(XlogError::Compilation(
351 "GpuCdclSolver workspace requires max_var_cap > 0".to_string(),
352 ));
353 }
354 if self.config.max_learned_clauses == 0 {
355 return Err(XlogError::Compilation(
356 "GpuCdclSolver requires max_learned_clauses > 0".to_string(),
357 ));
358 }
359 if self.config.max_learned_lits == 0 {
360 return Err(XlogError::Compilation(
361 "GpuCdclSolver requires max_learned_lits > 0".to_string(),
362 ));
363 }
364 if self.config.max_proof_u32 < 2 {
365 return Err(XlogError::Compilation(
366 "GpuCdclSolver requires max_proof_u32 >= 2".to_string(),
367 ));
368 }
369
370 let max_total_clauses = num_clauses_cap
371 .checked_add(max_learned_clauses)
372 .ok_or_else(|| XlogError::Kernel("SAT clause capacity overflow".to_string()))?;
373 let vars_plus_one =
374 checked_solver_len_add_one("SAT workspace variable arena", num_vars_cap)?;
375 let learned_offsets_len =
376 checked_solver_len_add_one("SAT workspace learned offsets", max_learned_clauses)?;
377 let watch_head_len = checked_solver_len_double("SAT workspace watch head", num_vars_cap)?;
378 let watch_clause_len =
379 checked_solver_len_double("SAT workspace watch clauses", max_total_clauses)?;
380
381 let memory = self.provider.memory();
382
383 Ok(GpuCdclWorkspace {
384 var_cap: num_vars_cap,
385 clause_total_cap: max_total_clauses,
386
387 assign: memory.alloc::<i8>(vars_plus_one)?,
389 level: memory.alloc::<u32>(vars_plus_one)?,
390 reason: memory.alloc::<i32>(vars_plus_one)?,
391 var_activity: memory.alloc::<u32>(vars_plus_one)?,
392 var_phase: memory.alloc::<i8>(vars_plus_one)?,
393 decision_heap: memory.alloc::<u32>(vars_plus_one)?,
394 decision_heap_pos: memory.alloc::<u32>(vars_plus_one)?,
395
396 trail: memory.alloc::<i32>(vars_plus_one)?,
398 trail_lim: memory.alloc::<u32>(vars_plus_one)?,
399
400 seen: memory.alloc::<u8>(vars_plus_one)?,
402 learnt_tmp: memory.alloc::<i32>(vars_plus_one)?,
403 proof_vars_tmp: memory.alloc::<u32>(vars_plus_one)?,
404 proof_reason_tmp: memory.alloc::<u32>(vars_plus_one)?,
405
406 watch0_pos: memory.alloc::<u32>(max_total_clauses)?,
408 watch1_pos: memory.alloc::<u32>(max_total_clauses)?,
409 watch_head: memory.alloc::<i32>(watch_head_len)?,
410 watch_next: memory.alloc::<i32>(watch_clause_len)?,
411 watch_prev: memory.alloc::<i32>(watch_clause_len)?,
412
413 learned_offsets: memory.alloc::<u32>(learned_offsets_len)?,
415 learned_lits: memory.alloc::<i32>(max_learned_lits)?,
416 learned_deleted: memory.alloc::<u8>(max_learned_clauses)?,
417 learned_lbd: memory.alloc::<u32>(max_learned_clauses)?,
418 learned_activity: memory.alloc::<u32>(max_learned_clauses)?,
419 learned_locked: memory.alloc::<u8>(max_learned_clauses)?,
420
421 proof_offsets: memory.alloc::<u32>(learned_offsets_len)?,
423 proof_data: memory.alloc::<u32>(max_proof_u32)?,
424
425 out_status: memory.alloc::<i32>(1)?,
427 out_error: memory.alloc::<i32>(1)?,
428 out_learned_count: memory.alloc::<u32>(1)?,
429 })
430 }
431
432 fn alloc_u32_scalar(&self, value: u32) -> Result<TrackedCudaSlice<u32>> {
433 let memory = self.provider.memory();
434 let mut gate = memory.alloc::<u32>(1)?;
435 self.provider
436 .htod_launch_metadata_sync_copy_into(&[value], &mut gate)
437 .map_err(|e| XlogError::Kernel(format!("GpuCdclSolver gate upload failed: {}", e)))?;
438 Ok(gate)
439 }
440
441 fn launch_cdcl_with_decision_ranges_gated(
442 &self,
443 cnf: &GpuCnf,
444 compile_needed: &TrackedCudaSlice<u32>,
445 decision_base_limit: &TrackedCudaSlice<u32>,
446 decision_extra_base: &TrackedCudaSlice<u32>,
447 decision_extra_count: &TrackedCudaSlice<u32>,
448 ) -> Result<GpuCdclRun> {
449 let num_vars_cap = cnf.var_cap as usize;
450 let num_clauses_cap = cnf.clause_cap as usize;
451
452 cnf.require_provider_memory(&self.provider, "GPU CDCL solver provider boundary")?;
453 self.require_slice_on_provider("compile_needed", compile_needed)?;
454 self.require_slice_on_provider("decision_base_limit", decision_base_limit)?;
455 self.require_slice_on_provider("decision_extra_base", decision_extra_base)?;
456 self.require_slice_on_provider("decision_extra_count", decision_extra_count)?;
457 if compile_needed.len() != 1 {
458 return Err(XlogError::Compilation(format!(
459 "GpuCdclSolver requires compile_needed len=1, got {}",
460 compile_needed.len()
461 )));
462 }
463
464 if cnf.var_cap == 0 {
465 return Err(XlogError::Compilation(
466 "GpuCdclSolver requires num_vars > 0".to_string(),
467 ));
468 }
469 if decision_base_limit.len() != 1 {
470 return Err(XlogError::Compilation(format!(
471 "GpuCdclSolver requires decision_base_limit len=1, got {}",
472 decision_base_limit.len()
473 )));
474 }
475 if decision_extra_base.len() != 1 {
476 return Err(XlogError::Compilation(format!(
477 "GpuCdclSolver requires decision_extra_base len=1, got {}",
478 decision_extra_base.len()
479 )));
480 }
481 if decision_extra_count.len() != 1 {
482 return Err(XlogError::Compilation(format!(
483 "GpuCdclSolver requires decision_extra_count len=1, got {}",
484 decision_extra_count.len()
485 )));
486 }
487 if self.config.max_learned_clauses == 0 {
488 return Err(XlogError::Compilation(
489 "GpuCdclSolver requires max_learned_clauses > 0".to_string(),
490 ));
491 }
492 if self.config.max_learned_lits == 0 {
493 return Err(XlogError::Compilation(
494 "GpuCdclSolver requires max_learned_lits > 0".to_string(),
495 ));
496 }
497 if self.config.max_proof_u32 < 2 {
498 return Err(XlogError::Compilation(
499 "GpuCdclSolver requires max_proof_u32 >= 2".to_string(),
500 ));
501 }
502
503 let max_learned_clauses = self.config.max_learned_clauses as usize;
504 let max_learned_lits = self.config.max_learned_lits as usize;
505 let max_proof_u32 = self.config.max_proof_u32 as usize;
506
507 let max_total_clauses = num_clauses_cap
508 .checked_add(max_learned_clauses)
509 .ok_or_else(|| XlogError::Kernel("SAT clause capacity overflow".to_string()))?;
510 let vars_plus_one = checked_solver_len_add_one("SAT variable arena", num_vars_cap)?;
511 let learned_offsets_len =
512 checked_solver_len_add_one("SAT learned offsets", max_learned_clauses)?;
513 let watch_head_len = checked_solver_len_double("SAT watch head", num_vars_cap)?;
514 let watch_clause_len = checked_solver_len_double("SAT watch clauses", max_total_clauses)?;
515
516 let memory = self.provider.memory();
517
518 let assign = memory.alloc::<i8>(vars_plus_one)?;
520 let level = memory.alloc::<u32>(vars_plus_one)?;
521 let reason = memory.alloc::<i32>(vars_plus_one)?;
522 let var_activity = memory.alloc::<u32>(vars_plus_one)?;
523 let var_phase = memory.alloc::<i8>(vars_plus_one)?;
524 let decision_heap = memory.alloc::<u32>(vars_plus_one)?;
525 let decision_heap_pos = memory.alloc::<u32>(vars_plus_one)?;
526
527 let trail = memory.alloc::<i32>(vars_plus_one)?;
529 let trail_lim = memory.alloc::<u32>(vars_plus_one)?;
530
531 let seen = memory.alloc::<u8>(vars_plus_one)?;
533 let learnt_tmp = memory.alloc::<i32>(vars_plus_one)?;
534 let proof_vars_tmp = memory.alloc::<u32>(vars_plus_one)?;
535 let proof_reason_tmp = memory.alloc::<u32>(vars_plus_one)?;
536
537 let watch0_pos = memory.alloc::<u32>(max_total_clauses)?;
539 let watch1_pos = memory.alloc::<u32>(max_total_clauses)?;
540 let watch_head = memory.alloc::<i32>(watch_head_len)?;
541 let watch_next = memory.alloc::<i32>(watch_clause_len)?;
542 let watch_prev = memory.alloc::<i32>(watch_clause_len)?;
543
544 let learned_offsets = memory.alloc::<u32>(learned_offsets_len)?;
546 let learned_lits = memory.alloc::<i32>(max_learned_lits)?;
547 let learned_deleted = memory.alloc::<u8>(max_learned_clauses)?;
548 let learned_lbd = memory.alloc::<u32>(max_learned_clauses)?;
549 let learned_activity = memory.alloc::<u32>(max_learned_clauses)?;
550 let learned_locked = memory.alloc::<u8>(max_learned_clauses)?;
551
552 let proof_offsets = memory.alloc::<u32>(learned_offsets_len)?;
554 let proof_data = memory.alloc::<u32>(max_proof_u32)?;
555
556 let out_status = memory.alloc::<i32>(1)?;
558 let out_error = memory.alloc::<i32>(1)?;
559 let mut out_learned_count = memory.alloc::<u32>(1)?;
560 self.provider
561 .htod_launch_metadata_sync_copy_into(&[0u32], &mut out_learned_count)
562 .map_err(|e| {
563 XlogError::Kernel(format!("Failed to init learned import count: {}", e))
564 })?;
565
566 let sat_fn = self
567 .provider
568 .device()
569 .inner()
570 .get_func(SAT_MODULE, sat_kernels::SAT_CDCL_SOLVE)
571 .ok_or_else(|| XlogError::Kernel("sat_cdcl_solve kernel not found".to_string()))?;
572
573 let cnf_var_cap = cnf.var_cap;
577 let cnf_clause_cap = cnf.clause_cap;
578 let cfg_max_learned_clauses = self.config.max_learned_clauses;
579 let cfg_max_learned_lits = self.config.max_learned_lits;
580 let cfg_max_proof_u32 = self.config.max_proof_u32;
581 let cfg_restart_base = self.config.restart_base;
582 let cfg_reduce_interval = self.config.reduce_interval;
583 let cfg_max_conflicts = self.config.max_conflicts;
584 let learned_import_count_param = (&out_learned_count).as_kernel_param();
585
586 let mut params: Vec<*mut c_void> = vec![
587 compile_needed.as_kernel_param(),
588 (&cnf.clause_offsets).as_kernel_param(),
589 (&cnf.literals).as_kernel_param(),
590 (&cnf.num_vars).as_kernel_param(),
591 (&cnf.num_clauses).as_kernel_param(),
592 decision_base_limit.as_kernel_param(),
593 decision_extra_base.as_kernel_param(),
594 decision_extra_count.as_kernel_param(),
595 cnf_var_cap.as_kernel_param(),
596 cnf_clause_cap.as_kernel_param(),
597 cfg_max_learned_clauses.as_kernel_param(),
598 cfg_max_learned_lits.as_kernel_param(),
599 cfg_max_proof_u32.as_kernel_param(),
600 cfg_restart_base.as_kernel_param(),
601 cfg_reduce_interval.as_kernel_param(),
602 cfg_max_conflicts.as_kernel_param(),
603 learned_import_count_param,
604 (&assign).as_kernel_param(),
605 (&level).as_kernel_param(),
606 (&reason).as_kernel_param(),
607 (&var_activity).as_kernel_param(),
608 (&var_phase).as_kernel_param(),
609 (&decision_heap).as_kernel_param(),
610 (&decision_heap_pos).as_kernel_param(),
611 (&trail).as_kernel_param(),
612 (&trail_lim).as_kernel_param(),
613 (&seen).as_kernel_param(),
614 (&learnt_tmp).as_kernel_param(),
615 (&proof_vars_tmp).as_kernel_param(),
616 (&proof_reason_tmp).as_kernel_param(),
617 (&watch0_pos).as_kernel_param(),
618 (&watch1_pos).as_kernel_param(),
619 (&watch_head).as_kernel_param(),
620 (&watch_next).as_kernel_param(),
621 (&watch_prev).as_kernel_param(),
622 (&learned_offsets).as_kernel_param(),
623 (&learned_lits).as_kernel_param(),
624 (&learned_deleted).as_kernel_param(),
625 (&learned_lbd).as_kernel_param(),
626 (&learned_activity).as_kernel_param(),
627 (&learned_locked).as_kernel_param(),
628 (&proof_offsets).as_kernel_param(),
629 (&proof_data).as_kernel_param(),
630 (&out_status).as_kernel_param(),
631 (&out_error).as_kernel_param(),
632 (&out_learned_count).as_kernel_param(),
633 ];
634
635 unsafe {
637 sat_fn.clone().launch(
638 LaunchConfig {
639 grid_dim: (1, 1, 1),
640 block_dim: (256, 1, 1),
643 shared_mem_bytes: 0,
644 },
645 &mut params,
646 )
647 }
648 .map_err(|e| XlogError::Kernel(format!("Failed to launch SAT solver kernel: {}", e)))?;
649
650 Ok(GpuCdclRun {
651 assignment: assign,
652 _level: level,
653 _reason: reason,
654 _var_activity: var_activity,
655 _var_phase: var_phase,
656 _decision_heap: decision_heap,
657 _decision_heap_pos: decision_heap_pos,
658 _trail: trail,
659 _trail_lim: trail_lim,
660 _seen: seen,
661 _learnt_tmp: learnt_tmp,
662 _proof_vars_tmp: proof_vars_tmp,
663 _proof_reason_tmp: proof_reason_tmp,
664 _watch0_pos: watch0_pos,
665 _watch1_pos: watch1_pos,
666 _watch_head: watch_head,
667 _watch_next: watch_next,
668 _watch_prev: watch_prev,
669 learned_offsets,
670 learned_lits,
671 _learned_deleted: learned_deleted,
672 _learned_lbd: learned_lbd,
673 _learned_activity: learned_activity,
674 _learned_locked: learned_locked,
675 proof_offsets,
676 proof_data,
677 out_status,
678 out_error,
679 out_learned_count,
680 })
681 }
682
683 #[expect(
688 clippy::too_many_arguments,
689 reason = "the kernel launch contract keeps its workspace, CNF, four device-side decision bounds, and learned-arena mode explicit"
690 )]
691 fn launch_cdcl_with_workspace_gated(
692 &self,
693 ws: &mut GpuCdclWorkspace,
694 cnf: &GpuCnf,
695 compile_needed: &TrackedCudaSlice<u32>,
696 decision_base_limit: &TrackedCudaSlice<u32>,
697 decision_extra_base: &TrackedCudaSlice<u32>,
698 decision_extra_count: &TrackedCudaSlice<u32>,
699 import_existing_learned: bool,
700 ) -> Result<()> {
701 cnf.require_provider_memory(&self.provider, "GPU CDCL solver provider boundary")?;
702 self.require_workspace_on_provider(ws)?;
703 self.require_slice_on_provider("compile_needed", compile_needed)?;
704 self.require_slice_on_provider("decision_base_limit", decision_base_limit)?;
705 self.require_slice_on_provider("decision_extra_base", decision_extra_base)?;
706 self.require_slice_on_provider("decision_extra_count", decision_extra_count)?;
707 if compile_needed.len() != 1 {
708 return Err(XlogError::Compilation(format!(
709 "GpuCdclSolver requires compile_needed len=1, got {}",
710 compile_needed.len()
711 )));
712 }
713
714 self.require_workspace_capacity_for_cnf(ws, cnf.var_cap, cnf.clause_cap)?;
715
716 if cnf.var_cap == 0 {
718 return Err(XlogError::Compilation(
719 "GpuCdclSolver requires num_vars > 0".to_string(),
720 ));
721 }
722 if decision_base_limit.len() != 1 {
723 return Err(XlogError::Compilation(format!(
724 "GpuCdclSolver requires decision_base_limit len=1, got {}",
725 decision_base_limit.len()
726 )));
727 }
728 if decision_extra_base.len() != 1 {
729 return Err(XlogError::Compilation(format!(
730 "GpuCdclSolver requires decision_extra_base len=1, got {}",
731 decision_extra_base.len()
732 )));
733 }
734 if decision_extra_count.len() != 1 {
735 return Err(XlogError::Compilation(format!(
736 "GpuCdclSolver requires decision_extra_count len=1, got {}",
737 decision_extra_count.len()
738 )));
739 }
740 if self.config.max_learned_clauses == 0 {
741 return Err(XlogError::Compilation(
742 "GpuCdclSolver requires max_learned_clauses > 0".to_string(),
743 ));
744 }
745 if self.config.max_learned_lits == 0 {
746 return Err(XlogError::Compilation(
747 "GpuCdclSolver requires max_learned_lits > 0".to_string(),
748 ));
749 }
750 if self.config.max_proof_u32 < 2 {
751 return Err(XlogError::Compilation(
752 "GpuCdclSolver requires max_proof_u32 >= 2".to_string(),
753 ));
754 }
755
756 ws.reset_for_solve();
758 if !import_existing_learned {
759 self.provider
760 .htod_launch_metadata_sync_copy_into(&[0u32], &mut ws.out_learned_count)
761 .map_err(|e| {
762 XlogError::Kernel(format!("Failed to init learned import count: {}", e))
763 })?;
764 }
765
766 let sat_fn = self
767 .provider
768 .device()
769 .inner()
770 .get_func(SAT_MODULE, sat_kernels::SAT_CDCL_SOLVE)
771 .ok_or_else(|| XlogError::Kernel("sat_cdcl_solve kernel not found".to_string()))?;
772
773 let cnf_var_cap = cnf.var_cap;
776 let cnf_clause_cap = cnf.clause_cap;
777 let cfg_max_learned_clauses = self.config.max_learned_clauses;
778 let cfg_max_learned_lits = self.config.max_learned_lits;
779 let cfg_max_proof_u32 = self.config.max_proof_u32;
780 let cfg_restart_base = self.config.restart_base;
781 let cfg_reduce_interval = self.config.reduce_interval;
782 let cfg_max_conflicts = self.config.max_conflicts;
783 let learned_import_count_param = (&ws.out_learned_count).as_kernel_param();
784
785 let mut params: Vec<*mut c_void> = vec![
787 compile_needed.as_kernel_param(),
788 (&cnf.clause_offsets).as_kernel_param(),
789 (&cnf.literals).as_kernel_param(),
790 (&cnf.num_vars).as_kernel_param(),
791 (&cnf.num_clauses).as_kernel_param(),
792 decision_base_limit.as_kernel_param(),
793 decision_extra_base.as_kernel_param(),
794 decision_extra_count.as_kernel_param(),
795 cnf_var_cap.as_kernel_param(),
796 cnf_clause_cap.as_kernel_param(),
797 cfg_max_learned_clauses.as_kernel_param(),
798 cfg_max_learned_lits.as_kernel_param(),
799 cfg_max_proof_u32.as_kernel_param(),
800 cfg_restart_base.as_kernel_param(),
801 cfg_reduce_interval.as_kernel_param(),
802 cfg_max_conflicts.as_kernel_param(),
803 learned_import_count_param,
804 (&ws.assign).as_kernel_param(),
805 (&ws.level).as_kernel_param(),
806 (&ws.reason).as_kernel_param(),
807 (&ws.var_activity).as_kernel_param(),
808 (&ws.var_phase).as_kernel_param(),
809 (&ws.decision_heap).as_kernel_param(),
810 (&ws.decision_heap_pos).as_kernel_param(),
811 (&ws.trail).as_kernel_param(),
812 (&ws.trail_lim).as_kernel_param(),
813 (&ws.seen).as_kernel_param(),
814 (&ws.learnt_tmp).as_kernel_param(),
815 (&ws.proof_vars_tmp).as_kernel_param(),
816 (&ws.proof_reason_tmp).as_kernel_param(),
817 (&ws.watch0_pos).as_kernel_param(),
818 (&ws.watch1_pos).as_kernel_param(),
819 (&ws.watch_head).as_kernel_param(),
820 (&ws.watch_next).as_kernel_param(),
821 (&ws.watch_prev).as_kernel_param(),
822 (&ws.learned_offsets).as_kernel_param(),
823 (&ws.learned_lits).as_kernel_param(),
824 (&ws.learned_deleted).as_kernel_param(),
825 (&ws.learned_lbd).as_kernel_param(),
826 (&ws.learned_activity).as_kernel_param(),
827 (&ws.learned_locked).as_kernel_param(),
828 (&ws.proof_offsets).as_kernel_param(),
829 (&ws.proof_data).as_kernel_param(),
830 (&ws.out_status).as_kernel_param(),
831 (&ws.out_error).as_kernel_param(),
832 (&ws.out_learned_count).as_kernel_param(),
833 ];
834
835 unsafe {
837 sat_fn.clone().launch(
838 LaunchConfig {
839 grid_dim: (1, 1, 1),
840 block_dim: (256, 1, 1),
841 shared_mem_bytes: 0,
842 },
843 &mut params,
844 )
845 }
846 .map_err(|e| XlogError::Kernel(format!("Failed to launch SAT solver kernel: {}", e)))?;
847
848 Ok(())
849 }
850
851 pub fn solve_raw_with_branch_limit(
856 &self,
857 cnf: &GpuCnf,
858 branch_var_limit: &TrackedCudaSlice<u32>,
859 ) -> Result<GpuCdclRawOutput> {
860 let compile_needed = self.alloc_u32_scalar(1)?;
861 self.solve_raw_with_branch_limit_gated(cnf, &compile_needed, branch_var_limit)
862 }
863
864 pub fn solve_raw_with_branch_limit_gated(
866 &self,
867 cnf: &GpuCnf,
868 compile_needed: &TrackedCudaSlice<u32>,
869 branch_var_limit: &TrackedCudaSlice<u32>,
870 ) -> Result<GpuCdclRawOutput> {
871 let zero = self.alloc_u32_scalar(0)?;
872 let run = self.launch_cdcl_with_decision_ranges_gated(
873 cnf,
874 compile_needed,
875 branch_var_limit,
876 &zero,
877 &zero,
878 )?;
879 self.provider.device().synchronize()?;
881
882 let GpuCdclRun {
883 assignment,
884 out_status,
885 out_error,
886 out_learned_count,
887 ..
888 } = run;
889
890 Ok(GpuCdclRawOutput {
891 assignment,
892 out_status,
893 out_error,
894 out_learned_count,
895 })
896 }
897
898 pub fn solve_raw_with_decision_ranges(
906 &self,
907 cnf: &GpuCnf,
908 decision_base_limit: &TrackedCudaSlice<u32>,
909 decision_extra_base: &TrackedCudaSlice<u32>,
910 decision_extra_count: &TrackedCudaSlice<u32>,
911 ) -> Result<GpuCdclRawOutput> {
912 let compile_needed = self.alloc_u32_scalar(1)?;
913 self.solve_raw_with_decision_ranges_gated(
914 cnf,
915 &compile_needed,
916 decision_base_limit,
917 decision_extra_base,
918 decision_extra_count,
919 )
920 }
921
922 pub fn solve_raw_with_decision_ranges_gated(
924 &self,
925 cnf: &GpuCnf,
926 compile_needed: &TrackedCudaSlice<u32>,
927 decision_base_limit: &TrackedCudaSlice<u32>,
928 decision_extra_base: &TrackedCudaSlice<u32>,
929 decision_extra_count: &TrackedCudaSlice<u32>,
930 ) -> Result<GpuCdclRawOutput> {
931 let run = self.launch_cdcl_with_decision_ranges_gated(
932 cnf,
933 compile_needed,
934 decision_base_limit,
935 decision_extra_base,
936 decision_extra_count,
937 )?;
938 self.provider.device().synchronize()?;
940
941 let GpuCdclRun {
942 assignment,
943 out_status,
944 out_error,
945 out_learned_count,
946 ..
947 } = run;
948
949 Ok(GpuCdclRawOutput {
950 assignment,
951 out_status,
952 out_error,
953 out_learned_count,
954 })
955 }
956
957 pub fn solve_expect_sat(&self, cnf: &GpuCnf) -> Result<TrackedCudaSlice<i8>> {
959 let compile_needed = self.alloc_u32_scalar(1)?;
960 self.solve_expect_sat_gated(cnf, &compile_needed)
961 }
962
963 pub fn solve_expect_sat_gated(
966 &self,
967 cnf: &GpuCnf,
968 compile_needed: &TrackedCudaSlice<u32>,
969 ) -> Result<TrackedCudaSlice<i8>> {
970 self.solve_expect_sat_with_branch_limit_gated(cnf, compile_needed, &cnf.num_vars)
971 }
972
973 pub fn solve_expect_sat_with_decision_ranges(
978 &self,
979 cnf: &GpuCnf,
980 decision_base_limit: &TrackedCudaSlice<u32>,
981 decision_extra_base: &TrackedCudaSlice<u32>,
982 decision_extra_count: &TrackedCudaSlice<u32>,
983 ) -> Result<TrackedCudaSlice<i8>> {
984 let compile_needed = self.alloc_u32_scalar(1)?;
985 self.solve_expect_sat_with_decision_ranges_gated(
986 cnf,
987 &compile_needed,
988 decision_base_limit,
989 decision_extra_base,
990 decision_extra_count,
991 )
992 }
993
994 pub fn solve_expect_sat_with_decision_ranges_gated(
996 &self,
997 cnf: &GpuCnf,
998 compile_needed: &TrackedCudaSlice<u32>,
999 decision_base_limit: &TrackedCudaSlice<u32>,
1000 decision_extra_base: &TrackedCudaSlice<u32>,
1001 decision_extra_count: &TrackedCudaSlice<u32>,
1002 ) -> Result<TrackedCudaSlice<i8>> {
1003 #[cfg(debug_assertions)]
1004 let trace = resolve_bool(None, "XLOG_CDCL_TRACE", false)?;
1005 #[cfg(debug_assertions)]
1006 let t0 = std::time::Instant::now();
1007
1008 let run = self.launch_cdcl_with_decision_ranges_gated(
1009 cnf,
1010 compile_needed,
1011 decision_base_limit,
1012 decision_extra_base,
1013 decision_extra_count,
1014 )?;
1015 self.require_expected_status(
1016 &run.out_status,
1017 &run.out_error,
1018 SAT_STATUS_SAT,
1019 "GPU CDCL SAT expectation",
1020 )?;
1021
1022 let device = self.provider.device().inner();
1023 let memory = self.provider.memory();
1024
1025 let assert_status_fn = device
1026 .get_func(SAT_MODULE, sat_kernels::SAT_ASSERT_STATUS)
1027 .ok_or_else(|| XlogError::Kernel("sat_assert_status kernel not found".to_string()))?;
1028 unsafe {
1030 assert_status_fn
1031 .clone()
1032 .launch(
1033 LaunchConfig {
1034 grid_dim: (1, 1, 1),
1035 block_dim: (1, 1, 1),
1036 shared_mem_bytes: 0,
1037 },
1038 (
1039 compile_needed,
1040 &run.out_status,
1041 &run.out_error,
1042 SAT_STATUS_SAT,
1043 ),
1044 )
1045 .map_err(|e| {
1046 XlogError::Kernel(format!("Failed to launch sat_assert_status: {}", e))
1047 })?;
1048 }
1049 self.provider.device().synchronize()?;
1051 #[cfg(debug_assertions)]
1052 if trace {
1053 eprintln!("[xlog-solve] cdcl(sat) time: {:?}", t0.elapsed());
1054 }
1055
1056 let mut out_ok = memory.alloc::<i32>(1)?;
1057 let check_fn = device
1058 .get_func(SAT_MODULE, sat_kernels::SAT_CHECK_MODEL)
1059 .ok_or_else(|| XlogError::Kernel("sat_check_model kernel not found".to_string()))?;
1060 unsafe {
1062 check_fn
1063 .clone()
1064 .launch(
1065 LaunchConfig {
1066 grid_dim: (1, 1, 1),
1067 block_dim: (256, 1, 1),
1068 shared_mem_bytes: 0,
1069 },
1070 (
1071 compile_needed,
1072 &cnf.clause_offsets,
1073 &cnf.literals,
1074 &cnf.num_clauses,
1075 &run.assignment,
1076 &mut out_ok,
1077 ),
1078 )
1079 .map_err(|e| {
1080 XlogError::Kernel(format!("Failed to launch SAT model check: {}", e))
1081 })?;
1082 }
1083
1084 let assert_ok_fn = device
1085 .get_func(SAT_MODULE, sat_kernels::SAT_ASSERT_OK)
1086 .ok_or_else(|| XlogError::Kernel("sat_assert_ok kernel not found".to_string()))?;
1087 unsafe {
1089 assert_ok_fn
1090 .clone()
1091 .launch(
1092 LaunchConfig {
1093 grid_dim: (1, 1, 1),
1094 block_dim: (1, 1, 1),
1095 shared_mem_bytes: 0,
1096 },
1097 (compile_needed, &out_ok),
1098 )
1099 .map_err(|e| XlogError::Kernel(format!("Failed to launch sat_assert_ok: {}", e)))?;
1100 }
1101 self.provider.device().synchronize()?;
1102 #[cfg(debug_assertions)]
1103 if trace {
1104 eprintln!(
1105 "[xlog-solve] cdcl(sat)+model_check time: {:?}",
1106 t0.elapsed()
1107 );
1108 }
1109
1110 Ok(run.assignment)
1111 }
1112
1113 pub fn solve_expect_sat_with_branch_limit(
1116 &self,
1117 cnf: &GpuCnf,
1118 branch_var_limit: &TrackedCudaSlice<u32>,
1119 ) -> Result<TrackedCudaSlice<i8>> {
1120 let compile_needed = self.alloc_u32_scalar(1)?;
1121 self.solve_expect_sat_with_branch_limit_gated(cnf, &compile_needed, branch_var_limit)
1122 }
1123
1124 pub fn solve_expect_sat_with_branch_limit_gated(
1128 &self,
1129 cnf: &GpuCnf,
1130 compile_needed: &TrackedCudaSlice<u32>,
1131 branch_var_limit: &TrackedCudaSlice<u32>,
1132 ) -> Result<TrackedCudaSlice<i8>> {
1133 let zero = self.alloc_u32_scalar(0)?;
1134 self.solve_expect_sat_with_decision_ranges_gated(
1135 cnf,
1136 compile_needed,
1137 branch_var_limit,
1138 &zero,
1139 &zero,
1140 )
1141 }
1142
1143 pub fn solve_expect_unsat(&self, cnf: &GpuCnf) -> Result<()> {
1145 let compile_needed = self.alloc_u32_scalar(1)?;
1146 self.solve_expect_unsat_gated(cnf, &compile_needed)
1147 }
1148
1149 pub fn solve_expect_unsat_gated(
1152 &self,
1153 cnf: &GpuCnf,
1154 compile_needed: &TrackedCudaSlice<u32>,
1155 ) -> Result<()> {
1156 self.solve_expect_unsat_with_branch_limit_gated(cnf, compile_needed, &cnf.num_vars)
1157 }
1158
1159 pub fn solve_expect_unsat_with_branch_limit(
1162 &self,
1163 cnf: &GpuCnf,
1164 branch_var_limit: &TrackedCudaSlice<u32>,
1165 ) -> Result<()> {
1166 let compile_needed = self.alloc_u32_scalar(1)?;
1167 self.solve_expect_unsat_with_branch_limit_gated(cnf, &compile_needed, branch_var_limit)
1168 }
1169
1170 pub fn solve_expect_unsat_with_decision_ranges(
1174 &self,
1175 cnf: &GpuCnf,
1176 decision_base_limit: &TrackedCudaSlice<u32>,
1177 decision_extra_base: &TrackedCudaSlice<u32>,
1178 decision_extra_count: &TrackedCudaSlice<u32>,
1179 ) -> Result<()> {
1180 let compile_needed = self.alloc_u32_scalar(1)?;
1181 self.solve_expect_unsat_with_decision_ranges_gated(
1182 cnf,
1183 &compile_needed,
1184 decision_base_limit,
1185 decision_extra_base,
1186 decision_extra_count,
1187 )
1188 }
1189
1190 pub fn solve_expect_unsat_with_branch_limit_gated(
1194 &self,
1195 cnf: &GpuCnf,
1196 compile_needed: &TrackedCudaSlice<u32>,
1197 branch_var_limit: &TrackedCudaSlice<u32>,
1198 ) -> Result<()> {
1199 let zero = self.alloc_u32_scalar(0)?;
1200 self.solve_expect_unsat_with_decision_ranges_gated(
1201 cnf,
1202 compile_needed,
1203 branch_var_limit,
1204 &zero,
1205 &zero,
1206 )
1207 }
1208
1209 pub fn solve_expect_unsat_with_decision_ranges_gated(
1214 &self,
1215 cnf: &GpuCnf,
1216 compile_needed: &TrackedCudaSlice<u32>,
1217 decision_base_limit: &TrackedCudaSlice<u32>,
1218 decision_extra_base: &TrackedCudaSlice<u32>,
1219 decision_extra_count: &TrackedCudaSlice<u32>,
1220 ) -> Result<()> {
1221 #[cfg(debug_assertions)]
1222 let trace = resolve_bool(None, "XLOG_CDCL_TRACE", false)?;
1223 #[cfg(debug_assertions)]
1224 let t0 = std::time::Instant::now();
1225
1226 let run = self.launch_cdcl_with_decision_ranges_gated(
1227 cnf,
1228 compile_needed,
1229 decision_base_limit,
1230 decision_extra_base,
1231 decision_extra_count,
1232 )?;
1233 self.require_expected_status(
1234 &run.out_status,
1235 &run.out_error,
1236 SAT_STATUS_UNSAT,
1237 "GPU CDCL UNSAT expectation",
1238 )?;
1239
1240 let device = self.provider.device().inner();
1241 let memory = self.provider.memory();
1242
1243 let assert_status_fn = device
1244 .get_func(SAT_MODULE, sat_kernels::SAT_ASSERT_STATUS)
1245 .ok_or_else(|| XlogError::Kernel("sat_assert_status kernel not found".to_string()))?;
1246 unsafe {
1248 assert_status_fn
1249 .clone()
1250 .launch(
1251 LaunchConfig {
1252 grid_dim: (1, 1, 1),
1253 block_dim: (1, 1, 1),
1254 shared_mem_bytes: 0,
1255 },
1256 (
1257 compile_needed,
1258 &run.out_status,
1259 &run.out_error,
1260 SAT_STATUS_UNSAT,
1261 ),
1262 )
1263 .map_err(|e| {
1264 XlogError::Kernel(format!("Failed to launch sat_assert_status: {}", e))
1265 })?;
1266 }
1267 self.provider.device().synchronize()?;
1269 #[cfg(debug_assertions)]
1270 if trace {
1271 eprintln!("[xlog-solve] cdcl(unsat) time: {:?}", t0.elapsed());
1272 }
1273
1274 let mut out_ok = memory.alloc::<i32>(1)?;
1275 self.provider
1276 .htod_launch_metadata_sync_copy_into(&[1i32], &mut out_ok)
1277 .map_err(|e| XlogError::Kernel(format!("Failed to init proof out_ok: {}", e)))?;
1278
1279 let scratch_cap_u32 = cnf
1283 .var_cap
1284 .checked_add(1)
1285 .ok_or_else(|| XlogError::Kernel("Proof scratch capacity overflow".to_string()))?;
1286 let scratch_cap = scratch_cap_u32 as usize;
1287
1288 let mut proof_blocks: usize = 1;
1289 let mut scratch_a = None;
1290 let mut scratch_b = None;
1291 let mut scratch_map = None;
1292 let mut last_alloc_err: Option<XlogError> = None;
1293 for blocks in [512usize, 256, 128, 64, 32, 16, 8, 4, 2, 1] {
1294 let len = match scratch_cap.checked_mul(blocks) {
1295 Some(v) => v,
1296 None => {
1297 last_alloc_err = Some(XlogError::Kernel(
1298 "Proof scratch allocation length overflow".to_string(),
1299 ));
1300 continue;
1301 }
1302 };
1303
1304 let a = match memory.alloc::<i32>(len) {
1305 Ok(buf) => buf,
1306 Err(e) => {
1307 last_alloc_err = Some(e);
1308 continue;
1309 }
1310 };
1311 let b = match memory.alloc::<i32>(len) {
1312 Ok(buf) => buf,
1313 Err(e) => {
1314 last_alloc_err = Some(e);
1315 drop(a);
1317 continue;
1318 }
1319 };
1320 let m = match memory.alloc::<u32>(len) {
1321 Ok(buf) => buf,
1322 Err(e) => {
1323 last_alloc_err = Some(e);
1324 drop(a);
1325 drop(b);
1326 continue;
1327 }
1328 };
1329
1330 proof_blocks = blocks;
1331 scratch_a = Some(a);
1332 scratch_b = Some(b);
1333 scratch_map = Some(m);
1334 break;
1335 }
1336 let scratch_a = scratch_a.ok_or_else(|| {
1337 last_alloc_err.unwrap_or_else(|| {
1338 XlogError::Kernel("Failed to allocate proof scratch buffers".to_string())
1339 })
1340 })?;
1341 let scratch_b = scratch_b
1342 .ok_or_else(|| XlogError::Kernel("Missing proof scratch buffer".to_string()))?;
1343 let mut scratch_map = scratch_map
1344 .ok_or_else(|| XlogError::Kernel("Missing proof scratch map buffer".to_string()))?;
1345 device
1346 .memset_zeros(&mut scratch_map)
1347 .map_err(|e| XlogError::Kernel(format!("Failed to zero proof scratch map: {}", e)))?;
1348 #[cfg(debug_assertions)]
1349 if trace {
1350 eprintln!("[xlog-solve] proof_check blocks: {}", proof_blocks);
1351 }
1352 #[cfg(debug_assertions)]
1353 let t_mark = std::time::Instant::now();
1354
1355 let needed_cap_u32 = self.config.max_learned_clauses;
1356 let needed_cap = needed_cap_u32 as usize;
1357 let mut needed = memory.alloc::<u8>(needed_cap)?;
1358 device
1359 .memset_zeros(&mut needed)
1360 .map_err(|e| XlogError::Kernel(format!("Failed to zero proof needed mask: {}", e)))?;
1361
1362 let mark_needed_fn = device
1363 .get_func(SAT_MODULE, sat_kernels::SAT_PROOF_MARK_NEEDED)
1364 .ok_or_else(|| {
1365 XlogError::Kernel("sat_proof_mark_needed kernel not found".to_string())
1366 })?;
1367 let mut mark_params: Vec<*mut c_void> = vec![
1368 compile_needed.as_kernel_param(),
1369 (&cnf.num_clauses).as_kernel_param(),
1370 (&run.out_learned_count).as_kernel_param(),
1371 (&run.proof_offsets).as_kernel_param(),
1372 (&run.proof_data).as_kernel_param(),
1373 needed_cap_u32.as_kernel_param(),
1374 (&needed).as_kernel_param(),
1375 ];
1376 unsafe {
1378 mark_needed_fn
1379 .clone()
1380 .launch(
1381 LaunchConfig {
1382 grid_dim: (1, 1, 1),
1383 block_dim: (1, 1, 1),
1384 shared_mem_bytes: 0,
1385 },
1386 &mut mark_params,
1387 )
1388 .map_err(|e| {
1389 XlogError::Kernel(format!("Failed to launch sat_proof_mark_needed: {}", e))
1390 })?;
1391 }
1392 self.provider.device().synchronize()?;
1393 #[cfg(debug_assertions)]
1394 if trace {
1395 eprintln!(
1396 "[xlog-solve] proof_mark_needed time: {:?}",
1397 t_mark.elapsed()
1398 );
1399 }
1400
1401 let proof_fn = device
1402 .get_func(SAT_MODULE, sat_kernels::SAT_PROOF_CHECK)
1403 .ok_or_else(|| XlogError::Kernel("sat_proof_check kernel not found".to_string()))?;
1404 #[cfg(debug_assertions)]
1405 let t_proof = std::time::Instant::now();
1406 let proof_blocks_u32 = u32::try_from(proof_blocks)
1407 .map_err(|_| XlogError::Kernel("Proof check grid dim exceeds u32::MAX".to_string()))?;
1408 let mut proof_params: Vec<*mut c_void> = vec![
1409 compile_needed.as_kernel_param(),
1410 (&cnf.clause_offsets).as_kernel_param(),
1411 (&cnf.literals).as_kernel_param(),
1412 (&cnf.num_clauses).as_kernel_param(),
1413 (&run.learned_offsets).as_kernel_param(),
1414 (&run.learned_lits).as_kernel_param(),
1415 (&run.out_learned_count).as_kernel_param(),
1416 (&run.proof_offsets).as_kernel_param(),
1417 (&run.proof_data).as_kernel_param(),
1418 (&needed).as_kernel_param(),
1419 needed_cap_u32.as_kernel_param(),
1420 (&scratch_a).as_kernel_param(),
1421 (&scratch_b).as_kernel_param(),
1422 (&scratch_map).as_kernel_param(),
1423 scratch_cap_u32.as_kernel_param(),
1424 (&out_ok).as_kernel_param(),
1425 ];
1426 unsafe {
1428 proof_fn
1429 .clone()
1430 .launch(
1431 LaunchConfig {
1432 grid_dim: (proof_blocks_u32, 1, 1),
1433 block_dim: (128, 1, 1),
1434 shared_mem_bytes: 0,
1435 },
1436 &mut proof_params,
1437 )
1438 .map_err(|e| {
1439 XlogError::Kernel(format!("Failed to launch SAT proof check: {}", e))
1440 })?;
1441 }
1442
1443 let assert_ok_fn = device
1444 .get_func(SAT_MODULE, sat_kernels::SAT_ASSERT_OK)
1445 .ok_or_else(|| XlogError::Kernel("sat_assert_ok kernel not found".to_string()))?;
1446 unsafe {
1448 assert_ok_fn
1449 .clone()
1450 .launch(
1451 LaunchConfig {
1452 grid_dim: (1, 1, 1),
1453 block_dim: (1, 1, 1),
1454 shared_mem_bytes: 0,
1455 },
1456 (compile_needed, &out_ok),
1457 )
1458 .map_err(|e| XlogError::Kernel(format!("Failed to launch sat_assert_ok: {}", e)))?;
1459 }
1460 self.provider.device().synchronize()?;
1461 #[cfg(debug_assertions)]
1462 if trace {
1463 eprintln!("[xlog-solve] proof_check time: {:?}", t_proof.elapsed());
1464 eprintln!(
1465 "[xlog-solve] cdcl(unsat)+proof_check time: {:?}",
1466 t0.elapsed()
1467 );
1468 }
1469
1470 Ok(())
1471 }
1472
1473 pub fn solve_expect_unsat_with_branch_limit_ws(
1478 &self,
1479 ws: &mut GpuCdclWorkspace,
1480 cnf: &GpuCnf,
1481 branch_var_limit: &TrackedCudaSlice<u32>,
1482 ) -> Result<()> {
1483 let compile_needed = self.alloc_u32_scalar(1)?;
1484 self.solve_expect_unsat_with_branch_limit_gated_ws(
1485 ws,
1486 cnf,
1487 &compile_needed,
1488 branch_var_limit,
1489 )
1490 }
1491
1492 pub fn solve_expect_unsat_with_branch_limit_gated_ws(
1496 &self,
1497 ws: &mut GpuCdclWorkspace,
1498 cnf: &GpuCnf,
1499 compile_needed: &TrackedCudaSlice<u32>,
1500 branch_var_limit: &TrackedCudaSlice<u32>,
1501 ) -> Result<()> {
1502 let zero = self.alloc_u32_scalar(0)?;
1503 self.solve_expect_unsat_with_decision_ranges_gated_ws(
1504 ws,
1505 cnf,
1506 compile_needed,
1507 branch_var_limit,
1508 &zero,
1509 &zero,
1510 )
1511 }
1512
1513 pub fn solve_expect_unsat_with_decision_ranges_ws(
1516 &self,
1517 ws: &mut GpuCdclWorkspace,
1518 cnf: &GpuCnf,
1519 decision_base_limit: &TrackedCudaSlice<u32>,
1520 decision_extra_base: &TrackedCudaSlice<u32>,
1521 decision_extra_count: &TrackedCudaSlice<u32>,
1522 ) -> Result<()> {
1523 let compile_needed = self.alloc_u32_scalar(1)?;
1524 self.solve_expect_unsat_with_decision_ranges_gated_ws(
1525 ws,
1526 cnf,
1527 &compile_needed,
1528 decision_base_limit,
1529 decision_extra_base,
1530 decision_extra_count,
1531 )
1532 }
1533
1534 pub fn solve_expect_unsat_with_decision_ranges_gated_ws(
1543 &self,
1544 ws: &mut GpuCdclWorkspace,
1545 cnf: &GpuCnf,
1546 compile_needed: &TrackedCudaSlice<u32>,
1547 decision_base_limit: &TrackedCudaSlice<u32>,
1548 decision_extra_base: &TrackedCudaSlice<u32>,
1549 decision_extra_count: &TrackedCudaSlice<u32>,
1550 ) -> Result<()> {
1551 self.solve_expect_unsat_with_decision_ranges_gated_ws_inner(
1552 ws,
1553 cnf,
1554 compile_needed,
1555 decision_base_limit,
1556 decision_extra_base,
1557 decision_extra_count,
1558 false,
1559 )
1560 }
1561
1562 pub fn solve_expect_unsat_with_branch_limit_ws_importing_learned(
1567 &self,
1568 ws: &mut GpuCdclWorkspace,
1569 cnf: &GpuCnf,
1570 branch_var_limit: &TrackedCudaSlice<u32>,
1571 ) -> Result<()> {
1572 let compile_needed = self.alloc_u32_scalar(1)?;
1573 let zero = self.alloc_u32_scalar(0)?;
1574 self.solve_expect_unsat_with_decision_ranges_gated_ws_inner(
1575 ws,
1576 cnf,
1577 &compile_needed,
1578 branch_var_limit,
1579 &zero,
1580 &zero,
1581 true,
1582 )
1583 }
1584
1585 #[expect(
1586 clippy::too_many_arguments,
1587 reason = "the workspace UNSAT operation preserves the exact kernel decision-range contract and learned-arena mode"
1588 )]
1589 fn solve_expect_unsat_with_decision_ranges_gated_ws_inner(
1590 &self,
1591 ws: &mut GpuCdclWorkspace,
1592 cnf: &GpuCnf,
1593 compile_needed: &TrackedCudaSlice<u32>,
1594 decision_base_limit: &TrackedCudaSlice<u32>,
1595 decision_extra_base: &TrackedCudaSlice<u32>,
1596 decision_extra_count: &TrackedCudaSlice<u32>,
1597 import_existing_learned: bool,
1598 ) -> Result<()> {
1599 #[cfg(debug_assertions)]
1600 let trace = resolve_bool(None, "XLOG_CDCL_TRACE", false)?;
1601 #[cfg(debug_assertions)]
1602 let t0 = std::time::Instant::now();
1603
1604 self.launch_cdcl_with_workspace_gated(
1605 ws,
1606 cnf,
1607 compile_needed,
1608 decision_base_limit,
1609 decision_extra_base,
1610 decision_extra_count,
1611 import_existing_learned,
1612 )?;
1613 self.require_expected_status(
1614 &ws.out_status,
1615 &ws.out_error,
1616 SAT_STATUS_UNSAT,
1617 "GPU CDCL workspace UNSAT expectation",
1618 )?;
1619
1620 let device = self.provider.device().inner();
1621 let memory = self.provider.memory();
1622
1623 let assert_status_fn = device
1624 .get_func(SAT_MODULE, sat_kernels::SAT_ASSERT_STATUS)
1625 .ok_or_else(|| XlogError::Kernel("sat_assert_status kernel not found".to_string()))?;
1626 unsafe {
1628 assert_status_fn
1629 .clone()
1630 .launch(
1631 LaunchConfig {
1632 grid_dim: (1, 1, 1),
1633 block_dim: (1, 1, 1),
1634 shared_mem_bytes: 0,
1635 },
1636 (
1637 compile_needed,
1638 &ws.out_status,
1639 &ws.out_error,
1640 SAT_STATUS_UNSAT,
1641 ),
1642 )
1643 .map_err(|e| {
1644 XlogError::Kernel(format!("Failed to launch sat_assert_status: {}", e))
1645 })?;
1646 }
1647 self.provider.device().synchronize()?;
1649 #[cfg(debug_assertions)]
1650 if trace {
1651 eprintln!("[xlog-solve] cdcl_ws(unsat) time: {:?}", t0.elapsed());
1652 }
1653
1654 let mut out_ok = memory.alloc::<i32>(1)?;
1655 self.provider
1656 .htod_launch_metadata_sync_copy_into(&[1i32], &mut out_ok)
1657 .map_err(|e| XlogError::Kernel(format!("Failed to init proof out_ok: {}", e)))?;
1658
1659 let scratch_cap_u32 = cnf
1663 .var_cap
1664 .checked_add(1)
1665 .ok_or_else(|| XlogError::Kernel("Proof scratch capacity overflow".to_string()))?;
1666 let scratch_cap = scratch_cap_u32 as usize;
1667
1668 let mut proof_blocks: usize = 1;
1669 let mut scratch_a = None;
1670 let mut scratch_b = None;
1671 let mut scratch_map = None;
1672 let mut last_alloc_err: Option<XlogError> = None;
1673 for blocks in [512usize, 256, 128, 64, 32, 16, 8, 4, 2, 1] {
1674 let len = match scratch_cap.checked_mul(blocks) {
1675 Some(v) => v,
1676 None => {
1677 last_alloc_err = Some(XlogError::Kernel(
1678 "Proof scratch allocation length overflow".to_string(),
1679 ));
1680 continue;
1681 }
1682 };
1683
1684 let a = match memory.alloc::<i32>(len) {
1685 Ok(buf) => buf,
1686 Err(e) => {
1687 last_alloc_err = Some(e);
1688 continue;
1689 }
1690 };
1691 let b = match memory.alloc::<i32>(len) {
1692 Ok(buf) => buf,
1693 Err(e) => {
1694 last_alloc_err = Some(e);
1695 drop(a);
1697 continue;
1698 }
1699 };
1700 let m = match memory.alloc::<u32>(len) {
1701 Ok(buf) => buf,
1702 Err(e) => {
1703 last_alloc_err = Some(e);
1704 drop(a);
1705 drop(b);
1706 continue;
1707 }
1708 };
1709
1710 proof_blocks = blocks;
1711 scratch_a = Some(a);
1712 scratch_b = Some(b);
1713 scratch_map = Some(m);
1714 break;
1715 }
1716 let scratch_a = scratch_a.ok_or_else(|| {
1717 last_alloc_err.unwrap_or_else(|| {
1718 XlogError::Kernel("Failed to allocate proof scratch buffers".to_string())
1719 })
1720 })?;
1721 let scratch_b = scratch_b
1722 .ok_or_else(|| XlogError::Kernel("Missing proof scratch buffer".to_string()))?;
1723 let mut scratch_map = scratch_map
1724 .ok_or_else(|| XlogError::Kernel("Missing proof scratch map buffer".to_string()))?;
1725 device
1726 .memset_zeros(&mut scratch_map)
1727 .map_err(|e| XlogError::Kernel(format!("Failed to zero proof scratch map: {}", e)))?;
1728 #[cfg(debug_assertions)]
1729 if trace {
1730 eprintln!("[xlog-solve] proof_check_ws blocks: {}", proof_blocks);
1731 }
1732 #[cfg(debug_assertions)]
1733 let t_mark = std::time::Instant::now();
1734
1735 let needed_cap_u32 = self.config.max_learned_clauses;
1736 let needed_cap = needed_cap_u32 as usize;
1737 let mut needed = memory.alloc::<u8>(needed_cap)?;
1738 device
1739 .memset_zeros(&mut needed)
1740 .map_err(|e| XlogError::Kernel(format!("Failed to zero proof needed mask: {}", e)))?;
1741
1742 let mark_needed_fn = device
1743 .get_func(SAT_MODULE, sat_kernels::SAT_PROOF_MARK_NEEDED)
1744 .ok_or_else(|| {
1745 XlogError::Kernel("sat_proof_mark_needed kernel not found".to_string())
1746 })?;
1747 let mut mark_params: Vec<*mut c_void> = vec![
1748 compile_needed.as_kernel_param(),
1749 (&cnf.num_clauses).as_kernel_param(),
1750 (&ws.out_learned_count).as_kernel_param(),
1751 (&ws.proof_offsets).as_kernel_param(),
1752 (&ws.proof_data).as_kernel_param(),
1753 needed_cap_u32.as_kernel_param(),
1754 (&needed).as_kernel_param(),
1755 ];
1756 unsafe {
1758 mark_needed_fn
1759 .clone()
1760 .launch(
1761 LaunchConfig {
1762 grid_dim: (1, 1, 1),
1763 block_dim: (1, 1, 1),
1764 shared_mem_bytes: 0,
1765 },
1766 &mut mark_params,
1767 )
1768 .map_err(|e| {
1769 XlogError::Kernel(format!("Failed to launch sat_proof_mark_needed: {}", e))
1770 })?;
1771 }
1772 self.provider.device().synchronize()?;
1773 #[cfg(debug_assertions)]
1774 if trace {
1775 eprintln!(
1776 "[xlog-solve] proof_mark_needed_ws time: {:?}",
1777 t_mark.elapsed()
1778 );
1779 }
1780
1781 let proof_fn = device
1782 .get_func(SAT_MODULE, sat_kernels::SAT_PROOF_CHECK)
1783 .ok_or_else(|| XlogError::Kernel("sat_proof_check kernel not found".to_string()))?;
1784 #[cfg(debug_assertions)]
1785 let t_proof = std::time::Instant::now();
1786 let proof_blocks_u32 = u32::try_from(proof_blocks)
1787 .map_err(|_| XlogError::Kernel("Proof check grid dim exceeds u32::MAX".to_string()))?;
1788 let mut proof_params: Vec<*mut c_void> = vec![
1789 compile_needed.as_kernel_param(),
1790 (&cnf.clause_offsets).as_kernel_param(),
1791 (&cnf.literals).as_kernel_param(),
1792 (&cnf.num_clauses).as_kernel_param(),
1793 (&ws.learned_offsets).as_kernel_param(),
1794 (&ws.learned_lits).as_kernel_param(),
1795 (&ws.out_learned_count).as_kernel_param(),
1796 (&ws.proof_offsets).as_kernel_param(),
1797 (&ws.proof_data).as_kernel_param(),
1798 (&needed).as_kernel_param(),
1799 needed_cap_u32.as_kernel_param(),
1800 (&scratch_a).as_kernel_param(),
1801 (&scratch_b).as_kernel_param(),
1802 (&scratch_map).as_kernel_param(),
1803 scratch_cap_u32.as_kernel_param(),
1804 (&out_ok).as_kernel_param(),
1805 ];
1806 unsafe {
1808 proof_fn
1809 .clone()
1810 .launch(
1811 LaunchConfig {
1812 grid_dim: (proof_blocks_u32, 1, 1),
1813 block_dim: (128, 1, 1),
1814 shared_mem_bytes: 0,
1815 },
1816 &mut proof_params,
1817 )
1818 .map_err(|e| {
1819 XlogError::Kernel(format!("Failed to launch SAT proof check: {}", e))
1820 })?;
1821 }
1822
1823 let assert_ok_fn = device
1824 .get_func(SAT_MODULE, sat_kernels::SAT_ASSERT_OK)
1825 .ok_or_else(|| XlogError::Kernel("sat_assert_ok kernel not found".to_string()))?;
1826 unsafe {
1828 assert_ok_fn
1829 .clone()
1830 .launch(
1831 LaunchConfig {
1832 grid_dim: (1, 1, 1),
1833 block_dim: (1, 1, 1),
1834 shared_mem_bytes: 0,
1835 },
1836 (compile_needed, &out_ok),
1837 )
1838 .map_err(|e| XlogError::Kernel(format!("Failed to launch sat_assert_ok: {}", e)))?;
1839 }
1840 self.provider.device().synchronize()?;
1841 #[cfg(debug_assertions)]
1842 if trace {
1843 eprintln!("[xlog-solve] proof_check_ws time: {:?}", t_proof.elapsed());
1844 eprintln!(
1845 "[xlog-solve] cdcl_ws(unsat)+proof_check time: {:?}",
1846 t0.elapsed()
1847 );
1848 }
1849
1850 Ok(())
1851 }
1852}