Skip to main content

xlog_prob/compilation/
validation.rs

1//! GPU-native equivalence validation (φ ≡ C) using the GPU CDCL verifier.
2
3use 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/// Configuration for GPU-native equivalence verification (phi equiv C).
49///
50/// Controls the CDCL solver parameters and whether to reuse the solver
51/// workspace across multiple equivalence checks. Workspace reuse amortizes
52/// device-memory allocation when verifying many circuits in sequence (e.g.,
53/// during incremental compilation).
54#[derive(Debug, Clone, Copy, Default)]
55#[non_exhaustive]
56pub struct GpuEquivalenceConfig {
57    /// CDCL solver configuration for the equivalence verifier.
58    pub cdcl: GpuCdclConfig,
59    /// Reuse the CDCL workspace across successive verifier invocations.
60    pub reuse_workspace: bool,
61}
62
63/// GPU-resident equivalence queries + device metadata required to solve them without host reads.
64pub struct GpuEquivalenceQueries {
65    /// CNF query that is satisfiable exactly when the source entails a circuit counterexample.
66    pub q1: GpuCnf,
67    /// CNF query that is satisfiable exactly when the circuit entails a source counterexample.
68    pub q2: GpuCnf,
69    /// Base variable id for the ¬phi selector vars in q2 (len=1, device-resident).
70    pub q2_unsat_var_base: TrackedCudaSlice<u32>,
71}
72
73struct CircuitCnf {
74    cnf: GpuCnf,
75    /// Exclusive prefix sum over `is_internal(node)` (len = num_nodes).
76    /// Used to map internal node ids -> Tseitin vars in kernels.
77    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    // Safe, host-known upper bounds (no device->host reads required).
122    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    // Per-node count arrays (len = num_nodes) used for exclusive scans.
148    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    // SAFETY: sat_xgcf_cnf_counts(compile_needed, node_type, child_offsets, num_nodes, internal_counts, clause_counts, lit_counts)
160    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    // Capture last elements before scans overwrite them.
181    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    // SAFETY: sat_xgcf_cnf_capture_last_counts(internal_counts, clause_counts, lit_counts, num_nodes, out_internal_last, out_clause_last, out_lit_last)
191    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    // No device synchronize: next ops are alloc + kernel launches on same stream.
215
216    // Output CNF buffers + device-resident meta.
217    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    // SAFETY: sat_xgcf_cnf_compute_totals(internal_prefix, clause_base, lit_base, internal_last*, clause_last*, lit_last*, num_nodes, base_num_vars, clause_cap, lit_cap, out_num_vars*, out_num_clauses*, out_num_lits*)
229    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    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
245    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    // sat_xgcf_cnf_emit(...) exceeds cudarc's tuple-arity impls for LaunchAsync, so launch with
262    // an explicit parameter list.
263    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    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
282    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    // sat_xgcf_cnf_emit does not write the CSR terminator; finalize deterministically on device.
295    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    // SAFETY: sat_cnf_write_terminator(out_offsets, num_clauses*, num_lits*)
301    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    // No device synchronize: returns device-resident CNF; same-stream ordering suffices.
313
314    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    // Copy phi (exact sizes) into the front.
384    // sat_cnf_copy_into(src_offsets, src_lits, src_num_clauses*, src_num_lits*, src_clause_cap, src_lit_cap,
385    //                  dst_clause_base*, dst_lit_base*, dst_clause_cap, dst_lit_cap, dst_offsets, dst_lits)
386    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
387    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    // Copy CNF(C) after phi using device-resident bases (phi.num_clauses/phi.num_lits).
413    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    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
418    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    // Finalize: append unit clause forcing root false + write device-resident totals for the combined query.
444    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    // IMPORTANT: When launching with an explicit `Vec<*mut c_void>` parameter list, scalar kernel
451    // arguments MUST be backed by stable host storage until `cuLaunchKernel` copies them. Do not
452    // pass temporaries like `circuit.root().as_kernel_param()` or `0i32.as_kernel_param()`.
453    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(), // force_false
467        (&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(), // extra_num_vars
473        (&d_zero).as_kernel_param(), // extra_num_clauses
474        (&d_zero).as_kernel_param(), // extra_num_lits
475        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    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
489    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    // No device synchronize: returns device-resident CNF; same-stream ordering suffices.
501
502    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    // ¬phi encoding:
528    // clauses_notphi = sum(len_j + 1) + 1 = L + m + 1
529    // lits_notphi = sum(3*len_j + 1) + m = 3L + 2m
530    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    // Device-resident exact extras for ¬phi (computed from phi.num_*).
575    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    // Copy CNF(C) into the front (exact sizes).
591    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    // sat_cnf_copy_into(...)
597    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
598    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    // Compute exact ¬phi size contributions on GPU.
624    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    // SAFETY: sat_not_phi_counts(phi_num_clauses*, phi_num_lits*, out_extra_num_vars*, out_extra_num_clauses*, out_extra_num_lits*)
628    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    // Prepare: insert unit clause forcing root true and compute device-resident totals / bases.
648    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    // IMPORTANT: See note in build_phi_and_not_c about stable scalar kernel parameters.
655    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(), // force_true
669        (&d_zero).as_kernel_param(),  // clause_base
670        (&d_zero).as_kernel_param(),  // lit_base
671        (&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(), // extra_num_vars (u_j vars)
675        (&d_extra_num_clauses).as_kernel_param(), // extra_num_clauses
676        (&d_extra_num_lits).as_kernel_param(), // extra_num_lits
677        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    // SAFETY: kernel arguments match the PTX signature; device buffers were allocated with sufficient size
691    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    // Emit ¬phi encoding after CNF(C) + unit using device-resident base indices.
704    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    // SAFETY: sat_emit_not_phi(phi_offsets, phi_lits, phi_num_clauses*, unsat_var_base*, out_clause_base*, out_lit_base*, out_offsets, out_lits)
712    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    // No device synchronize: returns device-resident CNF; same-stream ordering suffices.
734
735    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        // Fail-fast: if query CNFs are malformed, the solver may hang or misbehave.
762        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        // q1: decisions only on semantically meaningful phi vars (exclude internal/Tseitin vars).
772        solver.solve_expect_unsat_with_branch_limit_ws(
773            &mut ws,
774            &queries.q1,
775            phi_decision_var_limit,
776        )?;
777        // q2: decisions on semantically meaningful phi vars + ¬phi selector vars.
778        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        // q1: decisions only on semantically meaningful phi vars (exclude internal/Tseitin vars).
787        solver.solve_expect_unsat_with_branch_limit(&queries.q1, phi_decision_var_limit)?;
788        // q2: decisions on semantically meaningful phi vars + ¬phi selector vars.
789        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
799/// Build the two equivalence-check queries on GPU:
800/// - q1 = φ ∧ ¬C
801/// - q2 = C ∧ ¬φ
802///
803/// This helper exists so tests and tooling can inspect query CNFs without duplicating kernel
804/// orchestration logic.
805pub fn build_equivalence_queries_gpu(
806    phi: &GpuCnf,
807    circuit: &GpuXgcf,
808    provider: &Arc<CudaKernelProvider>,
809) -> Result<GpuEquivalenceQueries> {
810    // Non-gated path: force compilation/verification on.
811    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
955/// Pre-launch CNF size bound for the equivalence verifier.
956///
957/// The GPU CDCL equivalence check is treewidth-exponential; on a hard CNF it
958/// can run long enough to hit a CUDA launch failure, which poisons the primary
959/// context for every later compile in the process. Catching that
960/// after the fact is impossible — a poisoned context cannot be recovered
961/// in-process — so the only sound mitigation is to decline *before* launch.
962///
963/// The bound is on the host-side `var_cap`/`clause_cap` (capacity upper bounds,
964/// no device read). Defaults are unbounded (`u32::MAX`), so behavior is
965/// unchanged unless an operator sets `XLOG_D4_VERIFY_MAX_VARS` /
966/// `XLOG_D4_VERIFY_MAX_CLAUSES`. The recommended production default is a
967/// calibration follow-up against the measured explosion boundary.
968fn 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
984/// Pure size-bound decision: typed [`XlogError::VerifyBudgetExceeded`] when the
985/// CNF caps exceed the budget. GPU-free so the decline path is unit-testable
986/// without a device.
987fn 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        // A size-based decline is a COMPILE-capacity issue ("too big to
996        // compile safely"), distinct from the verify-phase conflict budget.
997        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
1008/// Decline the verify with a typed [`XlogError::VerifyBudgetExceeded`] when the
1009/// CNF exceeds the configured size bound, before any kernel launches.
1010///
1011/// Callable from the compile path so the size guard runs BEFORE `compile_gpu_d4`
1012/// — the D4 compile itself can crash (context-poisoning launch failure) on a
1013/// large CNF, earlier than the verify, so a size check only at the verify entry
1014/// is too late to prevent it.
1015pub(crate) fn check_verify_size_bound(phi: &GpuCnf, context: &str) -> Result<()> {
1016    // Operator/calibration diagnostic: log the CNF caps the bound sees, so the
1017    // safe value of XLOG_D4_VERIFY_MAX_VARS/_MAX_CLAUSES can be read off a real
1018    // workload (must sit above every program that compiles fine, below the
1019    // explosion boundary). Off unless XLOG_DEBUG_VERIFY_SIZE=1.
1020    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
1036/// Proves source-CNF and circuit equivalence with the GPU CDCL verifier.
1037pub 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
1048/// Proves equivalence only when the device-resident compilation gate is set.
1049pub 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    // A CNF over the configured bound must decline with a typed
1074    // VerifyBudgetExceeded carrying the tripping sizes — BEFORE any launch —
1075    // never a panic and never a doomed kernel that poisons the context.
1076    #[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            // Size declines are COMPILE-capacity, distinct from verify budget.
1082            XlogError::CompileCapacityExceeded { context, detail } => {
1083                assert_eq!(context, "ctx");
1084                // detail names the tripping sizes and the bound.
1085                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    // Default budget is unbounded (u32::MAX) => no regression: any real CNF
1106    // passes the bound unless an operator opts in via env.
1107    #[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}