Public dry run dryrun-20b59dc46b72
Source commit: 20b59dc46b72103c2f8a516c692b5cc3d54fab19 Public tree identity: sha256:aaa5ac7b58b2a0d23c6b11e5f76324bf3839ca1f62653a83deb736932adcfbd9
This commit is contained in:
commit
2bef715211
221 changed files with 79792 additions and 0 deletions
17
crates/disasmer-macros/Cargo.toml
Normal file
17
crates/disasmer-macros/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "disasmer-macros"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
hex.workspace = true
|
||||
proc-macro2.workspace = true
|
||||
quote.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha2.workspace = true
|
||||
syn.workspace = true
|
||||
453
crates/disasmer-macros/src/lib.rs
Normal file
453
crates/disasmer-macros/src/lib.rs
Normal file
|
|
@ -0,0 +1,453 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::{format_ident, quote, ToTokens};
|
||||
use sha2::{Digest as _, Sha256};
|
||||
use syn::{
|
||||
parse::Parser, parse_macro_input, Data, DeriveInput, Expr, Fields, FnArg, ItemFn, Lit,
|
||||
LitByteStr, Meta, ReturnType, Token, Type,
|
||||
};
|
||||
|
||||
#[proc_macro_derive(TaskArg)]
|
||||
pub fn derive_task_arg(item: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(item as DeriveInput);
|
||||
let name = &input.ident;
|
||||
let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
|
||||
let collect =
|
||||
match &input.data {
|
||||
Data::Struct(data) => collect_struct_fields(&data.fields),
|
||||
Data::Enum(data) => {
|
||||
let arms =
|
||||
data.variants.iter().map(|variant| {
|
||||
let variant_name = &variant.ident;
|
||||
match &variant.fields {
|
||||
Fields::Named(fields) => {
|
||||
let field_names = fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|field| field.ident.as_ref().expect("named field"))
|
||||
.collect::<Vec<_>>();
|
||||
let bindings = field_names
|
||||
.iter()
|
||||
.map(|field| format_ident!("__disasmer_{field}"))
|
||||
.collect::<Vec<_>>();
|
||||
let visits = bindings.iter().map(|binding| quote! {
|
||||
::disasmer::__private::CollectTaskHandles::collect_task_handles(
|
||||
#binding,
|
||||
handles,
|
||||
)?;
|
||||
});
|
||||
quote! {
|
||||
Self::#variant_name { #(#field_names: #bindings),* } => {
|
||||
#(#visits)*
|
||||
}
|
||||
}
|
||||
}
|
||||
Fields::Unnamed(fields) => {
|
||||
let bindings = (0..fields.unnamed.len())
|
||||
.map(|index| format_ident!("__disasmer_field_{index}"))
|
||||
.collect::<Vec<_>>();
|
||||
let visits = bindings.iter().map(|binding| quote! {
|
||||
::disasmer::__private::CollectTaskHandles::collect_task_handles(
|
||||
#binding,
|
||||
handles,
|
||||
)?;
|
||||
});
|
||||
quote! {
|
||||
Self::#variant_name(#(#bindings),*) => {
|
||||
#(#visits)*
|
||||
}
|
||||
}
|
||||
}
|
||||
Fields::Unit => quote! { Self::#variant_name => {} },
|
||||
}
|
||||
});
|
||||
quote! { match self { #(#arms),* } }
|
||||
}
|
||||
Data::Union(union) => {
|
||||
return syn::Error::new_spanned(
|
||||
union.union_token,
|
||||
"TaskArg cannot be derived for unions",
|
||||
)
|
||||
.into_compile_error()
|
||||
.into();
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
impl #impl_generics ::disasmer::__private::CollectTaskHandles
|
||||
for #name #type_generics #where_clause
|
||||
{
|
||||
fn collect_task_handles(
|
||||
&self,
|
||||
handles: &mut ::std::vec::Vec<::disasmer::core::TaskBoundaryHandle>,
|
||||
) -> ::std::result::Result<(), ::disasmer::TaskArgError> {
|
||||
#collect
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics ::disasmer::TaskArg for #name #type_generics #where_clause {
|
||||
fn task_arg_kind(&self) -> ::disasmer::TaskArgKind {
|
||||
::disasmer::TaskArgKind::Structured
|
||||
}
|
||||
|
||||
fn task_boundary_value(
|
||||
&self,
|
||||
) -> ::std::result::Result<
|
||||
::disasmer::core::TaskBoundaryValue,
|
||||
::disasmer::TaskArgError,
|
||||
> {
|
||||
::disasmer::__private::structured_task_boundary(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
fn collect_struct_fields(fields: &Fields) -> proc_macro2::TokenStream {
|
||||
match fields {
|
||||
Fields::Named(fields) => {
|
||||
let visits = fields.named.iter().map(|field| {
|
||||
let field = field.ident.as_ref().expect("named field");
|
||||
quote! {
|
||||
::disasmer::__private::CollectTaskHandles::collect_task_handles(
|
||||
&self.#field,
|
||||
handles,
|
||||
)?;
|
||||
}
|
||||
});
|
||||
quote! { #(#visits)* }
|
||||
}
|
||||
Fields::Unnamed(fields) => {
|
||||
let visits = fields.unnamed.iter().enumerate().map(|(index, _)| {
|
||||
let index = syn::Index::from(index);
|
||||
quote! {
|
||||
::disasmer::__private::CollectTaskHandles::collect_task_handles(
|
||||
&self.#index,
|
||||
handles,
|
||||
)?;
|
||||
}
|
||||
});
|
||||
quote! { #(#visits)* }
|
||||
}
|
||||
Fields::Unit => quote! {},
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn main(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let function = parse_macro_input!(item as ItemFn);
|
||||
if !function.sig.inputs.is_empty() {
|
||||
return syn::Error::new_spanned(
|
||||
&function.sig,
|
||||
"#[disasmer::main] requires a function with no arguments",
|
||||
)
|
||||
.into_compile_error()
|
||||
.into();
|
||||
}
|
||||
let function_name = function.sig.ident.to_string();
|
||||
let (entrypoint_name, _) = descriptor_options(
|
||||
attr,
|
||||
function_name
|
||||
.strip_suffix("_main")
|
||||
.unwrap_or(&function_name),
|
||||
);
|
||||
let descriptor = format_ident!(
|
||||
"__DISASMER_ENTRYPOINT_{}",
|
||||
function_name.to_ascii_uppercase()
|
||||
);
|
||||
let argument_schema = argument_schema(&function);
|
||||
let result_schema = result_schema(&function);
|
||||
let stable_id = stable_digest(
|
||||
"disasmer-entrypoint-id:v1",
|
||||
&[
|
||||
&entrypoint_name,
|
||||
&function_name,
|
||||
&argument_schema,
|
||||
&result_schema,
|
||||
],
|
||||
);
|
||||
let export_name = format!(
|
||||
"disasmer_entry_v1_{}",
|
||||
stable_id.trim_start_matches("sha256:")
|
||||
);
|
||||
let export_wrapper = format_ident!(
|
||||
"__disasmer_entry_export_{}",
|
||||
function_name.to_ascii_lowercase()
|
||||
);
|
||||
let export_invocation = invocation_helper(&function, true, quote! { #stable_id });
|
||||
let probe_symbol = format!("disasmer.probe.{function_name}");
|
||||
let manifest = serde_json::json!({
|
||||
"kind": "entrypoint",
|
||||
"name": entrypoint_name,
|
||||
"function": function_name,
|
||||
"export": export_name,
|
||||
"stable_id": stable_id,
|
||||
"argument_schema": argument_schema,
|
||||
"result_schema": result_schema,
|
||||
"abi_version": 1,
|
||||
"probe_symbol": probe_symbol,
|
||||
});
|
||||
let manifest = manifest_record(manifest);
|
||||
let manifest_static = format_ident!(
|
||||
"__DISASMER_ENTRYPOINT_MANIFEST_{}",
|
||||
function_name.to_ascii_uppercase()
|
||||
);
|
||||
|
||||
quote! {
|
||||
#function
|
||||
|
||||
#[doc(hidden)]
|
||||
pub const #descriptor: ::disasmer::EntrypointDescriptor = ::disasmer::EntrypointDescriptor {
|
||||
name: #entrypoint_name,
|
||||
function: #function_name,
|
||||
export: #export_name,
|
||||
stable_id: #stable_id,
|
||||
argument_schema: #argument_schema,
|
||||
result_schema: #result_schema,
|
||||
abi_version: 1,
|
||||
bundle_manifest_entry: true,
|
||||
};
|
||||
|
||||
#[doc(hidden)]
|
||||
#[used]
|
||||
#[cfg_attr(target_arch = "wasm32", unsafe(link_section = "disasmer.entrypoints"))]
|
||||
pub static #manifest_static: [u8; #manifest.len()] = *#manifest;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[unsafe(export_name = #export_name)]
|
||||
pub extern "C" fn #export_wrapper(input_pointer: u32, input_length: u32) -> u64 {
|
||||
::disasmer::debug::probe(#probe_symbol)
|
||||
.expect("Disasmer entrypoint debug probe host call failed");
|
||||
#export_invocation
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn task(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let function = parse_macro_input!(item as ItemFn);
|
||||
if function.sig.inputs.len() > 1 {
|
||||
return syn::Error::new_spanned(
|
||||
&function.sig,
|
||||
"#[disasmer::task] requires a function with zero or one owned argument",
|
||||
)
|
||||
.into_compile_error()
|
||||
.into();
|
||||
}
|
||||
let function_name = function.sig.ident.to_string();
|
||||
let (task_name, required_capabilities) = descriptor_options(attr, &function_name);
|
||||
let descriptor = format_ident!("__DISASMER_TASK_{}", function_name.to_ascii_uppercase());
|
||||
let argument_schema = argument_schema(&function);
|
||||
let result_schema = result_schema(&function);
|
||||
let capability_schema = required_capabilities.join(",");
|
||||
let stable_id = stable_digest(
|
||||
"disasmer-task-id:v1",
|
||||
&[&task_name, &function_name, &argument_schema, &result_schema],
|
||||
);
|
||||
let restart_compatibility_hash = stable_digest(
|
||||
"disasmer-task-restart:v1",
|
||||
&[
|
||||
&task_name,
|
||||
&argument_schema,
|
||||
&result_schema,
|
||||
&capability_schema,
|
||||
"abi:1",
|
||||
],
|
||||
);
|
||||
let export_name = format!(
|
||||
"disasmer_task_v1_{}",
|
||||
stable_id.trim_start_matches("sha256:")
|
||||
);
|
||||
let export_wrapper = format_ident!(
|
||||
"__disasmer_task_export_{}",
|
||||
function_name.to_ascii_lowercase()
|
||||
);
|
||||
let export_invocation = invocation_helper(&function, false, quote! { #task_name });
|
||||
let probe_symbol = format!("disasmer.probe.{function_name}");
|
||||
let manifest = serde_json::json!({
|
||||
"kind": "task",
|
||||
"name": task_name,
|
||||
"function": function_name,
|
||||
"export": export_name,
|
||||
"stable_id": stable_id,
|
||||
"argument_schema": argument_schema,
|
||||
"result_schema": result_schema,
|
||||
"required_capabilities": required_capabilities,
|
||||
"restart_compatibility_hash": restart_compatibility_hash,
|
||||
"abi_version": 1,
|
||||
"probe_symbol": probe_symbol,
|
||||
});
|
||||
let manifest = manifest_record(manifest);
|
||||
let manifest_static = format_ident!(
|
||||
"__DISASMER_TASK_MANIFEST_{}",
|
||||
function_name.to_ascii_uppercase()
|
||||
);
|
||||
|
||||
quote! {
|
||||
#function
|
||||
|
||||
#[doc(hidden)]
|
||||
pub const #descriptor: ::disasmer::TaskDescriptor = ::disasmer::TaskDescriptor {
|
||||
name: #task_name,
|
||||
function: #function_name,
|
||||
export: #export_name,
|
||||
stable_id: #stable_id,
|
||||
argument_schema: #argument_schema,
|
||||
result_schema: #result_schema,
|
||||
required_capabilities: &[#(#required_capabilities),*],
|
||||
restart_compatibility_hash: #restart_compatibility_hash,
|
||||
abi_version: 1,
|
||||
source_file: file!(),
|
||||
source_line: line!(),
|
||||
probe_symbol: #probe_symbol,
|
||||
bundle_manifest_entry: true,
|
||||
remotely_startable: true,
|
||||
};
|
||||
|
||||
#[doc(hidden)]
|
||||
#[used]
|
||||
#[cfg_attr(target_arch = "wasm32", unsafe(link_section = "disasmer.tasks"))]
|
||||
pub static #manifest_static: [u8; #manifest.len()] = *#manifest;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[unsafe(export_name = #export_name)]
|
||||
pub extern "C" fn #export_wrapper(input_pointer: u32, input_length: u32) -> u64 {
|
||||
::disasmer::debug::probe(#probe_symbol)
|
||||
.expect("Disasmer task debug probe host call failed");
|
||||
#export_invocation
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
fn invocation_helper(
|
||||
function: &ItemFn,
|
||||
is_entrypoint: bool,
|
||||
expected_definition: proc_macro2::TokenStream,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let function_ident = &function.sig.ident;
|
||||
let asynchronous = function.sig.asyncness.is_some();
|
||||
let result_return = returns_result(&function.sig.output);
|
||||
let helper = match (asynchronous, result_return, function.sig.inputs.len()) {
|
||||
(false, false, 0) => format_ident!("invoke_task0_v1"),
|
||||
(false, true, 0) => format_ident!("invoke_task0_result_v1"),
|
||||
(true, false, 0) => format_ident!("invoke_task0_async_v1"),
|
||||
(true, true, 0) => format_ident!("invoke_task0_async_result_v1"),
|
||||
(false, false, 1) => format_ident!("invoke_task1_v1"),
|
||||
(false, true, 1) => format_ident!("invoke_task1_result_v1"),
|
||||
(true, false, 1) => format_ident!("invoke_task1_async_v1"),
|
||||
(true, true, 1) => format_ident!("invoke_task1_async_result_v1"),
|
||||
_ => unreachable!("attribute input count was validated"),
|
||||
};
|
||||
match function.sig.inputs.first() {
|
||||
None => quote! {
|
||||
::disasmer::__private::#helper(
|
||||
#expected_definition,
|
||||
input_pointer,
|
||||
input_length,
|
||||
|| #function_ident(),
|
||||
)
|
||||
},
|
||||
Some(FnArg::Typed(argument)) if !is_entrypoint => {
|
||||
let argument_type = &argument.ty;
|
||||
quote! {
|
||||
::disasmer::__private::#helper(
|
||||
#expected_definition,
|
||||
input_pointer,
|
||||
input_length,
|
||||
|argument: #argument_type| #function_ident(argument),
|
||||
)
|
||||
}
|
||||
}
|
||||
Some(FnArg::Typed(_)) => unreachable!("entrypoint arguments were rejected"),
|
||||
Some(FnArg::Receiver(receiver)) => {
|
||||
syn::Error::new_spanned(receiver, "#[disasmer::task] cannot be applied to a method")
|
||||
.into_compile_error()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn returns_result(output: &ReturnType) -> bool {
|
||||
let ReturnType::Type(_, output) = output else {
|
||||
return false;
|
||||
};
|
||||
let Type::Path(path) = output.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
path.path
|
||||
.segments
|
||||
.last()
|
||||
.is_some_and(|segment| segment.ident == "Result")
|
||||
}
|
||||
|
||||
fn descriptor_options(attr: TokenStream, default: &str) -> (String, Vec<String>) {
|
||||
if attr.is_empty() {
|
||||
return (default.to_owned(), Vec::new());
|
||||
}
|
||||
|
||||
let parser = syn::punctuated::Punctuated::<Meta, Token![,]>::parse_terminated;
|
||||
let Ok(args) = parser.parse(attr) else {
|
||||
return (default.to_owned(), Vec::new());
|
||||
};
|
||||
|
||||
let mut name = default.to_owned();
|
||||
let mut capabilities = Vec::new();
|
||||
for meta in args {
|
||||
let Meta::NameValue(name_value) = meta else {
|
||||
continue;
|
||||
};
|
||||
if let Expr::Lit(expr) = name_value.value {
|
||||
if let Lit::Str(value) = expr.lit {
|
||||
if name_value.path.is_ident("name") {
|
||||
name = value.value();
|
||||
} else if name_value.path.is_ident("capabilities") {
|
||||
capabilities = value
|
||||
.value()
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|capability| !capability.is_empty())
|
||||
.map(str::to_owned)
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(name, capabilities)
|
||||
}
|
||||
|
||||
fn argument_schema(function: &ItemFn) -> String {
|
||||
function
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.map(ToTokens::to_token_stream)
|
||||
.map(|tokens| tokens.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
fn result_schema(function: &ItemFn) -> String {
|
||||
match &function.sig.output {
|
||||
ReturnType::Default => "()".to_owned(),
|
||||
ReturnType::Type(_, result) => result.to_token_stream().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn stable_digest(domain: &str, parts: &[&str]) -> String {
|
||||
let mut digest = Sha256::new();
|
||||
digest.update(domain.as_bytes());
|
||||
for part in parts {
|
||||
digest.update((part.len() as u64).to_be_bytes());
|
||||
digest.update(part.as_bytes());
|
||||
}
|
||||
format!("sha256:{}", hex::encode(digest.finalize()))
|
||||
}
|
||||
|
||||
fn manifest_record(value: serde_json::Value) -> LitByteStr {
|
||||
let mut record = serde_json::to_vec(&value).expect("descriptor manifest serializes");
|
||||
record.push(b'\n');
|
||||
LitByteStr::new(&record, proc_macro2::Span::call_site())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue