1#![allow(clippy::too_many_arguments)]
37use vyre::ir::Program;
38use vyre_foundation::execution_plan::fusion::fuse_programs;
39use vyre_primitives::bitset::and::bitset_and;
40use vyre_primitives::bitset::copy::bitset_copy;
41use vyre_primitives::fixpoint::bitset_fixpoint::bitset_fixpoint_warm_start;
42use vyre_primitives::graph::csr_forward_traverse::{bitset_words, csr_forward_traverse};
43use vyre_primitives::graph::program_graph::ProgramGraphShape;
44
45pub(crate) const OP_ID: &str = "weir::cross_language";
46
47pub const EDGE_KIND_FFI: u32 = 0x0001_0000;
52
53pub const EDGE_KIND_ALL: u32 = 0xFFFF_FFFF;
57
58pub fn cross_language(
75 node_count: u32,
76 source: &str,
77 sink: &str,
78 post_cross: &str,
79 current: &str,
80 next: &str,
81 changed: &str,
82 seed: &str,
83 out: &str,
84) -> Result<Program, String> {
85 let words = bitset_words(node_count);
86 let edge_capacity = node_count
87 .checked_mul(8)
88 .map(|capacity| capacity.max(1))
89 .ok_or_else(|| {
90 format!(
91 "Fix: cross-language reachability shape for node_count={node_count} exceeds u32 edge-capacity metadata. Partition the interprocedural graph before constructing the GPU program."
92 )
93 })?;
94 let shape = ProgramGraphShape::new(node_count, edge_capacity);
95
96 let stage_source_seed = bitset_copy(seed, source, words);
99 let stage_source_reach = bitset_fixpoint_warm_start(current, next, changed, seed, words);
100
101 let stage_bridge = csr_forward_traverse(shape, current, post_cross, EDGE_KIND_FFI);
105
106 let stage_seed = bitset_copy(seed, post_cross, words);
109 let stage_post_cross_reach = bitset_fixpoint_warm_start(current, next, changed, seed, words);
110
111 let stage_sink = bitset_and(current, sink, out, words);
114
115 let fused = fuse_programs(&[
116 stage_source_seed,
117 stage_source_reach,
118 stage_bridge,
119 stage_seed,
120 stage_post_cross_reach,
121 stage_sink,
122 ])
123 .map_err(|error| {
124 format!(
125 "Fix: cross-language reachability fusion failed for node_count={node_count}, words={words}: {error:?}"
126 )
127 })?;
128 Ok(vyre_harness::region::tag_program(OP_ID, fused))
129}
130
131#[must_use]
136#[cfg(any(test, feature = "cpu-parity"))]
137#[deprecated(
138 note = "reference oracle only; production code must dispatch the Weir Program on a concrete GPU backend or use weir::oracle for parity evidence"
139)]
140pub(crate) fn cpu_ref(
141 node_count: u32,
142 edge_offsets: &[u32],
143 edge_targets: &[u32],
144 edge_kind_mask: &[u32],
145 edge_is_ffi: &[u32],
146 source: &[u32],
147 sink: &[u32],
148) -> Vec<u32> {
149 let nodes =
150 usize::try_from(node_count).expect("cross_language_cpu_ref node_count must fit host usize");
151 let words = nodes.checked_add(31).unwrap_or_else(|| {
152 panic!(
153 "cross_language_cpu_ref node_count={node_count} overflows host bitset word calculation. Fix: shard the oracle graph."
154 )
155 }) / 32;
156 crate::dispatch_decode::require_csr_shape(
157 "cross_language_cpu_ref",
158 node_count,
159 edge_offsets,
160 edge_targets,
161 edge_kind_mask,
162 )
163 .expect("cross_language cpu_ref oracle received malformed CSR graph");
164 if edge_is_ffi.len() != edge_targets.len() {
165 panic!(
166 "cross_language cpu_ref oracle received malformed FFI edge mask: expected {} words, got {}",
167 edge_targets.len(),
168 edge_is_ffi.len()
169 );
170 }
171 crate::dispatch_decode::require_bitset_words("cross_language_cpu_ref source", source, words)
172 .expect("cross_language cpu_ref oracle received malformed source bitset");
173 crate::dispatch_decode::require_bitset_words("cross_language_cpu_ref sink", sink, words)
174 .expect("cross_language cpu_ref oracle received malformed sink bitset");
175 let test = |bs: &[u32], n: u32| -> bool {
176 let n = usize::try_from(n).expect("cross_language_cpu_ref bit index must fit usize");
177 let w = n / 32;
178 let b = n % 32;
179 bs[w] & (1u32 << b) != 0
180 };
181 let mark = |bs: &mut [u32], n: u32| -> bool {
182 let n = usize::try_from(n).expect("cross_language_cpu_ref bit index must fit usize");
183 let w = n / 32;
184 let b = n % 32;
185 let mask = 1u32 << b;
186 let was_set = bs[w] & mask != 0;
187 if was_set {
188 return false;
189 }
190 bs[w] |= 1u32 << b;
191 true
192 };
193
194 let mut source_side_reach = vec![0u32; words];
196 let mut queue = Vec::new();
197 for n in 0..node_count {
198 if test(source, n) && mark(&mut source_side_reach, n) {
199 queue.push(n);
200 }
201 }
202 let mut cursor = 0usize;
203 while cursor < queue.len() {
204 let n = queue[cursor];
205 cursor += 1;
206 let node = usize::try_from(n).expect("cross_language_cpu_ref node index must fit usize");
207 let start = usize::try_from(edge_offsets[node])
208 .expect("cross_language_cpu_ref CSR row start must fit usize");
209 let end = usize::try_from(edge_offsets[node + 1])
210 .expect("cross_language_cpu_ref CSR row end must fit usize");
211 for i in start..end {
212 let t = edge_targets[i];
213 if t < node_count && mark(&mut source_side_reach, t) {
214 queue.push(t);
215 }
216 }
217 }
218
219 let mut post_cross = vec![0u32; words];
221 queue.clear();
222 for n in 0..node_count {
223 if !test(&source_side_reach, n) {
224 continue;
225 }
226 let node = usize::try_from(n).expect("cross_language_cpu_ref node index must fit usize");
227 let start = usize::try_from(edge_offsets[node])
228 .expect("cross_language_cpu_ref CSR row start must fit usize");
229 let end = usize::try_from(edge_offsets[node + 1])
230 .expect("cross_language_cpu_ref CSR row end must fit usize");
231 for i in start..end {
232 let kind = edge_kind_mask[i];
233 let is_ffi = edge_is_ffi[i];
234 if (kind & EDGE_KIND_FFI) != 0 || is_ffi != 0 {
235 let t = edge_targets[i];
236 if t < node_count && mark(&mut post_cross, t) {
237 queue.push(t);
238 }
239 }
240 }
241 }
242
243 let mut reach = post_cross;
248 let mut cursor = 0usize;
249 while cursor < queue.len() {
250 let n = queue[cursor];
251 cursor += 1;
252 let node = usize::try_from(n).expect("cross_language_cpu_ref node index must fit usize");
253 let start = usize::try_from(edge_offsets[node])
254 .expect("cross_language_cpu_ref CSR row start must fit usize");
255 let end = usize::try_from(edge_offsets[node + 1])
256 .expect("cross_language_cpu_ref CSR row end must fit usize");
257 for i in start..end {
258 let t = edge_targets[i];
259 if t < node_count && mark(&mut reach, t) {
260 queue.push(t);
261 }
262 }
263 }
264
265 let mut out = vec![0u32; words];
267 for w in 0..words {
268 out[w] = reach[w] & sink[w];
269 }
270 out
271}
272
273#[derive(Clone, Copy, Debug, PartialEq, Eq)]
275pub struct CrossLanguage;
276impl super::soundness::SoundnessTagged for CrossLanguage {
277 fn soundness(&self) -> super::soundness::Soundness {
278 super::soundness::Soundness::MayOver
279 }
280}
281
282fn release_witness_program() -> Program {
283 match cross_language(
284 4,
285 "source",
286 "sink",
287 "post_cross",
288 "current",
289 "next",
290 "changed",
291 "seed",
292 "out",
293 ) {
294 Ok(program) => program,
295 Err(error) => {
296 unreachable!("weir::cross_language fixed release witness must fuse; error={error}")
297 }
298 }
299}
300
301fn release_witness_inputs() -> Vec<Vec<Vec<u8>>> {
302 let u32s = crate::dispatch_decode::pack_u32;
303 vec![vec![
304 u32s(&[0]), u32s(&[0b0001]), u32s(&[0]), u32s(&[0]), u32s(&[0]), u32s(&[0, 0, 0, 0]), u32s(&[0, 1, 1, 1, 1]), u32s(&[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), u32s(&[EDGE_KIND_FFI, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), u32s(&[0, 0, 0, 0]), u32s(&[0]), u32s(&[0b0010]), u32s(&[0]), ]]
318}
319
320fn release_witness_expected() -> Vec<Vec<Vec<u8>>> {
321 let u32s = crate::dispatch_decode::pack_u32;
322 vec![vec![
323 u32s(&[0b0010]), u32s(&[0b0011]), u32s(&[1]), u32s(&[0b0010]), u32s(&[0b0010]), ]]
329}
330
331inventory::submit! {
332 vyre_harness::OpEntry::new(
333 OP_ID,
334 release_witness_program,
335 Some(release_witness_inputs),
336 Some(release_witness_expected),
337 )
338}
339
340#[cfg(test)]
341#[allow(deprecated)]
342mod tests {
343 use super::*;
344
345 fn one(n: u32) -> Vec<u32> {
346 let w = n as usize / 32;
347 let b = n as usize % 32;
348 let mut v = vec![0u32; w + 1];
349 v[w] = 1u32 << b;
350 v
351 }
352
353 #[test]
354 fn flow_through_ffi_edge_reaches_sink() {
355 let edge_offsets = vec![0, 1, 2, 2];
358 let edge_targets = vec![1u32, 2];
359 let edge_kind_mask = vec![EDGE_KIND_FFI, 0x1];
360 let edge_is_ffi = vec![1u32, 0];
361 let source = one(0);
362 let sink = one(2);
363 let out = cpu_ref(
364 3,
365 &edge_offsets,
366 &edge_targets,
367 &edge_kind_mask,
368 &edge_is_ffi,
369 &source,
370 &sink,
371 );
372 assert!(out[0] & (1 << 2) != 0, "sink should be reached: {out:?}");
373 }
374
375 #[test]
376 fn intra_language_flow_does_not_reach_sink() {
377 let edge_offsets = vec![0, 1, 2, 2];
380 let edge_targets = vec![1u32, 2];
381 let edge_kind_mask = vec![0x1, 0x1];
382 let edge_is_ffi = vec![0u32, 0];
383 let source = one(0);
384 let sink = one(2);
385 let out = cpu_ref(
386 3,
387 &edge_offsets,
388 &edge_targets,
389 &edge_kind_mask,
390 &edge_is_ffi,
391 &source,
392 &sink,
393 );
394 assert_eq!(out, vec![0u32], "no FFI hop → no cross-lang reach: {out:?}");
395 }
396
397 #[test]
398 fn ffi_edge_after_intra_language_setup_reaches_sink() {
399 let edge_offsets = vec![0, 1, 2, 3, 3];
403 let edge_targets = vec![1u32, 2, 3];
404 let edge_kind_mask = vec![0x1, EDGE_KIND_FFI, 0x1];
405 let edge_is_ffi = vec![0u32, 1, 0];
406 let source = one(0);
407 let sink = one(3);
408 let out = cpu_ref(
409 4,
410 &edge_offsets,
411 &edge_targets,
412 &edge_kind_mask,
413 &edge_is_ffi,
414 &source,
415 &sink,
416 );
417 assert!(
418 out[0] & (1 << 3) != 0,
419 "FFI bridge after intra-language setup should reach sink: {out:?}"
420 );
421 }
422
423 #[test]
424 fn empty_source_yields_empty_output() {
425 let edge_offsets = vec![0, 1, 1];
426 let edge_targets = vec![1u32];
427 let edge_kind_mask = vec![EDGE_KIND_FFI];
428 let edge_is_ffi = vec![1u32];
429 let source = vec![0u32];
430 let sink = one(1);
431 let out = cpu_ref(
432 2,
433 &edge_offsets,
434 &edge_targets,
435 &edge_kind_mask,
436 &edge_is_ffi,
437 &source,
438 &sink,
439 );
440 assert_eq!(out, vec![0u32]);
441 }
442
443 #[test]
444 fn multi_step_post_cross_continuation_reaches_distant_sink() {
445 let edge_offsets = vec![0, 1, 2, 3, 3];
448 let edge_targets = vec![1u32, 2, 3];
449 let edge_kind_mask = vec![EDGE_KIND_FFI, 0x1, 0x1];
450 let edge_is_ffi = vec![1u32, 0, 0];
451 let source = one(0);
452 let sink = one(3);
453 let out = cpu_ref(
454 4,
455 &edge_offsets,
456 &edge_targets,
457 &edge_kind_mask,
458 &edge_is_ffi,
459 &source,
460 &sink,
461 );
462 assert!(out[0] & (1 << 3) != 0, "distant sink reachable: {out:?}");
463 }
464
465 #[test]
466 fn op_id_is_canonical() {
467 assert_eq!(OP_ID, "weir::cross_language");
468 }
469
470 #[test]
471 fn release_witness_matches_fused_program_buffer_contract() {
472 let program = release_witness_program();
473 let buffer_names = program
474 .buffers()
475 .iter()
476 .map(|buffer| buffer.name())
477 .collect::<Vec<_>>();
478 assert_eq!(
479 buffer_names,
480 vec![
481 "seed",
482 "source",
483 "current",
484 "next",
485 "changed",
486 "pg_nodes",
487 "pg_edge_offsets",
488 "pg_edge_targets",
489 "pg_edge_kind_mask",
490 "pg_node_tags",
491 "post_cross",
492 "sink",
493 "out",
494 ],
495 "release witness inputs must follow fused Program::buffers order"
496 );
497
498 let inputs = release_witness_inputs();
499 let case = &inputs[0];
500 assert_eq!(case.len(), program.buffers().len());
501 for (buffer, bytes) in program.buffers().iter().zip(case.iter()) {
502 let expected = buffer
503 .static_byte_len()
504 .expect("release witness buffers must have valid static lengths")
505 .expect("release witness buffers must be statically sized");
506 assert_eq!(
507 bytes.len(),
508 expected,
509 "release witness buffer `{}` has wrong byte length",
510 buffer.name()
511 );
512 }
513
514 let writable_names = program
515 .buffers()
516 .iter()
517 .filter(|buffer| {
518 matches!(
519 buffer.access(),
520 vyre::ir::BufferAccess::ReadWrite | vyre::ir::BufferAccess::WriteOnly
521 )
522 })
523 .map(|buffer| buffer.name())
524 .collect::<Vec<_>>();
525 assert_eq!(
526 writable_names,
527 vec!["seed", "current", "changed", "post_cross", "out"]
528 );
529 assert_eq!(
530 release_witness_expected()[0].len(),
531 writable_names.len(),
532 "expected outputs must match writable output order"
533 );
534 }
535
536 #[test]
537 fn cpu_ref_rejects_malformed_csr() {
538 let panic = std::panic::catch_unwind(|| {
539 let _ = cpu_ref(2, &[1, 1, 1], &[0], &[0], &[0], &[0b1], &[0b1]);
540 })
541 .expect_err("malformed CSR must panic");
542 let msg = panic_message(&panic);
543 assert!(msg.contains("malformed CSR graph"), "got {msg}");
544 }
545
546 #[test]
547 fn cpu_ref_rejects_mismatched_ffi_mask() {
548 let panic = std::panic::catch_unwind(|| {
549 let _ = cpu_ref(2, &[0, 1, 1], &[1], &[0], &[0, 0], &[0b1], &[0b1]);
550 })
551 .expect_err("mismatched FFI mask must panic");
552 let msg = panic_message(&panic);
553 assert!(msg.contains("malformed FFI edge mask"), "got {msg}");
554 }
555
556 #[test]
557 fn cpu_ref_rejects_malformed_source_bitset() {
558 let panic = std::panic::catch_unwind(|| {
559 let _ = cpu_ref(2, &[0, 1, 1], &[1], &[0], &[0], &[0b1, 0b0], &[0b1]);
560 })
561 .expect_err("malformed source must panic");
562 let msg = panic_message(&panic);
563 assert!(msg.contains("malformed source bitset"), "got {msg}");
564 }
565
566 #[test]
567 fn cpu_ref_rejects_malformed_sink_bitset() {
568 let panic = std::panic::catch_unwind(|| {
569 let _ = cpu_ref(2, &[0, 1, 1], &[1], &[0], &[0], &[0b1], &[0b1, 0b0]);
570 })
571 .expect_err("malformed sink must panic");
572 let msg = panic_message(&panic);
573 assert!(msg.contains("malformed sink bitset"), "got {msg}");
574 }
575
576 fn panic_message(panic: &Box<dyn std::any::Any + Send>) -> String {
577 if let Some(s) = (**panic).downcast_ref::<String>() {
578 s.clone()
579 } else if let Some(s) = (**panic).downcast_ref::<&'static str>() {
580 (*s).to_string()
581 } else {
582 "<non-string>".to_string()
583 }
584 }
585}