@@ -21,7 +21,7 @@ use cairo_lang_syntax::node::ids::{GreenId, SyntaxStablePtrId};
2121use cairo_lang_syntax:: node:: kind:: SyntaxKind ;
2222use cairo_lang_syntax:: node:: { SyntaxNode , SyntaxNodeId , TypedSyntaxNode , ast} ;
2323use cairo_lang_utils:: Intern ;
24- use cairo_lang_utils:: ordered_hash_map:: OrderedHashMap ;
24+ use cairo_lang_utils:: ordered_hash_map:: { Entry , OrderedHashMap } ;
2525use salsa:: Database ;
2626use serde:: { Deserialize , Serialize } ;
2727use thiserror:: Error ;
@@ -41,8 +41,11 @@ use crate::ids::{
4141} ;
4242use crate :: plugin:: { DynGeneratedFileAuxData , PluginDiagnostic } ;
4343
44+ /// The index of the `external` section in a crate cache blob.
45+ pub ( crate ) const EXTERNAL_CACHE_SECTION : u8 = 0 ;
46+
4447/// The index of the `def` section in a crate cache blob.
45- pub const DEF_CACHE_SECTION : u8 = 0 ;
48+ const DEF_CACHE_SECTION : u8 = 1 ;
4649
4750/// Metadata for a cached crate.
4851#[ derive( Serialize , Deserialize ) ]
@@ -99,7 +102,7 @@ pub fn load_cached_crate_modules<'db>(
99102 } ;
100103
101104 let ( metadata, module_data, defs_lookups) : DefCache < ' _ > =
102- CacheBlobReader :: read_section ( db, content, DEF_CACHE_SECTION , & crate_id) ;
105+ CacheBlobReader :: read_section ( db, content, DEF_CACHE_SECTION , crate_id) ;
103106
104107 validate_metadata ( crate_id, & metadata, db) ;
105108
@@ -178,7 +181,7 @@ impl<'a> CacheBlobReader<'a> {
178181 db : & ' db dyn Database ,
179182 content : & ' a [ u8 ] ,
180183 section : u8 ,
181- crate_id : & CrateId < ' db > ,
184+ crate_id : CrateId < ' db > ,
182185 ) -> T {
183186 let mut reader = CacheBlobReader :: new ( content) ;
184187 for _ in 0 ..section {
@@ -400,6 +403,12 @@ impl<'db> DefCacheSavingContext<'db> {
400403 pub fn new ( db : & ' db dyn Database , self_crate_id : CrateId < ' db > ) -> Self {
401404 Self { db, data : DefCacheSavingData :: default ( ) , self_crate_id }
402405 }
406+
407+ /// Serializes the external-file content as its own blob-section payload (read back by the
408+ /// `external_file_contents` query).
409+ pub fn serialize_external_file_contents ( & self ) -> Result < Vec < u8 > , postcard:: Error > {
410+ postcard:: to_allocvec ( & self . data . external_file_contents )
411+ }
403412}
404413
405414/// Data for saving cache from the database.
@@ -426,6 +435,9 @@ pub struct DefCacheSavingData<'db> {
426435 syntax_nodes : OrderedHashMap < SyntaxNode < ' db > , SyntaxNodeCached > ,
427436 file_ids : OrderedHashMap < FileId < ' db > , FileIdCached > ,
428437
438+ /// External (plugin-generated) file content, written as its own blob section in cache.
439+ external_file_contents : OrderedHashMap < ExternalFileKey , ExternalFileContentCached > ,
440+
429441 pub lookups : DefCacheLookups ,
430442}
431443
@@ -1941,14 +1953,116 @@ impl FileCached {
19411953 }
19421954}
19431955
1956+ /// Portable, parse-free identity for an external file — the key into the external-file content
1957+ /// table. Computed during cache load by reading only already-materialized fields, so it never
1958+ /// parses or re-runs plugins (which would cycle back into the cache).
1959+ #[ derive( Serialize , Deserialize , Clone , Eq , Hash , PartialEq , Debug ) ]
1960+ pub ( crate ) enum ExternalFileKey {
1961+ /// An on-disk file, identified by its path.
1962+ OnDisk ( PathBuf ) ,
1963+ /// A virtual file: parent (key + span), name, and a content hash (hashed, not stored, so a
1964+ /// shared ancestor isn't duplicated across keys).
1965+ Virtual { parent : Option < ( Box < ExternalFileKey > , TextSpan ) > , name : String , content_hash : u64 } ,
1966+ /// A plugin-generated file: the parent file's key, the stable-ptr offset within it, and name.
1967+ Generated { parent : Box < ExternalFileKey > , offset : u32 , name : String } ,
1968+ }
1969+
1970+ impl ExternalFileKey {
1971+ /// Computes the key for `file_id`; see the type docs for why this is parse/plugin/mint-free.
1972+ pub ( crate ) fn new < ' db > ( db : & ' db dyn Database , file_id : FileId < ' db > ) -> Self {
1973+ match file_id. long ( db) {
1974+ FileLongId :: OnDisk ( path) => ExternalFileKey :: OnDisk ( path. clone ( ) ) ,
1975+ FileLongId :: Virtual ( vf) => ExternalFileKey :: Virtual {
1976+ parent : vf. parent . map ( |parent| {
1977+ ( Box :: new ( ExternalFileKey :: new ( db, parent. file_id ) ) , parent. span )
1978+ } ) ,
1979+ name : vf. name . long ( db) . to_string ( ) ,
1980+ content_hash : {
1981+ let mut hasher = xxhash_rust:: xxh3:: Xxh3 :: default ( ) ;
1982+ vf. content . long ( db) . as_str ( ) . hash ( & mut hasher) ;
1983+ hasher. finish ( )
1984+ } ,
1985+ } ,
1986+ FileLongId :: External ( external_id) => {
1987+ let long_id = PluginGeneratedFileId :: from_intern_id ( * external_id) . long ( db) ;
1988+ ExternalFileKey :: Generated {
1989+ parent : Box :: new ( ExternalFileKey :: new ( db, long_id. stable_ptr . file_id ( db) ) ) ,
1990+ offset : long_id. stable_ptr . 0 . offset ( db) . as_u32 ( ) ,
1991+ name : long_id. name . clone ( ) ,
1992+ }
1993+ }
1994+ }
1995+ }
1996+ }
1997+
1998+ /// An external file's content, enough to rebuild its `VirtualFile` on load. `parent` and `name` are
1999+ /// not stored — they come from the runtime `PluginGeneratedFileLongId` at lookup.
2000+ #[ derive( Serialize , Deserialize , Clone , PartialEq , Eq , Debug ) ]
2001+ pub ( crate ) struct ExternalFileContentCached {
2002+ content : String ,
2003+ code_mappings : Vec < CodeMapping > ,
2004+ kind : FileKind ,
2005+ original_item_removed : bool ,
2006+ }
2007+
2008+ impl ExternalFileContentCached {
2009+ fn new < ' db > ( virtual_file : & VirtualFile < ' db > , db : & ' db dyn Database ) -> Self {
2010+ Self {
2011+ content : virtual_file. content . to_string ( db) ,
2012+ code_mappings : virtual_file. code_mappings . to_vec ( ) ,
2013+ kind : virtual_file. kind ,
2014+ original_item_removed : virtual_file. original_item_removed ,
2015+ }
2016+ }
2017+
2018+ /// Rebuilds the `VirtualFile`; `parent` and `name` come from the runtime stable ptr / long id.
2019+ pub ( crate ) fn to_virtual_file < ' db > (
2020+ & self ,
2021+ db : & ' db dyn Database ,
2022+ name : & str ,
2023+ parent : Option < SpanInFile < ' db > > ,
2024+ ) -> VirtualFile < ' db > {
2025+ VirtualFile {
2026+ parent,
2027+ name : SmolStrId :: from ( db, name) ,
2028+ content : SmolStrId :: from ( db, self . content . clone ( ) ) ,
2029+ code_mappings : self . code_mappings . clone ( ) . into ( ) ,
2030+ kind : self . kind ,
2031+ original_item_removed : self . original_item_removed ,
2032+ }
2033+ }
2034+ }
2035+
19442036#[ derive( Serialize , Deserialize , Clone , Copy , Eq , Hash , PartialEq , salsa:: Update , Debug ) ]
19452037pub struct FileIdCached ( usize ) ;
19462038impl FileIdCached {
19472039 fn new < ' db > ( id : FileId < ' db > , ctx : & mut DefCacheSavingContext < ' db > ) -> Self {
19482040 if let Some ( cached_id) = ctx. file_ids . get ( & id) {
19492041 return * cached_id;
19502042 }
1951- let cached = FileCached :: new ( id. long ( ctx. db ) , ctx) ;
2043+ let long_id = id. long ( ctx. db ) ;
2044+ // For external (plugin-generated) files, also cache their content so `ext_as_virtual` can
2045+ // serve it from the blob instead of re-running plugins.
2046+ if let FileLongId :: External ( external_id) = long_id {
2047+ // Distinct `FileId`s can map to the same `ExternalFileKey` (the key is coarser), so
2048+ // dedup against the keys already stored in `external_file_contents`.
2049+ let key = ExternalFileKey :: new ( ctx. db , id) ;
2050+ let virtual_file = cairo_lang_filesystem:: db:: ext_as_virtual ( ctx. db , * external_id) ;
2051+ let content = ExternalFileContentCached :: new ( virtual_file, ctx. db ) ;
2052+ match ctx. external_file_contents . entry ( key) {
2053+ Entry :: Vacant ( entry) => {
2054+ entry. insert ( content) ;
2055+ }
2056+ // Two distinct external files collapsing to one key must carry identical content,
2057+ // else the load would serve the wrong bytes. Holds today (generated files have
2058+ // distinct parent+offset); guard it so a future key change fails loud, not silent.
2059+ Entry :: Occupied ( entry) => debug_assert ! (
2060+ * entry. get( ) == content,
2061+ "external file content key collision with differing content"
2062+ ) ,
2063+ }
2064+ }
2065+ let cached = FileCached :: new ( long_id, ctx) ;
19522066 let cached_id = FileIdCached ( ctx. file_ids_lookup . len ( ) ) ;
19532067 ctx. file_ids_lookup . push ( cached) ;
19542068 ctx. file_ids . insert ( id, cached_id) ;
0 commit comments