From 38d73215115f949f3f983dc0184b8d3a0c26bf8e Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 6 Jun 2022 11:00:05 +0200 Subject: [PATCH] Remove non-WASI code --- Cargo.lock | 44 ----- crates/plugin_macros/src/lib.rs | 25 ++- crates/plugin_runtime/Cargo.toml | 4 +- crates/plugin_runtime/README.md | 48 ++++- crates/plugin_runtime/build.rs | 23 --- crates/plugin_runtime/heck.txt | 0 crates/plugin_runtime/plugin/Cargo.lock | 82 -------- crates/plugin_runtime/plugin/Cargo.toml | 2 - crates/plugin_runtime/plugin/cargo_test.lua | 16 -- .../plugin/cargo_test/Cargo.lock | 7 - .../plugin/cargo_test/Cargo.toml | 9 - .../plugin/cargo_test/src/main.rs | 14 -- crates/plugin_runtime/src/lib.rs | 14 -- crates/plugin_runtime/src/lua.rs | 62 ------ crates/plugin_runtime/src/main.rs | 53 ------ crates/plugin_runtime/src/runtime.rs | 77 -------- crates/plugin_runtime/src/wasi.rs | 62 +++--- crates/plugin_runtime/src/wasm.rs | 177 ------------------ crates/zed/src/languages/language_plugin.rs | 18 +- plugins/Cargo.lock | 25 +++ plugins/json_language/Cargo.toml | 4 +- plugins/json_language/src/lib.rs | 153 +++++++-------- script/build-plugins | 4 +- 23 files changed, 211 insertions(+), 712 deletions(-) delete mode 100644 crates/plugin_runtime/build.rs delete mode 100644 crates/plugin_runtime/heck.txt delete mode 100644 crates/plugin_runtime/plugin/Cargo.lock delete mode 100644 crates/plugin_runtime/plugin/Cargo.toml delete mode 100644 crates/plugin_runtime/plugin/cargo_test.lua delete mode 100644 crates/plugin_runtime/plugin/cargo_test/Cargo.lock delete mode 100644 crates/plugin_runtime/plugin/cargo_test/Cargo.toml delete mode 100644 crates/plugin_runtime/plugin/cargo_test/src/main.rs delete mode 100644 crates/plugin_runtime/src/lua.rs delete mode 100644 crates/plugin_runtime/src/main.rs delete mode 100644 crates/plugin_runtime/src/runtime.rs delete mode 100644 crates/plugin_runtime/src/wasm.rs diff --git a/Cargo.lock b/Cargo.lock index 35e88625f3..776f449779 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2936,24 +2936,6 @@ dependencies = [ "url", ] -[[package]] -name = "lua-src" -version = "544.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708ba3c844d5e9d38def4a09dd871c17c370f519b3c4b7261fbabe4a613a814c" -dependencies = [ - "cc", -] - -[[package]] -name = "luajit-src" -version = "210.4.0+resty124ff8d" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76fb2e2c0c7192e18719d321c9a148f7625c4dcbe3df5f4c19e685e4c286f6c" -dependencies = [ - "cc", -] - [[package]] name = "mach" version = "0.3.2" @@ -2972,12 +2954,6 @@ dependencies = [ "libc", ] -[[package]] -name = "map-macro" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308eec36c376312771da349d880afd5f092533499dbe9beb7bcba8c15eabe2c4" - [[package]] name = "matchers" version = "0.1.0" @@ -3202,24 +3178,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "mlua" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82d0b12c7c8d3bdda5933d2aa322c76e4833d822796495f299990ca652bd1bf" -dependencies = [ - "bstr", - "cc", - "erased-serde", - "lua-src", - "luajit-src", - "num-traits", - "once_cell", - "pkg-config", - "rustc-hash", - "serde", -] - [[package]] name = "more-asserts" version = "0.2.2" @@ -3759,8 +3717,6 @@ version = "0.1.0" dependencies = [ "anyhow", "bincode", - "map-macro", - "mlua", "serde", "wasmtime", "wasmtime-wasi", diff --git a/crates/plugin_macros/src/lib.rs b/crates/plugin_macros/src/lib.rs index 6cc4b6ea08..19f08a10b8 100644 --- a/crates/plugin_macros/src/lib.rs +++ b/crates/plugin_macros/src/lib.rs @@ -20,14 +20,21 @@ pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { let outer_fn_name = format_ident!("__{}", inner_fn_name); let variadic = inner_fn.sig.inputs.len(); - let mut args = vec![]; - for i in 0..variadic { - let ident = format_ident!("__{}_arg_{}", inner_fn_name, i); - args.push(ident); - } + let i = (0..variadic).map(syn::Index::from); + let t = (0..variadic).map(|_| quote! { _ }); - let args = quote! { - ( #(args ,) )* + // this is cursed... + let (args, ty) = if variadic != 1 { + ( + quote! { + #( data.#i ),* + }, + quote! { + ( #( #t ),* ) + }, + ) + } else { + (quote! { data }, quote! { _ }) }; TokenStream::from(quote! { @@ -41,8 +48,8 @@ pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream { let data = unsafe { buffer.to_vec() }; // operation - let #args = ::plugin::bincode::deserialize(&data).unwrap(); - let result = #inner_fn_name #args; + let data: #ty = ::plugin::bincode::deserialize(&data).unwrap(); + let result = #inner_fn_name(#args); let new_data: Result, _> = ::plugin::bincode::serialize(&result); let new_data = new_data.unwrap(); diff --git a/crates/plugin_runtime/Cargo.toml b/crates/plugin_runtime/Cargo.toml index c3a99ca007..f37147c00b 100644 --- a/crates/plugin_runtime/Cargo.toml +++ b/crates/plugin_runtime/Cargo.toml @@ -4,10 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -mlua = { version = "0.8.0-beta.5", features = ["lua54", "vendored", "serialize"] } -serde = "1.0" -map-macro = "0.2" wasmtime = "0.37.0" wasmtime-wasi = "0.37.0" anyhow = { version = "1.0", features = ["std"] } +serde = "1.0" bincode = "1.3" diff --git a/crates/plugin_runtime/README.md b/crates/plugin_runtime/README.md index 1ee7de139b..4e6693db4c 100644 --- a/crates/plugin_runtime/README.md +++ b/crates/plugin_runtime/README.md @@ -1,6 +1,48 @@ # Zed's Plugin Runner -This crate contains a fairly generic interface through which plugins may be added to extend the editor. Currently the intention of this plugin runtime is language server definitions. +Wasm plugins can be run through `wasmtime`, with supported for sandboxed system integration through WASI. There are three `plugin` crates that implement different things: -Anything that implements the `Runtime` trait may be used as a plugin. Plugin interfaces are declared by implementing the `Interface` trait. +1. `plugin_runtime` loads and runs compiled `Wasm` plugins, and handles setting up system bindings. -Wasm plugins can be run through `wasmtime`. We plan to add wasi support eventually. We also plan to add macros to generate bindings between Rust plugins compiled to Wasm and the host runtime. \ No newline at end of file +2. `plugin` is the crate that Rust Wasm plugins should depend on. It re-exports some required crates (e.g. `serde`, `bincode`) and provides some necessary macros for generating bindings that `plugin_runtime` can hook into. + +3. `plugin_macros` implements the proc macros required by `plugin`, like the `#[bind]` attribute macro. + +## ABI +The interface between the host Rust runtime ('Runtime') and plugins implemented in Wasm ('Plugin') is pretty simple. + +`Buffer` is a pair of two 4-byte (`u32`) fields: + +``` +struct Buffer { + ptr: u32, + len: u32, +} +``` + +All functions that Plugin exports must have the following properties: + +- Have the signature `fn(ptr: u32, len: u32) -> u32`, where the return type is a pointer to a `Buffer`, and the arguments are the component parts of a `Buffer`. + + - The input `Buffer` will contain the input arguments serialized to `bincode`. + - The output `Buffer` will contain the output arguments serialized to `bincode`. + +- Have a name starting with two underscores. + +Additionally, Plugin must export an: + +- `__alloc_buffer` function that, given a `u32` length, returns a `u32` pointer to a buffer of that length. + +Note that all of these requirements are automatically fullfilled for any Rust Wasm plugin that uses the `plugin` crate, and imports the `prelude`. + +Here's an example Rust Wasm plugin that doubles the value of every float in a `Vec` passed into it: + +```rust +use plugin::prelude::*; + +#[bind] +pub fn double(mut x: Vec) -> Vec { + x.into_iter().map(|x| x * 2.0).collect() +} +``` + +All the serialization code is automatically generated by `#[bind]`. \ No newline at end of file diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs deleted file mode 100644 index 4748d2d23a..0000000000 --- a/crates/plugin_runtime/build.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::process::Command; - -fn main() { - let cwd = std::env::current_dir().unwrap(); - let plugin_workspace = cwd.join("plugin").join("Cargo.toml"); - Command::new("cargo") - .args(&["clean", "--manifest-path"]) - .arg(&plugin_workspace) - .status() - .unwrap(); - Command::new("cargo") - .args(&[ - "build", - "--release", - "--target", - "wasm32-unknown-unknown", - "--manifest-path", - ]) - .arg(&plugin_workspace) - .status() - .unwrap(); - println!("cargo:warning=recompiling plugins") -} diff --git a/crates/plugin_runtime/heck.txt b/crates/plugin_runtime/heck.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/crates/plugin_runtime/plugin/Cargo.lock b/crates/plugin_runtime/plugin/Cargo.lock deleted file mode 100644 index e40b59046a..0000000000 --- a/crates/plugin_runtime/plugin/Cargo.lock +++ /dev/null @@ -1,82 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_test" -version = "0.1.0" -dependencies = [ - "bincode", - "plugin", - "serde", -] - -[[package]] -name = "plugin" -version = "0.1.0" -dependencies = [ - "bincode", - "plugin_macros", - "serde", -] - -[[package]] -name = "plugin_macros" -version = "0.1.0" -dependencies = [ - "bincode", - "proc-macro2", - "quote", - "serde", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "serde" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" - -[[package]] -name = "syn" -version = "1.0.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" diff --git a/crates/plugin_runtime/plugin/Cargo.toml b/crates/plugin_runtime/plugin/Cargo.toml deleted file mode 100644 index 83bca541cf..0000000000 --- a/crates/plugin_runtime/plugin/Cargo.toml +++ /dev/null @@ -1,2 +0,0 @@ -[workspace] -members = ["cargo_test"] \ No newline at end of file diff --git a/crates/plugin_runtime/plugin/cargo_test.lua b/crates/plugin_runtime/plugin/cargo_test.lua deleted file mode 100644 index bdae0401b5..0000000000 --- a/crates/plugin_runtime/plugin/cargo_test.lua +++ /dev/null @@ -1,16 +0,0 @@ -query = [[( - (attribute_item - (meta_item - (identifier) @test)) @attribute - . - (function_item - name: (identifier) @name) @funciton -)]] - -function run_test(name) - local command = 'cargo test -- ' .. name - local openPop = assert(io.popen(command, 'r')) - local output = openPop:read('*all') - openPop:close() - return output -end diff --git a/crates/plugin_runtime/plugin/cargo_test/Cargo.lock b/crates/plugin_runtime/plugin/cargo_test/Cargo.lock deleted file mode 100644 index db8b4f0b9a..0000000000 --- a/crates/plugin_runtime/plugin/cargo_test/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "cargo_test" -version = "0.1.0" diff --git a/crates/plugin_runtime/plugin/cargo_test/Cargo.toml b/crates/plugin_runtime/plugin/cargo_test/Cargo.toml deleted file mode 100644 index 9a94cad78d..0000000000 --- a/crates/plugin_runtime/plugin/cargo_test/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "cargo_test" -version = "0.1.0" -edition = "2021" - -[dependencies] -serde = "1.0" -bincode = "1.3" -plugin = { path = "../../../plugin" } diff --git a/crates/plugin_runtime/plugin/cargo_test/src/main.rs b/crates/plugin_runtime/plugin/cargo_test/src/main.rs deleted file mode 100644 index 65abd38399..0000000000 --- a/crates/plugin_runtime/plugin/cargo_test/src/main.rs +++ /dev/null @@ -1,14 +0,0 @@ -use plugin::prelude::*; - -#[bind] -pub fn sum_lengths(strings: Vec) -> usize { - let mut total = 0; - for string in strings { - total += string.len(); - } - return total; -} - -pub fn main() -> () { - () -} diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index cac99797bf..460e824da3 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -1,16 +1,2 @@ -use mlua::{Function, Lua, LuaSerdeExt, Value}; -use serde::{de::DeserializeOwned, Serialize}; - -pub use map_macro::{map, set}; - -pub mod runtime; -pub use runtime::*; - -pub mod lua; -pub use lua::*; - -pub mod wasm; -pub use wasm::*; - pub mod wasi; pub use wasi::*; diff --git a/crates/plugin_runtime/src/lua.rs b/crates/plugin_runtime/src/lua.rs deleted file mode 100644 index f2324c7ee4..0000000000 --- a/crates/plugin_runtime/src/lua.rs +++ /dev/null @@ -1,62 +0,0 @@ -use mlua::Result; - -use crate::*; - -impl Runtime for Lua { - type Plugin = LuaPlugin; - type Error = mlua::Error; - - fn init(module: Self::Plugin) -> Result { - let lua = Lua::new(); - - // for action in module.actions { - // action(&mut lua).ok()?; - // } - - lua.load(&module.source).exec()?; - return Ok(lua); - } - - // fn constant(&mut self, handle: &Handle) -> Result { - // let val: Value = self.globals().get(handle.inner())?; - // Ok(self.from_value(val)?) - // } - - fn call(&mut self, handle: &str, arg: A) -> Result { - let fun: Function = self.globals().get(handle.to_string())?; - let arg: Value = self.to_value(&arg)?; - let result = fun.call(arg)?; - Ok(self.from_value(result)?) - } - - // fn register_handle>(&mut self, name: T) -> bool { - // self.globals() - // .contains_key(name.as_ref().to_string()) - // .unwrap_or(false) - // } -} - -pub struct LuaPlugin { - // name: String, - source: String, - // actions: Vec Result<(), ()>>>, -} - -impl LuaPlugin { - pub fn new( - // name: String, - source: String, - ) -> LuaPlugin { - LuaPlugin { - // name, - source, - // actions: Vec::new(), - } - } - - // pub fn setup(mut self, action: fn(&mut Lua) -> Result<(), ()>) -> LuaPlugin { - // let action = Box::new(action); - // self.actions.push(action); - // self - // } -} diff --git a/crates/plugin_runtime/src/main.rs b/crates/plugin_runtime/src/main.rs deleted file mode 100644 index 4819c1420a..0000000000 --- a/crates/plugin_runtime/src/main.rs +++ /dev/null @@ -1,53 +0,0 @@ -use mlua::Lua; - -use plugin_runtime::*; - -pub fn main() -> anyhow::Result<()> { - let plugin = WasmPlugin { - source_bytes: include_bytes!( - "../plugin/target/wasm32-unknown-unknown/release/cargo_test.wasm" - ) - .to_vec(), - }; - - let mut sum = Wasm::init(plugin)?; - let strings = "I hope you have a nice day" - .split(" ") - .map(|x| x.to_string()) - .collect(); - let result = sum.sum_lengths(strings); - - dbg!(result); - - Ok(()) -} - -// struct SumLengths { -// sum_lengths: Handle, -// } - -// impl Interface for SumLengths { -// fn from_runtime(runtime: &mut T) -> Option { -// Some(SumLengths { -// sum_lengths: runtime.handle_for("sum_lengths")?, -// }) -// } -// } - -// impl SumLengths { -// fn sum_lengths(&self, runtime: &mut T, strings: Vec) -> Option { -// runtime.call(&self.sum_lengths, strings).ok() -// } -// } - -// #[plugin::interface] -trait SumLengths { - fn sum_lengths(&mut self, strings: Vec) -> usize; -} - -impl SumLengths for T { - fn sum_lengths(&mut self, strings: Vec) -> usize { - let result = self.call("sum_lengths", strings).ok().unwrap(); - return result; - } -} diff --git a/crates/plugin_runtime/src/runtime.rs b/crates/plugin_runtime/src/runtime.rs deleted file mode 100644 index 89305a309f..0000000000 --- a/crates/plugin_runtime/src/runtime.rs +++ /dev/null @@ -1,77 +0,0 @@ -// use std::Error; - -use serde::{de::DeserializeOwned, Serialize}; - -// /// Represents a handle to a constant or function in the Runtime. -// /// Should be constructed by calling [`Runtime::handle_for`]. -// #[derive(Debug, Clone, Hash, PartialEq, Eq)] -// pub struct Handle(String); - -// impl Handle { -// pub fn inner(&self) -> &str { -// &self.0 -// } -// } - -// /// Represents an interface that can be implemented by a plugin. -// pub trait Interface -// where -// Self: Sized, -// { -// /// Create an interface from a given runtime. -// /// All handles to be used by the interface should be registered and stored in `Self`. -// fn from_runtime(runtime: &mut T) -> Option; -// } - -pub trait Runtime -where - Self: Sized, -{ - /// Represents a plugin to be loaded by the runtime, - /// e.g. some source code + anything else needed to set up. - type Plugin; - - /// The error type for this module. - /// Ideally should implement the [`std::err::Error`] trait. - type Error; - - /// Initializes a plugin, returning a [`Runtime`] that can be queried. - /// Note that if you have any configuration, - fn init(plugin: Self::Plugin) -> Result; - - // /// Returns a top-level constant from the module. - // /// This can be used to extract configuration information from the module, for example. - // /// Before calling this function, get a handle into the runtime using [`handle_for`]. - // fn constant(&mut self, handle: &Handle) -> Result; - - /// Call a function defined in the module. - fn call( - &mut self, - handle: &str, - arg: A, - ) -> Result; - - // /// Registers a handle with the runtime. - // /// This is a mutable item if needed, but generally - // /// this should be an immutable operation. - // /// Returns whether the handle exists/was successfully registered. - // fn register_handle>(&mut self, name: T) -> bool; - - // /// Returns the handle for a given name if the handle is defined. - // /// Will only return an error if there was an error while trying to register the handle. - // /// This function uses [`register_handle`], no need to implement this one. - // fn handle_for>(&mut self, name: T) -> Option { - // if self.register_handle(&name) { - // Some(Handle(name.as_ref().to_string())) - // } else { - // None - // } - // } - - // /// Creates the given interface from the current module. - // /// Returns [`Error`] if the provided plugin does not match the expected interface. - // /// Essentially wraps the [`Interface`] trait. - // fn as_interface(&mut self) -> Option { - // Interface::from_runtime(self) - // } -} diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/wasi.rs index 745b6364fa..c2747c498a 100644 --- a/crates/plugin_runtime/src/wasi.rs +++ b/crates/plugin_runtime/src/wasi.rs @@ -1,11 +1,10 @@ -use std::collections::HashMap; +use std::{collections::HashMap, fs::File, path::Path}; -use anyhow::anyhow; +use anyhow::{anyhow, Error}; +use serde::{de::DeserializeOwned, Serialize}; use wasmtime::{Engine, Func, Instance, Linker, Memory, MemoryType, Module, Store, TypedFunc}; -use wasmtime_wasi::{WasiCtx, WasiCtxBuilder}; - -use crate::*; +use wasmtime_wasi::{dir, Dir, WasiCtx, WasiCtxBuilder}; pub struct Wasi { engine: Engine, @@ -17,7 +16,8 @@ pub struct Wasi { } pub struct WasiPlugin { - pub source_bytes: Vec, + pub module: Vec, + pub wasi_ctx: WasiCtx, } impl Wasi { @@ -39,24 +39,23 @@ impl Wasi { } } -impl Runtime for Wasi { - type Plugin = WasiPlugin; - type Error = anyhow::Error; +impl Wasi { + pub fn default_ctx() -> WasiCtx { + WasiCtxBuilder::new() + .inherit_stdout() + .inherit_stderr() + .build() + } - fn init(plugin: WasiPlugin) -> Result { + pub fn init(plugin: WasiPlugin) -> Result { let engine = Engine::default(); let mut linker = Linker::new(&engine); println!("linking"); wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; println!("linked"); - let wasi = WasiCtxBuilder::new() - .inherit_stdout() - .inherit_stderr() - .build(); - - let mut store: Store<_> = Store::new(&engine, wasi); + let mut store: Store<_> = Store::new(&engine, plugin.wasi_ctx); println!("moduling"); - let module = Module::new(&engine, plugin.source_bytes)?; + let module = Module::new(&engine, plugin.module)?; println!("moduled"); linker.module(&mut store, "", &module)?; @@ -78,13 +77,20 @@ impl Runtime for Wasi { }) } - // fn constant(&mut self, handle: &Handle) -> Result { - // let export = self - // .instance - // .get_export(&mut self.store, handle.inner()) - // .ok_or_else(|| anyhow!("Could not get export"))?; + pub fn attach_file>(&mut self, path: T) -> Result<(), Error> { + let ctx = self.store.data_mut(); + let file = File::open(&path).unwrap(); + let dir = Dir::from_std_file(file); + // this is a footgun and a half. + let dir = dir::Dir::from_cap_std(dir); + ctx.push_preopened_dir(Box::new(dir), path)?; + Ok(()) + } - // todo!() + // pub fn remove_file>(&mut self, path: T) -> Result<(), Error> { + // let ctx = self.store.data_mut(); + // ctx.remove + // Ok(()) // } // So this call function is kinda a dance, I figured it'd be a good idea to document it. @@ -131,11 +137,11 @@ impl Runtime for Wasi { // so the heap is still valid for our inspection when we want to pull things out. // TODO: dont' use as for conversions - fn call( + pub fn call( &mut self, handle: &str, arg: A, - ) -> Result { + ) -> Result { // serialize the argument using bincode let arg = bincode::serialize(&arg)?; let arg_buffer_len = arg.len(); @@ -185,10 +191,4 @@ impl Runtime for Wasi { return Ok(result); } - - // fn register_handle>(&mut self, name: T) -> bool { - // self.instance - // .get_export(&mut self.store, name.as_ref()) - // .is_some() - // } } diff --git a/crates/plugin_runtime/src/wasm.rs b/crates/plugin_runtime/src/wasm.rs deleted file mode 100644 index 34064d975e..0000000000 --- a/crates/plugin_runtime/src/wasm.rs +++ /dev/null @@ -1,177 +0,0 @@ -use std::collections::HashMap; - -use anyhow::anyhow; - -use wasmtime::{Engine, Func, Instance, Memory, MemoryType, Module, Store, TypedFunc}; - -use crate::*; - -pub struct Wasm { - engine: Engine, - module: Module, - store: Store<()>, - instance: Instance, - alloc_buffer: TypedFunc, - // free_buffer: TypedFunc<(i32, i32), ()>, -} - -pub struct WasmPlugin { - pub source_bytes: Vec, -} - -impl Wasm { - pub fn dump_memory(data: &[u8]) { - for (i, byte) in data.iter().enumerate() { - if i % 32 == 0 { - println!(); - } - if i % 4 == 0 { - print!("|"); - } - if *byte == 0 { - print!("__") - } else { - print!("{:02x}", byte); - } - } - println!(); - } -} - -impl Runtime for Wasm { - type Plugin = WasmPlugin; - type Error = anyhow::Error; - - fn init(plugin: WasmPlugin) -> Result { - let engine = Engine::default(); - let module = Module::new(&engine, plugin.source_bytes)?; - let mut store: Store<()> = Store::new(&engine, ()); - let instance = Instance::new(&mut store, &module, &[])?; - - let alloc_buffer = instance.get_typed_func(&mut store, "__alloc_buffer")?; - // let free_buffer = instance.get_typed_func(&mut store, "__free_buffer")?; - - Ok(Wasm { - engine, - module, - store, - instance, - alloc_buffer, - // free_buffer, - }) - } - - // fn constant(&mut self, handle: &Handle) -> Result { - // let export = self - // .instance - // .get_export(&mut self.store, handle.inner()) - // .ok_or_else(|| anyhow!("Could not get export"))?; - - // todo!() - // } - - // So this call function is kinda a dance, I figured it'd be a good idea to document it. - // the high level is we take a serde type, serialize it to a byte array, - // (we're doing this using bincode for now) - // then toss that byte array into webassembly. - // webassembly grabs that byte array, does some magic, - // and serializes the result into yet another byte array. - // we then grab *that* result byte array and deserialize it into a result. - // - // phew... - // - // now the problem is, webassambly doesn't support buffers. - // only really like i32s, that's it (yeah, it's sad. Not even unsigned!) - // (ok, I'm exaggerating a bit). - // - // the Wasm function that this calls must have a very specific signature: - // - // fn(pointer to byte array: i32, length of byte array: i32) - // -> pointer to ( - // pointer to byte_array: i32, - // length of byte array: i32, - // ): i32 - // - // This pair `(pointer to byte array, length of byte array)` is called a `Buffer` - // and can be found in the cargo_test plugin. - // - // so on the wasm side, we grab the two parameters to the function, - // stuff them into a `Buffer`, - // and then pray to the `unsafe` Rust gods above that a valid byte array pops out. - // - // On the flip side, when returning from a wasm function, - // we convert whatever serialized result we get into byte array, - // which we stuff into a Buffer and allocate on the heap, - // which pointer to we then return. - // Note the double indirection! - // - // So when returning from a function, we actually leak memory *twice*: - // - // 1) once when we leak the byte array - // 2) again when we leak the allocated `Buffer` - // - // This isn't a problem because Wasm stops executing after the function returns, - // so the heap is still valid for our inspection when we want to pull things out. - - // TODO: dont' use as for conversions - fn call( - &mut self, - handle: &str, - arg: A, - ) -> Result { - // serialize the argument using bincode - let arg = bincode::serialize(&arg)?; - let arg_buffer_len = arg.len(); - - // allocate a buffer and write the argument to that buffer - let arg_buffer_ptr = self - .alloc_buffer - .call(&mut self.store, arg_buffer_len as i32)?; - let plugin_memory = self - .instance - .get_memory(&mut self.store, "memory") - .ok_or_else(|| anyhow!("Could not grab slice of plugin memory"))?; - plugin_memory.write(&mut self.store, arg_buffer_ptr as usize, &arg)?; - - // get the webassembly function we want to actually call - // TODO: precompute handle - let fun_name = format!("__{}", handle); - let fun = self - .instance - .get_typed_func::<(i32, i32), i32, _>(&mut self.store, &fun_name)?; - - // call the function, passing in the buffer and its length - // this should return a pointer to a (ptr, lentgh) pair - let arg_buffer = (arg_buffer_ptr, arg_buffer_len as i32); - let result_buffer = fun.call(&mut self.store, arg_buffer)?; - - // create a buffer to read the (ptr, length) pair into - // this is a total of 4 + 4 = 8 bytes. - let buffer = &mut [0; 8]; - plugin_memory.read(&mut self.store, result_buffer as usize, buffer)?; - - // use these bytes (wasm stores things little-endian) - // to get a pointer to the buffer and its length - let b = buffer; - let result_buffer_ptr = u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as usize; - let result_buffer_len = u32::from_le_bytes([b[4], b[5], b[6], b[7]]) as usize; - let result_buffer_end = result_buffer_ptr + result_buffer_len; - - // read the buffer at this point into a byte array - // deserialize the byte array into the provided serde type - let result = &plugin_memory.data(&mut self.store)[result_buffer_ptr..result_buffer_end]; - let result = bincode::deserialize(result)?; - - // TODO: this is handled wasm-side, but I'd like to double-check - // // deallocate the argument buffer - // self.free_buffer.call(&mut self.store, arg_buffer); - - return Ok(result); - } - - // fn register_handle>(&mut self, name: T) -> bool { - // self.instance - // .get_export(&mut self.store, name.as_ref()) - // .is_some() - // } -} diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 9d25954487..f4c09c189b 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -4,15 +4,16 @@ use client::http::HttpClient; use futures::{future::BoxFuture, FutureExt, StreamExt}; use language::{LanguageServerName, LspAdapter}; use parking_lot::{Mutex, RwLock}; -use plugin_runtime::{Runtime, Wasi, WasiPlugin}; +use plugin_runtime::{Wasi, WasiPlugin}; use serde_json::json; -use smol::fs; +use std::fs; use std::{any::Any, path::PathBuf, sync::Arc}; use util::{ResultExt, TryFutureExt}; pub fn new_json() -> LanguagePluginLspAdapter { let plugin = WasiPlugin { - source_bytes: include_bytes!("../../../../plugins/bin/json_language.wasm").to_vec(), + module: include_bytes!("../../../../plugins/bin/json_language.wasm").to_vec(), + wasi_ctx: Wasi::default_ctx(), }; LanguagePluginLspAdapter::new(plugin) } @@ -69,10 +70,13 @@ impl LspAdapter for LanguagePluginLspAdapter { container_dir: PathBuf, ) -> BoxFuture<'static, Result> { // TODO: async runtime - let result = self - .runtime - .lock() - .call("fetch_server_binary", container_dir); + let mut runtime = self.runtime.lock(); + let result = runtime.attach_file(&container_dir); + let result = match result { + Ok(_) => runtime.call("fetch_server_binary", container_dir), + Err(e) => Err(e), + }; + async move { result }.boxed() } diff --git a/plugins/Cargo.lock b/plugins/Cargo.lock index e0a9bd2439..a6d421e386 100644 --- a/plugins/Cargo.lock +++ b/plugins/Cargo.lock @@ -11,11 +11,19 @@ dependencies = [ "serde", ] +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + [[package]] name = "json_language" version = "0.1.0" dependencies = [ "plugin", + "serde", + "serde_json", ] [[package]] @@ -56,12 +64,29 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + [[package]] name = "serde" version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "syn" version = "1.0.96" diff --git a/plugins/json_language/Cargo.toml b/plugins/json_language/Cargo.toml index ccb2511960..33415db798 100644 --- a/plugins/json_language/Cargo.toml +++ b/plugins/json_language/Cargo.toml @@ -5,6 +5,8 @@ edition = "2021" [dependencies] plugin = { path = "../../crates/plugin" } +serde = "1.0" +serde_json = "1.0" [lib] -crate-type = ["cdylib"] \ No newline at end of file +crate-type = ["cdylib"] diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index 7801b81b70..9687a09e83 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -1,107 +1,108 @@ use plugin::prelude::*; +use serde_json::json; use std::fs; +use std::path::PathBuf; -#[import] -fn command(string: String) -> Option; +// import +// fn command(string: String) -> Option; #[bind] -pub fn name(_: ()) -> &'static str { - println!("huh, let me see..."); - Command::new("sh") - .arg("-c") - .arg("echo hello") - .output() - .expect("failed to execute process"); +pub fn name() -> &'static str { "vscode-json-languageserver" } #[bind] -pub fn server_args(_: ()) -> Vec { +pub fn server_args() -> Vec { vec!["--stdio".into()] } #[bind] -fn fetch_latest_server_version() -> Option { - #[derive(Deserialize)] - struct NpmInfo { - versions: Vec, - } +pub fn fetch_latest_server_version() -> Option { + // #[derive(Deserialize)] + // struct NpmInfo { + // versions: Vec, + // } - let output = command("npm info vscode-json-languageserver --json")?; - if !output.status.success() { - return None; - } + // let output = command("npm info vscode-json-languageserver --json")?; + // if !output.status.success() { + // return None; + // } - let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; - info.versions.pop() + // let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; + // info.versions.pop() + println!("fetching server version"); + Some("1.3.4".into()) } -#[bind] -pub fn fetch_server_binary(version: String) -> Option { - let version_dir = container_dir.join(version.as_str()); - fs::create_dir_all(&version_dir) - .await - .context("failed to create version directory")?; - let binary_path = version_dir.join(Self::BIN_PATH); +// #[bind] +// pub fn fetch_server_binary(version: String) -> Option { +// let version_dir = container_dir.join(version.as_str()); +// fs::create_dir_all(&version_dir) +// .await +// .context("failed to create version directory")?; +// let binary_path = version_dir.join(Self::BIN_PATH); - if fs::metadata(&binary_path).await.is_err() { - let output = smol::process::Command::new("npm") - .current_dir(&version_dir) - .arg("install") - .arg(format!("vscode-json-languageserver@{}", version)) - .output() - .await - .context("failed to run npm install")?; - if !output.status.success() { - Err(anyhow!("failed to install vscode-json-languageserver"))?; - } +// if fs::metadata(&binary_path).await.is_err() { +// let output = smol::process::Command::new("npm") +// .current_dir(&version_dir) +// .arg("install") +// .arg(format!("vscode-json-languageserver@{}", version)) +// .output() +// .await +// .context("failed to run npm install")?; +// if !output.status.success() { +// Err(anyhow!("failed to install vscode-json-languageserver"))?; +// } - if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { - while let Some(entry) = entries.next().await { - if let Some(entry) = entry.log_err() { - let entry_path = entry.path(); - if entry_path.as_path() != version_dir { - fs::remove_dir_all(&entry_path).await.log_err(); - } - } - } - } - } +// if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { +// while let Some(entry) = entries.next().await { +// if let Some(entry) = entry.log_err() { +// let entry_path = entry.path(); +// if entry_path.as_path() != version_dir { +// fs::remove_dir_all(&entry_path).await.log_err(); +// } +// } +// } +// } +// } - Ok(binary_path) -} +// Ok(binary_path) +// } + +const BIN_PATH: &'static str = + "node_modules/vscode-json-languageserver/bin/vscode-json-languageserver"; + +// #[bind] +// pub fn cached_server_binary(container_dir: PathBuf) -> Option { +// println!("Finding cached server binary..."); +// let mut last_version_dir = None; +// let mut entries = fs::read_dir(&container_dir).ok()?; +// println!("Read Entries..."); +// while let Some(entry) = entries.next() { +// let entry = entry.ok()?; +// if entry.file_type().ok()?.is_dir() { +// last_version_dir = Some(entry.path()); +// } +// } +// let last_version_dir = last_version_dir?; +// let bin_path = last_version_dir.join(BIN_PATH); +// if bin_path.exists() { +// println!("{}", bin_path.display()); +// Some(bin_path) +// } else { +// None +// } +// } #[bind] -pub fn cached_server_binary(container_dir: PathBuf) -> Option { - let mut last_version_dir = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - let entry = entry?; - if entry.file_type().await?.is_dir() { - last_version_dir = Some(entry.path()); - } - } - let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let bin_path = last_version_dir.join(Self::BIN_PATH); - if bin_path.exists() { - Ok(bin_path) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - last_version_dir - )) - } -} - -#[bind] -pub fn initialization_options(_: ()) -> Option { +pub fn initialization_options() -> Option { Some(json!({ "provideFormatter": true })) } #[bind] -fn id_for_language(name: String) -> Option { +pub fn id_for_language(name: String) -> Option { if name == "JSON" { Some("jsonc".into()) } else { diff --git a/script/build-plugins b/script/build-plugins index 8417828c1f..32f9cedc14 100755 --- a/script/build-plugins +++ b/script/build-plugins @@ -2,8 +2,8 @@ set -e -echo "Clearing cached plugins..." -cargo clean --manifest-path plugins/Cargo.toml +# echo "Clearing cached plugins..." +# cargo clean --manifest-path plugins/Cargo.toml echo "Building Wasm plugins..." # cargo build --release --target wasm32-unknown-unknown --manifest-path plugins/Cargo.toml