lazy.rs 13 KB

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