forked from solana-foundation/anchor
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlazy.rs
More file actions
306 lines (265 loc) · 12.5 KB
/
lazy.rs
File metadata and controls
306 lines (265 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
use proc_macro2::{Literal, TokenStream};
use quote::{format_ident, quote, ToTokens};
pub fn gen_lazy(strct: &syn::ItemStruct) -> syn::Result<TokenStream> {
let ident = &strct.ident;
let lazy_ident = format_ident!("Lazy{}", ident);
let load_common_ident = to_private_ident("load_common");
let initialize_fields = to_private_ident("initialize_fields");
let lazy_acc_ty = quote! { anchor_lang::accounts::lazy_account::LazyAccount };
let disc_len = quote! { <#ident as anchor_lang::Discriminator>::DISCRIMINATOR.len() };
let load_common_docs = quote! {
/// The deserialized value is cached for future uses i.e. all subsequent calls to this
/// method do not deserialize the data again, instead, they return the cached value.
///
/// To reload the data from the underlying account info (e.g. after a CPI call), run
/// [`LazyAccount::unload`] before running this method.
///
/// See [`LazyAccount`]'s documentation for more information.
};
let load_panic_docs = quote! {
/// # Panics
///
/// If there is an existing mutable reference created by any of the `load_mut` methods.
};
let load_mut_panic_docs = quote! {
/// # Panics
///
/// If there is an existing reference (mutable or not) created by any of the `load` methods.
};
let (loader_signatures, loader_impls) = strct
.fields
.iter()
.enumerate()
.map(|(i, field)| {
let field_ident = to_field_ident(field, i);
let load_ident = format_ident!("load_{field_ident}");
let load_mut_ident = format_ident!("load_mut_{field_ident}");
let load_common_ident = to_private_ident(format!("load_common_{field_ident}"));
let offset_of_ident = to_private_ident(format!("offset_of_{field_ident}"));
let size_of_ident = to_private_ident(format!("size_of_{field_ident}"));
let offset = i.eq(&0).then(|| quote!(#disc_len)).unwrap_or_else(|| {
// Current offset is the previous field's offset + size
strct
.fields
.iter()
.nth(i - 1)
.map(|field| {
let field_ident = to_field_ident(field, i - 1);
let offset_of_ident = to_private_ident(format!("offset_of_{field_ident}"));
let size_of_ident = to_private_ident(format!("size_of_{field_ident}"));
quote! { self.#offset_of_ident() + self.#size_of_ident() }
})
.expect("Previous field should always exist when i > 0")
});
let ty = &field.ty;
let ty_as_lazy = quote! { <#ty as anchor_lang::__private::Lazy> };
let size = quote! {
// Calculating the offset is highly wasteful if the type is sized.
if #ty_as_lazy::SIZED {
#ty_as_lazy::size_of(&[])
} else {
#ty_as_lazy::size_of(&self.__info.data.borrow()[self.#offset_of_ident()..])
}
};
let signatures = quote! {
/// Load a reference to the field.
///
#load_common_docs
///
#load_panic_docs
fn #load_ident(&self) -> anchor_lang::Result<::core::cell::Ref<'_, #ty>>;
/// Load a mutable reference to the field.
///
#load_common_docs
///
#load_mut_panic_docs
fn #load_mut_ident(&self) -> anchor_lang::Result<::core::cell::RefMut<'_, #ty>>;
#[doc(hidden)]
fn #load_common_ident<R>(&self, f: impl FnOnce() -> R) -> anchor_lang::Result<R>;
#[doc(hidden)]
fn #offset_of_ident(&self) -> usize;
#[doc(hidden)]
fn #size_of_ident(&self) -> usize;
};
let impls = quote! {
fn #load_ident(&self) -> anchor_lang::Result<::core::cell::Ref<'_, #ty>> {
self.#load_common_ident(|| {
// SAFETY: The common load method makes sure the field is initialized.
::core::cell::Ref::map(self.__account.borrow(), |acc| unsafe {
&*::core::ptr::addr_of!((*acc.as_ptr()).#field_ident)
})
})
}
fn #load_mut_ident(&self) -> anchor_lang::Result<::core::cell::RefMut<'_, #ty>> {
self.#load_common_ident(|| {
// SAFETY: The common load method makes sure the field is initialized.
::core::cell::RefMut::map(self.__account.borrow_mut(), |acc| unsafe {
&mut *::core::ptr::addr_of_mut!((*acc.as_mut_ptr()).#field_ident)
})
})
}
#[inline(never)]
fn #load_common_ident<R>(&self, f: impl FnOnce() -> R) -> anchor_lang::Result<R> {
self.#initialize_fields();
// Return early if initialized
if self.__fields.borrow().as_ref().unwrap()[#i] {
return Ok(f());
}
// Deserialize and write
let offset = self.#offset_of_ident();
let size = self.#size_of_ident();
let data = self.__info.data.borrow();
let val = anchor_lang::AnchorDeserialize::try_from_slice(
&data[offset..offset + size]
)?;
unsafe {
::core::ptr::addr_of_mut!(
(*self.__account.borrow_mut().as_mut_ptr()).#field_ident
).write(val)
};
// Set initialized
self.__fields.borrow_mut().as_mut().unwrap()[#i] = true;
Ok(f())
}
// If this method gets inlined when there are >= 12 fields, compilation breaks with
// `LLVM ERROR: Branch target out of insn range`
#[inline(never)]
fn #offset_of_ident(&self) -> usize {
#offset
}
#[inline(always)]
fn #size_of_ident(&self) -> usize {
#size
}
};
Ok((signatures, impls))
})
.collect::<syn::Result<Vec<_>>>()?
.into_iter()
.unzip::<_, _, Vec<_>, Vec<_>>();
let load_idents = strct
.fields
.iter()
.enumerate()
.map(|(i, field)| to_field_ident(field, i))
.map(|field| format_ident!("load_{field}"));
let total_fields = strct.fields.len();
Ok(quote! {
pub trait #lazy_ident {
/// Load a reference to the entire account.
///
#load_common_docs
///
#load_panic_docs
fn load(&self) -> anchor_lang::Result<::core::cell::Ref<'_, #ident>>;
/// Load a mutable reference to the entire account.
///
#load_common_docs
///
#load_mut_panic_docs
fn load_mut(&self) -> anchor_lang::Result<::core::cell::RefMut<'_, #ident>>;
#[doc(hidden)]
fn #load_common_ident<R>(&self, f: impl FnOnce() -> R) -> anchor_lang::Result<R>;
#(#loader_signatures)*
#[doc(hidden)]
fn #initialize_fields(&self);
/// Run the exit routine of the account, similar to [`AccountsExit`] but implemented
/// as a regular method because we can't implement external traits for external structs.
fn exit(&self, program_id: &anchor_lang::prelude::Pubkey) -> anchor_lang::Result<()>;
}
impl<'info> #lazy_ident for #lazy_acc_ty<'info, #ident> {
fn load(&self) -> anchor_lang::Result<::core::cell::Ref<'_, #ident>> {
self.#load_common_ident(|| {
// SAFETY: The common load method makes sure all fields are initialized.
::core::cell::Ref::map(self.__account.borrow(), |acc| unsafe {
acc.assume_init_ref()
})
})
}
fn load_mut(&self) -> anchor_lang::Result<::core::cell::RefMut<'_, #ident>> {
self.#load_common_ident(|| {
// SAFETY: The common load method makes sure all fields are initialized.
::core::cell::RefMut::map(self.__account.borrow_mut(), |acc| unsafe {
acc.assume_init_mut()
})
})
}
#[inline(never)]
fn #load_common_ident<R>(&self, f: impl FnOnce() -> R) -> anchor_lang::Result<R> {
self.#initialize_fields();
// Create a scope to drop the `__fields` borrow
let all_uninit = {
// Return early if all fields are initialized
let fields = self.__fields.borrow();
let fields = fields.as_ref().unwrap();
if !fields.contains(&false) {
return Ok(f());
}
!fields.contains(&true)
};
if all_uninit {
// Nothing is initialized, initialize all
let offset = #disc_len;
let mut data = self.__info.data.borrow();
let val = anchor_lang::AnchorDeserialize::deserialize(&mut &data[offset..])?;
unsafe { self.__account.borrow_mut().as_mut_ptr().write(val) };
// Set fields to initialized
let mut fields = self.__fields.borrow_mut();
let fields = fields.as_mut().unwrap();
for field in fields {
*field = true;
}
} else {
// Only initialize uninitialized fields (`load` methods already do this).
//
// This is not exactly efficient because `load` methods have a bit of
// runtime ownership overhead. This could be optimized further, but it
// requires some refactoring and also makes the code harder to reason about.
//
// We can return back to this if benchmarks show this is a bottleneck.
#(self.#load_idents()?;)*
}
Ok(f())
}
#(#loader_impls)*
#[inline(always)]
fn #initialize_fields(&self) {
if self.__fields.borrow().is_none() {
*self.__fields.borrow_mut() = Some(vec![false; #total_fields]);
}
}
// TODO: This method can be optimized to *only* serialize the fields that we have
// initialized rather than deserializing the whole account, and then serializing it
// back, which consumes a lot more CUs than it should for most accounts.
fn exit(&self, program_id: &anchor_lang::prelude::Pubkey) -> anchor_lang::Result<()> {
// Only persist if the owner is the current program and the account is not closed
if &<#ident as anchor_lang::Owner>::owner() == program_id
&& !anchor_lang::__private::is_closed(self.__info)
{
// Make sure all fields are initialized
let acc = self.load()?;
let mut data = self.__info.try_borrow_mut_data()?;
let dst: &mut [u8] = &mut data;
let mut writer = anchor_lang::__private::BpfWriter::new(dst);
acc.try_serialize(&mut writer)?;
}
Ok(())
}
}
})
}
/// Get the field's ident and if the ident doesn't exist (e.g. for tuple structs), default to the
/// given index.
fn to_field_ident(field: &syn::Field, i: usize) -> TokenStream {
field
.ident
.as_ref()
.map(ToTokens::to_token_stream)
.unwrap_or_else(|| Literal::usize_unsuffixed(i).to_token_stream())
}
/// Convert to private ident.
///
/// This is used to indicate to the users that they shouldn't use this identifier.
fn to_private_ident<S: AsRef<str>>(ident: S) -> syn::Ident {
format_ident!("__{}", ident.as_ref())
}