123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- 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 crated 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())
- }
|