( obj: LeanList<LeanBorrowed<'_>>, )
| 1114 | fn decode_name_constant_info( |
| 1115 | obj: LeanBorrowed<'_>, |
| 1116 | global: &GlobalCache, |
| 1117 | ) -> (Name, ConstantInfo) { |
| 1118 | let mut cache = Cache::new(global); |
| 1119 | // Outer Prod (Name × ConstantInfo) has no public LeanProd<LeanBorrowed> |
| 1120 | // constructor, so read the two fields through the raw ctor. |
| 1121 | let pair = obj.as_ctor(); |
| 1122 | let name = decode_name(pair.get(0), global); |
| 1123 | let constant_info = decode_constant_info(pair.get(1), &mut cache); |
| 1124 | (name, constant_info) |
| 1125 | } |
| 1126 | |
| 1127 | /// Resident-decoded-constant bound for [`decode_env_lazy`]'s cache. |
| 1128 | /// |
| 1129 | /// Sized by sweep, not by hit rate: on FLT (511k consts) wall time is |
| 1130 | /// flat from 4096 through 1M entries (60–67 s; misses hide in the |
| 1131 | /// scheduler's dependency-stall slack) while peak RSS climbs from |
| 1132 | /// 11.5 GiB (4k) → 13.0 (64k) → 24.7 (1M); Mathlib (737k consts) |
| 1133 | /// confirms parity at this size (87.5 s / 17.0 GiB vs 87.3 s / |
| 1134 | /// 18.3 GiB at 64k). So the cache buys RAM, not speed, and the bound |
| 1135 | /// sits at the small end with headroom over the 4k floor measured to |
| 1136 | /// still not thrash. |
| 1137 | const LAZY_ENV_CACHE_ENTRIES: usize = 16384; |
| 1138 | |
| 1139 | /// Decode for the production compile FFI entries: an on-demand view |
| 1140 | /// that avoids materializing the full Rust copy of the environment — |
| 1141 | /// the eager copy is the single largest memory term at scale. The |
| 1142 | /// on-demand decode costs a few percent of wall (per-constant fetches |
| 1143 | /// vs one batched decode); `IX_COMPILE_EAGER=1` buys that back on |
| 1144 | /// machines with RAM to spare. Measured on FLT: −8 % wall for |
| 1145 | /// +13 GiB peak; Mathlib eager exceeds a 56 GB machine outright |
| 1146 | /// (the copy sits alongside the Lean-held env). Test and roundtrip |
| 1147 | /// entries keep [`decode_env`] (they re-read the env structurally |
| 1148 | /// throughout). |
| 1149 | pub fn decode_env_for_compile(list: LeanList<LeanBorrowed<'_>>) -> Env { |
| 1150 | if std::env::var("IX_COMPILE_EAGER").as_deref() == Ok("1") { |
| 1151 | return decode_env(list); |
| 1152 | } |
| 1153 | decode_env_lazy(list, LAZY_ENV_CACHE_ENTRIES) |
| 1154 | } |
| 1155 | |
| 1156 | /// Lazy variant of [`decode_env`]: decode only the *names* eagerly, |
| 1157 | /// keep a per-constant `LeanShared` handle, and decode `ConstantInfo`s |
| 1158 | /// on demand through `Env`'s bounded cache. |
| 1159 | /// |
| 1160 | /// Thread-safety is identical to the eager path, which already decodes |
| 1161 | /// in parallel: `lean_mark_mt` (via `LeanShared::new`) transitions the |
| 1162 | /// reachable graph to atomic refcounting, structural reads through |
| 1163 | /// `LeanBorrowed` accessors are refcount-silent, and each element's |
| 1164 | /// owned handle keeps its Lean objects alive for the `Env`'s lifetime |
| 1165 | /// regardless of what the Lean side does after the FFI call. |
| 1166 | pub fn decode_env_lazy( |
| 1167 | list: LeanList<LeanBorrowed<'_>>, |
| 1168 | cache_entries: usize, |
| 1169 | ) -> Env { |
| 1170 | let shared_list = LeanShared::new(list.inner().to_owned_ref()); |
| 1171 | let objs = collect_list_shared(shared_list.borrow().as_list()); |
| 1172 | let global = Arc::new(GlobalCache::with_capacity(objs.len() * 3)); |
| 1173 |
nothing calls this directly
no test coverage detected