1pub mod wit;
2
3use crate::ExtensionManifest;
4use anyhow::{Context as _, Result, anyhow, bail};
5use async_trait::async_trait;
6use dap::{DebugRequest, StartDebuggingRequestArgumentsRequest};
7use extension::{
8 CodeLabel, Command, Completion, ContextServerConfiguration, DebugAdapterBinary,
9 DebugTaskDefinition, ExtensionHostProxy, KeyValueStoreDelegate, ProjectDelegate, SlashCommand,
10 SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate,
11};
12use fs::{Fs, normalize_path};
13use futures::future::LocalBoxFuture;
14use futures::{
15 Future, FutureExt, StreamExt as _,
16 channel::{
17 mpsc::{self, UnboundedSender},
18 oneshot,
19 },
20 future::BoxFuture,
21};
22use gpui::{App, AsyncApp, BackgroundExecutor, Task, Timer};
23use http_client::HttpClient;
24use language::LanguageName;
25use lsp::LanguageServerName;
26use moka::sync::Cache;
27use node_runtime::NodeRuntime;
28use release_channel::ReleaseChannel;
29use semantic_version::SemanticVersion;
30use std::borrow::Cow;
31use std::sync::{LazyLock, OnceLock};
32use std::time::Duration;
33use std::{
34 path::{Path, PathBuf},
35 sync::Arc,
36};
37use task::{DebugScenario, SpawnInTerminal, TaskTemplate, ZedDebugConfig};
38use wasmtime::{
39 CacheStore, Engine, Store,
40 component::{Component, ResourceTable},
41};
42use wasmtime_wasi::{self as wasi, WasiView};
43use wit::Extension;
44
45pub struct WasmHost {
46 engine: Engine,
47 release_channel: ReleaseChannel,
48 http_client: Arc<dyn HttpClient>,
49 node_runtime: NodeRuntime,
50 pub(crate) proxy: Arc<ExtensionHostProxy>,
51 fs: Arc<dyn Fs>,
52 pub work_dir: PathBuf,
53 _main_thread_message_task: Task<()>,
54 main_thread_message_tx: mpsc::UnboundedSender<MainThreadCall>,
55}
56
57#[derive(Clone, Debug)]
58pub struct WasmExtension {
59 tx: UnboundedSender<ExtensionCall>,
60 pub manifest: Arc<ExtensionManifest>,
61 pub work_dir: Arc<Path>,
62 #[allow(unused)]
63 pub zed_api_version: SemanticVersion,
64}
65
66impl Drop for WasmExtension {
67 fn drop(&mut self) {
68 self.tx.close_channel();
69 }
70}
71
72#[async_trait]
73impl extension::Extension for WasmExtension {
74 fn manifest(&self) -> Arc<ExtensionManifest> {
75 self.manifest.clone()
76 }
77
78 fn work_dir(&self) -> Arc<Path> {
79 self.work_dir.clone()
80 }
81
82 async fn language_server_command(
83 &self,
84 language_server_id: LanguageServerName,
85 language_name: LanguageName,
86 worktree: Arc<dyn WorktreeDelegate>,
87 ) -> Result<Command> {
88 self.call(|extension, store| {
89 async move {
90 let resource = store.data_mut().table().push(worktree)?;
91 let command = extension
92 .call_language_server_command(
93 store,
94 &language_server_id,
95 &language_name,
96 resource,
97 )
98 .await?
99 .map_err(|err| store.data().extension_error(err))?;
100
101 Ok(command.into())
102 }
103 .boxed()
104 })
105 .await
106 }
107
108 async fn language_server_initialization_options(
109 &self,
110 language_server_id: LanguageServerName,
111 language_name: LanguageName,
112 worktree: Arc<dyn WorktreeDelegate>,
113 ) -> Result<Option<String>> {
114 self.call(|extension, store| {
115 async move {
116 let resource = store.data_mut().table().push(worktree)?;
117 let options = extension
118 .call_language_server_initialization_options(
119 store,
120 &language_server_id,
121 &language_name,
122 resource,
123 )
124 .await?
125 .map_err(|err| store.data().extension_error(err))?;
126 anyhow::Ok(options)
127 }
128 .boxed()
129 })
130 .await
131 }
132
133 async fn language_server_workspace_configuration(
134 &self,
135 language_server_id: LanguageServerName,
136 worktree: Arc<dyn WorktreeDelegate>,
137 ) -> Result<Option<String>> {
138 self.call(|extension, store| {
139 async move {
140 let resource = store.data_mut().table().push(worktree)?;
141 let options = extension
142 .call_language_server_workspace_configuration(
143 store,
144 &language_server_id,
145 resource,
146 )
147 .await?
148 .map_err(|err| store.data().extension_error(err))?;
149 anyhow::Ok(options)
150 }
151 .boxed()
152 })
153 .await
154 }
155
156 async fn language_server_additional_initialization_options(
157 &self,
158 language_server_id: LanguageServerName,
159 target_language_server_id: LanguageServerName,
160 worktree: Arc<dyn WorktreeDelegate>,
161 ) -> Result<Option<String>> {
162 self.call(|extension, store| {
163 async move {
164 let resource = store.data_mut().table().push(worktree)?;
165 let options = extension
166 .call_language_server_additional_initialization_options(
167 store,
168 &language_server_id,
169 &target_language_server_id,
170 resource,
171 )
172 .await?
173 .map_err(|err| store.data().extension_error(err))?;
174 anyhow::Ok(options)
175 }
176 .boxed()
177 })
178 .await
179 }
180
181 async fn language_server_additional_workspace_configuration(
182 &self,
183 language_server_id: LanguageServerName,
184 target_language_server_id: LanguageServerName,
185 worktree: Arc<dyn WorktreeDelegate>,
186 ) -> Result<Option<String>> {
187 self.call(|extension, store| {
188 async move {
189 let resource = store.data_mut().table().push(worktree)?;
190 let options = extension
191 .call_language_server_additional_workspace_configuration(
192 store,
193 &language_server_id,
194 &target_language_server_id,
195 resource,
196 )
197 .await?
198 .map_err(|err| store.data().extension_error(err))?;
199 anyhow::Ok(options)
200 }
201 .boxed()
202 })
203 .await
204 }
205
206 async fn labels_for_completions(
207 &self,
208 language_server_id: LanguageServerName,
209 completions: Vec<Completion>,
210 ) -> Result<Vec<Option<CodeLabel>>> {
211 self.call(|extension, store| {
212 async move {
213 let labels = extension
214 .call_labels_for_completions(
215 store,
216 &language_server_id,
217 completions.into_iter().map(Into::into).collect(),
218 )
219 .await?
220 .map_err(|err| store.data().extension_error(err))?;
221
222 Ok(labels
223 .into_iter()
224 .map(|label| label.map(Into::into))
225 .collect())
226 }
227 .boxed()
228 })
229 .await
230 }
231
232 async fn labels_for_symbols(
233 &self,
234 language_server_id: LanguageServerName,
235 symbols: Vec<Symbol>,
236 ) -> Result<Vec<Option<CodeLabel>>> {
237 self.call(|extension, store| {
238 async move {
239 let labels = extension
240 .call_labels_for_symbols(
241 store,
242 &language_server_id,
243 symbols.into_iter().map(Into::into).collect(),
244 )
245 .await?
246 .map_err(|err| store.data().extension_error(err))?;
247
248 Ok(labels
249 .into_iter()
250 .map(|label| label.map(Into::into))
251 .collect())
252 }
253 .boxed()
254 })
255 .await
256 }
257
258 async fn complete_slash_command_argument(
259 &self,
260 command: SlashCommand,
261 arguments: Vec<String>,
262 ) -> Result<Vec<SlashCommandArgumentCompletion>> {
263 self.call(|extension, store| {
264 async move {
265 let completions = extension
266 .call_complete_slash_command_argument(store, &command.into(), &arguments)
267 .await?
268 .map_err(|err| store.data().extension_error(err))?;
269
270 Ok(completions.into_iter().map(Into::into).collect())
271 }
272 .boxed()
273 })
274 .await
275 }
276
277 async fn run_slash_command(
278 &self,
279 command: SlashCommand,
280 arguments: Vec<String>,
281 delegate: Option<Arc<dyn WorktreeDelegate>>,
282 ) -> Result<SlashCommandOutput> {
283 self.call(|extension, store| {
284 async move {
285 let resource = if let Some(delegate) = delegate {
286 Some(store.data_mut().table().push(delegate)?)
287 } else {
288 None
289 };
290
291 let output = extension
292 .call_run_slash_command(store, &command.into(), &arguments, resource)
293 .await?
294 .map_err(|err| store.data().extension_error(err))?;
295
296 Ok(output.into())
297 }
298 .boxed()
299 })
300 .await
301 }
302
303 async fn context_server_command(
304 &self,
305 context_server_id: Arc<str>,
306 project: Arc<dyn ProjectDelegate>,
307 ) -> Result<Command> {
308 self.call(|extension, store| {
309 async move {
310 let project_resource = store.data_mut().table().push(project)?;
311 let command = extension
312 .call_context_server_command(store, context_server_id.clone(), project_resource)
313 .await?
314 .map_err(|err| store.data().extension_error(err))?;
315 anyhow::Ok(command.into())
316 }
317 .boxed()
318 })
319 .await
320 }
321
322 async fn context_server_configuration(
323 &self,
324 context_server_id: Arc<str>,
325 project: Arc<dyn ProjectDelegate>,
326 ) -> Result<Option<ContextServerConfiguration>> {
327 self.call(|extension, store| {
328 async move {
329 let project_resource = store.data_mut().table().push(project)?;
330 let Some(configuration) = extension
331 .call_context_server_configuration(
332 store,
333 context_server_id.clone(),
334 project_resource,
335 )
336 .await?
337 .map_err(|err| store.data().extension_error(err))?
338 else {
339 return Ok(None);
340 };
341
342 Ok(Some(configuration.try_into()?))
343 }
344 .boxed()
345 })
346 .await
347 }
348
349 async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>> {
350 self.call(|extension, store| {
351 async move {
352 let packages = extension
353 .call_suggest_docs_packages(store, provider.as_ref())
354 .await?
355 .map_err(|err| store.data().extension_error(err))?;
356
357 Ok(packages)
358 }
359 .boxed()
360 })
361 .await
362 }
363
364 async fn index_docs(
365 &self,
366 provider: Arc<str>,
367 package_name: Arc<str>,
368 kv_store: Arc<dyn KeyValueStoreDelegate>,
369 ) -> Result<()> {
370 self.call(|extension, store| {
371 async move {
372 let kv_store_resource = store.data_mut().table().push(kv_store)?;
373 extension
374 .call_index_docs(
375 store,
376 provider.as_ref(),
377 package_name.as_ref(),
378 kv_store_resource,
379 )
380 .await?
381 .map_err(|err| store.data().extension_error(err))?;
382
383 anyhow::Ok(())
384 }
385 .boxed()
386 })
387 .await
388 }
389
390 async fn get_dap_binary(
391 &self,
392 dap_name: Arc<str>,
393 config: DebugTaskDefinition,
394 user_installed_path: Option<PathBuf>,
395 worktree: Arc<dyn WorktreeDelegate>,
396 ) -> Result<DebugAdapterBinary> {
397 self.call(|extension, store| {
398 async move {
399 let resource = store.data_mut().table().push(worktree)?;
400 let dap_binary = extension
401 .call_get_dap_binary(store, dap_name, config, user_installed_path, resource)
402 .await?
403 .map_err(|err| store.data().extension_error(err))?;
404 let dap_binary = dap_binary.try_into()?;
405 Ok(dap_binary)
406 }
407 .boxed()
408 })
409 .await
410 }
411 async fn dap_request_kind(
412 &self,
413 dap_name: Arc<str>,
414 config: serde_json::Value,
415 ) -> Result<StartDebuggingRequestArgumentsRequest> {
416 self.call(|extension, store| {
417 async move {
418 let kind = extension
419 .call_dap_request_kind(store, dap_name, config)
420 .await?
421 .map_err(|err| store.data().extension_error(err))?;
422 Ok(kind.into())
423 }
424 .boxed()
425 })
426 .await
427 }
428
429 async fn dap_config_to_scenario(&self, config: ZedDebugConfig) -> Result<DebugScenario> {
430 self.call(|extension, store| {
431 async move {
432 let kind = extension
433 .call_dap_config_to_scenario(store, config)
434 .await?
435 .map_err(|err| store.data().extension_error(err))?;
436 Ok(kind)
437 }
438 .boxed()
439 })
440 .await
441 }
442
443 async fn dap_locator_create_scenario(
444 &self,
445 locator_name: String,
446 build_config_template: TaskTemplate,
447 resolved_label: String,
448 debug_adapter_name: String,
449 ) -> Result<Option<DebugScenario>> {
450 self.call(|extension, store| {
451 async move {
452 extension
453 .call_dap_locator_create_scenario(
454 store,
455 locator_name,
456 build_config_template,
457 resolved_label,
458 debug_adapter_name,
459 )
460 .await
461 }
462 .boxed()
463 })
464 .await
465 }
466 async fn run_dap_locator(
467 &self,
468 locator_name: String,
469 config: SpawnInTerminal,
470 ) -> Result<DebugRequest> {
471 self.call(|extension, store| {
472 async move {
473 extension
474 .call_run_dap_locator(store, locator_name, config)
475 .await?
476 .map_err(|err| store.data().extension_error(err))
477 }
478 .boxed()
479 })
480 .await
481 }
482}
483
484pub struct WasmState {
485 manifest: Arc<ExtensionManifest>,
486 pub table: ResourceTable,
487 ctx: wasi::WasiCtx,
488 pub host: Arc<WasmHost>,
489}
490
491type MainThreadCall = Box<dyn Send + for<'a> FnOnce(&'a mut AsyncApp) -> LocalBoxFuture<'a, ()>>;
492
493type ExtensionCall = Box<
494 dyn Send + for<'a> FnOnce(&'a mut Extension, &'a mut Store<WasmState>) -> BoxFuture<'a, ()>,
495>;
496
497fn wasm_engine(executor: &BackgroundExecutor) -> wasmtime::Engine {
498 static WASM_ENGINE: OnceLock<wasmtime::Engine> = OnceLock::new();
499 WASM_ENGINE
500 .get_or_init(|| {
501 let mut config = wasmtime::Config::new();
502 config.wasm_component_model(true);
503 config.async_support(true);
504 config
505 .enable_incremental_compilation(cache_store())
506 .unwrap();
507 // Async support introduces the issue that extension execution happens during `Future::poll`,
508 // which could block an async thread.
509 // https://docs.rs/wasmtime/latest/wasmtime/struct.Config.html#execution-in-poll
510 //
511 // Epoch interruption is a lightweight mechanism to allow the extensions to yield control
512 // back to the executor at regular intervals.
513 config.epoch_interruption(true);
514
515 let engine = wasmtime::Engine::new(&config).unwrap();
516
517 // It might be safer to do this on a non-async thread to make sure it makes progress
518 // regardless of if extensions are blocking.
519 // However, due to our current setup, this isn't a likely occurrence and we'd rather
520 // not have a dedicated thread just for this. If it becomes an issue, we can consider
521 // creating a separate thread for epoch interruption.
522 let engine_ref = engine.weak();
523 executor
524 .spawn(async move {
525 // Somewhat arbitrary interval, as it isn't a guaranteed interval.
526 // But this is a rough upper bound for how long the extension execution can block on
527 // `Future::poll`.
528 const EPOCH_INTERVAL: Duration = Duration::from_millis(100);
529 let mut timer = Timer::interval(EPOCH_INTERVAL);
530 while let Some(_) = timer.next().await {
531 // Exit the loop and thread once the engine is dropped.
532 let Some(engine) = engine_ref.upgrade() else {
533 break;
534 };
535 engine.increment_epoch();
536 }
537 })
538 .detach();
539
540 engine
541 })
542 .clone()
543}
544
545fn cache_store() -> Arc<IncrementalCompilationCache> {
546 static CACHE_STORE: LazyLock<Arc<IncrementalCompilationCache>> =
547 LazyLock::new(|| Arc::new(IncrementalCompilationCache::new()));
548 CACHE_STORE.clone()
549}
550
551impl WasmHost {
552 pub fn new(
553 fs: Arc<dyn Fs>,
554 http_client: Arc<dyn HttpClient>,
555 node_runtime: NodeRuntime,
556 proxy: Arc<ExtensionHostProxy>,
557 work_dir: PathBuf,
558 cx: &mut App,
559 ) -> Arc<Self> {
560 let (tx, mut rx) = mpsc::unbounded::<MainThreadCall>();
561 let task = cx.spawn(async move |cx| {
562 while let Some(message) = rx.next().await {
563 message(cx).await;
564 }
565 });
566 Arc::new(Self {
567 engine: wasm_engine(cx.background_executor()),
568 fs,
569 work_dir,
570 http_client,
571 node_runtime,
572 proxy,
573 release_channel: ReleaseChannel::global(cx),
574 _main_thread_message_task: task,
575 main_thread_message_tx: tx,
576 })
577 }
578
579 pub fn load_extension(
580 self: &Arc<Self>,
581 wasm_bytes: Vec<u8>,
582 manifest: &Arc<ExtensionManifest>,
583 executor: BackgroundExecutor,
584 ) -> Task<Result<WasmExtension>> {
585 let this = self.clone();
586 let manifest = manifest.clone();
587 executor.clone().spawn(async move {
588 let zed_api_version = parse_wasm_extension_version(&manifest.id, &wasm_bytes)?;
589
590 let component = Component::from_binary(&this.engine, &wasm_bytes)
591 .context("failed to compile wasm component")?;
592
593 let mut store = wasmtime::Store::new(
594 &this.engine,
595 WasmState {
596 ctx: this.build_wasi_ctx(&manifest).await?,
597 manifest: manifest.clone(),
598 table: ResourceTable::new(),
599 host: this.clone(),
600 },
601 );
602 // Store will yield after 1 tick, and get a new deadline of 1 tick after each yield.
603 store.set_epoch_deadline(1);
604 store.epoch_deadline_async_yield_and_update(1);
605
606 let mut extension = Extension::instantiate_async(
607 &executor,
608 &mut store,
609 this.release_channel,
610 zed_api_version,
611 &component,
612 )
613 .await?;
614
615 extension
616 .call_init_extension(&mut store)
617 .await
618 .context("failed to initialize wasm extension")?;
619
620 let (tx, mut rx) = mpsc::unbounded::<ExtensionCall>();
621 executor
622 .spawn(async move {
623 while let Some(call) = rx.next().await {
624 (call)(&mut extension, &mut store).await;
625 }
626 })
627 .detach();
628
629 Ok(WasmExtension {
630 manifest: manifest.clone(),
631 work_dir: this.work_dir.join(manifest.id.as_ref()).into(),
632 tx,
633 zed_api_version,
634 })
635 })
636 }
637
638 async fn build_wasi_ctx(&self, manifest: &Arc<ExtensionManifest>) -> Result<wasi::WasiCtx> {
639 let extension_work_dir = self.work_dir.join(manifest.id.as_ref());
640 self.fs
641 .create_dir(&extension_work_dir)
642 .await
643 .context("failed to create extension work dir")?;
644
645 let file_perms = wasi::FilePerms::all();
646 let dir_perms = wasi::DirPerms::all();
647
648 Ok(wasi::WasiCtxBuilder::new()
649 .inherit_stdio()
650 .preopened_dir(&extension_work_dir, ".", dir_perms, file_perms)?
651 .preopened_dir(
652 &extension_work_dir,
653 extension_work_dir.to_string_lossy(),
654 dir_perms,
655 file_perms,
656 )?
657 .env("PWD", extension_work_dir.to_string_lossy())
658 .env("RUST_BACKTRACE", "full")
659 .build())
660 }
661
662 pub fn writeable_path_from_extension(&self, id: &Arc<str>, path: &Path) -> Result<PathBuf> {
663 let extension_work_dir = self.work_dir.join(id.as_ref());
664 let path = normalize_path(&extension_work_dir.join(path));
665 anyhow::ensure!(
666 path.starts_with(&extension_work_dir),
667 "cannot write to path {path:?}",
668 );
669 Ok(path)
670 }
671}
672
673pub fn parse_wasm_extension_version(
674 extension_id: &str,
675 wasm_bytes: &[u8],
676) -> Result<SemanticVersion> {
677 let mut version = None;
678
679 for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
680 if let wasmparser::Payload::CustomSection(s) =
681 part.context("error parsing wasm extension")?
682 {
683 if s.name() == "zed:api-version" {
684 version = parse_wasm_extension_version_custom_section(s.data());
685 if version.is_none() {
686 bail!(
687 "extension {} has invalid zed:api-version section: {:?}",
688 extension_id,
689 s.data()
690 );
691 }
692 }
693 }
694 }
695
696 // The reason we wait until we're done parsing all of the Wasm bytes to return the version
697 // is to work around a panic that can happen inside of Wasmtime when the bytes are invalid.
698 //
699 // By parsing the entirety of the Wasm bytes before we return, we're able to detect this problem
700 // earlier as an `Err` rather than as a panic.
701 version.with_context(|| format!("extension {extension_id} has no zed:api-version section"))
702}
703
704fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<SemanticVersion> {
705 if data.len() == 6 {
706 Some(SemanticVersion::new(
707 u16::from_be_bytes([data[0], data[1]]) as _,
708 u16::from_be_bytes([data[2], data[3]]) as _,
709 u16::from_be_bytes([data[4], data[5]]) as _,
710 ))
711 } else {
712 None
713 }
714}
715
716impl WasmExtension {
717 pub async fn load(
718 extension_dir: &Path,
719 manifest: &Arc<ExtensionManifest>,
720 wasm_host: Arc<WasmHost>,
721 cx: &AsyncApp,
722 ) -> Result<Self> {
723 let path = extension_dir.join("extension.wasm");
724
725 let mut wasm_file = wasm_host
726 .fs
727 .open_sync(&path)
728 .await
729 .context("failed to open wasm file")?;
730
731 let mut wasm_bytes = Vec::new();
732 wasm_file
733 .read_to_end(&mut wasm_bytes)
734 .context("failed to read wasm")?;
735
736 wasm_host
737 .load_extension(wasm_bytes, manifest, cx.background_executor().clone())
738 .await
739 .with_context(|| format!("failed to load wasm extension {}", manifest.id))
740 }
741
742 pub async fn call<T, Fn>(&self, f: Fn) -> T
743 where
744 T: 'static + Send,
745 Fn: 'static
746 + Send
747 + for<'a> FnOnce(&'a mut Extension, &'a mut Store<WasmState>) -> BoxFuture<'a, T>,
748 {
749 let (return_tx, return_rx) = oneshot::channel();
750 self.tx
751 .unbounded_send(Box::new(move |extension, store| {
752 async {
753 let result = f(extension, store).await;
754 return_tx.send(result).ok();
755 }
756 .boxed()
757 }))
758 .expect("wasm extension channel should not be closed yet");
759 return_rx.await.expect("wasm extension channel")
760 }
761}
762
763impl WasmState {
764 fn on_main_thread<T, Fn>(&self, f: Fn) -> impl 'static + Future<Output = T>
765 where
766 T: 'static + Send,
767 Fn: 'static + Send + for<'a> FnOnce(&'a mut AsyncApp) -> LocalBoxFuture<'a, T>,
768 {
769 let (return_tx, return_rx) = oneshot::channel();
770 self.host
771 .main_thread_message_tx
772 .clone()
773 .unbounded_send(Box::new(move |cx| {
774 async {
775 let result = f(cx).await;
776 return_tx.send(result).ok();
777 }
778 .boxed_local()
779 }))
780 .expect("main thread message channel should not be closed yet");
781 async move { return_rx.await.expect("main thread message channel") }
782 }
783
784 fn work_dir(&self) -> PathBuf {
785 self.host.work_dir.join(self.manifest.id.as_ref())
786 }
787
788 fn extension_error(&self, message: String) -> anyhow::Error {
789 anyhow!(
790 "from extension \"{}\" version {}: {}",
791 self.manifest.name,
792 self.manifest.version,
793 message
794 )
795 }
796}
797
798impl wasi::WasiView for WasmState {
799 fn table(&mut self) -> &mut ResourceTable {
800 &mut self.table
801 }
802
803 fn ctx(&mut self) -> &mut wasi::WasiCtx {
804 &mut self.ctx
805 }
806}
807
808/// Wrapper around a mini-moka bounded cache for storing incremental compilation artifacts.
809/// Since wasm modules have many similar elements, this can save us a lot of work at the
810/// cost of a small memory footprint. However, we don't want this to be unbounded, so we use
811/// a LFU/LRU cache to evict less used cache entries.
812#[derive(Debug)]
813struct IncrementalCompilationCache {
814 cache: Cache<Vec<u8>, Vec<u8>>,
815}
816
817impl IncrementalCompilationCache {
818 fn new() -> Self {
819 let cache = Cache::builder()
820 // Cap this at 32 MB for now. Our extensions turn into roughly 512kb in the cache,
821 // which means we could store 64 completely novel extensions in the cache, but in
822 // practice we will more than that, which is more than enough for our use case.
823 .max_capacity(32 * 1024 * 1024)
824 .weigher(|k: &Vec<u8>, v: &Vec<u8>| (k.len() + v.len()).try_into().unwrap_or(u32::MAX))
825 .build();
826 Self { cache }
827 }
828}
829
830impl CacheStore for IncrementalCompilationCache {
831 fn get(&self, key: &[u8]) -> Option<Cow<'_, [u8]>> {
832 self.cache.get(key).map(|v| v.into())
833 }
834
835 fn insert(&self, key: &[u8], value: Vec<u8>) -> bool {
836 self.cache.insert(key.to_vec(), value);
837 true
838 }
839}