wasm_host.rs

  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}