oxc_allocator/address.rs
1// All methods are 1 instruction or less
2#![expect(clippy::inline_always)]
3
4use std::ptr::NonNull;
5
6use crate::Box;
7
8/// Memory address of an AST node in arena.
9//
10// At present, this is a `usize`, but it could be a `NonZeroUsize` instead, so that `Address` gains a niche,
11// which would reduce the size of `Option<Address>` from 16 bytes to 8 bytes.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[repr(transparent)]
14pub struct Address(usize);
15
16impl Address {
17 /// Dummy address.
18 ///
19 /// Never equal to any real `Address`, but is equal to itself.
20 pub const DUMMY: Self = Self(0);
21
22 /// Get the memory address of a pointer to an AST node in arena.
23 ///
24 /// **This method is an escape hatch only.**
25 /// Prefer using [`GetAddress::address`] or [`UnstableAddress::unstable_address`] instead,
26 /// because they are more likely to produce a stable [`Address`].
27 /// (Yes even `unstable_address` is more likely to produce a stable `Address` than this function!)
28 ///
29 /// If the AST node is in a [`Box`], the address is guaranteed to be a unique identifier
30 /// for the duration of the arena's existence.
31 ///
32 /// But if the node is in a [`Vec`], then the `Address` may not remain accurate if the `Vec`
33 /// is resized or has elements added or removed before this node.
34 ///
35 /// The pointer must point to an AST node in the arena (not on the stack),
36 /// or the returned `Address` will be meaningless.
37 ///
38 /// If called with a reference, the reference must point to an AST node in the arena (not on the stack),
39 /// or the returned `Address` will be meaningless. Be careful not to pass a double-reference to `from_ptr`,
40 /// or the resulting `Address` will point to the reference itself, instead of the thing being referenced.
41 ///
42 /// ```ignore
43 /// impl<'a> Visit<'a> for MyVisitor {
44 /// fn visit_identifier_reference(&mut self, ident: &IdentifierReference<'a>) {
45 /// // Correct - `address` is address of the `IdentifierReference`
46 /// let address = Address::from_ptr(ident);
47 /// // WRONG - `address` is address of `&IdentifierReference` reference itself, which is on the stack
48 /// let address = Address::from_ptr(&ident);
49 /// }
50 /// }
51 /// ```
52 ///
53 /// # SAFETY
54 ///
55 /// Pointer must be non-null.
56 ///
57 /// # Example
58 ///
59 /// Demonstration of the difference between `Address::from_ptr` and `GetAddress::address`:
60 ///
61 /// ```ignore
62 /// use oxc_allocator::{Address, GetAddress, Vec};
63 /// use oxc_span::SPAN;
64 ///
65 /// // Create a `Vec<Statement>` containing a single `BlockStatement`
66 /// let mut stmts = Vec::with_capacity_in(1, &allocator);
67 /// stmts.push(ast_builder.statement_block(SPAN, Vec::new_in(&allocator)));
68 ///
69 /// let block_address = stmts[0].address();
70 /// let stmt_address = Address::from_ptr(&stmts[0]);
71 ///
72 /// // Add another `Statement` to the `Vec`.
73 /// // This causes the `Vec` to grow and reallocate.
74 /// stmts.push(ast_builder.statement_empty(SPAN));
75 ///
76 /// let block_address_after_push = stmts[0].address();
77 /// let stmt_address_after_push = Address::from_ptr(&stmts[0]);
78 ///
79 /// // Address of the `BlockStatement` is unchanged
80 /// // (because the `Box`'s pointer still points to same memory location)
81 /// assert!(block_address_after_push == block_address);
82 /// // Address of the `Statement` has changed
83 /// // (because the `Vec` reallocated, so its contents have moved in memory)
84 /// assert!(stmt_address_after_push != stmt_address);
85 ///
86 /// // Insert a new `Statement` at start of the `Vec`.
87 /// // The `BlockStatement` is now at index 1.
88 /// stmts.insert(0, ast_builder.statement_empty(SPAN));
89 ///
90 /// let block_address_after_insert = stmts[1].address();
91 /// let stmt_address_after_insert = Address::from_ptr(&stmts[1]);
92 ///
93 /// // Address of the `BlockStatement` is still unchanged
94 /// assert!(block_address_after_insert == block_address_after_push);
95 /// // Address of the `Statement` has changed again
96 /// assert!(stmt_address_after_insert != stmt_address_after_push);
97 /// ```
98 ///
99 /// [`Box`]: crate::Box
100 /// [`Vec`]: crate::Vec
101 #[inline(always)] // Because it's a no-op
102 pub unsafe fn from_ptr<T>(p: *const T) -> Self {
103 Self(p as usize)
104 }
105}
106
107/// Trait for getting the memory address of an AST node.
108pub trait GetAddress {
109 /// Get the memory address of a value allocated in the arena.
110 fn address(&self) -> Address;
111}
112
113impl<T> GetAddress for Box<'_, T> {
114 /// Get the memory address of a value allocated in the arena.
115 ///
116 /// AST nodes in a `Box` in an arena are guaranteed to never move in memory,
117 /// so this address acts as a unique identifier for the duration of the arena's existence.
118 #[inline(always)] // Because it's only 1 instruction
119 fn address(&self) -> Address {
120 let ptr = Box::as_non_null(self);
121 Address(ptr.addr().get())
122 }
123}
124
125impl GetAddress for Address {
126 /// Address of an `Address` is itself.
127 #[inline(always)] // Because it's a no-op
128 fn address(&self) -> Address {
129 *self
130 }
131}
132
133/// Trait for getting the memory address of an AST node which is not necessarily stable.
134///
135/// See [`UnstableAddress::unstable_address`] for more details.
136///
137/// This trait is implemented for all AST struct types.
138///
139/// *DO NOT* implement this trait on any other type.
140pub trait UnstableAddress {
141 /// Get the memory [`Address`] of a reference to an AST node in arena, which is not necessarily stable.
142 ///
143 /// # Stable addresses
144 ///
145 /// It's ideal to obtain an `Address` for an AST node which is guaranteed to be stable for the life of the AST.
146 ///
147 /// Then you can reliably compare two `Address`es to determine if they refer to the same node,
148 /// without any risk of the result being wrong because one or other of the nodes has moved in memory.
149 ///
150 /// Types which have a stable address:
151 /// * [`Box<T>`]
152 /// * AST enums where all variants are `Box`es (e.g. `Statement`, `Expression`)
153 ///
154 /// Some other types guarantee stability for a shorter period of time:
155 /// * `oxc_ast::AstKind` - guaranteed stable address while `Semantic` is alive.
156 /// * `oxc_traverse::Ancestor` - guaranteed stable address while traversing descendents of the ancestor.
157 ///
158 /// The above types all implement [`GetAddress::address`]. If you have access to one of these types,
159 /// it's better to use `GetAddress::address` instead of this method.
160 ///
161 /// # Why this method exists
162 ///
163 /// Sometimes you only have access to a reference to an AST node, but you know from context that the node
164 /// will not move in memory during the time you need the `Address` to remain stable.
165 ///
166 /// You can use this method in such cases, but you need to be careful. For correct behavior, you must ensure
167 /// yourself that the node will not move in memory during the time you need the `Address` to remain accurate.
168 ///
169 /// When using this method, it is recommended to make a comment at the call site, explaining how you can prove
170 /// that the `Address` will remain accurate (the AST node will not move) for the time period that you need it to be.
171 ///
172 /// # When a type does not move in memory
173 ///
174 /// If the AST is immutable (e.g. in `Visit` trait), then any node in the AST is statically positioned in memory.
175 /// Therefore, in the linter, any reference to an AST node is guaranteed to have a stable `Address`.
176 ///
177 /// If an AST node is in `Vec`, then it'll remain at same memory address, but only as long as the `Vec` does
178 /// not reallocate (e.g. by `Vec::push`, `Vec::extend`).
179 ///
180 /// This method will return a stable `Address` for any AST node in such circumstances.
181 ///
182 /// # Common pitfalls
183 ///
184 /// ## References to AST nodes on the stack
185 ///
186 /// Ensure that the reference passed to this method is to a node which is in the arena, *not* on the stack.
187 ///
188 /// ```ignore
189 /// let binary_expr: BinaryExpression<'a> = get_owned_binary_expression_somehow();
190 /// // WRONG: `binary_expr` is on the stack, so the `Address` will be meaningless
191 /// let address = binary_expr.unstable_address();
192 /// // More correct: `binary_expr` is in the arena.
193 /// // Will have a stable `Address` as long as `vec` does not reallocate.
194 /// let mut vec = Vec::new_in(&allocator);
195 /// vec.push(binary_expr);
196 /// let address = vec[0].unstable_address();
197 /// ```
198 ///
199 /// ## AST nodes in `Vec`s
200 ///
201 /// ```ignore
202 /// let mut vec: &mut Vec<BinaryExpression<'a>> = get_vec_somehow();
203 ///
204 /// let address = vec[0].unstable_address();
205 /// vec.push(get_owned_binary_expression_somehow());
206 /// let address_after_push = vec[0].unstable_address();
207 ///
208 /// // This assertion may or may not pass, depending on whether `push` caused the `Vec` to reallocate.
209 /// // This depends on whether `vec` had spare capacity or not, prior to the `push` call.
210 /// assert!(address_after_push == address);
211 /// ```
212 ///
213 /// # Guardrails
214 ///
215 /// This method is less error-prone than [`Address::from_ptr`], because it provides a few guardrails:
216 ///
217 /// * [`UnstableAddress`] is only implemented on AST struct types, so you can't call it on a type which
218 /// it doesn't make sense to get the `Address` of.
219 ///
220 /// * You don't need to worry about passing it a double-reference (`&&T`), because this method will automatically
221 /// dereference as required.
222 ///
223 /// Even with these guardrails, usage of this method still requires care, for the reasons discussed above.
224 ///
225 /// [`Box<T>`]: crate::Box
226 #[inline(always)] // Because it's a no-op
227 fn unstable_address(&self) -> Address {
228 let p = NonNull::from_ref(self);
229 Address(p.addr().get())
230 }
231}