Skip to main content

celox/backend/
wasm_runtime.rs

1//! Wasmtime-based WASM backend for simulation.
2//!
3//! Compiles SIRT → WASM bytecode via [`wasm_codegen`], then instantiates and
4//! executes the generated modules with Wasmtime. Provides the same interface
5//! as [`JitBackend`] so that [`Simulator`] can use either backend.
6
7use num_bigint::BigUint;
8use wasmtime::{Engine, Linker, Memory, Module, Store, TypedFunc};
9
10use crate::{
11    HashMap, SimulatorOptions,
12    backend::{
13        MemoryLayout, SimulatorErrorCode, get_byte_size,
14        memory_layout::{
15            STATE_HEADER_COMB_CAPTURE_ENABLED_ADDR_OFFSET, STATE_HEADER_RUNTIME_EVENT_ADDR_OFFSET,
16        },
17    },
18    ir::{AbsoluteAddr, SignalRef},
19};
20
21use super::wasm_codegen;
22
23fn wasm_codegen_error(stage: &'static str, source: wasmtime::Error) -> crate::SimulatorError {
24    crate::CodegenError::Wasm { stage, source }.into()
25}
26
27/// Opaque handle to a compiled WASM event (clock/async-reset) function.
28#[derive(Clone, Copy)]
29pub struct WasmEventRef {
30    /// Index into the `event_instances` vec in `SharedWasmCode`.
31    instance_idx: usize,
32    pub addr: AbsoluteAddr,
33    pub id: usize,
34}
35
36impl std::fmt::Debug for WasmEventRef {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        f.debug_struct("WasmEventRef")
39            .field("instance_idx", &self.instance_idx)
40            .field("addr", &self.addr)
41            .field("id", &self.id)
42            .finish()
43    }
44}
45
46impl super::traits::EventHandle for WasmEventRef {
47    fn id(&self) -> usize {
48        self.id
49    }
50    fn addr(&self) -> AbsoluteAddr {
51        self.addr
52    }
53}
54
55impl super::traits::SimBackend for WasmBackend {
56    type Event = WasmEventRef;
57
58    fn eval_comb(&mut self) -> Result<(), super::SimulatorErrorCode> {
59        WasmBackend::eval_comb(self)
60    }
61    fn eval_apply_ff_at(&mut self, event: WasmEventRef) -> Result<(), super::SimulatorErrorCode> {
62        WasmBackend::eval_apply_ff_at(self, &event)
63    }
64    fn eval_only_ff_at(&mut self, event: WasmEventRef) -> Result<(), super::SimulatorErrorCode> {
65        WasmBackend::eval_only_ff_at(self, &event)
66    }
67    fn apply_ff_at(&mut self, event: WasmEventRef) -> Result<(), super::SimulatorErrorCode> {
68        WasmBackend::apply_ff_at(self, &event)
69    }
70    fn resolve_signal(&self, addr: &AbsoluteAddr) -> SignalRef {
71        WasmBackend::resolve_signal(self, addr)
72    }
73    fn resolve_event(&self, addr: &AbsoluteAddr) -> WasmEventRef {
74        WasmBackend::resolve_event(self, addr)
75    }
76    fn resolve_event_opt(&self, addr: &AbsoluteAddr) -> Option<WasmEventRef> {
77        WasmBackend::resolve_event_opt(self, addr)
78    }
79    fn resolve_eval_only_event(&self, addr: &AbsoluteAddr) -> Option<WasmEventRef> {
80        WasmBackend::resolve_eval_only_event(self, addr)
81    }
82    fn resolve_apply_event(&self, addr: &AbsoluteAddr) -> Option<WasmEventRef> {
83        WasmBackend::resolve_apply_event(self, addr)
84    }
85    fn set<T: Copy>(&mut self, signal: SignalRef, value: T) {
86        WasmBackend::set(self, signal, value)
87    }
88    fn set_wide(&mut self, signal: SignalRef, value: BigUint) {
89        WasmBackend::set_wide(self, signal, value)
90    }
91    fn set_four_state(&mut self, signal: SignalRef, value: BigUint, mask: BigUint) {
92        WasmBackend::set_four_state(self, signal, value, mask)
93    }
94    fn get(&self, signal: SignalRef) -> BigUint {
95        WasmBackend::get(self, signal)
96    }
97    fn get_as<T: Default + Copy>(&self, signal: SignalRef) -> T {
98        WasmBackend::get_as(self, signal)
99    }
100    fn get_four_state(&self, signal: SignalRef) -> (BigUint, BigUint) {
101        WasmBackend::get_four_state(self, signal)
102    }
103    fn memory_as_ptr(&self) -> (*const u8, usize) {
104        WasmBackend::memory_as_ptr(self)
105    }
106    fn memory_as_mut_ptr(&mut self) -> (*mut u8, usize) {
107        WasmBackend::memory_as_mut_ptr(self)
108    }
109    fn vcd_tracking_enabled(&self) -> bool {
110        !self.raw_view_exposed
111    }
112    fn runtime_event_buffer_as_ptr(&self) -> (*const u8, usize) {
113        WasmBackend::runtime_event_buffer_as_ptr(self)
114    }
115    fn set_comb_capture_event_enabled(&mut self, active_sites: &[bool]) {
116        WasmBackend::set_comb_capture_event_enabled(self, active_sites)
117    }
118    fn stable_region_size(&self) -> usize {
119        WasmBackend::stable_region_size(self)
120    }
121    fn layout(&self) -> &super::MemoryLayout {
122        WasmBackend::layout(self)
123    }
124    fn id_to_addr_slice(&self) -> &[AbsoluteAddr] {
125        WasmBackend::id_to_addr_slice(self)
126    }
127    fn id_to_event_slice(&self) -> &[WasmEventRef] {
128        WasmBackend::id_to_event_slice(self)
129    }
130    fn num_events(&self) -> usize {
131        WasmBackend::num_events(self)
132    }
133    fn clear_triggered_bits(&mut self) {
134        WasmBackend::clear_triggered_bits(self)
135    }
136    fn mark_triggered_bit(&mut self, id: usize) {
137        WasmBackend::mark_triggered_bit(self, id)
138    }
139    fn get_triggered_bits(&self) -> bit_set::BitSet {
140        WasmBackend::get_triggered_bits(self)
141    }
142}
143
144/// The runtime WASM backend.
145pub struct WasmBackend {
146    store: Store<()>,
147    memory: Memory,
148    raw_view_exposed: bool,
149    comb_func: TypedFunc<(), i64>,
150    event_funcs: HashMap<AbsoluteAddr, Vec<TypedFunc<(), i64>>>,
151    eval_only_funcs: HashMap<AbsoluteAddr, Vec<TypedFunc<(), i64>>>,
152    apply_funcs: HashMap<AbsoluteAddr, Vec<TypedFunc<(), i64>>>,
153    event_map: HashMap<AbsoluteAddr, WasmEventRef>,
154    eval_only_event_map: HashMap<AbsoluteAddr, WasmEventRef>,
155    apply_event_map: HashMap<AbsoluteAddr, WasmEventRef>,
156    id_to_addr: Vec<AbsoluteAddr>,
157    id_to_event: Vec<WasmEventRef>,
158    layout: MemoryLayout,
159    options: SimulatorOptions,
160}
161
162impl WasmBackend {
163    pub fn new(
164        laid_out: &crate::ir::LaidOutProgram,
165        options: &SimulatorOptions,
166    ) -> Result<Self, crate::SimulatorError> {
167        let engine = Engine::default();
168        let sir = laid_out;
169        let layout = laid_out.layout().clone();
170
171        // Compile eval_comb
172        let comb_wasm = wasm_codegen::compile_units(
173            &sir.sir.eval_comb,
174            &layout,
175            options.four_state,
176            options.emit_triggers,
177        );
178        let comb_module = Module::new(&engine, &comb_wasm.bytes)
179            .map_err(|source| wasm_codegen_error("eval_comb compilation", source))?;
180
181        // Compile event functions
182        let mut event_modules = Vec::new();
183        let mut eval_only_modules = Vec::new();
184        let mut apply_modules = Vec::new();
185        let mut event_map = HashMap::default();
186        let mut eval_only_event_map = HashMap::default();
187        let mut apply_event_map = HashMap::default();
188        let mut id_to_addr = Vec::new();
189        let mut addr_to_id = HashMap::default();
190        let mut next_id = 0usize;
191
192        let compile_ffs = |ff_map: &HashMap<
193            AbsoluteAddr,
194            Vec<crate::ir::ExecutionUnit<crate::ir::RegionedAbsoluteAddr>>,
195        >,
196                           modules: &mut Vec<(AbsoluteAddr, Module)>,
197                           emap: &mut HashMap<AbsoluteAddr, WasmEventRef>,
198                           addr_to_id: &mut HashMap<AbsoluteAddr, usize>,
199                           next_id: &mut usize,
200                           id_to_addr: &mut Vec<AbsoluteAddr>|
201         -> Result<(), crate::SimulatorError> {
202            for (clock, units) in ff_map {
203                let canonical = sir.design.events.canonical(*clock);
204                let id = *addr_to_id.entry(canonical).or_insert_with(|| {
205                    let id = *next_id;
206                    *next_id += 1;
207                    id_to_addr.push(canonical);
208                    id
209                });
210
211                let wasm = wasm_codegen::compile_units(
212                    units,
213                    &layout,
214                    options.four_state,
215                    options.emit_triggers,
216                );
217                let module = Module::new(&engine, &wasm.bytes)
218                    .map_err(|source| wasm_codegen_error("FF compilation", source))?;
219                let idx = modules.len();
220                modules.push((canonical, module));
221                emap.insert(
222                    canonical,
223                    WasmEventRef {
224                        instance_idx: idx,
225                        addr: canonical,
226                        id,
227                    },
228                );
229            }
230            Ok(())
231        };
232
233        compile_ffs(
234            &sir.sir.eval_apply_ffs,
235            &mut event_modules,
236            &mut event_map,
237            &mut addr_to_id,
238            &mut next_id,
239            &mut id_to_addr,
240        )?;
241        compile_ffs(
242            &sir.sir.eval_only_ffs,
243            &mut eval_only_modules,
244            &mut eval_only_event_map,
245            &mut addr_to_id,
246            &mut next_id,
247            &mut id_to_addr,
248        )?;
249        compile_ffs(
250            &sir.sir.apply_ffs,
251            &mut apply_modules,
252            &mut apply_event_map,
253            &mut addr_to_id,
254            &mut next_id,
255            &mut id_to_addr,
256        )?;
257
258        // Insert clock domain aliases
259        for (alias, canonical) in &sir.design.events.aliases {
260            if let Some(ev) = event_map.get(canonical).cloned() {
261                event_map.insert(*alias, ev);
262            }
263            if let Some(ev) = eval_only_event_map.get(canonical).cloned() {
264                eval_only_event_map.insert(*alias, ev);
265            }
266            if let Some(ev) = apply_event_map.get(canonical).cloned() {
267                apply_event_map.insert(*alias, ev);
268            }
269        }
270
271        // Pre-compute 4-state init regions
272        let mut four_state_inits = Vec::new();
273        if options.four_state {
274            for (addr, &offset) in &layout.offsets {
275                let width = layout.widths[addr];
276                let is_4state = sir
277                    .design
278                    .state_objects
279                    .get(addr)
280                    .map(|metadata| metadata.is_4state)
281                    .unwrap_or(false);
282                if is_4state {
283                    four_state_inits.push((offset, get_byte_size(width)));
284                }
285            }
286            for (addr, &rel_offset) in &layout.working_offsets {
287                let offset = layout.working_base_offset + rel_offset;
288                let width = layout.widths[addr];
289                let is_4state = sir
290                    .design
291                    .state_objects
292                    .get(addr)
293                    .map(|metadata| metadata.is_4state)
294                    .unwrap_or(false);
295                if is_4state {
296                    four_state_inits.push((offset, get_byte_size(width)));
297                }
298            }
299        }
300
301        // Create store and shared memory
302        let mut store = Store::new(&engine, ());
303        let runtime_event_base = layout.merged_total_size;
304        let comb_capture_enabled_base = runtime_event_base + layout.runtime_event_buffer_size;
305        let comb_capture_enabled_size = layout.runtime_event_site_layouts.len().max(1);
306        let wasm_state_size = comb_capture_enabled_base + comb_capture_enabled_size;
307        let mem_pages = wasm_state_size.div_ceil(65536) as u64;
308        let mem_pages = mem_pages.max(1);
309        let memory = Memory::new(
310            &mut store,
311            wasmtime::MemoryType::new(mem_pages as u32, None),
312        )
313        .map_err(|source| wasm_codegen_error("memory creation", source))?;
314
315        // Initialize 4-state regions to X
316        {
317            let mem_data = memory.data_mut(&mut store);
318            mem_data[STATE_HEADER_RUNTIME_EVENT_ADDR_OFFSET..][..8]
319                .copy_from_slice(&(runtime_event_base as u64).to_le_bytes());
320            mem_data[STATE_HEADER_COMB_CAPTURE_ENABLED_ADDR_OFFSET..][..8]
321                .copy_from_slice(&(comb_capture_enabled_base as u64).to_le_bytes());
322            for &(offset, allocated_size) in &four_state_inits {
323                // value bytes = 0xFF, mask bytes = 0xFF
324                for i in 0..allocated_size {
325                    if offset + i < mem_data.len() {
326                        mem_data[offset + i] = 0xFF;
327                    }
328                    if offset + allocated_size + i < mem_data.len() {
329                        mem_data[offset + allocated_size + i] = 0xFF;
330                    }
331                }
332            }
333        }
334
335        // Helper: instantiate a WASM module with the shared memory.
336        fn instantiate_module(
337            engine: &Engine,
338            store: &mut Store<()>,
339            module: &Module,
340            memory: &Memory,
341        ) -> Result<TypedFunc<(), i64>, crate::SimulatorError> {
342            let mut linker = Linker::new(engine);
343            linker
344                .define(&mut *store, "env", "memory", *memory)
345                .map_err(|source| wasm_codegen_error("linking", source))?;
346            let instance = linker
347                .instantiate(&mut *store, module)
348                .map_err(|source| wasm_codegen_error("instantiation", source))?;
349            let func = instance
350                .get_typed_func::<(), i64>(&mut *store, "run")
351                .map_err(|source| wasm_codegen_error("entry-point lookup", source))?;
352            Ok(func)
353        }
354
355        let comb_func = instantiate_module(&engine, &mut store, &comb_module, &memory)?;
356
357        let mut event_funcs: HashMap<AbsoluteAddr, Vec<TypedFunc<(), i64>>> = HashMap::default();
358        for (addr, module) in &event_modules {
359            let func = instantiate_module(&engine, &mut store, module, &memory)?;
360            event_funcs.entry(*addr).or_default().push(func);
361        }
362        let mut eval_only_funcs: HashMap<AbsoluteAddr, Vec<TypedFunc<(), i64>>> =
363            HashMap::default();
364        for (addr, module) in &eval_only_modules {
365            let func = instantiate_module(&engine, &mut store, module, &memory)?;
366            eval_only_funcs.entry(*addr).or_default().push(func);
367        }
368        let mut apply_funcs: HashMap<AbsoluteAddr, Vec<TypedFunc<(), i64>>> = HashMap::default();
369        for (addr, module) in &apply_modules {
370            let func = instantiate_module(&engine, &mut store, module, &memory)?;
371            apply_funcs.entry(*addr).or_default().push(func);
372        }
373
374        let id_to_event: Vec<WasmEventRef> =
375            id_to_addr.iter().map(|addr| event_map[addr]).collect();
376
377        Ok(Self {
378            store,
379            memory,
380            raw_view_exposed: false,
381            comb_func,
382            event_funcs,
383            eval_only_funcs,
384            apply_funcs,
385            event_map,
386            eval_only_event_map,
387            apply_event_map,
388            id_to_addr,
389            id_to_event,
390            layout,
391            options: options.clone(),
392        })
393    }
394
395    fn run_func(&mut self, func: &TypedFunc<(), i64>) -> Result<(), SimulatorErrorCode> {
396        let res = func.call(&mut self.store, ()).unwrap_or(2);
397        match res {
398            0 => Ok(()),
399            1 => Err(SimulatorErrorCode::DetectedTrueLoopCode(1)),
400            code if code >= 2000 => Err(SimulatorErrorCode::DetectedTrueLoopCode(code)),
401            _ => Err(SimulatorErrorCode::InternalError),
402        }
403    }
404
405    pub fn eval_comb(&mut self) -> Result<(), SimulatorErrorCode> {
406        let func = self.comb_func.clone();
407        self.run_func(&func)
408    }
409
410    pub fn eval_apply_ff_at(&mut self, event: &WasmEventRef) -> Result<(), SimulatorErrorCode> {
411        if let Some(funcs) = self.event_funcs.get(&event.addr).cloned() {
412            for func in funcs {
413                self.run_func(&func)?;
414            }
415        }
416        Ok(())
417    }
418
419    pub fn eval_only_ff_at(&mut self, event: &WasmEventRef) -> Result<(), SimulatorErrorCode> {
420        if let Some(funcs) = self.eval_only_funcs.get(&event.addr).cloned() {
421            for func in funcs {
422                self.run_func(&func)?;
423            }
424        }
425        Ok(())
426    }
427
428    pub fn apply_ff_at(&mut self, event: &WasmEventRef) -> Result<(), SimulatorErrorCode> {
429        if let Some(funcs) = self.apply_funcs.get(&event.addr).cloned() {
430            for func in funcs {
431                self.run_func(&func)?;
432            }
433        }
434        Ok(())
435    }
436
437    pub fn resolve_signal(&self, addr: &AbsoluteAddr) -> SignalRef {
438        let offset = self.layout.offsets[addr];
439        let width = self.layout.widths[addr];
440        let is_4state = self.layout.is_4states[addr];
441        SignalRef {
442            offset,
443            width,
444            is_4state,
445            array_layout: None,
446        }
447    }
448
449    pub fn resolve_event(&self, addr: &AbsoluteAddr) -> WasmEventRef {
450        self.event_map[addr]
451    }
452
453    pub fn resolve_event_opt(&self, addr: &AbsoluteAddr) -> Option<WasmEventRef> {
454        self.event_map.get(addr).cloned()
455    }
456
457    pub fn resolve_eval_only_event(&self, addr: &AbsoluteAddr) -> Option<WasmEventRef> {
458        self.eval_only_event_map.get(addr).cloned()
459    }
460
461    pub fn resolve_apply_event(&self, addr: &AbsoluteAddr) -> Option<WasmEventRef> {
462        self.apply_event_map.get(addr).cloned()
463    }
464
465    pub fn set<T: Copy>(&mut self, signal: SignalRef, value: T) {
466        celox_runtime::backend::SimBackend::mark_vcd_signal(self, signal);
467        let allocated_size = get_byte_size(signal.width);
468        let provided_size = std::mem::size_of::<T>();
469        assert!(provided_size <= allocated_size);
470
471        let mem = self.memory.data_mut(&mut self.store);
472        // Zero the allocated region
473        for i in 0..allocated_size {
474            mem[signal.offset + i] = 0;
475        }
476        // Write value
477        let bytes =
478            unsafe { std::slice::from_raw_parts(&value as *const T as *const u8, provided_size) };
479        mem[signal.offset..signal.offset + provided_size].copy_from_slice(bytes);
480
481        // Clear 4-state mask
482        if self.options.four_state && signal.is_4state {
483            for i in 0..allocated_size {
484                mem[signal.offset + allocated_size + i] = 0;
485            }
486        }
487    }
488
489    pub fn set_wide(&mut self, signal: SignalRef, value: BigUint) {
490        celox_runtime::backend::SimBackend::mark_vcd_signal(self, signal);
491        let allocated_size = get_byte_size(signal.width);
492        let mut bytes = value.to_bytes_le();
493        bytes.resize(allocated_size, 0u8);
494
495        let mem = self.memory.data_mut(&mut self.store);
496        mem[signal.offset..signal.offset + allocated_size]
497            .copy_from_slice(&bytes[..allocated_size]);
498
499        if self.options.four_state && signal.is_4state {
500            for i in 0..allocated_size {
501                mem[signal.offset + allocated_size + i] = 0;
502            }
503        }
504    }
505
506    pub fn set_four_state(&mut self, signal: SignalRef, value: BigUint, mask: BigUint) {
507        celox_runtime::backend::SimBackend::mark_vcd_signal(self, signal);
508        let allocated_size = get_byte_size(signal.width);
509        let mut v_bytes = value.to_bytes_le();
510        v_bytes.resize(allocated_size, 0u8);
511
512        let mem = self.memory.data_mut(&mut self.store);
513        mem[signal.offset..signal.offset + allocated_size]
514            .copy_from_slice(&v_bytes[..allocated_size]);
515
516        if self.options.four_state && signal.is_4state {
517            let mut m_bytes = mask.to_bytes_le();
518            m_bytes.resize(allocated_size, 0u8);
519            mem[signal.offset + allocated_size..signal.offset + 2 * allocated_size]
520                .copy_from_slice(&m_bytes[..allocated_size]);
521        }
522    }
523
524    pub fn get(&self, signal: SignalRef) -> BigUint {
525        let byte_size = get_byte_size(signal.width);
526        let mem = self.memory.data(&self.store);
527        let slice = &mem[signal.offset..signal.offset + byte_size];
528        let mut val = BigUint::from_bytes_le(slice);
529        let extra_bits = byte_size * 8 - signal.width;
530        if extra_bits > 0 {
531            let mask = (BigUint::from(1u32) << signal.width) - 1u32;
532            val &= mask;
533        }
534        val
535    }
536
537    pub fn get_as<T: Default + Copy>(&self, signal: SignalRef) -> T {
538        let byte_size = get_byte_size(signal.width);
539        let provided_size = std::mem::size_of::<T>();
540        assert!(byte_size <= provided_size);
541
542        let mem = self.memory.data(&self.store);
543        let slice = &mem[signal.offset..signal.offset + byte_size];
544
545        let mut val = T::default();
546        unsafe {
547            let val_ptr = &mut val as *mut T as *mut u8;
548            std::ptr::copy_nonoverlapping(slice.as_ptr(), val_ptr, byte_size);
549        }
550        val
551    }
552
553    pub fn get_four_state(&self, signal: SignalRef) -> (BigUint, BigUint) {
554        let byte_size = get_byte_size(signal.width);
555        let mem = self.memory.data(&self.store);
556        let v_slice = &mem[signal.offset..signal.offset + byte_size];
557        let mut v_val = BigUint::from_bytes_le(v_slice);
558
559        let mut m_val = if self.options.four_state && signal.is_4state {
560            let m_slice = &mem[signal.offset + byte_size..signal.offset + 2 * byte_size];
561            BigUint::from_bytes_le(m_slice)
562        } else {
563            BigUint::from(0u32)
564        };
565
566        let extra_bits = byte_size * 8 - signal.width;
567        if extra_bits > 0 {
568            let bitmask = (BigUint::from(1u32) << signal.width) - 1u32;
569            v_val &= &bitmask;
570            m_val &= &bitmask;
571        }
572
573        (v_val, m_val)
574    }
575
576    pub fn memory_as_ptr(&self) -> (*const u8, usize) {
577        let data = self.memory.data(&self.store);
578        (data.as_ptr(), self.layout.merged_total_size)
579    }
580
581    pub fn memory_as_mut_ptr(&mut self) -> (*mut u8, usize) {
582        self.raw_view_exposed = true;
583        let data = self.memory.data_mut(&mut self.store);
584        (data.as_mut_ptr(), self.layout.merged_total_size)
585    }
586
587    pub fn runtime_event_buffer_as_ptr(&self) -> (*const u8, usize) {
588        let data = self.memory.data(&self.store);
589        let event_base = self.layout.merged_total_size;
590        (
591            unsafe { data.as_ptr().add(event_base) },
592            self.layout.runtime_event_buffer_size,
593        )
594    }
595
596    pub fn set_comb_capture_event_enabled(&mut self, active_sites: &[bool]) {
597        let enabled_base = self.layout.merged_total_size + self.layout.runtime_event_buffer_size;
598        let enabled_len = self.layout.runtime_event_site_layouts.len().max(1);
599        let mem = self.memory.data_mut(&mut self.store);
600        mem[enabled_base..enabled_base + enabled_len].fill(0);
601        for (idx, &active) in active_sites.iter().enumerate().take(enabled_len) {
602            if active {
603                mem[enabled_base + idx] = 1;
604            }
605        }
606    }
607
608    pub fn stable_region_size(&self) -> usize {
609        self.layout.total_size
610    }
611
612    pub fn layout(&self) -> &MemoryLayout {
613        &self.layout
614    }
615
616    pub fn id_to_addr_slice(&self) -> &[AbsoluteAddr] {
617        &self.id_to_addr
618    }
619
620    pub fn id_to_event_slice(&self) -> &[WasmEventRef] {
621        &self.id_to_event
622    }
623
624    pub fn num_events(&self) -> usize {
625        let mut max_id = 0;
626        for ev in self.event_map.values() {
627            max_id = max_id.max(ev.id);
628        }
629        for ev in self.eval_only_event_map.values() {
630            max_id = max_id.max(ev.id);
631        }
632        for ev in self.apply_event_map.values() {
633            max_id = max_id.max(ev.id);
634        }
635        if self.event_map.is_empty()
636            && self.eval_only_event_map.is_empty()
637            && self.apply_event_map.is_empty()
638        {
639            0
640        } else {
641            max_id + 1
642        }
643    }
644
645    pub fn clear_triggered_bits(&mut self) {
646        let offset = self.layout.triggered_bits_offset;
647        let size = self.layout.triggered_bits_total_size;
648        let mem = self.memory.data_mut(&mut self.store);
649        for i in 0..size {
650            mem[offset + i] = 0;
651        }
652    }
653
654    pub fn mark_triggered_bit(&mut self, id: usize) {
655        let byte_idx = id / 8;
656        let bit_idx = id % 8;
657        let offset = self.layout.triggered_bits_offset + byte_idx;
658        let mem = self.memory.data_mut(&mut self.store);
659        mem[offset] |= 1 << bit_idx;
660    }
661
662    pub fn get_triggered_bits(&self) -> bit_set::BitSet {
663        let mut bits = bit_set::BitSet::with_capacity(self.num_events());
664        let mem = self.memory.data(&self.store);
665        let offset = self.layout.triggered_bits_offset;
666        let size = self.layout.triggered_bits_total_size;
667
668        for i in 0..size {
669            let byte = mem[offset + i];
670            if byte != 0 {
671                for j in 0..8 {
672                    if (byte & (1 << j)) != 0 {
673                        bits.insert(i * 8 + j);
674                    }
675                }
676            }
677        }
678        bits
679    }
680}
681
682// Tests: crates/celox/tests/wasm_backend.rs