nice_plug/wrapper/
util.rs1use backtrace::Backtrace;
2use nice_plug_core::plugin::Plugin;
3use std::cmp;
4use std::marker::PhantomData;
5use std::os::raw::c_char;
6use std::sync::atomic::AtomicBool;
7
8use crate::util::permit_alloc;
9
10pub(crate) mod buffer_management;
11#[cfg(debug_assertions)]
12pub(crate) mod context_checks;
13
14#[cfg(all(not(miri), target_feature = "sse", feature = "unsafe_flush_denormals"))]
21const SSE_FTZ_BIT: u32 = 1 << 15;
22
23#[cfg(all(not(miri), target_arch = "aarch64", feature = "unsafe_flush_denormals"))]
28const AARCH64_FTZ_BIT: u64 = 1 << 24;
29
30#[cfg(all(
31 debug_assertions,
32 feature = "assert_process_allocs",
33 all(windows, target_env = "gnu")
34))]
35compile_error!(
36 "The 'assert_process_allocs' feature does not work correctly in combination with the 'x86_64-pc-windows-gnu' target, see https://github.com/Windfisch/rust-assert-no-alloc/issues/7"
37);
38
39#[cfg(all(debug_assertions, feature = "assert_process_allocs"))]
40#[global_allocator]
41static A: nice_assert_no_alloc::AllocDisabler = nice_assert_no_alloc::AllocDisabler;
42
43pub fn hash_param_id(id: &str) -> u32 {
45 let mut hash: u32 = 0;
46 for char in id.bytes() {
47 hash = hash.wrapping_mul(31).wrapping_add(char as u32);
48 }
49
50 hash &= !(1 << 31);
53
54 hash
55}
56
57pub fn strlcpy(dest: &mut [c_char], src: &str) {
61 if dest.is_empty() {
62 return;
63 }
64
65 let src_bytes: &[u8] = src.as_bytes();
66 let src_bytes_signed: &[c_char] = unsafe { &*(src_bytes as *const [u8] as *const [c_char]) };
69
70 let copy_len = cmp::min(dest.len() - 1, src.len());
72 dest[..copy_len].copy_from_slice(&src_bytes_signed[..copy_len]);
73 dest[copy_len] = 0;
74}
75
76#[inline]
79pub fn clamp_input_event_timing(timing: u32, total_buffer_len: u32) -> u32 {
80 let last_valid_index = total_buffer_len.saturating_sub(1);
82
83 crate::nice_debug_assert!(
84 timing <= last_valid_index,
85 "Input event is out of bounds, will be clamped to the buffer's size"
86 );
87
88 timing.min(last_valid_index)
89}
90
91#[inline]
94pub fn clamp_output_event_timing(timing: u32, total_buffer_len: u32) -> u32 {
95 let last_valid_index = total_buffer_len.saturating_sub(1);
96
97 crate::nice_debug_assert!(
98 timing <= last_valid_index,
99 "Output event is out of bounds, will be clamped to the buffer's size"
100 );
101
102 timing.min(last_valid_index)
103}
104
105pub fn setup_logger<P: Plugin>() {
118 static DID_SETUP: AtomicBool = AtomicBool::new(false);
119
120 let did_setup = DID_SETUP.swap(true, std::sync::atomic::Ordering::SeqCst);
121 if !did_setup {
122 if let Some(is_ok) = P::setup_logger() {
123 if is_ok {
124 log_panics();
125 }
126
127 return;
128 }
129
130 #[cfg(feature = "tracing-subscriber")]
131 {
132 #[cfg(debug_assertions)]
133 let sub = tracing_subscriber::FmtSubscriber::builder()
134 .with_max_level(tracing::level_filters::LevelFilter::DEBUG)
135 .with_target(true)
136 .with_ansi(false)
137 .with_writer(nice_log::writer_from_env())
138 .finish();
139
140 #[cfg(not(debug_assertions))]
141 let sub = tracing_subscriber::FmtSubscriber::builder()
142 .with_max_level(tracing::level_filters::LevelFilter::INFO)
143 .with_target(false)
144 .without_time()
145 .with_ansi(false)
146 .with_writer(nice_log::writer_from_env())
147 .finish();
148
149 if tracing::subscriber::set_global_default(sub).is_ok() {
150 log_panics();
151 }
152 }
153 }
154}
155
156fn log_panics() {
159 std::panic::set_hook(Box::new(|info| {
160 permit_alloc(|| {
161 let backtrace = Backtrace::new();
164
165 let thread = std::thread::current();
166 let thread = thread.name().unwrap_or("unnamed");
167
168 let msg = match info.payload().downcast_ref::<&'static str>() {
169 Some(s) => *s,
170 None => match info.payload().downcast_ref::<String>() {
171 Some(s) => &**s,
172 None => "Box<Any>",
173 },
174 };
175
176 match info.location() {
177 Some(location) => {
178 crate::nice_error!(
179 target: "panic", "thread '{}' panicked at '{}': {}:{}\n{:?}",
180 thread,
181 msg,
182 location.file(),
183 location.line(),
184 backtrace
185 );
186 }
187 None => {
188 crate::nice_error!(
189 target: "panic",
190 "thread '{}' panicked at '{}'\n{:?}",
191 thread,
192 msg,
193 backtrace
194 )
195 }
196 }
197 })
198 }));
199}
200
201pub fn process_wrapper<T, F: FnOnce() -> T>(f: F) -> T {
205 let _ftz_guard = ScopedFtz::enable();
207
208 #[cfg(all(debug_assertions, feature = "assert_process_allocs"))]
209 return nice_assert_no_alloc::assert_no_alloc(f);
210
211 #[cfg(not(all(debug_assertions, feature = "assert_process_allocs")))]
212 return f();
213}
214
215struct ScopedFtz {
218 #[allow(unused)]
222 should_disable_again: bool,
223 _send_sync_marker: PhantomData<*const ()>,
227}
228
229impl ScopedFtz {
230 fn enable() -> Self {
231 #[cfg(all(not(miri), feature = "unsafe_flush_denormals"))]
232 {
233 #[cfg(target_feature = "sse")]
234 {
235 let mut mxcsr: u32 = 0;
241 unsafe { std::arch::asm!("stmxcsr [{}]", in(reg) &mut mxcsr) };
242 let should_disable_again = mxcsr & SSE_FTZ_BIT == 0;
243 if should_disable_again {
244 unsafe { std::arch::asm!("ldmxcsr [{}]", in(reg) &(mxcsr | SSE_FTZ_BIT)) };
245 }
246
247 return Self {
248 should_disable_again,
249 _send_sync_marker: PhantomData,
250 };
251 }
252
253 #[cfg(target_arch = "aarch64")]
254 {
255 let mut fpcr: u64;
259 unsafe { std::arch::asm!("mrs {}, fpcr", out(reg) fpcr) };
260
261 let should_disable_again = fpcr & AARCH64_FTZ_BIT == 0;
262 if should_disable_again {
263 unsafe { std::arch::asm!("msr fpcr, {}", in(reg) fpcr | AARCH64_FTZ_BIT) };
264 }
265
266 return Self {
267 should_disable_again,
268 _send_sync_marker: PhantomData,
269 };
270 }
271 }
272
273 #[allow(unreachable_code)]
276 Self {
277 should_disable_again: false,
278 _send_sync_marker: PhantomData,
279 }
280 }
281}
282
283impl Drop for ScopedFtz {
284 fn drop(&mut self) {
285 #[cfg(all(not(miri), feature = "unsafe_flush_denormals"))]
286 if self.should_disable_again {
287 #[cfg(target_feature = "sse")]
288 {
289 let mut mxcsr: u32 = 0;
290 unsafe { std::arch::asm!("stmxcsr [{}]", in(reg) &mut mxcsr) };
291 unsafe { std::arch::asm!("ldmxcsr [{}]", in(reg) &(mxcsr & !SSE_FTZ_BIT)) };
292 }
293
294 #[cfg(target_arch = "aarch64")]
295 {
296 let mut fpcr: u64;
297 unsafe { std::arch::asm!("mrs {}, fpcr", out(reg) fpcr) };
298 unsafe { std::arch::asm!("msr fpcr, {}", in(reg) fpcr & !AARCH64_FTZ_BIT) };
299 }
300 }
301 }
302}
303
304#[cfg(test)]
305mod miri {
306 use std::ffi::CStr;
307
308 use super::*;
309
310 #[test]
311 fn strlcpy_normal() {
312 let mut dest = [0; 256];
313 strlcpy(&mut dest, "Hello, world!");
314
315 assert_eq!(
316 unsafe { CStr::from_ptr(dest.as_ptr()) }.to_str(),
317 Ok("Hello, world!")
318 );
319 }
320
321 #[test]
322 fn strlcpy_overflow() {
323 let mut dest = [0; 6];
324 strlcpy(&mut dest, "Hello, world!");
325
326 assert_eq!(
327 unsafe { CStr::from_ptr(dest.as_ptr()) }.to_str(),
328 Ok("Hello")
329 );
330 }
331}