Public dry run dryrun-20b59dc46b72

Source commit: 20b59dc46b72103c2f8a516c692b5cc3d54fab19

Public tree identity: sha256:aaa5ac7b58b2a0d23c6b11e5f76324bf3839ca1f62653a83deb736932adcfbd9
This commit is contained in:
Disasmer release dry run 2026-07-14 10:08:15 +02:00
commit 2bef715211
221 changed files with 79792 additions and 0 deletions

View 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

View 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())
}