beamer_core/buffer_storage.rs
1//! Pre-allocated buffer storage for real-time safe audio processing.
2//!
3//! This module provides [`ProcessBufferStorage`], which pre-allocates capacity
4//! for channel pointers during plugin setup. The storage is then reused for each
5//! render call without allocations.
6//!
7//! # Pattern
8//!
9//! This storage follows a consistent pattern across all plugin formats:
10//! 1. Allocate storage once during setup (non-real-time)
11//! 2. Clear storage at start of each render (O(1), no deallocation)
12//! 3. Push pointers from host buffers (never exceeds capacity)
13//! 4. Build slices from pointers
14//!
15//! # Memory Optimization Strategy
16//!
17//! The allocation strategy is **config-based, not worst-case**:
18//!
19//! - **Channel counts**: Allocates exact number of channels from bus config, not MAX_CHANNELS
20//! - **Bus counts**: Allocates only for buses that exist, not MAX_BUSES
21//! - **Lazy aux allocation**: No heap allocation for aux buses if plugin doesn't use them
22//! - **Asymmetric support**: Mono input can have stereo output (allocates 1 + 2, not 2 + 2)
23//!
24//! Examples:
25//! - Mono plugin (1in/1out): Allocates 2 pointers (16 bytes on 64-bit)
26//! - Stereo plugin (2in/2out): Allocates 4 pointers (32 bytes on 64-bit)
27//! - Stereo with sidechain (2+2in/2out): Allocates 6 pointers (48 bytes on 64-bit)
28//! - Worst-case (32ch x 16 buses): Would be MAX_CHANNELS * MAX_BUSES = 512 pointers (4KB)
29//!
30//! This means simple plugins use **32x less memory** than worst-case allocation.
31//!
32//! # Real-Time Safety
33//!
34//! - `clear()` is O(1) - only sets Vec lengths to 0
35//! - `push()` never allocates - capacity is pre-reserved
36//! - No heap operations during audio processing
37//! - All allocations happen in `allocate_from_config()` (non-real-time)
38
39use crate::bus_config::CachedBusConfig;
40use crate::sample::Sample;
41use std::slice;
42
43/// Pre-allocated storage for audio processing channel pointers.
44///
45/// Stores channel pointers collected from host audio buffers during render.
46/// The Vecs have pre-allocated capacity matching the **actual** bus configuration,
47/// ensuring no allocations occur during audio callbacks while minimizing memory usage.
48///
49/// # Memory Layout
50///
51/// The storage is optimized based on the actual plugin configuration:
52/// - `main_inputs`: Capacity = actual input channel count (e.g., 1 for mono, 2 for stereo)
53/// - `main_outputs`: Capacity = actual output channel count (e.g., 1 for mono, 2 for stereo)
54/// - `aux_inputs`: Only allocated if plugin declares aux input buses
55/// - `aux_outputs`: Only allocated if plugin declares aux output buses
56///
57/// This means a simple stereo plugin uses only 32 bytes (4 pointers x 8 bytes),
58/// not the worst-case 4KB (MAX_CHANNELS x MAX_BUSES x pointer size).
59///
60/// # Type Parameter
61///
62/// `S` is the sample type (`f32` or `f64`).
63#[derive(Clone)]
64pub struct ProcessBufferStorage<S: Sample> {
65 /// Main input channel pointers (capacity = actual channel count).
66 /// Format wrappers may access this directly for performance-critical inline collection.
67 pub main_inputs: Vec<*const S>,
68 /// Main output channel pointers (capacity = actual channel count).
69 /// Format wrappers may access this directly for performance-critical inline collection.
70 pub main_outputs: Vec<*mut S>,
71 /// Auxiliary input buses (only allocated if plugin uses them).
72 /// Format wrappers may access this directly for performance-critical inline collection.
73 pub aux_inputs: Vec<Vec<*const S>>,
74 /// Auxiliary output buses (only allocated if plugin uses them).
75 /// Format wrappers may access this directly for performance-critical inline collection.
76 pub aux_outputs: Vec<Vec<*mut S>>,
77 /// Internal output buffers for instruments (when host provides null pointers).
78 /// Only allocated for plugins with no input buses (instruments/generators).
79 /// When a host provides null output pointers, the format wrapper can use these
80 /// buffers and update the host's buffer list to point to them.
81 pub internal_output_buffers: Option<Vec<Vec<S>>>,
82 /// Max frames for internal buffers (set during allocation).
83 /// Used to validate that num_samples doesn't exceed buffer capacity.
84 pub max_frames: usize,
85}
86
87impl<S: Sample> ProcessBufferStorage<S> {
88 /// Create empty storage (no capacity reserved).
89 pub fn new() -> Self {
90 Self {
91 main_inputs: Vec::new(),
92 main_outputs: Vec::new(),
93 aux_inputs: Vec::new(),
94 aux_outputs: Vec::new(),
95 internal_output_buffers: None,
96 max_frames: 0,
97 }
98 }
99
100 /// Create storage from cached bus configuration (recommended).
101 ///
102 /// This is the preferred way to allocate storage as it automatically
103 /// extracts the correct channel counts from the bus configuration.
104 /// Should be called during plugin setup (non-real-time).
105 ///
106 /// # Memory Optimization
107 ///
108 /// This method implements smart allocation strategies:
109 /// - Allocates only for channels actually present in the config
110 /// - No pre-allocation for aux buses if plugin doesn't use them
111 /// - Uses actual channel counts, not MAX_CHANNELS worst-case
112 /// - Zero heap allocation for simple mono/stereo plugins without aux buses
113 ///
114 /// # Arguments
115 ///
116 /// * `bus_config` - Cached bus configuration
117 /// * `max_frames` - Maximum frames per render call (for internal buffer allocation)
118 ///
119 /// # Example
120 ///
121 /// ```ignore
122 /// let config = extract_bus_config();
123 /// config.validate()?;
124 /// let storage = ProcessBufferStorage::allocate_from_config(&config, 4096);
125 /// ```
126 pub fn allocate_from_config(bus_config: &CachedBusConfig, max_frames: usize) -> Self {
127 // Extract main bus channel counts (bus 0)
128 let main_in_channels = bus_config
129 .input_bus_info(0)
130 .map(|b| b.channel_count)
131 .unwrap_or(0);
132 let main_out_channels = bus_config
133 .output_bus_info(0)
134 .map(|b| b.channel_count)
135 .unwrap_or(0);
136
137 // Count auxiliary buses (all buses except main bus 0)
138 let aux_in_buses = bus_config.input_bus_count.saturating_sub(1);
139 let aux_out_buses = bus_config.output_bus_count.saturating_sub(1);
140
141 // Optimization: Only allocate aux bus storage if actually needed.
142 // For simple plugins (mono/stereo with no aux), this avoids any
143 // heap allocation for the outer Vec containers.
144 let aux_inputs = if aux_in_buses > 0 {
145 let mut vec = Vec::with_capacity(aux_in_buses);
146 for i in 1..=aux_in_buses {
147 let channels = bus_config
148 .input_bus_info(i)
149 .map(|b| b.channel_count)
150 .unwrap_or(0);
151 vec.push(Vec::with_capacity(channels));
152 }
153 vec
154 } else {
155 Vec::new() // Zero-capacity allocation - no heap memory
156 };
157
158 let aux_outputs = if aux_out_buses > 0 {
159 let mut vec = Vec::with_capacity(aux_out_buses);
160 for i in 1..=aux_out_buses {
161 let channels = bus_config
162 .output_bus_info(i)
163 .map(|b| b.channel_count)
164 .unwrap_or(0);
165 vec.push(Vec::with_capacity(channels));
166 }
167 vec
168 } else {
169 Vec::new() // Zero-capacity allocation - no heap memory
170 };
171
172 // For instruments (no input buses), allocate internal output buffers.
173 // Some hosts (Logic Pro, Reaper) may provide null output buffer pointers,
174 // expecting the plugin to use its own buffers.
175 let internal_output_buffers = if main_in_channels == 0 && main_out_channels > 0 {
176 let mut buffers = Vec::with_capacity(main_out_channels);
177 for _ in 0..main_out_channels {
178 buffers.push(vec![S::ZERO; max_frames]);
179 }
180 Some(buffers)
181 } else {
182 None
183 };
184
185 Self {
186 main_inputs: Vec::with_capacity(main_in_channels),
187 main_outputs: Vec::with_capacity(main_out_channels),
188 aux_inputs,
189 aux_outputs,
190 internal_output_buffers,
191 max_frames,
192 }
193 }
194
195 /// Create new storage with pre-allocated capacity (manual).
196 ///
197 /// This is a lower-level method for manual capacity specification.
198 /// Prefer `allocate_from_config()` when possible as it's less error-prone.
199 ///
200 /// # Arguments
201 ///
202 /// * `main_in_channels` - Number of main input channels
203 /// * `main_out_channels` - Number of main output channels
204 /// * `aux_in_buses` - Number of auxiliary input buses
205 /// * `aux_out_buses` - Number of auxiliary output buses
206 /// * `aux_channels` - Channels per aux bus (assumes uniform)
207 pub fn allocate(
208 main_in_channels: usize,
209 main_out_channels: usize,
210 aux_in_buses: usize,
211 aux_out_buses: usize,
212 aux_channels: usize,
213 ) -> Self {
214 let mut aux_inputs = Vec::with_capacity(aux_in_buses);
215 for _ in 0..aux_in_buses {
216 aux_inputs.push(Vec::with_capacity(aux_channels));
217 }
218
219 let mut aux_outputs = Vec::with_capacity(aux_out_buses);
220 for _ in 0..aux_out_buses {
221 aux_outputs.push(Vec::with_capacity(aux_channels));
222 }
223
224 Self {
225 main_inputs: Vec::with_capacity(main_in_channels),
226 main_outputs: Vec::with_capacity(main_out_channels),
227 aux_inputs,
228 aux_outputs,
229 internal_output_buffers: None, // Manual allocation doesn't set up internal buffers
230 max_frames: 0,
231 }
232 }
233
234 /// Clear all pointer storage without deallocating.
235 ///
236 /// This is O(1) - it only sets Vec lengths to 0 while preserving capacity.
237 /// Call this at the start of each render call.
238 #[inline]
239 pub fn clear(&mut self) {
240 self.main_inputs.clear();
241 self.main_outputs.clear();
242 for bus in &mut self.aux_inputs {
243 bus.clear();
244 }
245 for bus in &mut self.aux_outputs {
246 bus.clear();
247 }
248 }
249
250 /// Get the number of input channels collected.
251 #[inline]
252 pub fn input_channel_count(&self) -> usize {
253 self.main_inputs.len()
254 }
255
256 /// Get the number of output channels collected.
257 #[inline]
258 pub fn output_channel_count(&self) -> usize {
259 self.main_outputs.len()
260 }
261
262 /// Get the number of auxiliary input buses.
263 #[inline]
264 pub fn aux_input_bus_count(&self) -> usize {
265 self.aux_inputs.len()
266 }
267
268 /// Get the number of auxiliary output buses.
269 #[inline]
270 pub fn aux_output_bus_count(&self) -> usize {
271 self.aux_outputs.len()
272 }
273
274 /// Get the maximum frames for internal buffers.
275 #[inline]
276 pub fn max_frames(&self) -> usize {
277 self.max_frames
278 }
279
280 /// Check if internal output buffers are available.
281 #[inline]
282 pub fn has_internal_output_buffers(&self) -> bool {
283 self.internal_output_buffers.is_some()
284 }
285
286 /// Push a main input pointer.
287 ///
288 /// # Safety
289 ///
290 /// The pointer must be valid for the duration of the current render call.
291 #[inline]
292 pub unsafe fn push_main_input(&mut self, ptr: *const S) {
293 self.main_inputs.push(ptr);
294 }
295
296 /// Push a main output pointer.
297 ///
298 /// # Safety
299 ///
300 /// The pointer must be valid for the duration of the current render call.
301 #[inline]
302 pub unsafe fn push_main_output(&mut self, ptr: *mut S) {
303 self.main_outputs.push(ptr);
304 }
305
306 /// Push an auxiliary input pointer for a specific bus.
307 ///
308 /// # Safety
309 ///
310 /// The pointer must be valid for the duration of the current render call.
311 #[inline]
312 pub unsafe fn push_aux_input(&mut self, bus_index: usize, ptr: *const S) {
313 if bus_index < self.aux_inputs.len() {
314 self.aux_inputs[bus_index].push(ptr);
315 }
316 }
317
318 /// Push an auxiliary output pointer for a specific bus.
319 ///
320 /// # Safety
321 ///
322 /// The pointer must be valid for the duration of the current render call.
323 #[inline]
324 pub unsafe fn push_aux_output(&mut self, bus_index: usize, ptr: *mut S) {
325 if bus_index < self.aux_outputs.len() {
326 self.aux_outputs[bus_index].push(ptr);
327 }
328 }
329
330 /// Get the main input capacity.
331 #[inline]
332 pub fn main_input_capacity(&self) -> usize {
333 self.main_inputs.capacity()
334 }
335
336 /// Get the main output capacity.
337 #[inline]
338 pub fn main_output_capacity(&self) -> usize {
339 self.main_outputs.capacity()
340 }
341
342 /// Get an auxiliary input bus capacity.
343 #[inline]
344 pub fn aux_input_capacity(&self, bus_index: usize) -> usize {
345 self.aux_inputs.get(bus_index).map(|v| v.capacity()).unwrap_or(0)
346 }
347
348 /// Get an auxiliary output bus capacity.
349 #[inline]
350 pub fn aux_output_capacity(&self, bus_index: usize) -> usize {
351 self.aux_outputs.get(bus_index).map(|v| v.capacity()).unwrap_or(0)
352 }
353
354 /// Build input slices from collected pointers.
355 ///
356 /// # Safety
357 ///
358 /// - Pointers must still be valid (within same render call)
359 /// - num_samples must match what was used in collection
360 #[inline]
361 pub unsafe fn input_slices(&self, num_samples: usize) -> Vec<&[S]> {
362 self.main_inputs
363 .iter()
364 .map(|&ptr| slice::from_raw_parts(ptr, num_samples))
365 .collect()
366 }
367
368 /// Build output slices from collected pointers.
369 ///
370 /// # Safety
371 ///
372 /// - Pointers must still be valid (within same render call)
373 /// - num_samples must match what was used in collection
374 ///
375 /// # Clippy Allow: mut_from_ref
376 ///
377 /// Returns `&mut [S]` from `&self` because we're converting raw pointers stored in the struct,
378 /// not mutating `self`. This is a common and safe FFI pattern where:
379 /// - Raw pointers (`*mut S`) are stored during collection
380 /// - Those pointers are then converted back to safe references
381 /// - The mutable references are to the external buffer memory, not to `self`
382 /// - Host guarantees single-threaded render access, preventing aliasing
383 #[inline]
384 #[allow(clippy::mut_from_ref)]
385 pub unsafe fn output_slices(&self, num_samples: usize) -> Vec<&mut [S]> {
386 self.main_outputs
387 .iter()
388 .map(|&ptr| slice::from_raw_parts_mut(ptr, num_samples))
389 .collect()
390 }
391
392 /// Build auxiliary input slices from collected pointers.
393 ///
394 /// Returns a Vec of buses, where each bus is a Vec of channel slices.
395 ///
396 /// # Safety
397 ///
398 /// - Pointers must still be valid (within same render call)
399 /// - num_samples must match what was used in collection
400 #[inline]
401 pub unsafe fn aux_input_slices(&self, num_samples: usize) -> Vec<Vec<&[S]>> {
402 self.aux_inputs
403 .iter()
404 .map(|bus| {
405 bus.iter()
406 .map(|&ptr| slice::from_raw_parts(ptr, num_samples))
407 .collect()
408 })
409 .collect()
410 }
411
412 /// Build auxiliary output slices from collected pointers.
413 ///
414 /// Returns a Vec of buses, where each bus is a Vec of channel slices.
415 ///
416 /// # Safety
417 ///
418 /// - Pointers must still be valid (within same render call)
419 /// - num_samples must match what was used in collection
420 ///
421 /// # Clippy Allow: mut_from_ref
422 ///
423 /// Same justification as `output_slices` - converts raw pointers to mutable references.
424 #[inline]
425 #[allow(clippy::mut_from_ref)]
426 pub unsafe fn aux_output_slices(&self, num_samples: usize) -> Vec<Vec<&mut [S]>> {
427 self.aux_outputs
428 .iter()
429 .map(|bus| {
430 bus.iter()
431 .map(|&ptr| slice::from_raw_parts_mut(ptr, num_samples))
432 .collect()
433 })
434 .collect()
435 }
436
437 /// Get a mutable pointer to an internal output buffer channel.
438 ///
439 /// Returns `None` if internal buffers are not allocated or channel is out of range.
440 ///
441 /// # Arguments
442 ///
443 /// * `channel` - The channel index
444 #[inline]
445 pub fn internal_output_buffer_mut(&mut self, channel: usize) -> Option<&mut Vec<S>> {
446 self.internal_output_buffers
447 .as_mut()
448 .and_then(|buffers| buffers.get_mut(channel))
449 }
450
451 /// Get a reference to an internal output buffer channel.
452 ///
453 /// Returns `None` if internal buffers are not allocated or channel is out of range.
454 #[inline]
455 pub fn internal_output_buffer(&self, channel: usize) -> Option<&Vec<S>> {
456 self.internal_output_buffers
457 .as_ref()
458 .and_then(|buffers| buffers.get(channel))
459 }
460
461 /// Get the number of internal output buffer channels.
462 #[inline]
463 pub fn internal_output_buffer_count(&self) -> usize {
464 self.internal_output_buffers
465 .as_ref()
466 .map(|b| b.len())
467 .unwrap_or(0)
468 }
469}
470
471impl<S: Sample> Default for ProcessBufferStorage<S> {
472 fn default() -> Self {
473 Self::new()
474 }
475}
476
477// SAFETY: The raw pointers are only used within a single render call
478// where the host guarantees single-threaded access.
479unsafe impl<S: Sample> Send for ProcessBufferStorage<S> {}
480unsafe impl<S: Sample> Sync for ProcessBufferStorage<S> {}
481
482#[cfg(test)]
483mod tests {
484 use super::*;
485 use crate::bus_config::{CachedBusInfo};
486 use crate::plugin::BusType;
487 use crate::types::MAX_CHANNELS;
488
489 #[test]
490 fn test_validate_bus_limits_success() {
491 let config = CachedBusConfig::new(
492 vec![CachedBusInfo::new(2, BusType::Main)],
493 vec![CachedBusInfo::new(2, BusType::Main)],
494 );
495
496 assert!(config.validate().is_ok());
497 }
498
499 #[test]
500 fn test_validate_bus_limits_too_many_channels() {
501 let config = CachedBusConfig::new(
502 vec![CachedBusInfo::new(MAX_CHANNELS + 1, BusType::Main)],
503 vec![CachedBusInfo::new(2, BusType::Main)],
504 );
505
506 assert!(config.validate().is_err());
507 }
508
509 #[test]
510 fn test_allocate_from_config_stereo() {
511 let config = CachedBusConfig::default(); // 2in/2out
512 let storage: ProcessBufferStorage<f32> =
513 ProcessBufferStorage::allocate_from_config(&config, 4096);
514
515 assert_eq!(storage.main_inputs.capacity(), 2);
516 assert_eq!(storage.main_outputs.capacity(), 2);
517 assert_eq!(storage.aux_inputs.len(), 0);
518 assert_eq!(storage.aux_outputs.len(), 0);
519 }
520
521 #[test]
522 fn test_allocate_from_config_with_aux() {
523 let config = CachedBusConfig::new(
524 vec![
525 CachedBusInfo::new(2, BusType::Main),
526 CachedBusInfo::new(2, BusType::Aux),
527 ],
528 vec![CachedBusInfo::new(6, BusType::Main)],
529 );
530
531 let storage: ProcessBufferStorage<f32> =
532 ProcessBufferStorage::allocate_from_config(&config, 4096);
533
534 assert_eq!(storage.main_inputs.capacity(), 2);
535 assert_eq!(storage.main_outputs.capacity(), 6);
536 assert_eq!(storage.aux_inputs.len(), 1);
537 assert_eq!(storage.aux_inputs[0].capacity(), 2);
538 assert_eq!(storage.aux_outputs.len(), 0);
539 }
540
541 #[test]
542 fn test_allocate_and_clear() {
543 let mut storage: ProcessBufferStorage<f32> = ProcessBufferStorage::allocate(2, 2, 1, 0, 2);
544
545 // Verify capacities
546 assert_eq!(storage.main_inputs.capacity(), 2);
547 assert_eq!(storage.main_outputs.capacity(), 2);
548 assert_eq!(storage.aux_inputs.len(), 1);
549 assert_eq!(storage.aux_inputs[0].capacity(), 2);
550
551 // Simulate pushing pointers
552 let dummy: f32 = 0.0;
553 unsafe {
554 storage.push_main_input(&dummy as *const f32);
555 storage.push_main_input(&dummy as *const f32);
556 }
557
558 assert_eq!(storage.main_inputs.len(), 2);
559
560 // Clear should reset length but not capacity
561 storage.clear();
562 assert_eq!(storage.main_inputs.len(), 0);
563 assert_eq!(storage.main_inputs.capacity(), 2);
564 }
565
566 #[test]
567 fn test_allocate_from_config_mono() {
568 // Mono plugin: 1 in, 1 out, no aux buses
569 let config = CachedBusConfig::new(
570 vec![CachedBusInfo::new(1, BusType::Main)],
571 vec![CachedBusInfo::new(1, BusType::Main)],
572 );
573
574 let storage: ProcessBufferStorage<f32> =
575 ProcessBufferStorage::allocate_from_config(&config, 4096);
576
577 assert_eq!(storage.main_inputs.capacity(), 1);
578 assert_eq!(storage.main_outputs.capacity(), 1);
579 assert_eq!(storage.aux_inputs.len(), 0);
580 assert_eq!(storage.aux_outputs.len(), 0);
581 }
582
583 #[test]
584 fn test_instrument_internal_buffers_allocated() {
585 // Instruments have 0 inputs and >0 outputs - should allocate internal buffers
586 let config = CachedBusConfig::new(
587 vec![], // No input buses (instrument)
588 vec![CachedBusInfo::new(2, BusType::Main)],
589 );
590
591 let storage: ProcessBufferStorage<f32> =
592 ProcessBufferStorage::allocate_from_config(&config, 512);
593
594 // Internal buffers should be allocated for instruments
595 assert!(storage.internal_output_buffers.is_some());
596 let internal = storage.internal_output_buffers.as_ref().unwrap();
597 assert_eq!(internal.len(), 2); // Stereo
598 assert_eq!(internal[0].len(), 512); // max_frames
599 assert_eq!(internal[1].len(), 512);
600 assert_eq!(storage.max_frames, 512);
601 }
602
603 #[test]
604 fn test_effect_no_internal_buffers() {
605 // Effects have inputs - should NOT allocate internal buffers
606 let config = CachedBusConfig::default(); // 2in/2out
607
608 let storage: ProcessBufferStorage<f32> =
609 ProcessBufferStorage::allocate_from_config(&config, 512);
610
611 // Effects don't need internal buffers
612 assert!(storage.internal_output_buffers.is_none());
613 }
614
615 #[test]
616 fn test_clear_maintains_capacity() {
617 let config = CachedBusConfig::new(
618 vec![
619 CachedBusInfo::new(2, BusType::Main),
620 CachedBusInfo::new(2, BusType::Aux),
621 ],
622 vec![CachedBusInfo::new(2, BusType::Main)],
623 );
624
625 let mut storage: ProcessBufferStorage<f32> =
626 ProcessBufferStorage::allocate_from_config(&config, 4096);
627
628 // Record initial capacities
629 let main_in_cap = storage.main_inputs.capacity();
630 let main_out_cap = storage.main_outputs.capacity();
631 let aux_in_count = storage.aux_inputs.len();
632 let aux_in_cap = if aux_in_count > 0 {
633 storage.aux_inputs[0].capacity()
634 } else {
635 0
636 };
637
638 // Simulate some usage
639 let dummy: f32 = 0.0;
640 unsafe {
641 storage.push_main_input(&dummy as *const f32);
642 storage.push_main_input(&dummy as *const f32);
643 if aux_in_count > 0 {
644 storage.push_aux_input(0, &dummy as *const f32);
645 }
646 }
647
648 // Clear and verify capacities are unchanged
649 storage.clear();
650
651 assert_eq!(storage.main_inputs.capacity(), main_in_cap);
652 assert_eq!(storage.main_outputs.capacity(), main_out_cap);
653 assert_eq!(storage.aux_inputs.len(), aux_in_count);
654 if aux_in_count > 0 {
655 assert_eq!(storage.aux_inputs[0].capacity(), aux_in_cap);
656 }
657
658 // Verify lengths are reset to 0
659 assert_eq!(storage.main_inputs.len(), 0);
660 assert_eq!(storage.main_outputs.len(), 0);
661 if aux_in_count > 0 {
662 assert_eq!(storage.aux_inputs[0].len(), 0);
663 }
664 }
665}