Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote Debugger #8

Merged
merged 9 commits into from Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 0 additions & 2 deletions .cargo/config.toml

This file was deleted.

8 changes: 7 additions & 1 deletion .devcontainer/Dockerfile
Expand Up @@ -45,5 +45,11 @@ RUN pip3 --disable-pip-version-check --no-cache-dir install mypy wasmtime \

# configure rust
RUN rustup target add wasm32-unknown-unknown wasm32-wasi
RUN cargo install twiggy cargo-wasi cargo-expand mdbook
RUN cargo install \
twiggy \
cargo-wasi \
cargo-expand \
mdbook \
cargo-get \
cargo-workspaces
RUN cargo install --git https://github.com/bytecodealliance/wit-bindgen wit-bindgen-cli
3 changes: 3 additions & 0 deletions .devcontainer/devcontainer.json
Expand Up @@ -15,6 +15,9 @@
}
},

// needed for debugging
"runArgs": ["--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"],

// Set *default* container specific settings.json values on container create.
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python",
Expand Down
13 changes: 13 additions & 0 deletions .vscode/launch.json
@@ -0,0 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "attach",
"name": "Debug",
"program": "${workspaceFolder}/target/debug/debugger",
"preLaunchTask": "setup: debugger",
"postDebugTask": "Terminate All Tasks"
}
]
}
68 changes: 68 additions & 0 deletions .vscode/tasks.json
@@ -0,0 +1,68 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "cargo",
"group": "build",
"label": "build: debugger",
"command": "build",
"args": ["-p", "debugger"],
"problemMatcher": ["$rustc"]
},
{
"type": "cargo",
"group": "build",
"label": "build: rust wasm modules",
"command": "build",
"args": [
"--target=wasm32-wasi",
"--workspace",
"--exclude",
"debugger",
"--exclude",
"debugger-macro"
],
"problemMatcher": ["$rustc"]
},
{
"group": "build",
"label": "setup: debugger",
"dependsOn": ["build: debugger", "build: rust wasm modules"],
"type": "process",
"command": "scripts/debug",
"args": ["${fileDirname}"],
"isBackground": true,
"problemMatcher": [
{
"pattern": [
{
"regexp": ".",
"file": 1,
"location": 2,
"message": 3
}
],
"background": {
"activeOnStart": true,
"beginsPattern": ".",
"endsPattern": "."
}
}
]
},
{
"label": "Terminate All Tasks",
"command": "echo ${input:terminate}",
"type": "shell",
"problemMatcher": []
}
],
"inputs": [
{
"id": "terminate",
"type": "command",
"command": "workbench.action.tasks.terminate",
"args": "terminateAll"
}
]
}
2 changes: 1 addition & 1 deletion Cargo.toml
@@ -1,2 +1,2 @@
[workspace]
members = ["examples/rust/*"]
members = ["examples/rust/*", "crates/*"]
18 changes: 18 additions & 0 deletions crates/debugger-macro-impl/Cargo.toml
@@ -0,0 +1,18 @@
[package]
name = "debugger-macro-impl"
version = "0.1.0"
edition = "2021"

[lib]
proc-macro = true

[dependencies]
syn = { version = "1.0", features = [
"full",
"printing",
"visit",
"extra-traits",
"parsing",
] }
quote = "1.0"
proc-macro2 = "1.0"
84 changes: 84 additions & 0 deletions crates/debugger-macro-impl/src/lib.rs
@@ -0,0 +1,84 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we need both?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a long story - short story: proc_macro2 has a bunch of nice extensions which make building proc macros much easier. Mostly related to interpolating in/out of other streams via the quote! macro. Basically, we want to keep everything in TokenStream2 until we finally return it from the proc_macro which has to use TokenStream.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope someone just merges the proc_macro2 crate into upstream at some hypothetical future point in time since it's pretty silly. (there are lots of reasons that hasn't happened and may not ever happen... fun rust compiler stuff)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙃

use quote::quote;
use syn::{parse_macro_input, visit::Visit};

struct Handler {
name: String,
src: TokenStream2,
}

struct HandleVisitor {
impl_trait: syn::Path,
impl_type: syn::Type,
handlers: Vec<Handler>,
}

impl<'ast> Visit<'ast> for HandleVisitor {
fn visit_impl_item_method(&mut self, node: &'ast syn::ImplItemMethod) {
let sig = &node.sig;
let name = &sig.ident;

let typed_args = sig.inputs.iter().filter_map(|input| match input {
syn::FnArg::Typed(x) => Some((*x.ty).clone()),
_ => None,
});
let indexes = (0..typed_args.clone().count()).map(syn::Index::from);

let args = quote! { (#(#typed_args,)*) };
let args_splat = quote! { #(args.#indexes),* };

let impl_trait = &self.impl_trait;
let impl_type = &self.impl_type;

self.handlers.push(Handler {
name: name.to_string(),
src: quote! {
let args: #args = serde_json::from_slice(&json).unwrap();
let result = <#impl_type as #impl_trait>::#name(#args_splat);
serde_json::to_vec(&result).unwrap()
},
});

syn::visit::visit_impl_item_method(self, node);
}
}

#[proc_macro_attribute]
pub fn export_debug_handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as syn::ItemImpl);

let (_, path, _) = input.trait_.as_ref().expect("expected trait");

let mut visitor = HandleVisitor {
impl_trait: path.clone(),
impl_type: (*input.self_ty).clone(),
handlers: vec![],
};
visitor.visit_item_impl(&input);

let handle_names = visitor.handlers.iter().map(|h| &h.name);
let handle_srcs = visitor.handlers.iter().map(|h| &h.src);

let debugger_wit = include_str!("../../debugger/debugger.wit");

quote! {
#input

use ::debugger_macro::serde_json;

struct DebuggerImpl;
wit_bindgen_rust::export!({
src["debugger_impl"]: #debugger_wit
});
impl debugger_impl::DebuggerImpl for DebuggerImpl {
fn handle_json(name: String, json: Vec<u8>) -> Vec<u8> {
match name.as_str() {
#(#handle_names => {#handle_srcs})*
_ => panic!("unknown handler")
}
}
}
}
.into()
}
9 changes: 9 additions & 0 deletions crates/debugger-macro/Cargo.toml
@@ -0,0 +1,9 @@
[package]
name = "debugger-macro"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
debugger-macro-impl = { path = "../debugger-macro-impl" }
2 changes: 2 additions & 0 deletions crates/debugger-macro/src/lib.rs
@@ -0,0 +1,2 @@
pub use ::serde_json;
pub use debugger_macro_impl::export_debug_handler;
17 changes: 17 additions & 0 deletions crates/debugger/Cargo.toml
@@ -0,0 +1,17 @@
[package]
name = "debugger"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
regex = "1"
anyhow = "1.0"
wasmtime = "0.35.3"
wasmtime-wasi = "0.35.3"
wit-bindgen-wasmtime = { git = "https://github.com/bytecodealliance/wit-bindgen.git" }
tide = "0.16.0"
async-std = { version = "1.8.0", features = ["attributes"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"