1#![cfg_attr(not(feature = "std"), no_std)]
52#![deny(unsafe_op_in_unsafe_fn)]
53#![warn(missing_docs)]
54#![allow(clippy::missing_safety_doc)]
55
56#[cfg(not(feature = "std"))]
57extern crate alloc;
58
59#[cfg(all(not(feature = "std"), not(test)))]
61mod allocator {
62 use core::alloc::{GlobalAlloc, Layout};
63
64 struct BumpAllocator;
67
68 const HEAP_SIZE: usize = 65536;
70 static mut HEAP: [u8; HEAP_SIZE] = [0; HEAP_SIZE];
71 static mut HEAP_PTR: usize = 0;
72
73 unsafe impl GlobalAlloc for BumpAllocator {
74 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
75 let size = layout.size();
76 let align = layout.align();
77
78 unsafe {
79 let aligned = (HEAP_PTR + align - 1) & !(align - 1);
81
82 if aligned + size > HEAP_SIZE {
83 core::ptr::null_mut()
84 } else {
85 HEAP_PTR = aligned + size;
86 HEAP.as_mut_ptr().add(aligned)
87 }
88 }
89 }
90
91 unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
92 }
95 }
96
97 #[global_allocator]
98 static ALLOCATOR: BumpAllocator = BumpAllocator;
99}
100
101#[cfg(all(not(feature = "std"), not(test), target_arch = "wasm32"))]
103#[panic_handler]
104fn panic(_info: &core::panic::PanicInfo) -> ! {
105 core::arch::wasm32::unreachable()
107}
108
109#[cfg(all(not(feature = "std"), not(test), not(target_arch = "wasm32")))]
111#[panic_handler]
112fn panic(_info: &core::panic::PanicInfo) -> ! {
113 loop {}
114}
115
116pub mod delta;
117pub mod evidence;
118pub mod report;
119pub mod shard;
120
121use crate::delta::{Delta, DeltaTag};
122use crate::evidence::EvidenceAccumulator;
123use crate::report::{TileReport, TileStatus, WitnessFragment};
124use crate::shard::CompactGraph;
125use core::mem::size_of;
126
127pub const MAX_DELTA_BUFFER: usize = 64;
129
130#[repr(C)]
132pub struct TileState {
133 pub tile_id: u8,
135 pub status: u8,
137 pub tick: u32,
139 pub generation: u16,
141 pub _reserved: [u8; 2],
143 pub graph: CompactGraph,
145 pub evidence: EvidenceAccumulator,
147 pub delta_buffer: [Delta; MAX_DELTA_BUFFER],
149 pub delta_count: u16,
151 pub delta_head: u16,
153 pub last_report: TileReport,
155}
156
157impl TileState {
158 pub const STATUS_INITIALIZED: u8 = 0x01;
160 pub const STATUS_HAS_DELTAS: u8 = 0x02;
162 pub const STATUS_DIRTY: u8 = 0x04;
164 pub const STATUS_ERROR: u8 = 0x80;
166
167 pub fn new(tile_id: u8) -> Self {
169 Self {
170 tile_id,
171 status: Self::STATUS_INITIALIZED,
172 tick: 0,
173 generation: 0,
174 _reserved: [0; 2],
175 graph: CompactGraph::new(),
176 evidence: EvidenceAccumulator::new(),
177 delta_buffer: [Delta::nop(); MAX_DELTA_BUFFER],
178 delta_count: 0,
179 delta_head: 0,
180 last_report: TileReport::new(tile_id),
181 }
182 }
183
184 pub fn ingest_delta(&mut self, delta: &Delta) -> bool {
189 if self.delta_count as usize >= MAX_DELTA_BUFFER {
190 return false;
191 }
192
193 let idx = (self.delta_head as usize + self.delta_count as usize) % MAX_DELTA_BUFFER;
194 self.delta_buffer[idx] = *delta;
195 self.delta_count += 1;
196 self.status |= Self::STATUS_HAS_DELTAS;
197 true
198 }
199
200 #[inline]
207 pub unsafe fn ingest_delta_raw(&mut self, ptr: *const u8) -> bool {
208 let delta = unsafe { &*(ptr as *const Delta) };
209 self.ingest_delta(delta)
210 }
211
212 pub fn tick(&mut self, tick_number: u32) -> TileReport {
220 self.tick = tick_number;
221 let tick_start = self.current_time_us();
222
223 let deltas_processed = self.process_deltas();
225
226 if self.graph.status & CompactGraph::STATUS_DIRTY != 0 {
228 self.graph.recompute_components();
229 }
230
231 let mut report = TileReport::new(self.tile_id);
233 report.tick = tick_number;
234 report.generation = self.generation;
235 report.status = TileStatus::Complete;
236
237 report.num_vertices = self.graph.num_vertices;
239 report.num_edges = self.graph.num_edges;
240 report.num_components = self.graph.num_components;
241 report.set_connected(self.graph.is_connected());
242
243 if self.graph.status & CompactGraph::STATUS_DIRTY != 0 {
244 report.graph_flags |= TileReport::GRAPH_DIRTY;
245 }
246
247 report.log_e_value = self.evidence.global_log_e;
249 report.obs_count = self.evidence.total_obs as u16;
250 report.rejected_count = self.evidence.rejected_count;
251
252 report.witness = self.compute_witness_fragment();
254
255 let tick_end = self.current_time_us();
257 report.tick_time_us = (tick_end - tick_start) as u16;
258 report.deltas_processed = deltas_processed as u16;
259 report.memory_kb = (Self::memory_size() / 1024) as u16;
260
261 self.last_report = report;
262 report
263 }
264
265 pub fn get_witness_fragment(&self) -> WitnessFragment {
267 self.last_report.witness
268 }
269
270 fn process_deltas(&mut self) -> usize {
272 let mut processed = 0;
273
274 while self.delta_count > 0 {
275 let delta = self.delta_buffer[self.delta_head as usize];
276 self.delta_head = ((self.delta_head as usize + 1) % MAX_DELTA_BUFFER) as u16;
277 self.delta_count -= 1;
278
279 self.apply_delta(&delta);
280 processed += 1;
281 }
282
283 self.status &= !Self::STATUS_HAS_DELTAS;
284 processed
285 }
286
287 fn apply_delta(&mut self, delta: &Delta) {
289 match delta.tag {
290 DeltaTag::Nop => {}
291 DeltaTag::EdgeAdd => {
292 let ea = unsafe { delta.get_edge_add() };
293 self.graph.add_edge(ea.source, ea.target, ea.weight);
294 self.generation = self.generation.wrapping_add(1);
295 }
296 DeltaTag::EdgeRemove => {
297 let er = unsafe { delta.get_edge_remove() };
298 self.graph.remove_edge(er.source, er.target);
299 self.generation = self.generation.wrapping_add(1);
300 }
301 DeltaTag::WeightUpdate => {
302 let wu = unsafe { delta.get_weight_update() };
303 self.graph.update_weight(wu.source, wu.target, wu.new_weight);
304 }
305 DeltaTag::Observation => {
306 let obs = unsafe { *delta.get_observation() };
307 self.evidence.process_observation(obs, self.tick);
308 }
309 DeltaTag::BatchEnd => {
310 self.status |= Self::STATUS_DIRTY;
312 }
313 DeltaTag::Checkpoint => {
314 }
316 DeltaTag::Reset => {
317 self.graph.clear();
318 self.evidence.reset();
319 self.generation = 0;
320 }
321 }
322 }
323
324 fn compute_witness_fragment(&self) -> WitnessFragment {
326 let mut min_degree = u8::MAX;
328 let mut seed = 0u16;
329
330 for v in 0..shard::MAX_SHARD_VERTICES {
331 if self.graph.vertices[v].is_active() {
332 let degree = self.graph.vertices[v].degree;
333 if degree < min_degree && degree > 0 {
334 min_degree = degree;
335 seed = v as u16;
336 }
337 }
338 }
339
340 let mut boundary = 0u16;
342 for v in 0..shard::MAX_SHARD_VERTICES {
343 if self.graph.vertices[v].is_active()
344 && (self.graph.vertices[v].flags & shard::VertexEntry::FLAG_BOUNDARY) != 0
345 {
346 boundary += 1;
347 }
348 }
349
350 let local_min_cut = if min_degree == u8::MAX {
353 0
354 } else {
355 min_degree as u16 * 100 };
358
359 let mut fragment = WitnessFragment::new(
360 seed,
361 boundary,
362 self.graph.num_vertices,
363 local_min_cut,
364 );
365 fragment.component = self.graph.num_components;
366 fragment.compute_hash();
367
368 fragment
369 }
370
371 #[inline]
373 fn current_time_us(&self) -> u32 {
374 self.tick * 1000
377 }
378
379 pub const fn memory_size() -> usize {
381 size_of::<Self>()
382 }
383
384 pub fn reset(&mut self) {
386 self.graph.clear();
387 self.evidence.reset();
388 self.delta_count = 0;
389 self.delta_head = 0;
390 self.tick = 0;
391 self.generation = 0;
392 self.status = Self::STATUS_INITIALIZED;
393 }
394
395 #[inline]
397 pub fn has_pending_deltas(&self) -> bool {
398 self.delta_count > 0
399 }
400
401 #[inline]
403 pub fn is_error(&self) -> bool {
404 self.status & Self::STATUS_ERROR != 0
405 }
406}
407
408static mut TILE_STATE: Option<TileState> = None;
414
415#[no_mangle]
422pub unsafe extern "C" fn init_tile(tile_id: u8) {
423 unsafe {
424 TILE_STATE = Some(TileState::new(tile_id));
425 }
426}
427
428#[no_mangle]
437pub unsafe extern "C" fn ingest_delta(ptr: *const u8) -> i32 {
438 unsafe {
439 match TILE_STATE.as_mut() {
440 Some(tile) => {
441 if tile.ingest_delta_raw(ptr) {
442 1
443 } else {
444 0
445 }
446 }
447 None => 0,
448 }
449 }
450}
451
452#[no_mangle]
461pub unsafe extern "C" fn tick(tick_number: u32, report_ptr: *mut u8) -> i32 {
462 unsafe {
463 match TILE_STATE.as_mut() {
464 Some(tile) => {
465 let report = tile.tick(tick_number);
466 let report_bytes =
468 core::slice::from_raw_parts(&report as *const TileReport as *const u8, 64);
469 core::ptr::copy_nonoverlapping(report_bytes.as_ptr(), report_ptr, 64);
470 1
471 }
472 None => 0,
473 }
474 }
475}
476
477#[no_mangle]
486pub unsafe extern "C" fn get_witness_fragment(fragment_ptr: *mut u8) -> i32 {
487 unsafe {
488 match TILE_STATE.as_ref() {
489 Some(tile) => {
490 let fragment = tile.get_witness_fragment();
491 let fragment_bytes = core::slice::from_raw_parts(
492 &fragment as *const WitnessFragment as *const u8,
493 16,
494 );
495 core::ptr::copy_nonoverlapping(fragment_bytes.as_ptr(), fragment_ptr, 16);
496 1
497 }
498 None => 0,
499 }
500 }
501}
502
503#[no_mangle]
511pub unsafe extern "C" fn get_status() -> u8 {
512 unsafe {
513 match TILE_STATE.as_ref() {
514 Some(tile) => tile.status,
515 None => 0xFF,
516 }
517 }
518}
519
520#[no_mangle]
526pub unsafe extern "C" fn reset_tile() {
527 unsafe {
528 if let Some(tile) = TILE_STATE.as_mut() {
529 tile.reset();
530 }
531 }
532}
533
534#[no_mangle]
536pub extern "C" fn get_memory_usage() -> u32 {
537 TileState::memory_size() as u32
538}
539
540#[cfg(test)]
545mod tests {
546 use super::*;
547 use crate::delta::Observation;
548
549 #[test]
550 fn test_tile_state_new() {
551 let tile = TileState::new(42);
552 assert_eq!(tile.tile_id, 42);
553 assert_eq!(tile.tick, 0);
554 assert_eq!(tile.delta_count, 0);
555 }
556
557 #[test]
558 fn test_ingest_delta() {
559 let mut tile = TileState::new(0);
560
561 let delta = Delta::edge_add(1, 2, 100);
562 assert!(tile.ingest_delta(&delta));
563 assert_eq!(tile.delta_count, 1);
564 assert!(tile.has_pending_deltas());
565 }
566
567 #[test]
568 fn test_ingest_buffer_full() {
569 let mut tile = TileState::new(0);
570
571 for i in 0..MAX_DELTA_BUFFER {
573 let delta = Delta::edge_add(i as u16, (i + 1) as u16, 100);
574 assert!(tile.ingest_delta(&delta));
575 }
576
577 let delta = Delta::edge_add(100, 101, 100);
579 assert!(!tile.ingest_delta(&delta));
580 }
581
582 #[test]
583 fn test_tick_processes_deltas() {
584 let mut tile = TileState::new(0);
585
586 tile.ingest_delta(&Delta::edge_add(0, 1, 100));
588 tile.ingest_delta(&Delta::edge_add(1, 2, 100));
589 tile.ingest_delta(&Delta::edge_add(2, 0, 100));
590
591 let report = tile.tick(1);
593
594 assert_eq!(report.tile_id, 0);
595 assert_eq!(report.tick, 1);
596 assert_eq!(report.status, TileStatus::Complete);
597 assert_eq!(report.num_vertices, 3);
598 assert_eq!(report.num_edges, 3);
599 assert_eq!(report.deltas_processed, 3);
600 assert!(!tile.has_pending_deltas());
601 }
602
603 #[test]
604 fn test_tick_connectivity() {
605 let mut tile = TileState::new(0);
606
607 tile.ingest_delta(&Delta::edge_add(0, 1, 100));
609 tile.ingest_delta(&Delta::edge_add(1, 2, 100));
610
611 let report = tile.tick(1);
612 assert!(report.is_connected());
613 assert_eq!(report.num_components, 1);
614 }
615
616 #[test]
617 fn test_tick_disconnected() {
618 let mut tile = TileState::new(0);
619
620 tile.ingest_delta(&Delta::edge_add(0, 1, 100));
622 tile.ingest_delta(&Delta::edge_add(2, 3, 100));
623
624 let report = tile.tick(1);
625 assert!(!report.is_connected());
626 assert_eq!(report.num_components, 2);
627 }
628
629 #[test]
630 fn test_observation_processing() {
631 let mut tile = TileState::new(0);
632
633 tile.evidence.add_connectivity_hypothesis(5);
635
636 for i in 0..5 {
638 let obs = Observation::connectivity(5, true);
639 tile.ingest_delta(&Delta::observation(obs));
640 tile.tick(i);
641 }
642
643 assert!(tile.evidence.global_e_value() > 1.0);
644 }
645
646 #[test]
647 fn test_witness_fragment() {
648 let mut tile = TileState::new(0);
649
650 tile.ingest_delta(&Delta::edge_add(0, 1, 100));
651 tile.ingest_delta(&Delta::edge_add(1, 2, 100));
652 tile.ingest_delta(&Delta::edge_add(2, 0, 100));
653
654 tile.tick(1);
655 let witness = tile.get_witness_fragment();
656
657 assert!(!witness.is_empty());
658 assert_eq!(witness.cardinality, 3);
659 assert_ne!(witness.hash, 0);
660 }
661
662 #[test]
663 fn test_reset() {
664 let mut tile = TileState::new(0);
665
666 tile.ingest_delta(&Delta::edge_add(0, 1, 100));
667 tile.tick(1);
668
669 assert_eq!(tile.graph.num_edges, 1);
670
671 tile.reset();
672
673 assert_eq!(tile.graph.num_edges, 0);
674 assert_eq!(tile.graph.num_vertices, 0);
675 assert_eq!(tile.tick, 0);
676 }
677
678 #[test]
679 fn test_memory_size() {
680 let size = TileState::memory_size();
681 assert!(size <= 65536, "TileState exceeds 64KB: {} bytes", size);
683 }
684
685 #[test]
686 fn test_edge_removal() {
687 let mut tile = TileState::new(0);
688
689 tile.ingest_delta(&Delta::edge_add(0, 1, 100));
690 tile.ingest_delta(&Delta::edge_add(1, 2, 100));
691 tile.tick(1);
692
693 assert_eq!(tile.graph.num_edges, 2);
694
695 tile.ingest_delta(&Delta::edge_remove(0, 1));
696 tile.tick(2);
697
698 assert_eq!(tile.graph.num_edges, 1);
699 }
700
701 #[test]
702 fn test_weight_update() {
703 let mut tile = TileState::new(0);
704
705 tile.ingest_delta(&Delta::edge_add(0, 1, 100));
706 tile.tick(1);
707
708 assert_eq!(tile.graph.edge_weight(0, 1), Some(100));
709
710 tile.ingest_delta(&Delta::weight_update(0, 1, 200));
711 tile.tick(2);
712
713 assert_eq!(tile.graph.edge_weight(0, 1), Some(200));
714 }
715}