Skip to main content

kstring/
interned_category.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::interned_string::InternedString;
6use core::sync::atomic::AtomicU32;
7
8/// A binary-stable, transparent representation of `fxt::InternedCategory`.
9///
10/// Under the C++ ABI, `fxt::InternedCategory` consists of:
11/// 1. A reference to `InternedString`, represented as a `&'static InternedString` (8 bytes).
12/// 2. A mutable atomic `u32` representing the category's index (4 bytes).
13/// 3. 4 bytes of padding (implied by alignment rules on 64-bit platforms).
14#[repr(C)]
15pub struct InternedCategory {
16    label: &'static InternedString,
17    index: AtomicU32,
18}
19
20// SAFETY: InternedCategory points to a static InternedString and contains an atomic index.
21// It is completely thread-safe to share and access across threads.
22unsafe impl Sync for InternedCategory {}
23unsafe impl Send for InternedCategory {}
24
25impl InternedCategory {
26    /// The index value representing an invalid or unregistered category.
27    pub const INVALID_INDEX: u32 = u32::MAX;
28
29    /// Creates a new `InternedCategory` from a reference to an `InternedString`.
30    #[inline]
31    pub const fn new(label: &'static InternedString) -> Self {
32        Self { label, index: AtomicU32::new(Self::INVALID_INDEX) }
33    }
34
35    /// Returns a reference to the underlying `InternedString`.
36    #[inline]
37    pub fn label(&self) -> &'static InternedString {
38        self.label
39    }
40
41    /// Returns the static category name as a safe C string reference.
42    #[inline]
43    pub fn string(&self) -> &'static core::ffi::CStr {
44        self.label().as_c_str()
45    }
46
47    /// Returns the index associated with the category.
48    #[inline]
49    pub fn index(&self) -> u32 {
50        self.index.load(core::sync::atomic::Ordering::Acquire)
51    }
52
53    /// Sets the index for the category if it has the expected previous value. This provides a
54    /// simple way to prevent re-initialization if RegisterCategories() is called more than once,
55    /// while also providing a way to override the value. This can be removed once the index can be
56    /// automatically derived from the section offset when the kernel supports extensible
57    /// categories.
58    #[inline]
59    pub fn set_index(&self, index: u32, expected: u32) {
60        let _ = self.index.compare_exchange(
61            expected,
62            index,
63            core::sync::atomic::Ordering::AcqRel,
64            core::sync::atomic::Ordering::Acquire,
65        );
66    }
67}
68
69/// Statically declares a new `InternedCategory`.
70///
71/// By default, this macro allocates the category inside the special
72/// `__fxt_interned_category_table` linker section.
73///
74/// If the `extern` parameter is provided, the macro instead references an external
75/// symbol (e.g. C++ template-allocated) with the C++ mangled name for the category,
76/// preventing duplicate physical allocation in the linker section.
77///
78/// # Examples
79///
80/// Local allocation:
81/// ```rust
82/// declare_interned_category!(MY_CATEGORY, "kernel:sched");
83/// ```
84///
85/// External reference (references C++ symbol, avoids physical duplicates):
86/// ```rust
87/// declare_interned_category!(MY_CATEGORY, "kernel:meta", extern);
88/// ```
89#[macro_export]
90macro_rules! declare_interned_category {
91    ($var_name:ident, $str_lit:literal) => {
92        #[allow(non_snake_case)]
93        mod $var_name {
94            $crate::declare_interned_string!(STRING, $str_lit);
95
96            #[$crate::interned_category_export_name($str_lit)]
97            #[unsafe(link_section = "__fxt_interned_category_table")]
98            #[used]
99            pub static CATEGORY: $crate::interned_category::InternedCategory =
100                $crate::interned_category::InternedCategory::new(&STRING);
101        }
102
103        pub static $var_name: &$crate::interned_category::InternedCategory = &$var_name::CATEGORY;
104    };
105
106    ($var_name:ident, $str_lit:literal, extern) => {
107        #[allow(non_snake_case)]
108        mod $var_name {
109            $crate::import_category!(CATEGORY, $str_lit);
110        }
111
112        pub static $var_name: &$crate::interned_category::InternedCategory =
113            unsafe { &$var_name::CATEGORY };
114    };
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    declare_interned_category!(TEST_CAT_1, "foo");
122    declare_interned_category!(TEST_CAT_2, "bar");
123
124    #[test]
125    fn test_label_and_string() {
126        assert_eq!(TEST_CAT_1.string(), c"foo");
127        assert_eq!(TEST_CAT_2.string(), c"bar");
128        assert_eq!(TEST_CAT_1.label().as_c_str(), c"foo");
129    }
130
131    #[test]
132    fn test_index_management() {
133        assert_eq!(TEST_CAT_1.index(), InternedCategory::INVALID_INDEX);
134
135        // Set index from INVALID_INDEX to 42.
136        TEST_CAT_1.set_index(42, InternedCategory::INVALID_INDEX);
137        assert_eq!(TEST_CAT_1.index(), 42);
138
139        // Trying to set it again with a wrong expected value should fail/do nothing.
140        TEST_CAT_1.set_index(100, 0);
141        assert_eq!(TEST_CAT_1.index(), 42);
142    }
143
144    // This linker section bound test is only valid on ELF targets (Fuchsia/Linux)
145    // where the linker generates __start and __stop symbols for orphan sections.
146    #[cfg(any(target_os = "fuchsia", target_os = "linux"))]
147    #[test]
148    fn test_linker_section_allocation() {
149        unsafe extern "C" {
150            #[link_name = "__start___fxt_interned_category_table"]
151            static START: InternedCategory;
152            #[link_name = "__stop___fxt_interned_category_table"]
153            static STOP: InternedCategory;
154        }
155
156        let start_ptr = unsafe { &START as *const InternedCategory };
157        let stop_ptr = unsafe { &STOP as *const InternedCategory };
158
159        // Ensure the boundary is valid and holds our entries
160        assert!(start_ptr <= stop_ptr);
161        let diff =
162            (stop_ptr as usize - start_ptr as usize) / core::mem::size_of::<InternedCategory>();
163        assert!(diff >= 2, "Expected at least 2 entries in the table, found {diff}");
164
165        // Verify our static variables reside strictly within the linker bounds
166        let p1 = TEST_CAT_1 as *const InternedCategory;
167        let p2 = TEST_CAT_2 as *const InternedCategory;
168
169        assert!(
170            p1 >= start_ptr && p1 < stop_ptr,
171            "TEST_CAT_1 pointer {p1:p} is outside bounds [{start_ptr:p}, {stop_ptr:p})"
172        );
173        assert!(
174            p2 >= start_ptr && p2 < stop_ptr,
175            "TEST_CAT_2 pointer {p2:p} is outside bounds [{start_ptr:p}, {stop_ptr:p})"
176        );
177    }
178
179    #[test]
180    fn test_symbol_name_matching() {
181        // "foo" should mangle to the exact C++ symbol name:
182        // _ZN3fxt8internal23InternedCategoryStorageIJLc102ELc111ELc111EEE17interned_categoryE
183        unsafe extern "C" {
184            #[link_name = "_ZN3fxt8internal23InternedCategoryStorageIJLc102ELc111ELc111EEE17interned_categoryE"]
185            static EXPECTED_SYMBOL: InternedCategory;
186        }
187
188        let p_expected = unsafe { &EXPECTED_SYMBOL as *const InternedCategory };
189        let p_actual = TEST_CAT_1 as *const InternedCategory;
190        assert_eq!(p_actual, p_expected);
191    }
192}