mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-28 15:44:20 +03:00
Start working on host-side functions
This commit is contained in:
parent
71e0555763
commit
53e56f1284
@ -49,5 +49,5 @@ impl __Buffer {
|
|||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use super::{__Buffer, __alloc_buffer};
|
pub use super::{__Buffer, __alloc_buffer};
|
||||||
pub use plugin_macros::bind;
|
pub use plugin_macros::{export, import};
|
||||||
}
|
}
|
||||||
|
@ -5,15 +5,15 @@ use quote::{format_ident, quote};
|
|||||||
use syn::{parse_macro_input, FnArg, ItemFn, Type, Visibility};
|
use syn::{parse_macro_input, FnArg, ItemFn, Type, Visibility};
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream {
|
pub fn export(args: TokenStream, function: TokenStream) -> TokenStream {
|
||||||
if !args.is_empty() {
|
if !args.is_empty() {
|
||||||
panic!("The bind attribute does not take any arguments");
|
panic!("The export attribute does not take any arguments");
|
||||||
}
|
}
|
||||||
|
|
||||||
let inner_fn = parse_macro_input!(function as ItemFn);
|
let inner_fn = parse_macro_input!(function as ItemFn);
|
||||||
if let Visibility::Public(_) = inner_fn.vis {
|
if let Visibility::Public(_) = inner_fn.vis {
|
||||||
} else {
|
} else {
|
||||||
panic!("The bind attribute only works for public functions");
|
panic!("The export attribute only works for public functions");
|
||||||
}
|
}
|
||||||
|
|
||||||
let inner_fn_name = format_ident!("{}", inner_fn.sig.ident);
|
let inner_fn_name = format_ident!("{}", inner_fn.sig.ident);
|
||||||
@ -53,6 +53,7 @@ pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream {
|
|||||||
#inner_fn
|
#inner_fn
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
// TODO: switch len from usize to u32?
|
||||||
pub extern "C" fn #outer_fn_name(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer {
|
pub extern "C" fn #outer_fn_name(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer {
|
||||||
// setup
|
// setup
|
||||||
let buffer = ::plugin::__Buffer { ptr, len };
|
let buffer = ::plugin::__Buffer { ptr, len };
|
||||||
@ -73,3 +74,70 @@ pub fn bind(args: TokenStream, function: TokenStream) -> TokenStream {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn import(args: TokenStream, function: TokenStream) -> TokenStream {
|
||||||
|
todo!()
|
||||||
|
// if !args.is_empty() {
|
||||||
|
// panic!("The import attribute does not take any arguments");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let inner_fn = parse_macro_input!(function as ItemFn);
|
||||||
|
|
||||||
|
// let inner_fn_name = format_ident!("{}", inner_fn.sig.ident);
|
||||||
|
// // let outer_fn_name = format_ident!("__{}", inner_fn_name);
|
||||||
|
|
||||||
|
// let variadic = inner_fn.sig.inputs.len();
|
||||||
|
// let i = (0..variadic).map(syn::Index::from);
|
||||||
|
// let t: Vec<Type> = inner_fn
|
||||||
|
// .sig
|
||||||
|
// .inputs
|
||||||
|
// .iter()
|
||||||
|
// .map(|x| match x {
|
||||||
|
// FnArg::Receiver(_) => {
|
||||||
|
// panic!("all arguments must have specified types, no `self` allowed")
|
||||||
|
// }
|
||||||
|
// FnArg::Typed(item) => *item.ty.clone(),
|
||||||
|
// })
|
||||||
|
// .collect();
|
||||||
|
|
||||||
|
// // this is cursed...
|
||||||
|
// let (args, ty) = if variadic != 1 {
|
||||||
|
// (
|
||||||
|
// quote! {
|
||||||
|
// #( data.#i ),*
|
||||||
|
// },
|
||||||
|
// quote! {
|
||||||
|
// ( #( #t ),* )
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
// } else {
|
||||||
|
// let ty = &t[0];
|
||||||
|
// (quote! { data }, quote! { #ty })
|
||||||
|
// };
|
||||||
|
|
||||||
|
// TokenStream::from(quote! {
|
||||||
|
// #[no_mangle]
|
||||||
|
// #inner_fn
|
||||||
|
|
||||||
|
// #[no_mangle]
|
||||||
|
// pub extern "C" fn #outer_fn_name(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer {
|
||||||
|
// // setup
|
||||||
|
// let buffer = ::plugin::__Buffer { ptr, len };
|
||||||
|
// let data = unsafe { buffer.to_vec() };
|
||||||
|
|
||||||
|
// // operation
|
||||||
|
// let data: #ty = match ::plugin::bincode::deserialize(&data) {
|
||||||
|
// Ok(d) => d,
|
||||||
|
// Err(e) => panic!("Data passed to function not deserializable."),
|
||||||
|
// };
|
||||||
|
// let result = #inner_fn_name(#args);
|
||||||
|
// let new_data: Result<Vec<u8>, _> = ::plugin::bincode::serialize(&result);
|
||||||
|
// let new_data = new_data.unwrap();
|
||||||
|
|
||||||
|
// // teardown
|
||||||
|
// let new_buffer = unsafe { ::plugin::__Buffer::from_vec(new_data) };
|
||||||
|
// return new_buffer.leak_to_heap();
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
use std::{fs::File, marker::PhantomData, path::Path};
|
use std::{
|
||||||
|
collections::HashMap, fs::File, future::Future, marker::PhantomData, path::Path, pin::Pin,
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
|
||||||
use wasi_common::{dir, file};
|
use wasi_common::{dir, file};
|
||||||
use wasmtime::{Config, Engine, Instance, Linker, Module, Store, TypedFunc};
|
use wasmtime::IntoFunc;
|
||||||
|
use wasmtime::{Caller, Config, Engine, Instance, Linker, Module, Store, TypedFunc};
|
||||||
use wasmtime_wasi::{Dir, WasiCtx, WasiCtxBuilder};
|
use wasmtime_wasi::{Dir, WasiCtx, WasiCtxBuilder};
|
||||||
|
|
||||||
pub struct WasiResource(u32);
|
pub struct WasiResource(u32);
|
||||||
@ -41,9 +44,93 @@ pub struct Wasi {
|
|||||||
// free_buffer: TypedFunc<(u32, u32), ()>,
|
// free_buffer: TypedFunc<(u32, u32), ()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// type signature derived from:
|
||||||
|
// https://docs.rs/wasmtime/latest/wasmtime/struct.Linker.html#method.func_wrap2_async
|
||||||
|
// macro_rules! dynHostFunction {
|
||||||
|
// () => {
|
||||||
|
// Box<
|
||||||
|
// dyn for<'a> Fn(Caller<'a, WasiCtx>, u32, u32)
|
||||||
|
// -> Box<dyn Future<Output = u32> + Send + 'a>
|
||||||
|
// + Send
|
||||||
|
// + Sync
|
||||||
|
// + 'static
|
||||||
|
// >
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// macro_rules! implHostFunction {
|
||||||
|
// () => {
|
||||||
|
// impl for<'a> Fn(Caller<'a, WasiCtx>, u32, u32)
|
||||||
|
// -> Box<dyn Future<Output = u32> + Send + 'a>
|
||||||
|
// + Send
|
||||||
|
// + Sync
|
||||||
|
// + 'static
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// This type signature goodness gracious
|
||||||
|
pub type HostFunction = Box<dyn IntoFunc<WasiCtx, (u32, u32), u32>>;
|
||||||
|
|
||||||
|
pub struct WasiPluginBuilder {
|
||||||
|
host_functions: HashMap<String, HostFunction>,
|
||||||
|
wasi_ctx_builder: WasiCtxBuilder,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasiPluginBuilder {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
WasiPluginBuilder {
|
||||||
|
host_functions: HashMap::new(),
|
||||||
|
wasi_ctx_builder: WasiCtxBuilder::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_default_ctx() -> WasiPluginBuilder {
|
||||||
|
let mut this = Self::new();
|
||||||
|
this.wasi_ctx_builder = this.wasi_ctx_builder.inherit_stdin().inherit_stderr();
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_host_function<A: Serialize, R: DeserializeOwned>(
|
||||||
|
function: impl Fn(A) -> R + Send + Sync + 'static,
|
||||||
|
) -> HostFunction {
|
||||||
|
Box::new(move |ptr, len| {
|
||||||
|
function(todo!());
|
||||||
|
todo!()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn host_function<A: Serialize, R: DeserializeOwned>(
|
||||||
|
mut self,
|
||||||
|
name: &str,
|
||||||
|
function: impl Fn(A) -> R + Send + Sync + 'static,
|
||||||
|
) -> Self {
|
||||||
|
self.host_functions
|
||||||
|
.insert(name.to_string(), Self::wrap_host_function(function));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wasi_ctx(mut self, config: impl FnOnce(WasiCtxBuilder) -> WasiCtxBuilder) -> Self {
|
||||||
|
self.wasi_ctx_builder = config(self.wasi_ctx_builder);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn init<T: AsRef<[u8]>>(self, module: T) -> Result<Wasi, Error> {
|
||||||
|
let plugin = WasiPlugin {
|
||||||
|
module: module.as_ref().to_vec(),
|
||||||
|
wasi_ctx: self.wasi_ctx_builder.build(),
|
||||||
|
host_functions: self.host_functions,
|
||||||
|
};
|
||||||
|
|
||||||
|
Wasi::init(plugin).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a to-be-initialized plugin.
|
||||||
|
/// Please use [`WasiPluginBuilder`], don't use this directly.
|
||||||
pub struct WasiPlugin {
|
pub struct WasiPlugin {
|
||||||
pub module: Vec<u8>,
|
pub module: Vec<u8>,
|
||||||
pub wasi_ctx: WasiCtx,
|
pub wasi_ctx: WasiCtx,
|
||||||
|
pub host_functions: HashMap<String, HostFunction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Wasi {
|
impl Wasi {
|
||||||
@ -66,19 +153,15 @@ impl Wasi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Wasi {
|
impl Wasi {
|
||||||
pub fn default_ctx() -> WasiCtx {
|
async fn init(plugin: WasiPlugin) -> Result<Self, Error> {
|
||||||
WasiCtxBuilder::new()
|
|
||||||
.inherit_stdout()
|
|
||||||
.inherit_stderr()
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn init(plugin: WasiPlugin) -> Result<Self, Error> {
|
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
config.async_support(true);
|
config.async_support(true);
|
||||||
let engine = Engine::new(&config)?;
|
let engine = Engine::new(&config)?;
|
||||||
let mut linker = Linker::new(&engine);
|
let mut linker = Linker::new(&engine);
|
||||||
|
|
||||||
|
linker
|
||||||
|
.func_wrap("env", "__command", |x: u32, y: u32| x + y)
|
||||||
|
.unwrap();
|
||||||
linker.func_wrap("env", "__hello", |x: u32| x * 2).unwrap();
|
linker.func_wrap("env", "__hello", |x: u32| x * 2).unwrap();
|
||||||
linker.func_wrap("env", "__bye", |x: u32| x / 2).unwrap();
|
linker.func_wrap("env", "__bye", |x: u32| x / 2).unwrap();
|
||||||
|
|
||||||
|
@ -6,17 +6,21 @@ use futures::{future::BoxFuture, FutureExt, StreamExt};
|
|||||||
use gpui::executor::{self, Background};
|
use gpui::executor::{self, Background};
|
||||||
use isahc::http::version;
|
use isahc::http::version;
|
||||||
use language::{LanguageServerName, LspAdapter};
|
use language::{LanguageServerName, LspAdapter};
|
||||||
use plugin_runtime::{Wasi, WasiFn, WasiPlugin};
|
use plugin_runtime::{Wasi, WasiFn, WasiPlugin, WasiPluginBuilder};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::{any::Any, path::PathBuf, sync::Arc};
|
use std::{any::Any, path::PathBuf, sync::Arc};
|
||||||
use util::{ResultExt, TryFutureExt};
|
use util::{ResultExt, TryFutureExt};
|
||||||
|
|
||||||
pub async fn new_json(executor: Arc<Background>) -> Result<PluginLspAdapter> {
|
pub async fn new_json(executor: Arc<Background>) -> Result<PluginLspAdapter> {
|
||||||
let plugin = WasiPlugin {
|
let plugin = WasiPluginBuilder::new_with_default_ctx()
|
||||||
module: include_bytes!("../../../../plugins/bin/json_language.wasm").to_vec(),
|
.host_function("command", |command: String| {
|
||||||
wasi_ctx: Wasi::default_ctx(),
|
// TODO: actual thing
|
||||||
};
|
std::process::Command::new(command).output().unwrap();
|
||||||
|
Some("Hello".to_string())
|
||||||
|
})
|
||||||
|
.init(include_bytes!("../../../../plugins/bin/json_language.wasm"))
|
||||||
|
.await?;
|
||||||
PluginLspAdapter::new(plugin, executor).await
|
PluginLspAdapter::new(plugin, executor).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,8 +37,7 @@ pub struct PluginLspAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PluginLspAdapter {
|
impl PluginLspAdapter {
|
||||||
pub async fn new(plugin: WasiPlugin, executor: Arc<Background>) -> Result<Self> {
|
pub async fn new(mut plugin: Wasi, executor: Arc<Background>) -> Result<Self> {
|
||||||
let mut plugin = Wasi::init(plugin).await?;
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name: plugin.function("name")?,
|
name: plugin.function("name")?,
|
||||||
server_args: plugin.function("server_args")?,
|
server_args: plugin.function("server_args")?,
|
||||||
|
@ -5,8 +5,56 @@ use std::fs;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
// #[import]
|
// #[import]
|
||||||
|
// fn command(string: &str) -> Option<String>;
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#[no_mangle]
|
||||||
|
fn __command(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[no_mangle]
|
||||||
|
// // TODO: switch len from usize to u32?
|
||||||
|
// pub extern "C" fn #outer_fn_name(ptr: *const u8, len: usize) -> *const ::plugin::__Buffer {
|
||||||
|
// // setup
|
||||||
|
// let buffer = ::plugin::__Buffer { ptr, len };
|
||||||
|
// let data = unsafe { buffer.to_vec() };
|
||||||
|
|
||||||
|
// // operation
|
||||||
|
// let data: #ty = match ::plugin::bincode::deserialize(&data) {
|
||||||
|
// Ok(d) => d,
|
||||||
|
// Err(e) => panic!("Data passed to function not deserializable."),
|
||||||
|
// };
|
||||||
|
// let result = #inner_fn_name(#args);
|
||||||
|
// let new_data: Result<Vec<u8>, _> = ::plugin::bincode::serialize(&result);
|
||||||
|
// let new_data = new_data.unwrap();
|
||||||
|
|
||||||
|
// // teardown
|
||||||
|
// let new_buffer = unsafe { ::plugin::__Buffer::from_vec(new_data) };
|
||||||
|
// return new_buffer.leak_to_heap();
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
fn command(string: &str) -> Option<String> {
|
fn command(string: &str) -> Option<String> {
|
||||||
None
|
println!("executing command: {}", string);
|
||||||
|
// serialize data
|
||||||
|
let data = string;
|
||||||
|
let data = ::plugin::bincode::serialize(&data).unwrap();
|
||||||
|
let buffer = unsafe { ::plugin::__Buffer::from_vec(data) };
|
||||||
|
let ptr = buffer.ptr;
|
||||||
|
let len = buffer.len;
|
||||||
|
// leak data to heap
|
||||||
|
buffer.leak_to_heap();
|
||||||
|
// call extern function
|
||||||
|
let result = unsafe { __command(ptr, len) };
|
||||||
|
// get result
|
||||||
|
let result = todo!(); // convert into box
|
||||||
|
|
||||||
|
// deserialize data
|
||||||
|
let data: Option<String> = match ::plugin::bincode::deserialize(&data) {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => panic!("Data passed to function not deserializable."),
|
||||||
|
};
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: some sort of macro to generate ABI bindings
|
// TODO: some sort of macro to generate ABI bindings
|
||||||
@ -30,7 +78,7 @@ extern "C" {
|
|||||||
const BIN_PATH: &'static str =
|
const BIN_PATH: &'static str =
|
||||||
"node_modules/vscode-json-languageserver/bin/vscode-json-languageserver";
|
"node_modules/vscode-json-languageserver/bin/vscode-json-languageserver";
|
||||||
|
|
||||||
#[bind]
|
#[export]
|
||||||
pub fn name() -> &'static str {
|
pub fn name() -> &'static str {
|
||||||
// let number = unsafe { hello(27) };
|
// let number = unsafe { hello(27) };
|
||||||
// println!("got: {}", number);
|
// println!("got: {}", number);
|
||||||
@ -39,12 +87,12 @@ pub fn name() -> &'static str {
|
|||||||
"vscode-json-languageserver"
|
"vscode-json-languageserver"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bind]
|
#[export]
|
||||||
pub fn server_args() -> Vec<String> {
|
pub fn server_args() -> Vec<String> {
|
||||||
vec!["--stdio".into()]
|
vec!["--stdio".into()]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bind]
|
#[export]
|
||||||
pub fn fetch_latest_server_version() -> Option<String> {
|
pub fn fetch_latest_server_version() -> Option<String> {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct NpmInfo {
|
struct NpmInfo {
|
||||||
@ -61,7 +109,7 @@ pub fn fetch_latest_server_version() -> Option<String> {
|
|||||||
info.versions.pop()
|
info.versions.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bind]
|
#[export]
|
||||||
pub fn fetch_server_binary(container_dir: PathBuf, version: String) -> Result<PathBuf, String> {
|
pub fn fetch_server_binary(container_dir: PathBuf, version: String) -> Result<PathBuf, String> {
|
||||||
let version_dir = container_dir.join(version.as_str());
|
let version_dir = container_dir.join(version.as_str());
|
||||||
fs::create_dir_all(&version_dir)
|
fs::create_dir_all(&version_dir)
|
||||||
@ -92,7 +140,7 @@ pub fn fetch_server_binary(container_dir: PathBuf, version: String) -> Result<Pa
|
|||||||
Ok(binary_path)
|
Ok(binary_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bind]
|
#[export]
|
||||||
pub fn cached_server_binary(container_dir: PathBuf) -> Option<PathBuf> {
|
pub fn cached_server_binary(container_dir: PathBuf) -> Option<PathBuf> {
|
||||||
let mut last_version_dir = None;
|
let mut last_version_dir = None;
|
||||||
let mut entries = fs::read_dir(&container_dir).ok()?;
|
let mut entries = fs::read_dir(&container_dir).ok()?;
|
||||||
@ -113,17 +161,17 @@ pub fn cached_server_binary(container_dir: PathBuf) -> Option<PathBuf> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bind]
|
#[export]
|
||||||
pub fn label_for_completion(label: String) -> Option<String> {
|
pub fn label_for_completion(label: String) -> Option<String> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bind]
|
#[export]
|
||||||
pub fn initialization_options() -> Option<String> {
|
pub fn initialization_options() -> Option<String> {
|
||||||
Some("{ \"provideFormatter\": true }".to_string())
|
Some("{ \"provideFormatter\": true }".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bind]
|
#[export]
|
||||||
pub fn id_for_language(name: String) -> Option<String> {
|
pub fn id_for_language(name: String) -> Option<String> {
|
||||||
if name == "JSON" {
|
if name == "JSON" {
|
||||||
Some("jsonc".into())
|
Some("jsonc".into())
|
||||||
|
Loading…
Reference in New Issue
Block a user