static_key/
static_match.rs

1use std::cell::Cell;
2
3use crate::patch::TextGuard;
4
5/// An object that is rarely modified but frequently matched on.
6///
7/// This type can only be constructed using [`static_key!`] macro.
8///
9/// [`static_key!`]: `crate::static_key!`
10pub struct StaticKey<T: 'static> {
11    /// Call/use sites that reference this key.
12    callsites: Cell<Option<&'static CallSite<T>>>,
13    value: Cell<T>,
14}
15
16unsafe impl<T: Send> Send for StaticKey<T> {}
17unsafe impl<T: Send> Sync for StaticKey<T> {}
18
19impl<T> core::fmt::Debug for StaticKey<T> {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        f.debug_struct("StaticKey").finish()
22    }
23}
24
25/// Individual call sites of a specific static key.
26#[doc(hidden)]
27#[repr(C)]
28pub struct CallSite<T: 'static> {
29    key: &'static StaticKey<T>,
30    /// Address of the callsite.
31    address: *mut u8,
32    matcher: fn(&T) -> usize,
33    next: Cell<Option<&'static CallSite<T>>>,
34    labels: [u32; 0],
35}
36
37unsafe impl<T: Send> Send for CallSite<T> {}
38unsafe impl<T: Send> Sync for CallSite<T> {}
39
40impl<T> CallSite<T> {
41    #[cfg(target_arch = "x86_64")]
42    /// Update the callsite
43    fn update(&self, text: &mut TextGuard, value: &T) {
44        let callee = (self.matcher)(&value);
45
46        let mut patch;
47        if callee == usize::MAX {
48            // 5-byte nop.
49            patch = [0x0f, 0x1f, 0x44, 0x00, 0x00];
50        } else {
51            let offset = unsafe { *self.labels.as_ptr().add(callee) };
52            patch = [0xe9, 0x00, 0x00, 0x00, 0x00];
53            patch[1..].copy_from_slice(&offset.to_le_bytes());
54        }
55
56        unsafe {
57            crate::patch::replace_instruction(text, self.address, &patch);
58        }
59    }
60
61    #[cfg(target_arch = "riscv64")]
62    /// Update the callsite
63    fn update(&self, text: &mut TextGuard, value: &T) {
64        let callee = (self.matcher)(&value);
65
66        let patch;
67        if callee == usize::MAX {
68            // uncompressed nop.
69            patch = 0x00000013;
70        } else {
71            let offset = unsafe { *self.labels.as_ptr().add(callee) };
72            patch = (offset >> 20 & 1) << 31
73                | (offset >> 1 & 0x3FF) << 21
74                | (offset >> 11 & 1) << 20
75                | (offset >> 12 & 0xFF) << 12
76                | 0x6F;
77        }
78
79        unsafe {
80            crate::patch::replace_instruction(text, self.address, &patch.to_ne_bytes());
81        }
82    }
83
84    /// Register a callsite. Must be called before potential execution of the binary.
85    pub unsafe extern "C" fn register(&'static self) {
86        let mut text = crate::patch::lock_text();
87
88        #[cfg(target_arch = "x86_64")]
89        let insn_len = 5;
90
91        #[cfg(target_arch = "riscv64")]
92        let insn_len = 4;
93
94        // Register the address to be patched to be eligible for modification.
95        unsafe { crate::patch::register_code(&mut text, self.address, insn_len) };
96
97        // Insert the callsite into the key linked list.
98        self.next.set(self.key.callsites.get());
99        self.key.callsites.set(Some(self));
100        self.update(&mut text, unsafe { &*self.key.value.as_ptr() });
101
102        // Registration happens at binary loading time and normal execution hasn't started.
103        // So syncing is not necessary here.
104        unsafe { text.skip_sync() };
105    }
106}
107
108impl<T> StaticKey<T> {
109    #[doc(hidden)]
110    #[inline]
111    pub const unsafe fn new(state: T) -> Self {
112        Self {
113            value: Cell::new(state),
114            callsites: Cell::new(None),
115        }
116    }
117
118    /// Acquire a reference to the value of this key.
119    ///
120    /// # Performance
121    ///
122    /// `StaticKey`s are not intended to be used like cell/locks, so this is not an especially
123    /// optimised accessor. This method should be rarely used.
124    pub fn with<R>(&self, callback: impl FnOnce(&T) -> R) -> R {
125        let _text = crate::patch::lock_text();
126        callback(unsafe { &*self.value.as_ptr() })
127    }
128
129    /// Returns a copy of the value of this key.
130    ///
131    /// # Performance
132    ///
133    /// `StaticKey`s are not intended to be used like cell/locks, so this is not an especially
134    /// optimised accessor. This method should be rarely used.
135    pub fn get(&self) -> T
136    where
137        T: Copy,
138    {
139        self.with(|x| *x)
140    }
141
142    /// Sets the value of this key.
143    ///
144    /// # Performance
145    ///
146    /// `StaticKey`s are not intended to be frequently updated, so this is slow in performance.
147    /// This method should be rarely used.
148    pub fn set(&self, value: T) {
149        let mut text = crate::patch::lock_text();
150        let mut callsite = self.callsites.get();
151        while let Some(site) = callsite {
152            site.update(&mut text, &value);
153            callsite = site.next.get();
154        }
155        self.value.set(value);
156    }
157}
158
159/// Declares a new static key.
160///
161/// # Syntax
162///
163/// ```
164/// # use static_key::*;
165/// static_key!(pub FOO: u32 = 1);
166/// static_key!(BAR: bool = false);
167/// ```
168///
169/// The key must be `Send`:
170/// ```compile_fail
171/// # use static_key::*;
172/// static_key!(BAZ: *const () = core::ptr::null()); // ERROR
173/// ```
174///
175/// and the initializer must be const:
176/// ```compile_fail
177/// # use static_key::*;
178/// fn baz() -> u32 { 1 }
179/// static_key!(BAZ: u32 = baz()); // ERROR
180/// ```
181#[macro_export]
182macro_rules! static_key {
183    ($vis:vis $name: ident: $ty:ty = $init_value:expr) => {
184        $vis static $name: $crate::StaticKey<$ty> = {
185            let value: $ty = $init_value;
186            unsafe { $crate::StaticKey::new(value) }
187        };
188    };
189}
190
191#[doc(hidden)]
192#[macro_export]
193#[cfg(target_arch = "x86_64")]
194macro_rules! with_arch {
195    ($callback: ident, $($tt:tt)*) => {
196        $crate::$callback!("x86_64", $($tt)*)
197    };
198}
199
200#[doc(hidden)]
201#[macro_export]
202#[cfg(target_arch = "riscv64")]
203macro_rules! with_arch {
204    ($callback: path, $($tt:tt)*) => {
205        $crate::$callback!("riscv64", $($tt)*)
206    };
207}
208
209/// Match on a static key.
210///
211/// # Syntax
212///
213/// This macro uses a syntax similar to `match`:
214/// ```
215/// # #![feature(asm_goto)]
216/// # use static_key::*;
217/// static_key!(FOO: u32 = 1);
218///
219/// let value = static_match! {
220///   // First declare what's the key and its type
221///   FOO: u32;
222///   // Then match arm follows. Note that the type being matched is a reference type.
223///   1 => 1,
224///   x if *x == 2 => {
225///     println!("Guard is supported");
226///     2
227///   }
228///   // You can put `#[likely]` on an arm to optimise for common case.
229///   #[likely]
230///   0 => 0,
231///   _ => unreachable!(),
232/// };
233/// ```
234///
235/// The entire `static_match!` will be compiled to either a single instruction, either
236/// a no-op or a single jump.
237///
238/// Note that the match arm may bind variables for the guard only; bindings are not visible
239/// inside the match arm body.
240#[macro_export]
241macro_rules! static_match {
242    ($($tt:tt)*) => {
243        $crate::with_arch!(parse_static_match, $crate; $($tt)*);
244    };
245}