nice_plug/wrapper/
util.rs1use backtrace::Backtrace;
2use std::cmp;
3use std::marker::PhantomData;
4use std::os::raw::c_char;
5
6use crate::util::permit_alloc;
7
8pub(crate) mod buffer_management;
9#[cfg(debug_assertions)]
10pub(crate) mod context_checks;
11
12#[cfg(all(not(miri), target_feature = "sse", feature = "unsafe_flush_denormals"))]
19const SSE_FTZ_BIT: u32 = 1 << 15;
20
21#[cfg(all(not(miri), target_arch = "aarch64", feature = "unsafe_flush_denormals"))]
26const AARCH64_FTZ_BIT: u64 = 1 << 24;
27
28#[cfg(all(
29 debug_assertions,
30 feature = "assert_process_allocs",
31 all(windows, target_env = "gnu")
32))]
33compile_error!(
34 "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"
35);
36
37#[cfg(all(debug_assertions, feature = "assert_process_allocs"))]
38#[global_allocator]
39static A: nice_assert_no_alloc::AllocDisabler = nice_assert_no_alloc::AllocDisabler;
40
41pub fn hash_param_id(id: &str) -> u32 {
43 let mut hash: u32 = 0;
44 for char in id.bytes() {
45 hash = hash.wrapping_mul(31).wrapping_add(char as u32);
46 }
47
48 hash &= !(1 << 31);
51
52 hash
53}
54
55pub fn strlcpy(dest: &mut [c_char], src: &str) {
59 if dest.is_empty() {
60 return;
61 }
62
63 let src_bytes: &[u8] = src.as_bytes();
64 let src_bytes_signed: &[c_char] = unsafe { &*(src_bytes as *const [u8] as *const [c_char]) };
67
68 let copy_len = cmp::min(dest.len() - 1, src.len());
70 dest[..copy_len].copy_from_slice(&src_bytes_signed[..copy_len]);
71 dest[copy_len] = 0;
72}
73
74#[inline]
77pub fn clamp_input_event_timing(timing: u32, total_buffer_len: u32) -> u32 {
78 let last_valid_index = total_buffer_len.saturating_sub(1);
80
81 crate::nice_debug_assert!(
82 timing <= last_valid_index,
83 "Input event is out of bounds, will be clamped to the buffer's size"
84 );
85
86 timing.min(last_valid_index)
87}
88
89#[inline]
92pub fn clamp_output_event_timing(timing: u32, total_buffer_len: u32) -> u32 {
93 let last_valid_index = total_buffer_len.saturating_sub(1);
94
95 crate::nice_debug_assert!(
96 timing <= last_valid_index,
97 "Output event is out of bounds, will be clamped to the buffer's size"
98 );
99
100 timing.min(last_valid_index)
101}
102
103pub fn setup_logger() {
116 let log_level = if cfg!(debug_assertions) {
117 log::LevelFilter::Trace
118 } else {
119 log::LevelFilter::Info
120 };
121
122 let logger_builder = nice_log::LoggerBuilder::new(log_level)
123 .filter_module("cosmic_text::buffer")
124 .filter_module("cosmic_text::shape")
125 .filter_module("selectors::matching");
126
127 #[cfg(debug_assertions)]
130 let logger_builder = logger_builder.always_show_module_path();
131
132 #[cfg(not(debug_assertions))]
135 let logger_builder = logger_builder.filter_module("cosmic_text::font::system::std");
136
137 let logger_set = logger_builder.build_global().is_ok();
138 if logger_set {
139 log_panics();
140 }
141}
142
143fn log_panics() {
146 std::panic::set_hook(Box::new(|info| {
147 permit_alloc(|| {
148 let backtrace = Backtrace::new();
151
152 let thread = std::thread::current();
153 let thread = thread.name().unwrap_or("unnamed");
154
155 let msg = match info.payload().downcast_ref::<&'static str>() {
156 Some(s) => *s,
157 None => match info.payload().downcast_ref::<String>() {
158 Some(s) => &**s,
159 None => "Box<Any>",
160 },
161 };
162
163 match info.location() {
164 Some(location) => {
165 crate::nice_error!(
166 target: "panic", "thread '{}' panicked at '{}': {}:{}\n{:?}",
167 thread,
168 msg,
169 location.file(),
170 location.line(),
171 backtrace
172 );
173 }
174 None => {
175 crate::nice_error!(
176 target: "panic",
177 "thread '{}' panicked at '{}'\n{:?}",
178 thread,
179 msg,
180 backtrace
181 )
182 }
183 }
184 })
185 }));
186}
187
188pub fn process_wrapper<T, F: FnOnce() -> T>(f: F) -> T {
192 let _ftz_guard = ScopedFtz::enable();
194
195 #[cfg(all(debug_assertions, feature = "assert_process_allocs"))]
196 return nice_assert_no_alloc::assert_no_alloc(f);
197
198 #[cfg(not(all(debug_assertions, feature = "assert_process_allocs")))]
199 return f();
200}
201
202struct ScopedFtz {
205 #[allow(unused)]
209 should_disable_again: bool,
210 _send_sync_marker: PhantomData<*const ()>,
214}
215
216impl ScopedFtz {
217 fn enable() -> Self {
218 #[cfg(all(not(miri), feature = "unsafe_flush_denormals"))]
219 {
220 #[cfg(target_feature = "sse")]
221 {
222 let mut mxcsr: u32 = 0;
228 unsafe { std::arch::asm!("stmxcsr [{}]", in(reg) &mut mxcsr) };
229 let should_disable_again = mxcsr & SSE_FTZ_BIT == 0;
230 if should_disable_again {
231 unsafe { std::arch::asm!("ldmxcsr [{}]", in(reg) &(mxcsr | SSE_FTZ_BIT)) };
232 }
233
234 return Self {
235 should_disable_again,
236 _send_sync_marker: PhantomData,
237 };
238 }
239
240 #[cfg(target_arch = "aarch64")]
241 {
242 let mut fpcr: u64;
246 unsafe { std::arch::asm!("mrs {}, fpcr", out(reg) fpcr) };
247
248 let should_disable_again = fpcr & AARCH64_FTZ_BIT == 0;
249 if should_disable_again {
250 unsafe { std::arch::asm!("msr fpcr, {}", in(reg) fpcr | AARCH64_FTZ_BIT) };
251 }
252
253 return Self {
254 should_disable_again,
255 _send_sync_marker: PhantomData,
256 };
257 }
258 }
259
260 #[allow(unreachable_code)]
263 Self {
264 should_disable_again: false,
265 _send_sync_marker: PhantomData,
266 }
267 }
268}
269
270impl Drop for ScopedFtz {
271 fn drop(&mut self) {
272 #[cfg(all(not(miri), feature = "unsafe_flush_denormals"))]
273 if self.should_disable_again {
274 #[cfg(target_feature = "sse")]
275 {
276 let mut mxcsr: u32 = 0;
277 unsafe { std::arch::asm!("stmxcsr [{}]", in(reg) &mut mxcsr) };
278 unsafe { std::arch::asm!("ldmxcsr [{}]", in(reg) &(mxcsr & !SSE_FTZ_BIT)) };
279 }
280
281 #[cfg(target_arch = "aarch64")]
282 {
283 let mut fpcr: u64;
284 unsafe { std::arch::asm!("mrs {}, fpcr", out(reg) fpcr) };
285 unsafe { std::arch::asm!("msr fpcr, {}", in(reg) fpcr & !AARCH64_FTZ_BIT) };
286 }
287 }
288 }
289}
290
291#[cfg(test)]
292mod miri {
293 use std::ffi::CStr;
294
295 use super::*;
296
297 #[test]
298 fn strlcpy_normal() {
299 let mut dest = [0; 256];
300 strlcpy(&mut dest, "Hello, world!");
301
302 assert_eq!(
303 unsafe { CStr::from_ptr(dest.as_ptr()) }.to_str(),
304 Ok("Hello, world!")
305 );
306 }
307
308 #[test]
309 fn strlcpy_overflow() {
310 let mut dest = [0; 6];
311 strlcpy(&mut dest, "Hello, world!");
312
313 assert_eq!(
314 unsafe { CStr::from_ptr(dest.as_ptr()) }.to_str(),
315 Ok("Hello")
316 );
317 }
318}