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