Enable assert_no_alloc in debug builds
This commit is contained in:
parent
2ca54d220d
commit
dfedd7b2c4
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -8,6 +8,12 @@ version = "1.0.53"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0"
|
checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "assert_no_alloc"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "55ca83137a482d61d916ceb1eba52a684f98004f18e0cafea230fe5579c178a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -142,6 +148,8 @@ dependencies = [
|
||||||
name = "nih_plug"
|
name = "nih_plug"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"assert_no_alloc",
|
||||||
|
"cfg-if",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"nih_plug_derive",
|
"nih_plug_derive",
|
||||||
|
|
|
@ -18,9 +18,18 @@ members = [
|
||||||
nih_plug_derive = { path = "nih_plug_derive" }
|
nih_plug_derive = { path = "nih_plug_derive" }
|
||||||
|
|
||||||
crossbeam = "0.8"
|
crossbeam = "0.8"
|
||||||
|
cfg-if = "1.0"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
parking_lot = "0.12"
|
parking_lot = "0.12"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
vst3-sys = { git = "https://github.com/robbert-vdh/vst3-sys.git", branch = "fix/atomic-reference-count" }
|
vst3-sys = { git = "https://github.com/robbert-vdh/vst3-sys.git", branch = "fix/atomic-reference-count" }
|
||||||
widestring = "1.0.0-beta.1"
|
widestring = "1.0.0-beta.1"
|
||||||
|
|
||||||
|
assert_no_alloc = { version = "1.1", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
# Enabling this feature will cause the plugin to terminate when allocations
|
||||||
|
# occur in the processing function while compiling in debug mode.
|
||||||
|
assert_process_allocs = ["assert_no_alloc"]
|
||||||
|
|
|
@ -8,6 +8,6 @@ license = "GPL-3.0-or-later"
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nih_plug = { path = "../../" }
|
nih_plug = { path = "../../", features = ["assert_process_allocs"] }
|
||||||
|
|
||||||
parking_lot = "0.12"
|
parking_lot = "0.12"
|
||||||
|
|
|
@ -8,4 +8,4 @@ license = "GPL-3.0-or-later"
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nih_plug = { path = "../../" }
|
nih_plug = { path = "../../", features = ["assert_process_allocs"] }
|
||||||
|
|
|
@ -19,6 +19,10 @@ use std::os::raw::c_char;
|
||||||
use vst3_sys::vst::TChar;
|
use vst3_sys::vst::TChar;
|
||||||
use widestring::U16CString;
|
use widestring::U16CString;
|
||||||
|
|
||||||
|
#[cfg(all(debug_assertions, feature = "assert_process_allocs"))]
|
||||||
|
#[global_allocator]
|
||||||
|
static A: assert_no_alloc::AllocDisabler = assert_no_alloc::AllocDisabler;
|
||||||
|
|
||||||
/// A Rabin fingerprint based string hash for parameter ID strings.
|
/// A Rabin fingerprint based string hash for parameter ID strings.
|
||||||
pub fn hash_param_id(id: &str) -> u32 {
|
pub fn hash_param_id(id: &str) -> u32 {
|
||||||
let mut overflow;
|
let mut overflow;
|
||||||
|
@ -83,3 +87,16 @@ pub fn u16strlcpy(dest: &mut [TChar], src: &str) {
|
||||||
dest[..copy_len].copy_from_slice(&src_utf16_chars_signed[..copy_len]);
|
dest[..copy_len].copy_from_slice(&src_utf16_chars_signed[..copy_len]);
|
||||||
dest[copy_len] = 0;
|
dest[copy_len] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A wrapper around the entire process function, including the plugin wrapper parts. This sets up
|
||||||
|
/// `assert_no_alloc` if needed, while also making sure that things like FTZ are set up correctly if
|
||||||
|
/// the host has not already done so.
|
||||||
|
pub fn process_wrapper<T, F: FnOnce() -> T>(f: F) -> T {
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(all(debug_assertions, feature = "assert_process_allocs"))] {
|
||||||
|
assert_no_alloc::assert_no_alloc(f)
|
||||||
|
} else {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ use crate::param::range::Range;
|
||||||
use crate::param::Param;
|
use crate::param::Param;
|
||||||
use crate::plugin::{BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin};
|
use crate::plugin::{BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin};
|
||||||
use crate::wrapper::state::{ParamValue, State};
|
use crate::wrapper::state::{ParamValue, State};
|
||||||
use crate::wrapper::util::{hash_param_id, strlcpy, u16strlcpy};
|
use crate::wrapper::util::{hash_param_id, process_wrapper, strlcpy, u16strlcpy};
|
||||||
|
|
||||||
// Alias needed for the VST3 attribute macro
|
// Alias needed for the VST3 attribute macro
|
||||||
use vst3_sys as vst3_com;
|
use vst3_sys as vst3_com;
|
||||||
|
@ -972,124 +972,131 @@ impl<P: Plugin> IAudioProcessor for Wrapper<'_, P> {
|
||||||
unsafe fn process(&self, data: *mut vst3_sys::vst::ProcessData) -> tresult {
|
unsafe fn process(&self, data: *mut vst3_sys::vst::ProcessData) -> tresult {
|
||||||
check_null_ptr!(data);
|
check_null_ptr!(data);
|
||||||
|
|
||||||
// We need to handle incoming automation first
|
// Panic on allocations if the `assert_process_allocs` feature has been enabled, and make
|
||||||
let data = &*data;
|
// sure that FTZ is set up correctly
|
||||||
let sample_rate = self
|
process_wrapper(|| {
|
||||||
.inner
|
// We need to handle incoming automation first
|
||||||
.current_buffer_config
|
let data = &*data;
|
||||||
.load()
|
let sample_rate = self
|
||||||
.map(|c| c.sample_rate);
|
.inner
|
||||||
if let Some(param_changes) = data.input_param_changes.upgrade() {
|
.current_buffer_config
|
||||||
let num_param_queues = param_changes.get_parameter_count();
|
.load()
|
||||||
for change_queue_idx in 0..num_param_queues {
|
.map(|c| c.sample_rate);
|
||||||
if let Some(param_change_queue) =
|
if let Some(param_changes) = data.input_param_changes.upgrade() {
|
||||||
param_changes.get_parameter_data(change_queue_idx).upgrade()
|
let num_param_queues = param_changes.get_parameter_count();
|
||||||
{
|
for change_queue_idx in 0..num_param_queues {
|
||||||
let param_hash = param_change_queue.get_parameter_id();
|
if let Some(param_change_queue) =
|
||||||
let num_changes = param_change_queue.get_point_count();
|
param_changes.get_parameter_data(change_queue_idx).upgrade()
|
||||||
|
|
||||||
// TODO: Handle sample accurate parameter changes, possibly in a similar way to
|
|
||||||
// the smoothing
|
|
||||||
let mut sample_offset = 0i32;
|
|
||||||
let mut value = 0.0f64;
|
|
||||||
if num_changes > 0
|
|
||||||
&& param_change_queue.get_point(
|
|
||||||
num_changes - 1,
|
|
||||||
&mut sample_offset,
|
|
||||||
&mut value,
|
|
||||||
) == kResultOk
|
|
||||||
{
|
{
|
||||||
self.inner
|
let param_hash = param_change_queue.get_parameter_id();
|
||||||
.set_normalized_value_by_hash(param_hash, value, sample_rate);
|
let num_changes = param_change_queue.get_point_count();
|
||||||
|
|
||||||
|
// TODO: Handle sample accurate parameter changes, possibly in a similar way to
|
||||||
|
// the smoothing
|
||||||
|
let mut sample_offset = 0i32;
|
||||||
|
let mut value = 0.0f64;
|
||||||
|
if num_changes > 0
|
||||||
|
&& param_change_queue.get_point(
|
||||||
|
num_changes - 1,
|
||||||
|
&mut sample_offset,
|
||||||
|
&mut value,
|
||||||
|
) == kResultOk
|
||||||
|
{
|
||||||
|
self.inner
|
||||||
|
.set_normalized_value_by_hash(param_hash, value, sample_rate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// It's possible the host only wanted to send new parameter values
|
// It's possible the host only wanted to send new parameter values
|
||||||
if data.num_outputs == 0 {
|
if data.num_outputs == 0 {
|
||||||
nih_log!("VST3 parameter flush");
|
nih_log!("VST3 parameter flush");
|
||||||
return kResultOk;
|
return kResultOk;
|
||||||
}
|
|
||||||
|
|
||||||
// The setups we suppport are:
|
|
||||||
// - 1 input bus
|
|
||||||
// - 1 output bus
|
|
||||||
// - 1 input bus and 1 output bus
|
|
||||||
nih_debug_assert!(
|
|
||||||
data.num_inputs >= 0
|
|
||||||
&& data.num_inputs <= 1
|
|
||||||
&& data.num_outputs >= 0
|
|
||||||
&& data.num_outputs <= 1,
|
|
||||||
"The host provides more than one input or output bus"
|
|
||||||
);
|
|
||||||
nih_debug_assert_eq!(
|
|
||||||
data.symbolic_sample_size,
|
|
||||||
vst3_sys::vst::SymbolicSampleSizes::kSample32 as i32
|
|
||||||
);
|
|
||||||
nih_debug_assert!(data.num_samples >= 0);
|
|
||||||
|
|
||||||
let num_output_channels = (*data.outputs).num_channels as usize;
|
|
||||||
check_null_ptr_msg!(
|
|
||||||
"Process output pointer is null",
|
|
||||||
data.outputs,
|
|
||||||
(*data.outputs).buffers,
|
|
||||||
);
|
|
||||||
|
|
||||||
// This vector has been reallocated to contain enough slices as there are output channels
|
|
||||||
let mut output_buffer = self.inner.output_buffer.write();
|
|
||||||
{
|
|
||||||
let output_slices = output_buffer.as_raw_vec();
|
|
||||||
nih_debug_assert_eq!(num_output_channels, output_slices.len());
|
|
||||||
for (output_channel_idx, output_channel_slice) in output_slices.iter_mut().enumerate() {
|
|
||||||
// SAFETY: These pointers may not be valid outside of this function even though
|
|
||||||
// their lifetime is equal to this structs. This is still safe because they are only
|
|
||||||
// dereferenced here later as part of this process function.
|
|
||||||
*output_channel_slice = std::slice::from_raw_parts_mut(
|
|
||||||
*((*data.outputs).buffers as *mut *mut f32).add(output_channel_idx),
|
|
||||||
data.num_samples as usize,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Most hosts process data in place, in which case we don't need to do any copying
|
// The setups we suppport are:
|
||||||
// ourselves. If the pointers do not alias, then we'll do the copy here and then the plugin
|
// - 1 input bus
|
||||||
// can just do normal in place processing.
|
// - 1 output bus
|
||||||
if !data.inputs.is_null() {
|
// - 1 input bus and 1 output bus
|
||||||
let num_input_channels = (*data.inputs).num_channels as usize;
|
|
||||||
nih_debug_assert!(
|
nih_debug_assert!(
|
||||||
num_input_channels <= num_output_channels,
|
data.num_inputs >= 0
|
||||||
"Stereo to mono and similar configurations are not supported"
|
&& data.num_inputs <= 1
|
||||||
|
&& data.num_outputs >= 0
|
||||||
|
&& data.num_outputs <= 1,
|
||||||
|
"The host provides more than one input or output bus"
|
||||||
);
|
);
|
||||||
for input_channel_idx in 0..cmp::min(num_input_channels, num_output_channels) {
|
nih_debug_assert_eq!(
|
||||||
let output_channel_ptr =
|
data.symbolic_sample_size,
|
||||||
*((*data.outputs).buffers as *mut *mut f32).add(input_channel_idx);
|
vst3_sys::vst::SymbolicSampleSizes::kSample32 as i32
|
||||||
let input_channel_ptr =
|
);
|
||||||
*((*data.inputs).buffers as *const *const f32).add(input_channel_idx);
|
nih_debug_assert!(data.num_samples >= 0);
|
||||||
if input_channel_ptr != output_channel_ptr {
|
|
||||||
ptr::copy_nonoverlapping(
|
let num_output_channels = (*data.outputs).num_channels as usize;
|
||||||
input_channel_ptr,
|
check_null_ptr_msg!(
|
||||||
output_channel_ptr,
|
"Process output pointer is null",
|
||||||
|
data.outputs,
|
||||||
|
(*data.outputs).buffers,
|
||||||
|
);
|
||||||
|
|
||||||
|
// This vector has been reallocated to contain enough slices as there are output
|
||||||
|
// channels
|
||||||
|
let mut output_buffer = self.inner.output_buffer.write();
|
||||||
|
{
|
||||||
|
let output_slices = output_buffer.as_raw_vec();
|
||||||
|
nih_debug_assert_eq!(num_output_channels, output_slices.len());
|
||||||
|
for (output_channel_idx, output_channel_slice) in
|
||||||
|
output_slices.iter_mut().enumerate()
|
||||||
|
{
|
||||||
|
// SAFETY: These pointers may not be valid outside of this function even though
|
||||||
|
// their lifetime is equal to this structs. This is still safe because they are
|
||||||
|
// only dereferenced here later as part of this process function.
|
||||||
|
*output_channel_slice = std::slice::from_raw_parts_mut(
|
||||||
|
*((*data.outputs).buffers as *mut *mut f32).add(output_channel_idx),
|
||||||
data.num_samples as usize,
|
data.num_samples as usize,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
match self
|
// Most hosts process data in place, in which case we don't need to do any copying
|
||||||
.inner
|
// ourselves. If the pointers do not alias, then we'll do the copy here and then the
|
||||||
.plugin
|
// plugin can just do normal in place processing.
|
||||||
.write()
|
if !data.inputs.is_null() {
|
||||||
// SAFETY: Same here
|
let num_input_channels = (*data.inputs).num_channels as usize;
|
||||||
.process(&mut output_buffer, self.inner.as_ref())
|
nih_debug_assert!(
|
||||||
{
|
num_input_channels <= num_output_channels,
|
||||||
ProcessStatus::Error(err) => {
|
"Stereo to mono and similar configurations are not supported"
|
||||||
nih_debug_assert_failure!("Process error: {}", err);
|
);
|
||||||
|
for input_channel_idx in 0..cmp::min(num_input_channels, num_output_channels) {
|
||||||
kResultFalse
|
let output_channel_ptr =
|
||||||
|
*((*data.outputs).buffers as *mut *mut f32).add(input_channel_idx);
|
||||||
|
let input_channel_ptr =
|
||||||
|
*((*data.inputs).buffers as *const *const f32).add(input_channel_idx);
|
||||||
|
if input_channel_ptr != output_channel_ptr {
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
input_channel_ptr,
|
||||||
|
output_channel_ptr,
|
||||||
|
data.num_samples as usize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => kResultOk,
|
|
||||||
}
|
match self
|
||||||
|
.inner
|
||||||
|
.plugin
|
||||||
|
.write()
|
||||||
|
// SAFETY: Same here
|
||||||
|
.process(&mut output_buffer, self.inner.as_ref())
|
||||||
|
{
|
||||||
|
ProcessStatus::Error(err) => {
|
||||||
|
nih_debug_assert_failure!("Process error: {}", err);
|
||||||
|
|
||||||
|
kResultFalse
|
||||||
|
}
|
||||||
|
_ => kResultOk,
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn get_tail_samples(&self) -> u32 {
|
unsafe fn get_tail_samples(&self) -> u32 {
|
||||||
|
|
Loading…
Reference in a new issue