Add basic support for precompiling plugins

Isaac Clayton created

Change summary

crates/plugin_runtime/Cargo.toml            |  3 ++
crates/plugin_runtime/build.rs              | 32 +++++++++++++++++++---
crates/plugin_runtime/src/lib.rs            |  5 ++
crates/plugin_runtime/src/plugin.rs         | 16 ++++++++--
crates/zed/src/languages/language_plugin.rs |  5 ++
5 files changed, 50 insertions(+), 11 deletions(-)

Detailed changes

crates/plugin_runtime/build.rs 🔗

@@ -1,4 +1,5 @@
-use std::path::Path;
+use std::{io::Write, path::Path};
+use wasmtime::{Config, Engine};
 
 fn main() {
     let base = Path::new("../../plugins");
@@ -28,6 +29,8 @@ fn main() {
         .expect("Could not find compiled plugins in target");
     println!("cargo:warning={:?}", binaries);
 
+    let engine = create_engine();
+
     for file in binaries {
         let is_wasm = || {
             let path = file.ok()?.path();
@@ -39,11 +42,30 @@ fn main() {
         };
 
         if let Some(path) = is_wasm() {
-            std::fs::copy(&path, base.join("bin").join(path.file_name().unwrap()))
-                .expect("Could not copy compiled plugin to bin");
+            let out_path = base.join("bin").join(path.file_name().unwrap());
+            std::fs::copy(&path, &out_path).expect("Could not copy compiled plugin to bin");
+            precompile(&out_path, &engine);
         }
     }
+}
+
+fn create_engine() -> Engine {
+    let mut config = Config::default();
+    config.async_support(true);
+    // config.epoch_interruption(true);
+    Engine::new(&config).expect("Could not create engine")
+}
 
-    // TODO: create .wat versions
-    // TODO: optimize with wasm-opt
+fn precompile(path: &Path, engine: &Engine) {
+    let bytes = std::fs::read(path).expect("Could not read wasm module");
+    let compiled = engine
+        .precompile_module(&bytes)
+        .expect("Could not precompile module");
+    let out_path = path.parent().unwrap().join(&format!(
+        "{}.pre",
+        path.file_name().unwrap().to_string_lossy()
+    ));
+    let mut out_file = std::fs::File::create(out_path)
+        .expect("Could not create output file for precompiled module");
+    out_file.write_all(&compiled).unwrap();
 }

crates/plugin_runtime/src/lib.rs 🔗

@@ -51,7 +51,10 @@ mod tests {
                         })
                 })
                 .unwrap()
-                .init(include_bytes!("../../../plugins/bin/test_plugin.wasm"))
+                .init(
+                    false,
+                    include_bytes!("../../../plugins/bin/test_plugin.wasm"),
+                )
                 .await
                 .unwrap();
 

crates/plugin_runtime/src/plugin.rs 🔗

@@ -231,9 +231,9 @@ impl PluginBuilder {
 
     /// Initializes a [`Plugin`] from a given compiled Wasm module.
     /// Both binary (`.wasm`) and text (`.wat`) module formats are supported.
-    pub async fn init<T: AsRef<[u8]>>(self, module: T) -> Result<Plugin, Error> {
+    pub async fn init<T: AsRef<[u8]>>(self, precompiled: bool, module: T) -> Result<Plugin, Error> {
         dbg!("initializing plugin");
-        Plugin::init(module.as_ref().to_vec(), self).await
+        Plugin::init(precompiled, module.as_ref().to_vec(), self).await
     }
 }
 
@@ -297,7 +297,11 @@ impl Plugin {
 }
 
 impl Plugin {
-    async fn init(module: Vec<u8>, plugin: PluginBuilder) -> Result<Self, Error> {
+    async fn init(
+        precompiled: bool,
+        module: Vec<u8>,
+        plugin: PluginBuilder,
+    ) -> Result<Self, Error> {
         dbg!("Initializing new plugin");
         // initialize the WebAssembly System Interface context
         let engine = plugin.engine;
@@ -314,7 +318,11 @@ impl Plugin {
             },
         );
         // store.epoch_deadline_async_yield_and_update(todo!());
-        let module = Module::new(&engine, module)?;
+        let module = if precompiled {
+            unsafe { Module::deserialize(&engine, module)? }
+        } else {
+            Module::new(&engine, module)?
+        };
 
         // load the provided module into the asynchronous runtime
         linker.module_async(&mut store, "", &module).await?;

crates/zed/src/languages/language_plugin.rs 🔗

@@ -21,7 +21,10 @@ pub async fn new_json(executor: Arc<Background>) -> Result<PluginLspAdapter> {
                 .log_err()
                 .map(|output| output.stdout)
         })?
-        .init(include_bytes!("../../../../plugins/bin/json_language.wasm"))
+        .init(
+            true,
+            include_bytes!("../../../../plugins/bin/json_language.wasm.pre"),
+        )
         .await?;
     PluginLspAdapter::new(plugin, executor).await
 }