synfx_dsp_jit/context.rs
1// Copyright (c) 2022 Weird Constructor <weirdconstructor@gmail.com>
2// This file is a part of synfx-dsp-jit. Released under GPL-3.0-or-later.
3// See README.md and COPYING for details.
4
5use crate::locked::*;
6use cranelift_jit::JITModule;
7use std::cell::RefCell;
8use std::collections::HashMap;
9use std::mem;
10use std::rc::Rc;
11use std::sync::Arc;
12use synfx_dsp::AtomicFloat;
13
14/// Default size of undeclared buffers.
15pub const BUFFER_DEFAULT_SIZE: usize = 16;
16
17/// Auxilary variables to access directly from the machine code.
18pub(crate) const AUX_VAR_COUNT: usize = 3;
19
20pub(crate) const AUX_VAR_IDX_SRATE: usize = 0;
21pub(crate) const AUX_VAR_IDX_ISRATE: usize = 1;
22pub(crate) const AUX_VAR_IDX_RESET: usize = 2;
23
24pub enum DSPNodeContextError {
25 UnknownTable(usize),
26 WrongTableSize { tbl_idx: usize, new_size: usize, old_size: usize },
27}
28
29/// Configures the environment that will be available to the [DSPFunction]
30/// that is provided by [DSPNodeContext].
31///
32/// This could for instance be the number of atoms to be used by `atomr`/`atomw`, the
33/// number and length of buffers or the audio samples...
34#[derive(Debug, Clone)]
35pub struct DSPContextConfig {
36 /// The number of atoms available to `atomr`/`atomw`.
37 pub atom_count: usize,
38 /// The number of buffers available to `bufr`/`bufw`.
39 pub buffer_count: usize,
40 /// The number of available tables for the `tblr`/`tblw` operations.
41 /// The tables can be swapped out at runtime using the [DSPNodeContext::send_table] method.
42 pub tables: Vec<Arc<Vec<f32>>>,
43}
44
45impl Default for DSPContextConfig {
46 fn default() -> Self {
47 let mut tables = vec![];
48 for _ in 0..16 {
49 tables.push(Arc::new(vec![0.0; 1]));
50 }
51 Self { atom_count: 32, buffer_count: 16, tables }
52 }
53}
54
55/// This table holds all the DSP state including the state of the individual DSP nodes
56/// that were created by the [crate::jit::DSPFunctionTranslator].
57pub struct DSPNodeContext {
58 /// The environment configuration for the [DSPFunction] to operate in.
59 pub(crate) config: DSPContextConfig,
60 /// The global DSP state that is passed to all stateful DSP nodes.
61 state: *mut DSPState,
62 /// Persistent variables:
63 persistent_var_index: usize,
64 /// An assignment of persistent variables to their index in the `persistent_vars` vector.
65 persistent_var_map: HashMap<String, usize>,
66 /// A map of unique DSP node instances (identified by dsp_node_uid) that need private state.
67 node_states: HashMap<u64, Box<DSPNodeState>>,
68 /// A generation counter to determine whether some [DSPNodeState] instances in `node_states`
69 /// can be cleaned up.
70 generation: u64,
71 /// Contains the currently compiled [DSPFunction].
72 next_dsp_fun: Option<Box<DSPFunction>>,
73 /// If enabled, some extra data will be collected.
74 debug_enabled: bool,
75 /// If [DSPNodeContext::set_debug] is enabled, this contains the most recently compiled piece
76 /// of cranelift intermedite representation. You can receive this via [DSPNodeContext::get_ir_dump].
77 pub(crate) cranelift_ir_dump: String,
78 /// An array of atomic floats to exchange data with the live real time thread.
79 /// These AtomicFloats will be shared via the [DSPState] structure and read/written using
80 /// the `atomw` and `atomr` nodes.
81 atoms: Vec<Arc<AtomicFloat>>,
82 /// Holds the current buffer lengths, they are updated
83 /// in [DSPNodeContext::finalize_dsp_function].
84 buffer_lengths: Vec<usize>,
85 /// Holds the most recently declared buffer lengths, these are used to determine
86 /// if we need to send a buffer update with the [DSPFunction]
87 /// in [DSPNodeContext::finalize_dsp_function].
88 pub(crate) buffer_declare: Vec<usize>,
89}
90
91impl DSPNodeContext {
92 fn new() -> Self {
93 Self::new_with_config(DSPContextConfig::default())
94 }
95
96 fn new_with_config(config: DSPContextConfig) -> Self {
97 let mut atoms = vec![];
98 atoms.resize_with(config.atom_count, || Arc::new(AtomicFloat::new(0.0)));
99 let atoms_state = atoms.clone();
100
101 let mut buffer_lengths = vec![];
102 let mut buffers = vec![];
103 for _ in 0..config.buffer_count {
104 buffers.push(vec![0.0; BUFFER_DEFAULT_SIZE]);
105 buffer_lengths.push(BUFFER_DEFAULT_SIZE);
106 }
107 let buffers = LockedMutPtrs::new(buffers);
108 let buffer_declare = buffer_lengths.clone();
109
110 let tables = LockedPtrs::new(config.tables.clone());
111
112 Self {
113 config,
114 state: Box::into_raw(Box::new(DSPState {
115 x: 0.0,
116 y: 0.0,
117 srate: 44100.0,
118 israte: 1.0 / 44100.0,
119 atoms: atoms_state,
120 buffers,
121 tables,
122 })),
123 node_states: HashMap::new(),
124 generation: 0,
125 next_dsp_fun: None,
126 persistent_var_map: HashMap::new(),
127 persistent_var_index: 0,
128 debug_enabled: false,
129 cranelift_ir_dump: String::from(""),
130 atoms,
131 buffer_lengths,
132 buffer_declare,
133 }
134 }
135
136 /// Creates a new [DSPNodeContext] that you can pass into [crate::JIT::new].
137 pub fn new_ref() -> Rc<RefCell<Self>> {
138 Rc::new(RefCell::new(Self::new()))
139 }
140
141 pub(crate) fn init_dsp_function(&mut self) {
142 self.generation += 1;
143 self.next_dsp_fun = Some(Box::new(DSPFunction::new(self.state, self.generation)));
144 }
145
146 /// Enabled debug information collection. See also [DSPNodeContext::get_ir_dump].
147 pub fn set_debug(&mut self, enabled: bool) {
148 self.debug_enabled = enabled;
149 }
150
151 /// Returns if debug is enabled.
152 pub fn debug_enabled(&self) -> bool {
153 self.debug_enabled
154 }
155
156 /// If [DSPNodeContext::set_debug] is enabled, this will return the most recent
157 /// IR code for the most recently compiled [DSPFunction].
158 pub fn get_ir_dump(&self) -> &str {
159 &self.cranelift_ir_dump
160 }
161
162 /// Returns you a reference to the specified atom connected with the DSP backend.
163 /// These atoms can be read and written in the [DSPFunction] using the `atomr` and `atomw`
164 /// nodes.
165 pub fn atom(&self, idx: usize) -> Option<Arc<AtomicFloat>> {
166 self.atoms.get(idx).cloned()
167 }
168
169 /// Retrieve the index into the most recently compiled [DSPFunction].
170 /// To be used by [DSPFunction::access_persistent_var].
171 pub fn get_persistent_variable_index_by_name(&self, pers_var_name: &str) -> Option<usize> {
172 self.persistent_var_map.get(pers_var_name).map(|i| *i)
173 }
174
175 /// Retrieve the index into the persistent variable vector passed in as "&pv".
176 pub(crate) fn get_persistent_variable_index(
177 &mut self,
178 pers_var_name: &str,
179 ) -> Result<usize, String> {
180 let index = if let Some(index) = self.persistent_var_map.get(pers_var_name) {
181 *index
182 } else {
183 let index = self.persistent_var_index;
184 self.persistent_var_index += 1;
185 self.persistent_var_map.insert(pers_var_name.to_string(), index);
186 index
187 };
188
189 if let Some(next_dsp_fun) = &mut self.next_dsp_fun {
190 next_dsp_fun.touch_persistent_var_index(index);
191 Ok(index)
192 } else {
193 Err("No DSPFunction in DSPNodeContext".to_string())
194 }
195 }
196
197 /// Tries to send a new table to the backend. You have to make sure the table
198 /// has exactly the same size as the previous table given in the [DSPContextConfig].
199 /// Otherwise a [DSPNodeContextError] is returned.
200 pub fn send_table(
201 &mut self,
202 tbl_idx: usize,
203 table: Arc<Vec<f64>>,
204 ) -> Result<(), DSPNodeContextError> {
205 let config_tbl_len = 0;
206
207 // Err(DSPNodeContextError::UnknwonTable(tbl_idx)
208
209 Err(DSPNodeContextError::WrongTableSize {
210 tbl_idx,
211 new_size: table.len(),
212 old_size: config_tbl_len,
213 })
214 }
215
216 /// Adds a [DSPNodeState] to the currently compiled [DSPFunction] and returns
217 /// the index into the node state vector in the [DSPFunction], so that the JIT
218 /// code can index into that vector to find the right state pointer.
219 pub(crate) fn add_dsp_node_instance(
220 &mut self,
221 node_type: Arc<dyn DSPNodeType>,
222 dsp_node_uid: u64,
223 ) -> Result<usize, String> {
224 if let Some(next_dsp_fun) = &mut self.next_dsp_fun {
225 if next_dsp_fun.has_dsp_node_state_uid(dsp_node_uid) {
226 return Err(format!(
227 "node_state_uid has been used multiple times in same AST: {}",
228 dsp_node_uid
229 ));
230 }
231
232 if !self.node_states.contains_key(&dsp_node_uid) {
233 self.node_states.insert(
234 dsp_node_uid,
235 Box::new(DSPNodeState::new(dsp_node_uid, node_type.clone())),
236 );
237 }
238
239 if let Some(state) = self.node_states.get_mut(&dsp_node_uid) {
240 if state.node_type().name() != node_type.name() {
241 return Err(format!(
242 "Different DSPNodeType for uid {}: {} != {}",
243 dsp_node_uid,
244 state.node_type().name(),
245 node_type.name()
246 ));
247 }
248
249 Ok(next_dsp_fun.install(state))
250 } else {
251 Err(format!("NodeState does not exist, but it should... bad! {}", dsp_node_uid))
252 }
253 } else {
254 Err("No DSPFunction in DSPNodeContext".to_string())
255 }
256 }
257
258 pub(crate) fn finalize_dsp_function(
259 &mut self,
260 function_ptr: *const u8,
261 module: JITModule,
262 ) -> Option<Box<DSPFunction>> {
263 if let Some(mut next_dsp_fun) = self.next_dsp_fun.take() {
264 for (i, (len, declare)) in
265 self.buffer_lengths.iter().zip(self.buffer_declare.iter()).enumerate()
266 {
267 if *len != *declare {
268 next_dsp_fun.add_buffer_update(i, *declare);
269 }
270 }
271
272 for (len, declare) in self.buffer_lengths.iter_mut().zip(self.buffer_declare.iter_mut())
273 {
274 *len = *declare;
275 }
276
277 next_dsp_fun.set_function_ptr(function_ptr, module);
278
279 for (_, node_state) in self.node_states.iter_mut() {
280 node_state.set_initialized();
281 }
282
283 Some(next_dsp_fun)
284 } else {
285 None
286 }
287 }
288
289 /// If you received a [DSPFunction] back from the audio thread, you should
290 /// pass it into this function. It will make sure to purge old unused [DSPNodeState] instances.
291 pub fn cleanup_dsp_fun_after_user(&mut self, _fun: Box<DSPFunction>) {
292 // TODO: Garbage collect and free unused node state!
293 // But this must happen by the backend/frontend thread separation.
294 // Best would be to provide DSPNodeContext::cleaup_dsp_function_after_use(DSPFunction).
295 }
296
297 /// You must call this after all [DSPFunction] instances compiled with this state are done executing.
298 /// If you don't call this, you might get a memory leak.
299 /// The API is a bit manual at this point, because usually [DSPFunction]
300 /// will be executed on a different thread, and synchronizing this would come with
301 /// additional overhead that I wanted to save.
302 pub fn free(&mut self) {
303 if !self.state.is_null() {
304 let _ = unsafe { Box::from_raw(self.state) };
305 self.state = std::ptr::null_mut();
306 }
307 }
308}
309
310impl Drop for DSPNodeContext {
311 fn drop(&mut self) {
312 if !self.state.is_null() {
313 eprintln!("WBlockDSP JIT DSPNodeContext not cleaned up on exit. Forgot to call free() or keep it alive long enough?");
314 }
315 }
316}
317
318/// This structure holds all the [DSPNodeType] definitions and provides
319/// them to the [crate::JIT] and [crate::jit::DSPFunctionTranslator].
320pub struct DSPNodeTypeLibrary {
321 type_by_name: HashMap<String, Arc<dyn DSPNodeType>>,
322 types: Vec<Arc<dyn DSPNodeType>>,
323}
324
325impl DSPNodeTypeLibrary {
326 /// Create a new instance of this.
327 pub fn new() -> Self {
328 Self { types: vec![], type_by_name: HashMap::new() }
329 }
330
331 /// Add the given [DSPNodeType] to this library.
332 pub fn add(&mut self, typ: Arc<dyn DSPNodeType>) {
333 self.types.push(typ.clone());
334 self.type_by_name.insert(typ.name().to_string(), typ);
335 }
336
337 /// Retrieves a [DSPNodeType] by it's name.
338 pub fn get_type_by_name(&self, typ_name: &str) -> Option<Arc<dyn DSPNodeType>> {
339 self.type_by_name.get(typ_name).cloned()
340 }
341
342 /// Iterate through all types in the Library:
343 ///
344 ///```
345 /// use synfx_dsp_jit::*;
346 ///
347 /// let lib = DSPNodeTypeLibrary::new();
348 /// // ...
349 /// lib.for_each(|typ| -> Result<(), ()> {
350 /// println!("Type available: {}", typ.name());
351 /// Ok(())
352 /// }).expect("no error");
353 ///```
354 pub fn for_each<T, F: FnMut(&Arc<dyn DSPNodeType>) -> Result<(), T>>(
355 &self,
356 mut f: F,
357 ) -> Result<(), T> {
358 for t in self.types.iter() {
359 f(t)?;
360 }
361 Ok(())
362 }
363}
364
365/// This macro can help you defining new stateful DSP nodes.
366///
367///```
368/// use synfx_dsp_jit::*;
369///
370/// struct MyDSPNode {
371/// value: f64,
372/// }
373///
374/// impl MyDSPNode {
375/// fn reset(&mut self, _state: &mut DSPState) {
376/// *self = Self::default();
377/// }
378/// }
379///
380/// impl Default for MyDSPNode {
381/// fn default() -> Self {
382/// Self { value: 0.0 }
383/// }
384/// }
385///
386/// extern "C" fn process_my_dsp_node(my_state: *mut MyDSPNode) -> f64 {
387/// let mut my_state = unsafe { &mut *my_state };
388/// my_state.value += 1.0;
389/// my_state.value
390/// }
391///
392/// // DIYNodeType is the type that is newly defined here, that one you
393/// // pass to DSPNodeTypeLibrary::add
394/// synfx_dsp_jit::stateful_dsp_node_type! {
395/// DIYNodeType, MyDSPNode => process_my_dsp_node "my_dsp" "Sr"
396/// doc
397/// "This is a simple counter. It counts by increments of 1.0 everytime it's called."
398/// inputs
399/// outputs
400/// 0 "sum"
401/// }
402///
403/// // Then use the type by adding it:
404/// fn make_library() -> DSPNodeTypeLibrary {
405/// let mut lib = DSPNodeTypeLibrary::new();
406/// lib.add(DIYNodeType::new_ref());
407/// lib
408/// }
409///```
410///
411/// You might've guessed, `process_my_dsp_node` is the function identifier in the Rust
412/// code. The `"my_dsp"` is the name you can use to refer to this in [crate::ASTNode::Call]:
413/// `ASTNode::Call("my_dsp".to_string(), 1, ...)`.
414/// **Attention:** Make sure to provide unique state IDs here!
415///
416/// The `"Sr"` is a string that specifies the signature of the function. Following characters
417/// are available:
418///
419/// - "v" - A floating point value
420/// - "D" - The global [crate::DSPState] pointer
421/// - "S" - The node specific state pointer (`MyDSPNode`)
422/// - "M" - A pointer to the multi return value array, of type `*mut [f64; 5]`. These can be accessed
423/// by the variables "%1" to "%5" after the call.
424/// - "r" - Must be specified as last one, defines that this function returns something.
425///
426#[macro_export]
427macro_rules! stateful_dsp_node_type {
428 ($node_type: ident, $struct_type: ident =>
429 $func_name: ident $jit_name: literal $signature: literal
430 doc $doc: literal
431 inputs $($idx: literal $inp: literal)*
432 outputs $($idxo: literal $out: literal)*) => {
433 struct $node_type;
434 impl $node_type {
435 fn new_ref() -> std::sync::Arc<Self> {
436 std::sync::Arc::new(Self {})
437 }
438 }
439 impl DSPNodeType for $node_type {
440 fn name(&self) -> &str {
441 $jit_name
442 }
443
444 fn function_ptr(&self) -> *const u8 {
445 $func_name as *const u8
446 }
447
448 fn signature(&self, i: usize) -> Option<DSPNodeSigBit> {
449 match $signature.chars().nth(i) {
450 Some('v') => Some(DSPNodeSigBit::Value),
451 Some('D') => Some(DSPNodeSigBit::DSPStatePtr),
452 Some('S') => Some(DSPNodeSigBit::NodeStatePtr),
453 Some('M') => Some(DSPNodeSigBit::MultReturnPtr),
454 _ => None,
455 }
456 }
457
458 fn has_return_value(&self) -> bool {
459 $signature.find("r").is_some()
460 }
461
462 fn reset_state(&self, dsp_state: *mut DSPState, state_ptr: *mut u8) {
463 let ptr = state_ptr as *mut $struct_type;
464 unsafe {
465 (*ptr).reset(&mut (*dsp_state));
466 }
467 }
468
469 fn allocate_state(&self) -> Option<*mut u8> {
470 Some(Box::into_raw(Box::new($struct_type::default())) as *mut u8)
471 }
472
473 fn deallocate_state(&self, ptr: *mut u8) {
474 let _ = unsafe { Box::from_raw(ptr as *mut $struct_type) };
475 }
476
477 fn documentation(&self) -> &str {
478 $doc
479 }
480
481 fn input_names(&self, index: usize) -> Option<&str> {
482 match index {
483 $($idx => Some($inp),)*
484 _ => None
485 }
486 }
487
488 fn input_index_by_name(&self, name: &str) -> Option<usize> {
489 match name {
490 $($inp => Some($idx),)*
491 _ => None
492 }
493 }
494
495 fn output_names(&self, index: usize) -> Option<&str> {
496 match index {
497 $($idxo => Some($out),)*
498 _ => None
499 }
500 }
501
502 fn output_index_by_name(&self, name: &str) -> Option<usize> {
503 match name {
504 $($out => Some($idxo),)*
505 _ => None
506 }
507 }
508 }
509 };
510}
511
512/// This macro can help you defining new stateless DSP nodes.
513///
514///```
515/// use synfx_dsp_jit::*;
516///
517/// extern "C" fn process_mul2(v: f64) -> f64 {
518/// v * 2.0
519/// }
520///
521/// synfx_dsp_jit::stateless_dsp_node_type! {
522/// Mul2NodeType => process_mul2 "mul2" "vr"
523/// doc
524/// "A simple multiplication by 2.0. Using '*' is simpler thought..."
525/// inputs
526/// 0 ""
527/// outputs
528/// 0 ""
529/// }
530///
531/// // Then use the type by adding it:
532/// fn make_library() -> DSPNodeTypeLibrary {
533/// let mut lib = DSPNodeTypeLibrary::new();
534/// lib.add(Mul2NodeType::new_ref());
535/// lib
536/// }
537///```
538///
539/// The `"vr"` is a string that specifies the signature of the function. Following characters
540/// are available:
541///
542/// - "v" - A floating point value
543/// - "D" - The global [crate::DSPState] pointer
544/// - "M" - A pointer to the multi return value array, of type `*mut [f64; 5]`. These can be accessed
545/// by the variables "%1" to "%5" after the call.
546/// - "r" - Must be specified as last one, defines that this function returns something.
547///
548#[macro_export]
549macro_rules! stateless_dsp_node_type {
550 ($node_type: ident =>
551 $func_name: ident $jit_name: literal $signature: literal
552 doc $doc: literal
553 inputs $($idx: literal $inp: literal)*
554 outputs $($idxo: literal $out: literal)*) => {
555 #[derive(Default)]
556 struct $node_type;
557 impl $node_type {
558 #[allow(dead_code)]
559 fn new_ref() -> std::sync::Arc<Self> {
560 std::sync::Arc::new(Self {})
561 }
562 }
563 impl DSPNodeType for $node_type {
564 fn name(&self) -> &str {
565 $jit_name
566 }
567
568 fn function_ptr(&self) -> *const u8 {
569 $func_name as *const u8
570 }
571
572 fn signature(&self, i: usize) -> Option<DSPNodeSigBit> {
573 match $signature.chars().nth(i) {
574 Some('v') => Some(DSPNodeSigBit::Value),
575 Some('D') => Some(DSPNodeSigBit::DSPStatePtr),
576 Some('M') => Some(DSPNodeSigBit::MultReturnPtr),
577 _ => None,
578 }
579 }
580
581 fn has_return_value(&self) -> bool {
582 $signature.find("r").is_some()
583 }
584
585 fn documentation(&self) -> &str {
586 $doc
587 }
588
589 fn input_names(&self, index: usize) -> Option<&str> {
590 match index {
591 $($idx => Some($inp),)*
592 _ => None
593 }
594 }
595
596 fn input_index_by_name(&self, name: &str) -> Option<usize> {
597 match name {
598 $($inp => Some($idx),)*
599 _ => None
600 }
601 }
602
603 fn output_names(&self, index: usize) -> Option<&str> {
604 match index {
605 $($idxo => Some($out),)*
606 _ => None
607 }
608 }
609
610 fn output_index_by_name(&self, name: &str) -> Option<usize> {
611 match name {
612 $($out => Some($idxo),)*
613 _ => None
614 }
615 }
616 }
617 };
618}
619
620/// This is the result of the JIT compiled [crate::ast::ASTNode] tree.
621/// You can send this structure to the audio backend thread and execute it
622/// using [DSPFunction::exec].
623///
624/// To execute this [DSPFunction] properly, you have to call [DSPFunction::init]
625/// once the newly allocated structure is received by the DSP executing thread.
626///
627/// If the sample rate changes or the stateful DSP stuff must be resetted,
628/// you should call [DSPFunction::reset] or [DSPFunction::set_sample_rate].
629/// Of course also only on the DSP executing thread.
630pub struct DSPFunction {
631 state: *mut DSPState,
632 /// Contains the types of the corresponding `node_states`. The [DSPNodeType] is
633 /// necessary to reset the state pointed to by the pointers in `node_states`.
634 node_state_types: Vec<Arc<dyn DSPNodeType>>,
635 /// Contains the actual pointers to the state that was constructed by the corresponding [DSPNodeState].
636 node_states: Vec<*mut u8>,
637 /// Constains indices into `node_states`, so that they can be reset/initialized by [DSPFunction::init].
638 /// Only contains recently added (as determined by [DSPNodeContext]) and uninitialized state indices.
639 node_state_init_reset: Vec<usize>,
640 /// Keeps the node_state_uid of the [DSPNodeState] pieces used already in this
641 /// function. It's for error detection when building this [DSPFunction], to prevent
642 /// the user from evaluating a stateful DSP node multiple times.
643 node_state_uids: Vec<u64>,
644 /// Generation of the corresponding [DSPNodeContext].
645 dsp_ctx_generation: u64,
646 /// The JITModule that is the home for the `function` pointer. It must be kept alive
647 /// as long as the `function` pointer is in use.
648 module: Option<JITModule>,
649 /// Storage of persistent variables:
650 persistent_vars: Vec<f64>,
651 /// Buffer updates for the buffers in [DSPState], these are determined and set
652 /// in [DSPNodeContext::finalize_dsp_function].
653 buffer_updates: Option<Vec<(usize, Vec<f64>)>>,
654 /// This is just a flag as precaution, in case init() is accidentally called
655 /// multiple times.
656 buffer_updates_done: bool,
657 /// Auxilary variables to access directly from the machine code. Holds information such as
658 /// the sample rate or the inverse of the sample rate.
659 aux_vars: [f64; AUX_VAR_COUNT],
660 /// Is true directly after reset.
661 resetted: bool,
662 function: Option<
663 fn(
664 f64,
665 f64,
666 f64,
667 f64,
668 f64,
669 f64,
670 *mut f64,
671 *mut f64,
672 *mut f64,
673 *mut DSPState,
674 *const *mut u8,
675 *mut f64,
676 *mut f64,
677 *const *mut f64,
678 *const u64,
679 *const *const f32,
680 *const u64,
681 ) -> f64,
682 >,
683}
684
685unsafe impl Send for DSPFunction {}
686unsafe impl Sync for DSPFunction {}
687
688impl DSPFunction {
689 /// Used by [DSPNodeContext] to create a new instance of this.
690 pub(crate) fn new(state: *mut DSPState, dsp_ctx_generation: u64) -> Self {
691 Self {
692 state,
693 node_state_types: vec![],
694 node_states: vec![],
695 node_state_init_reset: vec![],
696 node_state_uids: vec![],
697 persistent_vars: vec![],
698 aux_vars: [0.0; AUX_VAR_COUNT],
699 function: None,
700 dsp_ctx_generation,
701 module: None,
702 resetted: false,
703 buffer_updates: Some(vec![]),
704 buffer_updates_done: true,
705 }
706 }
707
708 /// At the end of the compilation the [crate::JIT] will put the resulting function
709 /// pointer into this function.
710 pub(crate) fn set_function_ptr(&mut self, function: *const u8, module: JITModule) {
711 self.module = Some(module);
712 self.function = Some(unsafe {
713 mem::transmute::<
714 _,
715 fn(
716 f64,
717 f64,
718 f64,
719 f64,
720 f64,
721 f64,
722 *mut f64,
723 *mut f64,
724 *mut f64,
725 *mut DSPState,
726 *const *mut u8,
727 *mut f64,
728 *mut f64,
729 *const *mut f64,
730 *const u64,
731 *const *const f32,
732 *const u64,
733 ) -> f64,
734 >(function)
735 });
736 }
737
738 /// Appends a buffer update to this [DSPFunction], to update the buffers
739 /// according to [crate::ast::ASTNode::BufDeclare]. Buffers are only updated
740 /// if they get a new length though.
741 pub(crate) fn add_buffer_update(&mut self, buf_idx: usize, length: usize) {
742 if let Some(updates) = &mut self.buffer_updates {
743 updates.push((buf_idx, vec![0.0; length]));
744 }
745 self.buffer_updates_done = false;
746 }
747
748 /// This function must be called before [DSPFunction::exec]!
749 /// otherwise your states might not be properly initialized or preserved.
750 ///
751 /// If you recompiled a function, pass the old one on the audio thread to
752 /// the `previous_function` parameter here. It will take care of preserving
753 /// state, such as persistent variables (those that start with "*": `crate::build::var("*abc")`).
754 pub fn init(&mut self, srate: f64, previous_function: Option<&DSPFunction>) {
755 if let Some(previous_function) = previous_function {
756 let prev_len = previous_function.persistent_vars.len();
757 let now_len = self.persistent_vars.len();
758 let len = prev_len.min(now_len);
759 self.persistent_vars[0..len].copy_from_slice(&previous_function.persistent_vars[0..len])
760 } else {
761 self.resetted = true;
762 }
763
764 if !self.buffer_updates_done {
765 if let Some(mut updates) = self.buffer_updates.take() {
766 for (idx, new_vec) in updates.iter_mut() {
767 let _ = self.swap_buffer(*idx, new_vec, true);
768 }
769 self.buffer_updates = Some(updates);
770 }
771 self.buffer_updates_done = true;
772 }
773
774 unsafe {
775 (*self.state).srate = srate;
776 (*self.state).israte = 1.0 / srate;
777 }
778 self.aux_vars[AUX_VAR_IDX_SRATE] = srate;
779 self.aux_vars[AUX_VAR_IDX_ISRATE] = 1.0 / srate;
780
781 for idx in self.node_state_init_reset.iter() {
782 let typ = &self.node_state_types[*idx as usize];
783 let ptr = self.node_states[*idx as usize];
784 typ.reset_state(self.state, ptr);
785 }
786 }
787
788 /// Swaps out the buffer at the given index with the new buffer. The contents
789 /// of the Vec will be swapped with the current contents of the buffer, unless
790 /// you specify `preserve_old_samples` which will try to preserve as many samples
791 /// from the previous buffer as possible.
792 pub fn swap_buffer(
793 &mut self,
794 index: usize,
795 new_buf: &mut Vec<f64>,
796 preserve_old_samples: bool,
797 ) -> Result<(), ()> {
798 unsafe {
799 if index >= (*self.state).buffers.len() {
800 return Err(());
801 }
802 if preserve_old_samples {
803 let old_len = (*self.state).buffers.element_len(index);
804 let old_vec = (*self.state).buffers.pointers()[index];
805 let min_len = old_len.min(new_buf.len());
806 std::ptr::copy_nonoverlapping(old_vec, new_buf.as_mut_ptr(), min_len);
807 }
808 let _ = (*self.state).buffers.swap_element(index, new_buf);
809 }
810 Ok(())
811 }
812
813 /// Swaps out the table at the given index with the new table.
814 pub fn swap_table(
815 &mut self,
816 index: usize,
817 new_table: &mut Arc<Vec<f32>>,
818 ) -> Result<(), ()> {
819 unsafe {
820 if index >= (*self.state).tables.len() {
821 return Err(());
822 }
823 let _ = (*self.state).tables.swap_element(index, new_table);
824 }
825 Ok(())
826 }
827
828 /// If the audio thread changes the sampling rate, call this function, it will update
829 /// the [DSPState] and reset all [DSPNodeState]s.
830 pub fn set_sample_rate(&mut self, srate: f64) {
831 unsafe {
832 (*self.state).srate = srate;
833 (*self.state).israte = 1.0 / srate;
834 }
835 self.aux_vars[AUX_VAR_IDX_SRATE] = srate;
836 self.aux_vars[AUX_VAR_IDX_ISRATE] = 1.0 / srate;
837
838 self.reset();
839 }
840
841 /// If the DSP state needs to be resetted, call this on the audio thread.
842 pub fn reset(&mut self) {
843 self.resetted = true;
844 for (typ, ptr) in self.node_state_types.iter().zip(self.node_states.iter_mut()) {
845 typ.reset_state(self.state, *ptr);
846 }
847 self.persistent_vars.fill(0.0);
848 }
849
850 /// Use this to retrieve a pointer to the [DSPState] to access it between
851 /// calls to [DSPFunction::exec].
852 pub fn get_dsp_state_ptr(&self) -> *mut DSPState {
853 self.state
854 }
855
856 /// Use this to access the [DSPState] pointer between calls to [DSPFunction::exec].
857 ///
858 /// # Safety
859 ///
860 /// You must not create multiple aliasing references from that DSP state!
861 pub unsafe fn with_dsp_state<R, F: FnMut(*mut DSPState) -> R>(&mut self, mut f: F) -> R {
862 f(self.get_dsp_state_ptr())
863 }
864
865 /// Use this to access the state of a specific DSP node state pointer between
866 /// calls to [DSPFunction::exec].
867 ///
868 /// The `node_state_uid` and the type you pass here must match! It's your responsibility
869 /// to make sure this works!
870 ///
871 /// # Safety
872 ///
873 /// You absolutely must know which ID has which [DSPNodeType], otherwise this will badly go wrong!
874 ///
875 ///```
876 /// use synfx_dsp_jit::*;
877 /// use synfx_dsp_jit::build::*;
878 /// use synfx_dsp_jit::stdlib::AccumNodeState;
879 ///
880 /// let (ctx, mut fun) = instant_compile_ast(call("accum", 21, &[var("in1"), literal(0.0)])).unwrap();
881 ///
882 /// fun.init(44100.0, None);
883 /// // Accumulate 42.0 here:
884 /// fun.exec_2in_2out(21.0, 0.0);
885 /// fun.exec_2in_2out(21.0, 0.0);
886 ///
887 /// unsafe {
888 /// // Check 42.0 and set 99.0
889 /// fun.with_node_state(21, |state: *mut AccumNodeState| {
890 /// assert!(((*state).value - 42.0).abs() < 0.0001);
891 /// (*state).value = 99.0;
892 /// })
893 /// };
894 ///
895 /// // Accumulate up to 100.0 here:
896 /// let (_, _, ret) = fun.exec_2in_2out(1.0, 0.0);
897 /// assert!((ret - 100.0).abs() < 0.0001);
898 ///
899 /// ctx.borrow_mut().free();
900 ///```
901 #[allow(clippy::result_unit_err)]
902 pub unsafe fn with_node_state<T, R, F: FnMut(*mut T) -> R>(
903 &mut self,
904 node_state_uid: u64,
905 mut f: F,
906 ) -> Result<R, ()> {
907 if let Some(state_ptr) = self.get_node_state_ptr(node_state_uid) {
908 Ok(f(state_ptr as *mut T))
909 } else {
910 Err(())
911 }
912 }
913
914 /// Retrieves the DSP node state pointer for a certain unique node state id.
915 ///
916 /// # Safety
917 ///
918 /// You are responsible afterwards for knowing what type the actual pointer is of.
919 pub fn get_node_state_ptr(&self, node_state_uid: u64) -> Option<*mut u8> {
920 for (i, uid) in self.node_state_uids.iter().enumerate() {
921 if *uid == node_state_uid {
922 return Some(self.node_states[i]);
923 }
924 }
925
926 None
927 }
928
929 /// Helper function, it lets you specify only the contents of the parameters
930 /// `"in1"` and `"in2"`. It also returns you the values for `"&sig1"` and `"&sig2"`
931 /// after execution. The third value is the return value of the compiled expression.
932 pub fn exec_2in_2out(&mut self, in1: f64, in2: f64) -> (f64, f64, f64) {
933 let mut s1 = 0.0;
934 let mut s2 = 0.0;
935 let r = self.exec(in1, in2, 0.0, 0.0, 0.0, 0.0, &mut s1, &mut s2);
936 (s1, s2, r)
937 }
938
939 /// Executes the machine code and provides the following parameters in order:
940 /// `"in1", "in2", "alpha", "beta", "delta", "gamma", "&sig1", "&sig2"`
941 ///
942 /// It returns the return value of the computation. For addition outputs you can
943 /// write to `"&sig1"` or `"&sig2"` with for instance: `assign(var("&sig1"), literal(10.0))`.
944 #[allow(clippy::too_many_arguments)]
945 pub fn exec(
946 &mut self,
947 in1: f64,
948 in2: f64,
949 alpha: f64,
950 beta: f64,
951 delta: f64,
952 gamma: f64,
953 sig1: &mut f64,
954 sig2: &mut f64,
955 ) -> f64 {
956 {
957 self.aux_vars[AUX_VAR_IDX_RESET] = if self.resetted {
958 self.resetted = false;
959 1.0
960 } else {
961 0.0
962 };
963 }
964 let states_ptr: *const *mut u8 = self.node_states.as_mut_ptr();
965 let pers_vars_ptr: *mut f64 = self.persistent_vars.as_mut_ptr();
966 let aux_vars: *mut f64 = self.aux_vars.as_mut_ptr();
967 let bufs: *const *mut f64 = unsafe { (*self.state).buffers.pointers().as_ptr() };
968 let buf_lens: *const u64 = unsafe { (*self.state).buffers.lens().as_ptr() };
969 let tables: *const *const f32 = unsafe { (*self.state).tables.pointers().as_ptr() };
970 let table_lens: *const u64 = unsafe { (*self.state).tables.lens().as_ptr() };
971 let mut multi_returns = [0.0; 5];
972
973 (unsafe { self.function.unwrap_unchecked() })(
974 in1,
975 in2,
976 alpha,
977 beta,
978 delta,
979 gamma,
980 sig1,
981 sig2,
982 aux_vars,
983 self.state,
984 states_ptr,
985 pers_vars_ptr,
986 (&mut multi_returns) as *mut f64,
987 bufs,
988 buf_lens,
989 tables,
990 table_lens,
991 )
992 }
993
994 pub(crate) fn install(&mut self, node_state: &mut DSPNodeState) -> usize {
995 let idx = self.node_states.len();
996 node_state.mark(self.dsp_ctx_generation, idx);
997
998 self.node_states.push(node_state.ptr());
999 self.node_state_types.push(node_state.node_type());
1000 self.node_state_uids.push(node_state.uid());
1001
1002 if !node_state.is_initialized() {
1003 self.node_state_init_reset.push(idx);
1004 }
1005
1006 idx
1007 }
1008
1009 pub(crate) fn touch_persistent_var_index(&mut self, idx: usize) {
1010 if idx >= self.persistent_vars.len() {
1011 self.persistent_vars.resize(idx + 1, 0.0);
1012 }
1013 }
1014
1015 /// Gives you access to the persistent variables. To get the index of the
1016 /// persistent variable you must use [DSPNodeContext::get_persistent_variable_index_by_name].
1017 pub fn access_persistent_var(&mut self, idx: usize) -> Option<&mut f64> {
1018 self.persistent_vars.get_mut(idx)
1019 }
1020
1021 /// Checks if the DSP function actually has the state for a certain unique DSP node state ID.
1022 pub fn has_dsp_node_state_uid(&self, uid: u64) -> bool {
1023 for i in self.node_state_uids.iter() {
1024 if *i == uid {
1025 return true;
1026 }
1027 }
1028
1029 false
1030 }
1031}
1032
1033impl Drop for DSPFunction {
1034 fn drop(&mut self) {
1035 unsafe {
1036 if let Some(module) = self.module.take() {
1037 module.free_memory();
1038 }
1039 };
1040 }
1041}
1042
1043/// The global DSP state that all stateful [DSPNodeType] DSP nodes share.
1044pub struct DSPState {
1045 pub x: f64,
1046 pub y: f64,
1047 pub srate: f64,
1048 pub israte: f64,
1049 pub atoms: Vec<Arc<AtomicFloat>>,
1050 pub buffers: LockedMutPtrs<Vec<f64>, f64>,
1051 pub tables: LockedPtrs<Arc<Vec<f32>>, f32>,
1052}
1053
1054/// An enum to specify the position of value and [DSPState] and [DSPNodeState] parameters
1055/// for the JIT compiler.
1056#[derive(Debug, Clone, Copy, PartialEq)]
1057pub enum DSPNodeSigBit {
1058 /// Signature placeholder for f64
1059 Value,
1060 /// Signature placeholder for the [DSPState] pointer
1061 DSPStatePtr,
1062 /// Signature placeholder for the [DSPNodeState] pointer that belongs to this node
1063 NodeStatePtr,
1064 /// Signature placeholder for a pointer to the multi return value array (max size is 5! `*mut [f64; 5]`)
1065 MultReturnPtr,
1066}
1067
1068/// This trait allows you to define your own DSP stateful and stateless primitives.
1069/// Among defining a few important properties for the compiler, it handles allocation and
1070/// deallocation of the state that belongs to a DSPNodeType.
1071///
1072/// ## Stateless DSP Nodes/Primitives
1073///
1074/// Here is a simple example how to define a stateless DSP function:
1075///
1076///```
1077/// use std::rc::Rc;
1078/// use std::cell::RefCell;
1079/// use synfx_dsp_jit::{DSPNodeType, DSPNodeSigBit, DSPNodeTypeLibrary};
1080///
1081/// let lib = Rc::new(RefCell::new(DSPNodeTypeLibrary::new()));
1082///
1083/// pub struct MyPrimitive;
1084///
1085/// extern "C" fn my_primitive_function(a: f64, b: f64) -> f64 {
1086/// (2.0 * a * b.cos()).sin()
1087/// }
1088///
1089/// impl DSPNodeType for MyPrimitive {
1090/// // make a name, so you can refer to it via `ASTNode::Call("my_prim", ...)`.
1091/// fn name(&self) -> &str { "my_prim" }
1092///
1093/// // Provide a pointer:
1094/// fn function_ptr(&self) -> *const u8 { my_primitive_function as *const u8 }
1095///
1096/// // Define the function signature for the JIT compiler:
1097/// fn signature(&self, i: usize) -> Option<DSPNodeSigBit> {
1098/// match i {
1099/// 0 | 1 => Some(DSPNodeSigBit::Value),
1100/// _ => None, // Return None to signal we only take 2 parameters
1101/// }
1102/// }
1103///
1104/// // Tell the JIT compiler that you return a value:
1105/// fn has_return_value(&self) -> bool { true }
1106///
1107/// // The other trait functions do not need to be provided, because this is
1108/// // a stateless primitive.
1109/// }
1110///
1111/// lib.borrow_mut().add(std::sync::Arc::new(MyPrimitive {}));
1112///
1113/// use synfx_dsp_jit::{ASTFun, JIT, DSPNodeContext};
1114/// let ctx = DSPNodeContext::new_ref();
1115/// let jit = JIT::new(lib.clone(), ctx.clone());
1116///
1117/// use synfx_dsp_jit::build::*;
1118/// let mut fun = jit.compile(ASTFun::new(
1119/// op_add(call("my_prim", 0, &[var("in1"), var("in2")]), literal(10.0))))
1120/// .expect("no compile error");
1121///
1122/// fun.init(44100.0, None);
1123///
1124/// let (_s1, _s2, ret) = fun.exec_2in_2out(1.0, 1.5);
1125///
1126/// assert!((ret - 10.1410029).abs() < 0.000001);
1127///
1128/// ctx.borrow_mut().free();
1129///```
1130///
1131/// ## Stateful DSP Nodes/Primitives
1132///
1133/// Here is a simple example how to define a stateful DSP function,
1134/// in this example just an accumulator.
1135///
1136/// There is a little helper macro that might help you: [crate::stateful_dsp_node_type]
1137///
1138///```
1139/// use std::rc::Rc;
1140/// use std::cell::RefCell;
1141/// use synfx_dsp_jit::{DSPNodeType, DSPState, DSPNodeSigBit, DSPNodeTypeLibrary};
1142///
1143/// let lib = Rc::new(RefCell::new(DSPNodeTypeLibrary::new()));
1144///
1145/// pub struct MyPrimitive;
1146///
1147/// struct MyPrimAccumulator {
1148/// count: f64,
1149/// }
1150///
1151/// // Be careful defining the signature of this primitive, there is no safety net here!
1152/// // Check twice with DSPNodeType::signature()!
1153/// extern "C" fn my_primitive_accum(add: f64, state: *mut u8) -> f64 {
1154/// let state = unsafe { &mut *(state as *mut MyPrimAccumulator) };
1155/// state.count += add;
1156/// state.count
1157/// }
1158///
1159/// impl DSPNodeType for MyPrimitive {
1160/// // make a name, so you can refer to it via `ASTNode::Call("my_prim", ...)`.
1161/// fn name(&self) -> &str { "accum" }
1162///
1163/// // Provide a pointer:
1164/// fn function_ptr(&self) -> *const u8 { my_primitive_accum as *const u8 }
1165///
1166/// // Define the function signature for the JIT compiler. Be really careful though,
1167/// // There is no safety net here.
1168/// fn signature(&self, i: usize) -> Option<DSPNodeSigBit> {
1169/// match i {
1170/// 0 => Some(DSPNodeSigBit::Value),
1171/// 1 => Some(DSPNodeSigBit::NodeStatePtr),
1172/// _ => None, // Return None to signal we only take 1 parameter
1173/// }
1174/// }
1175///
1176/// // Tell the JIT compiler that you return a value:
1177/// fn has_return_value(&self) -> bool { true }
1178///
1179/// // Specify how to reset the state:
1180/// fn reset_state(&self, _dsp_state: *mut DSPState, state_ptr: *mut u8) {
1181/// unsafe { (*(state_ptr as *mut MyPrimAccumulator)).count = 0.0 };
1182/// }
1183///
1184/// // Allocate our state:
1185/// fn allocate_state(&self) -> Option<*mut u8> {
1186/// Some(Box::into_raw(Box::new(MyPrimAccumulator { count: 0.0 })) as *mut u8)
1187/// }
1188///
1189/// // Deallocate our state:
1190/// fn deallocate_state(&self, ptr: *mut u8) {
1191/// let _ = unsafe { Box::from_raw(ptr as *mut MyPrimAccumulator) };
1192/// }
1193/// }
1194///
1195/// lib.borrow_mut().add(std::sync::Arc::new(MyPrimitive {}));
1196///
1197/// use synfx_dsp_jit::{ASTFun, JIT, DSPNodeContext};
1198/// let ctx = DSPNodeContext::new_ref();
1199/// let jit = JIT::new(lib.clone(), ctx.clone());
1200///
1201/// use synfx_dsp_jit::build::*;
1202/// let mut fun =
1203/// jit.compile(ASTFun::new(call("accum", 0, &[var("in1")]))).expect("no compile error");
1204///
1205/// fun.init(44100.0, None);
1206///
1207/// let (_s1, _s2, ret) = fun.exec_2in_2out(1.0, 0.0);
1208/// assert!((ret - 1.0).abs() < 0.000001);
1209///
1210/// let (_s1, _s2, ret) = fun.exec_2in_2out(1.0, 0.0);
1211/// assert!((ret - 2.0).abs() < 0.000001);
1212///
1213/// let (_s1, _s2, ret) = fun.exec_2in_2out(1.0, 0.0);
1214/// assert!((ret - 3.0).abs() < 0.000001);
1215///
1216/// // You can cause a reset eg. with fun.set_sample_rate() or fun.reset():
1217/// fun.reset();
1218///
1219/// // Counting will restart:
1220/// let (_s1, _s2, ret) = fun.exec_2in_2out(1.0, 0.0);
1221/// assert!((ret - 1.0).abs() < 0.000001);
1222///
1223/// ctx.borrow_mut().free();
1224///```
1225pub trait DSPNodeType: Sync + Send {
1226 /// The name of this DSP node, by this name it can be called from
1227 /// the [crate::ast::ASTFun].
1228 fn name(&self) -> &str;
1229
1230 /// Document what this node does and how to use it.
1231 /// Format should be in Markdown.
1232 ///
1233 /// Documenting the node will make it easier for library implementors
1234 /// and even eventual end users to figure out what this node
1235 /// does and how to use it.
1236 ///
1237 /// For instance, this text should define what the input and output
1238 /// parameters do. And also define which value ranges these operate in.
1239 fn documentation(&self) -> &str {
1240 "undocumented"
1241 }
1242
1243 /// Returns the name of each input port of this node.
1244 /// Choose descriptive but short names.
1245 /// These names will be used by compiler frontends to identify the ports,
1246 /// and it will make it easier to stay compatible if indices change.
1247 fn input_names(&self, _index: usize) -> Option<&str> {
1248 None
1249 }
1250
1251 /// Returns the name of each output port of this node.
1252 /// Choose descriptive but short names.
1253 /// These names will be used by compiler frontends to identify the ports,
1254 /// and it will make it easier to stay compatible if indices change.
1255 fn output_names(&self, _index: usize) -> Option<&str> {
1256 None
1257 }
1258
1259 /// Returns the index of the output by it's name.
1260 fn input_index_by_name(&self, name: &str) -> Option<usize> {
1261 let mut i = 0;
1262
1263 while let Some(iname) = self.input_names(i) {
1264 if iname == name {
1265 return Some(i);
1266 }
1267 i += 1;
1268 }
1269
1270 None
1271 }
1272
1273 /// Returns the index of the output by it's name.
1274 fn output_index_by_name(&self, name: &str) -> Option<usize> {
1275 let mut i = 0;
1276
1277 while let Some(oname) = self.output_names(i) {
1278 if oname == name {
1279 return Some(i);
1280 }
1281 i += 1;
1282 }
1283
1284 None
1285 }
1286
1287 /// Number of input ports
1288 fn input_count(&self) -> usize {
1289 let mut i = 0;
1290 while self.input_names(i).is_some() {
1291 i += 1;
1292 }
1293 i
1294 }
1295
1296 /// Number of output ports
1297 fn output_count(&self) -> usize {
1298 let mut i = 0;
1299 while self.output_names(i).is_some() {
1300 i += 1;
1301 }
1302 i
1303 }
1304
1305 /// Returns true if this node type requires state.
1306 fn is_stateful(&self) -> bool {
1307 let mut i = 0;
1308 while let Some(sig) = self.signature(i) {
1309 if let DSPNodeSigBit::NodeStatePtr = sig {
1310 return true;
1311 }
1312
1313 i += 1;
1314 }
1315
1316 false
1317 }
1318
1319 /// The function pointer that should be inserted.
1320 fn function_ptr(&self) -> *const u8;
1321
1322 /// Should return the signature type for input parameter `i`.
1323 fn signature(&self, _i: usize) -> Option<DSPNodeSigBit> {
1324 None
1325 }
1326
1327 /// Should return true if the function for [DSPNodeType::function_ptr]
1328 /// returns something.
1329 fn has_return_value(&self) -> bool;
1330
1331 /// Will be called when the node state should be resetted.
1332 /// This should be used to store the sample rate for instance or
1333 /// do other sample rate dependent recomputations.
1334 /// Also things delay lines should zero their buffers.
1335 fn reset_state(&self, _dsp_state: *mut DSPState, _state_ptr: *mut u8) {}
1336
1337 /// Allocates a new piece of state for this [DSPNodeType].
1338 /// Must be deallocated using [DSPNodeType::deallocate_state].
1339 fn allocate_state(&self) -> Option<*mut u8> {
1340 None
1341 }
1342
1343 /// Deallocates the private state of this [DSPNodeType].
1344 fn deallocate_state(&self, _ptr: *mut u8) {}
1345}
1346
1347/// A handle to manage the state of a DSP node
1348/// that was created while the [crate::jit::DSPFunctionTranslator] compiled the given AST
1349/// to machine code. The AST needs to take care to refer to the same piece
1350/// of state with the same type across different compilations of the AST with the
1351/// same [DSPNodeContext].
1352///
1353/// It holds a pointer to the state of a single DSP node. The internal state
1354/// pointer will be shared with the execution thread that will execute the
1355/// complete DSP function/graph.
1356///
1357/// You will not have to allocate and manage this manually, see also [DSPFunction].
1358pub(crate) struct DSPNodeState {
1359 /// The node_state_uid that identifies this piece of state uniquely across multiple
1360 /// ASTs.
1361 uid: u64,
1362 /// Holds the type of this piece of state.
1363 node_type: Arc<dyn DSPNodeType>,
1364 /// A pointer to the allocated piece of state. It will be shared
1365 /// with the execution thread. So you must not touch the data that is referenced
1366 /// here.
1367 ptr: *mut u8,
1368 /// A generation counter that is used by [DSPNodeContext] to determine
1369 /// if a piece of state is not used anymore.
1370 generation: u64,
1371 /// The current index into the most recent [DSPFunction] that was
1372 /// constructed by [DSPNodeContext].
1373 function_index: usize,
1374 /// A flag that stores if this DSPNodeState instance was already initialized.
1375 /// It is set by [DSPNodeContext] if a finished [DSPFunction] was successfully compiled.
1376 initialized: bool,
1377}
1378
1379impl DSPNodeState {
1380 /// Creates a fresh piece of DSP node state.
1381 pub(crate) fn new(uid: u64, node_type: Arc<dyn DSPNodeType>) -> Self {
1382 Self {
1383 uid,
1384 node_type: node_type.clone(),
1385 ptr: node_type.allocate_state().expect("DSPNodeState created for stateful node type"),
1386 generation: 0,
1387 function_index: 0,
1388 initialized: false,
1389 }
1390 }
1391
1392 /// Returns the unique ID of this piece of DSP node state.
1393 pub(crate) fn uid(&self) -> u64 {
1394 self.uid
1395 }
1396
1397 /// Marks this piece of DSP state as used and deposits the
1398 /// index into the current [DSPFunction].
1399 pub(crate) fn mark(&mut self, gen: u64, index: usize) {
1400 self.generation = gen;
1401 self.function_index = index;
1402 }
1403
1404 /// Checks if the [DSPNodeState] was initialized by the most recently compiled [DSPFunction]
1405 pub(crate) fn is_initialized(&self) -> bool {
1406 self.initialized
1407 }
1408
1409 /// Sets that the [DSPNodeState] is initialized.
1410 ///
1411 /// This happens once the [DSPNodeContext] finished compiling a [DSPFunction].
1412 /// The user of the [DSPNodeContext] or rather the [crate::JIT] needs to make sure to
1413 /// actually really call [DSPFunction::init] of course. Otherwise this state tracking
1414 /// all falls apart. But this happens across different threads, so the synchronizing effort
1415 /// for this is not worth it (regarding development time) at the moment I think.
1416 pub(crate) fn set_initialized(&mut self) {
1417 self.initialized = true;
1418 }
1419
1420 /// Returns the state pointer for this DSPNodeState instance.
1421 /// Primarily used by [DSPFunction::install].
1422 pub(crate) fn ptr(&self) -> *mut u8 {
1423 self.ptr
1424 }
1425
1426 /// Returns the [DSPNodeType] for this [DSPNodeState].
1427 pub(crate) fn node_type(&self) -> Arc<dyn DSPNodeType> {
1428 self.node_type.clone()
1429 }
1430}
1431
1432impl Drop for DSPNodeState {
1433 /// This should only be dropped when the [DSPNodeContext] determined
1434 /// that the pointer that was shared with the execution thread is no longer
1435 /// in use.
1436 fn drop(&mut self) {
1437 self.node_type.deallocate_state(self.ptr);
1438 self.ptr = std::ptr::null_mut();
1439 }
1440}