1use std::sync::Arc;
2
3use xlog_core::{Result, XlogError};
4use xlog_cuda::memory::TrackedCudaSlice;
5use xlog_cuda::CudaKernelProvider;
6
7use crate::instance::SolveInstance;
8
9pub struct GpuCnf {
13 pub var_cap: u32,
15 pub clause_cap: u32,
17 pub lit_cap: u32,
19 pub num_vars: TrackedCudaSlice<u32>,
21 pub num_clauses: TrackedCudaSlice<u32>,
23 pub num_lits: TrackedCudaSlice<u32>,
25 pub clause_offsets: TrackedCudaSlice<u32>,
27 pub literals: TrackedCudaSlice<i32>,
29}
30
31impl GpuCnf {
32 pub(crate) fn require_provider_memory(
33 &self,
34 provider: &CudaKernelProvider,
35 context: &'static str,
36 ) -> Result<()> {
37 let expected_memory = Arc::as_ptr(provider.memory()) as usize;
38 self.require_slice_provider_memory(
39 context,
40 "num_vars",
41 self.num_vars.memory_manager_ptr_value(),
42 expected_memory,
43 )?;
44 self.require_slice_provider_memory(
45 context,
46 "num_clauses",
47 self.num_clauses.memory_manager_ptr_value(),
48 expected_memory,
49 )?;
50 self.require_slice_provider_memory(
51 context,
52 "num_lits",
53 self.num_lits.memory_manager_ptr_value(),
54 expected_memory,
55 )?;
56 self.require_slice_provider_memory(
57 context,
58 "clause_offsets",
59 self.clause_offsets.memory_manager_ptr_value(),
60 expected_memory,
61 )?;
62 self.require_slice_provider_memory(
63 context,
64 "literals",
65 self.literals.memory_manager_ptr_value(),
66 expected_memory,
67 )?;
68
69 if self.num_vars.len() != 1 || self.num_clauses.len() != 1 || self.num_lits.len() != 1 {
70 return Err(XlogError::UnsupportedEpistemicConstruct {
71 construct: context.to_string(),
72 context: format!(
73 "GPU CNF scalar buffers must have len=1, got num_vars={} num_clauses={} num_lits={}",
74 self.num_vars.len(),
75 self.num_clauses.len(),
76 self.num_lits.len()
77 ),
78 });
79 }
80 let expected_offsets = (self.clause_cap as usize).checked_add(1).ok_or_else(|| {
81 XlogError::UnsupportedEpistemicConstruct {
82 construct: context.to_string(),
83 context: "GPU CNF clause offset length overflowed".to_string(),
84 }
85 })?;
86 if self.clause_offsets.len() != expected_offsets
87 || self.literals.len() != self.lit_cap as usize
88 {
89 return Err(XlogError::UnsupportedEpistemicConstruct {
90 construct: context.to_string(),
91 context: format!(
92 "GPU CNF buffer lengths must match capacities, got offsets={}/{} literals={}/{}",
93 self.clause_offsets.len(),
94 expected_offsets,
95 self.literals.len(),
96 self.lit_cap
97 ),
98 });
99 }
100 Ok(())
101 }
102
103 fn require_slice_provider_memory(
104 &self,
105 context: &'static str,
106 name: &'static str,
107 actual_memory: usize,
108 expected_memory: usize,
109 ) -> Result<()> {
110 if actual_memory != expected_memory {
111 return Err(XlogError::UnsupportedEpistemicConstruct {
112 construct: context.to_string(),
113 context: format!(
114 "GPU CNF buffer {name} belongs to memory manager {actual_memory}, expected {expected_memory}"
115 ),
116 });
117 }
118 Ok(())
119 }
120
121 #[inline]
122 pub fn num_literals_cap(&self) -> usize {
124 self.lit_cap as usize
125 }
126
127 pub fn from_host(instance: &SolveInstance, provider: &Arc<CudaKernelProvider>) -> Result<Self> {
131 if instance.objective != crate::Objective::Satisfaction {
132 return Err(XlogError::Compilation(format!(
133 "GpuCnf::from_host only supports Objective::Satisfaction, got {:?}",
134 instance.objective
135 )));
136 }
137 if instance.num_vars == 0 {
138 return Err(XlogError::Compilation(
139 "GpuCnf::from_host requires num_vars > 0".to_string(),
140 ));
141 }
142 if instance.num_vars > i32::MAX as u32 {
143 return Err(XlogError::Compilation(
144 "GpuCnf::from_host requires DIMACS variables to fit i32".to_string(),
145 ));
146 }
147 if !instance.validate() {
148 return Err(XlogError::Compilation(
149 "GpuCnf::from_host saw a literal variable outside num_vars".to_string(),
150 ));
151 }
152
153 let num_vars = instance.num_vars;
154 let num_clauses = u32::try_from(instance.clauses.len()).map_err(|_| {
155 XlogError::Compilation("GpuCnf::from_host clause count exceeds u32".to_string())
156 })?;
157 let offsets_len = instance.clauses.len().checked_add(1).ok_or_else(|| {
158 XlogError::Compilation("GpuCnf::from_host clause offset count overflow".to_string())
159 })?;
160 let total_literals = instance.clauses.iter().try_fold(0usize, |acc, clause| {
161 acc.checked_add(clause.literals.len()).ok_or_else(|| {
162 XlogError::Compilation("GpuCnf::from_host literal count overflow".to_string())
163 })
164 })?;
165 let lit_cap = u32::try_from(total_literals).map_err(|_| {
166 XlogError::Compilation("GpuCnf::from_host literal count exceeds u32".to_string())
167 })?;
168
169 let mut clause_offsets: Vec<u32> = Vec::with_capacity(offsets_len);
171 clause_offsets.push(0);
172
173 let mut literals: Vec<i32> = Vec::with_capacity(total_literals);
174 for clause in &instance.clauses {
175 let start = clause_offsets.last().copied().ok_or_else(|| {
176 XlogError::Kernel(
177 "GpuCnf::from_host internal error: missing initial clause offset".to_string(),
178 )
179 })?;
180 let len = u32::try_from(clause.literals.len()).map_err(|_| {
181 XlogError::Compilation(
182 "GpuCnf::from_host clause literal count exceeds u32".to_string(),
183 )
184 })?;
185 let end = start
186 .checked_add(len)
187 .ok_or_else(|| XlogError::Compilation("CNF literal count overflow".to_string()))?;
188 clause_offsets.push(end);
189
190 for &lit in &clause.literals {
191 let dimacs = lit.to_dimacs();
192 if dimacs == 0 {
193 return Err(XlogError::Compilation(
194 "CNF contains DIMACS 0 literal".to_string(),
195 ));
196 }
197 literals.push(dimacs);
198 }
199 }
200
201 if clause_offsets.len() != offsets_len {
202 return Err(XlogError::Kernel(
203 "GpuCnf::from_host internal error: offsets length mismatch".to_string(),
204 ));
205 }
206 if literals.len() != total_literals {
207 return Err(XlogError::Kernel(
208 "GpuCnf::from_host internal error: literal length mismatch".to_string(),
209 ));
210 }
211
212 let memory = provider.memory();
213
214 let mut d_num_vars = memory.alloc::<u32>(1)?;
216 let mut d_num_clauses = memory.alloc::<u32>(1)?;
217 let mut d_num_lits = memory.alloc::<u32>(1)?;
218
219 provider
220 .htod_launch_metadata_sync_copy_into(&[num_vars], &mut d_num_vars)
221 .map_err(|e| XlogError::Kernel(format!("Failed to upload CNF num_vars: {}", e)))?;
222 provider
223 .htod_launch_metadata_sync_copy_into(&[num_clauses], &mut d_num_clauses)
224 .map_err(|e| XlogError::Kernel(format!("Failed to upload CNF num_clauses: {}", e)))?;
225
226 let mut d_offsets = memory.alloc::<u32>(clause_offsets.len())?;
227 let mut d_lits = memory.alloc::<i32>(literals.len())?;
228
229 provider
230 .htod_sync_copy_into_tracked(&clause_offsets, &mut d_offsets)
231 .map_err(|e| XlogError::Kernel(format!("Failed to upload CNF offsets: {}", e)))?;
232 provider
233 .htod_sync_copy_into_tracked(&literals, &mut d_lits)
234 .map_err(|e| XlogError::Kernel(format!("Failed to upload CNF lits: {}", e)))?;
235
236 provider
237 .htod_launch_metadata_sync_copy_into(&[lit_cap], &mut d_num_lits)
238 .map_err(|e| XlogError::Kernel(format!("Failed to upload CNF num_lits: {}", e)))?;
239
240 Ok(Self {
241 var_cap: num_vars,
242 clause_cap: num_clauses,
243 lit_cap,
244 num_vars: d_num_vars,
245 num_clauses: d_num_clauses,
246 num_lits: d_num_lits,
247 clause_offsets: d_offsets,
248 literals: d_lits,
249 })
250 }
251}