commit 2fd9cf11283e5f6eec151d0a68b7aaba27512406 Author: Mark Poliakov Date: Fri Mar 8 22:16:50 2024 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..cb89790b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,86 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "example-abi" +version = "0.1.0" +dependencies = [ + "syscall-generator", +] + +[[package]] +name = "prettyplease" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syscall-generator" +version = "0.1.0" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "syn", + "thiserror", +] + +[[package]] +name = "thiserror" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..bc98dd3a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "syscall-generator" +version = "0.1.0" +edition = "2021" + +[dependencies] +prettyplease = "0.2.16" +proc-macro2 = "1.0.78" +quote = "1.0.35" +syn = { version = "2.0.52", features = ["full"] } +thiserror = "1.0.57" + +[workspace] +members = ["example-abi"] diff --git a/example-abi/Cargo.toml b/example-abi/Cargo.toml new file mode 100644 index 00000000..4495e912 --- /dev/null +++ b/example-abi/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "example-abi" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +syscall-generator = { path = ".." } diff --git a/example-abi/build.rs b/example-abi/build.rs new file mode 100644 index 00000000..8ee074ad --- /dev/null +++ b/example-abi/build.rs @@ -0,0 +1,25 @@ +use std::{env, path::Path}; + +use syscall_generator::{ + abi::{ty::TypeWidth, AbiBuilder}, + parse::UnwrapFancy, + TargetEnv, +}; + +fn main() { + println!("cargo:rerun-if-changed={}", "syscall.abi"); + let output_dir = env::var("OUT_DIR").unwrap(); + let output = Path::new(&output_dir); + + let abi = AbiBuilder::from_file( + "syscall.abi", + TargetEnv { + thin_pointer_width: TypeWidth::U64, + fat_pointer_width: TypeWidth::U128, + }, + ) + .unwrap_fancy("syscall.abi"); + + abi.write(output.join("generated_abi.rs")) + .expect("Could not write the generated ABI file"); +} diff --git a/example-abi/src/main.rs b/example-abi/src/main.rs new file mode 100644 index 00000000..339efe1f --- /dev/null +++ b/example-abi/src/main.rs @@ -0,0 +1,118 @@ +#![feature(exposed_provenance)] + +use std::{num::NonZeroUsize, sync::Mutex}; + +#[derive(Debug)] +pub enum Error {} + +pub enum MappingSource {} +pub struct MountOptions; +pub struct UnmountOptions; +pub struct DirectoryEntry; +pub struct FileAttr; +pub struct SpawnOptions; +pub struct ThreadSpawnOptions; +pub enum MutexOperation {} +pub struct SignalEntryData; +pub struct DeviceRequest; +pub enum SentMessage {} +pub enum ReceivedMessageMetadata {} +pub struct TerminalOptions; +pub struct TerminalSize; +pub struct ExecveOptions; +pub struct FileMetadataUpdate; +pub enum ProcessInfoElement {} +pub enum SocketOption {} +pub enum SystemInfo {} + +impl FromSyscallResult for Result<(), Error> { + fn from_syscall_result(value: usize) -> Self { + if (value as isize) < 0 { + todo!() + } else { + Ok(()) + } + } +} + +impl FromSyscallResult for Result { + fn from_syscall_result(value: usize) -> Self { + if (value as isize) < 0 { + todo!() + } else { + Ok(value as _) + } + } +} + +impl IntoSyscallArgument for Option { + fn into_syscall_argument(self) -> usize { + match self { + Some(value) => value.into(), + None => 0, + } + } +} + +macro_rules! syscall { + ($f:expr $(,)?) => { + syscall0($f) + }; + ($f:expr, $a0:expr $(,)?) => { + syscall1($f, $a0) + }; + ($f:expr, $a0:expr, $a1:expr $(,)?) => { + syscall2($f, $a0, $a1) + }; + ($f:expr, $a0:expr, $a1:expr, $a2:expr $(,)?) => { + syscall3($f, $a0, $a1, $a2) + }; + ($f:expr, $a0:expr, $a1:expr, $a2:expr, $a3:expr $(,)?) => { + syscall4($f, $a0, $a1, $a2, $a3) + }; + ($f:expr, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr $(,)?) => { + syscall5($f, $a0, $a1, $a2, $a3, $a4) + }; +} + +static SYSCALL_TRACE: Mutex)>> = Mutex::new(Vec::new()); + +fn syscall0(f: SyscallFunction) -> usize { + SYSCALL_TRACE.lock().unwrap().push((f, vec![])); + 0 +} + +fn syscall1(f: SyscallFunction, a0: usize) -> usize { + SYSCALL_TRACE.lock().unwrap().push((f, vec![a0])); + 0 +} + +fn syscall2(f: SyscallFunction, a0: usize, a1: usize) -> usize { + SYSCALL_TRACE.lock().unwrap().push((f, vec![a0, a1])); + 0 +} + +fn syscall3(f: SyscallFunction, a0: usize, a1: usize, a2: usize) -> usize { + SYSCALL_TRACE.lock().unwrap().push((f, vec![a0, a1, a2])); + 0 +} + +fn syscall4(f: SyscallFunction, a0: usize, a1: usize, a2: usize, a3: usize) -> usize { + SYSCALL_TRACE + .lock() + .unwrap() + .push((f, vec![a0, a1, a2, a3])); + 0 +} + +fn syscall5(f: SyscallFunction, a0: usize, a1: usize, a2: usize, a3: usize, a4: usize) -> usize { + SYSCALL_TRACE + .lock() + .unwrap() + .push((f, vec![a0, a1, a2, a3, a4])); + 0 +} + +include!(concat!(env!("OUT_DIR"), "/generated_abi.rs")); + +fn main() {} diff --git a/example-abi/syscall.abi b/example-abi/syscall.abi new file mode 100644 index 00000000..7352af28 --- /dev/null +++ b/example-abi/syscall.abi @@ -0,0 +1,141 @@ +// vi:syntax=yggdrasil_abi: + +extern { + type Duration = core::time::Duration; + type Mutex = core::sync::atomic::AtomicU32; + type SocketAddr = core::net::SocketAddr; + + type SystemInfo; + type MappingSource; + type SpawnOptions; + type ThreadSpawnOptions; + type MutexOperation; + type SignalEntryData; + type ProcessInfoElement; + type ExecveOptions; + type MountOptions; + type UnmountOptions; + type DirectoryEntry; + type FileAttr; + type DeviceRequest; + type SentMessage; + type ReceivedMessageMetadata; + type TerminalOptions; + type TerminalSize; + type FileMetadataUpdate; + type SocketOption; +}; + +newtype Fd(u32); +newtype FileMode(u32); +newtype ProcessId(u32); +newtype ThreadId(u32); +newtype ProcessGroupId(u32); + +// TODO define these as enums +newtype OpenOptions(u32); +newtype FileMode(u32); +newtype ExitCode(i32); +newtype SeekFrom(u64); +newtype Signal(u32); +newtype PollControl(u32); +newtype MessageDestination(u32); +newtype SocketType(u32); + +// Misc + +syscall debug_trace(msg: &str); +syscall get_random(buffer: &mut [u8]); +syscall get_system_info(info: &mut SystemInfo) -> Result; + +// Memory management + +syscall map_memory(hint: Option, len: usize, source: &MappingSource) -> Result; +syscall unmap_memory(virt: usize, len: usize) -> Result<()>; + +// Process/thread management + +syscall spawn_process(options: &SpawnOptions) -> Result; +syscall spawn_thread(options: &ThreadSpawnOptions) -> Result; + +syscall exit_thread(code: ExitCode) -> !; +syscall exit_process(code: ExitCode) -> !; +syscall wait_process(pid: ProcessId, status: &mut ExitCode) -> Result<()>; +syscall send_signal(pid: ProcessId, signal: Signal) -> Result<()>; +syscall nanosleep(duration: &Duration); + +syscall mutex(mutex: &Mutex, op: &MutexOperation) -> Result<()>; + +syscall set_signal_entry(entry: usize, stack: usize); +syscall exit_signal(frame: &SignalEntryData) -> !; + +syscall get_pid() -> ProcessId; + +syscall start_session() -> Result<()>; +syscall set_process_group_id(pid: ProcessId, pgid: Option) -> Result; + +syscall get_process_info(what: &mut ProcessInfoElement) -> Result<()>; +syscall set_process_info(what: &ProcessInfoElement) -> Result<()>; + +// C compat + +syscall fork() -> Result; +syscall execve(opts: &ExecveOptions) -> Result<()>; + +// I/O + +syscall write(fd: Fd, data: &[u8]) -> Result; +syscall read(fd: Fd, data: &mut [u8]) -> Result; +syscall open(at: Option, path: &str, opts: OpenOptions, mode: FileMode) -> Result; +syscall close(fd: Fd) -> Result<()>; +syscall mount(options: &MountOptions) -> Result<()>; +syscall unmount(options: &UnmountOptions) -> Result<()>; +syscall open_directory(at: Option, path: &str) -> Result; +syscall read_directory_entries(fd: Fd, buffer: &mut [MaybeUninit]) -> Result; +syscall create_directory(at: Option, path: &str, mode: FileMode) -> Result<()>; +syscall remove(at: Option, path: &str) -> Result<()>; +syscall remove_directory(at: Option, path: &str) -> Result<()>; +syscall get_metadata(at: Option, path: &str, metadata: &mut FileAttr, follow: bool) -> Result<()>; +// TODO this one is broken +syscall seek(fd: Fd, pos: SeekFrom) -> Result; + +syscall device_request(fd: Fd, req: &mut DeviceRequest) -> Result<()>; + +syscall create_pipe(ends: &mut [MaybeUninit; 2]) -> Result<()>; + +syscall create_poll_channel() -> Result; +syscall poll_channel_control(poll_fd: Fd, ctl: PollControl, fd: Fd) -> Result<()>; +syscall poll_channel_wait(poll_fd: Fd, timeout: &Option, out: &mut Option) -> Result<()>; + +syscall open_channel(name: &str, subscribe: bool) -> Result; +syscall send_message(fd: Fd, message: &SentMessage, destination: MessageDestination) -> Result<()>; +syscall receive_message( + fd: Fd, + metadata: &mut MaybeUninit, + buffer: &mut [u8], + from: &mut MaybeUninit +) -> Result; + +syscall create_shared_memory(size: usize) -> Result; +syscall create_pty(options: &TerminalOptions, size: &TerminalSize, fds: &mut [MaybeUninit; 2]) -> Result<()>; + +syscall clone_fd(source: Fd, target: Option) -> Result; + +syscall update_metadata(at: Option, path: &str, update: &FileMetadataUpdate) -> Result<()>; + +syscall create_timer(repeat: bool) -> Result; + +// Network + +syscall bind_socket(listen_address: &SocketAddr, ty: SocketType) -> Result; +syscall connect_socket( + socket_fd: Option, + remote_address: &SocketAddr, + ty: SocketType, + local_address: &mut MaybeUninit +) -> Result; +syscall send_to(socket_fd: Fd, data: &[u8], recepient: &Option) -> Result; +syscall receive_from(socket_fd: Fd, buffer: &mut [u8], sender: &mut MaybeUninit) -> Result; +syscall get_socket_option(socket_fd: Fd, option: &mut SocketOption) -> Result<()>; +syscall set_socket_option(socket_fd: Fd, option: &SocketOption) -> Result<()>; +syscall accept(listener: Fd, address: &mut MaybeUninit) -> Result; diff --git a/src/abi/mod.rs b/src/abi/mod.rs new file mode 100644 index 00000000..164da5c8 --- /dev/null +++ b/src/abi/mod.rs @@ -0,0 +1,305 @@ +use std::{collections::HashMap, fs::File, io::Write, path::Path, rc::Rc}; + +use proc_macro2::TokenStream; +use quote::{quote, TokenStreamExt}; +use syn::{punctuated::Punctuated, spanned::Spanned, token}; + +pub mod ty; + +use crate::{ + abi::ty::{ComplexType, SimpleType, Type, TypeWidth}, + error::Error, + parse::{parse_abi, ParseError}, + Syscall, TargetEnv, +}; + +#[derive(Debug)] +pub struct Abi { + pub types: HashMap>, + // Contains syscalls and their corresponding SyscallFunction enum variants + pub syscalls: Vec<(syn::Ident, Syscall)>, +} + +pub struct AbiBuilder { + abi: Abi, + target_env: TargetEnv, + emit_syscall_traits: bool, +} + +pub fn syscall_enum_variant_identifier(name: &syn::Ident) -> syn::Ident { + let name = name.to_string(); + let string = name.split('_').fold(String::new(), |mut acc, word| { + word.chars().enumerate().for_each(|(i, c)| { + if i == 0 { + acc.extend(c.to_uppercase()); + } else { + acc.push(c); + } + }); + acc + }); + syn::Ident::new(string.as_str(), name.span()) +} + +impl AbiBuilder { + pub fn from_file>(path: P, target_env: TargetEnv) -> Result { + let abi = Abi::read(path)?; + Ok(Self { + abi, + target_env, + emit_syscall_traits: true, + }) + } + + pub fn write>(self, path: P) -> Result<(), Error> { + // Generate preamble + let mut stream = TokenStream::new(); + + if self.emit_syscall_traits { + stream.append_all(quote! { + pub trait IntoSyscallArgument { + fn into_syscall_argument(self) -> usize; + } + + pub trait FromSyscallArgument { + fn from_syscall_argument(value: usize) -> Self; + } + + pub trait FromSyscallResult { + fn from_syscall_result(value: usize) -> Self; + } + }); + } + + let syscall_discriminants = self + .abi + .syscalls + .iter() + .enumerate() + .map(|(index, (variant, _))| { + quote! { #variant = #index } + }) + .collect::>(); + + stream.append_all(quote! { + #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)] + #[repr(usize)] + pub enum SyscallFunction { + #syscall_discriminants + } + }); + + stream.append_all(self.abi.generate_user_side(&self.target_env)); + + let file = syn::parse_file(&stream.to_string())?; + let out = prettyplease::unparse(&file); + + let mut output = File::create(path)?; + output.write_all(out.as_bytes())?; + + Ok(()) + } +} + +impl Abi { + pub fn read>(path: P) -> Result { + let data = std::fs::read_to_string(path)?; + parse_abi(&data) + } + + pub fn write>(&self, path: P) -> Result<(), Error> { + // TODO read TargetEnv + let target = TargetEnv { + thin_pointer_width: TypeWidth::U64, + fat_pointer_width: TypeWidth::U128, + }; + let mut output = File::create(path)?; + let stream = self.generate_user_side(&target); + let file = syn::parse_file(&stream.to_string())?; + let out = prettyplease::unparse(&file); + output.write_all(out.as_bytes())?; + Ok(()) + } + + pub fn generate_user_side(&self, env: &TargetEnv) -> TokenStream { + let mut stream = TokenStream::new(); + + for (_, ty) in &self.types { + let definition = + if let ComplexType::Simple(SimpleType::Transparent { name, inner }) = ty.as_ref() { + let ty = inner.as_rust_type(); + let mut res = quote! { + #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] + #[repr(transparent)] + pub struct #name(pub(crate) #ty); + }; + + if inner.width(env) < env.thin_pointer_width { + res.append_all(quote! { + impl IntoSyscallArgument for Option<#name> { + fn into_syscall_argument(self) -> usize { + match self { + Some(value) => value.0 as usize, + None => usize::MAX + } + } + } + + impl FromSyscallArgument for Option<#name> { + fn from_syscall_argument(value: usize) -> Self { + match value { + usize::MAX => None, + _ => Some(#name(value as _)) + } + } + } + + impl FromSyscallResult for Result<#name, Error> { + fn from_syscall_result(value: usize) -> Self { + if (value as isize) < 0 { + todo!() + } else { + Ok(#name(value as _)) + } + } + } + + impl FromSyscallResult for #name { + fn from_syscall_result(value: usize) -> Self { + Self(value as _) + } + } + }); + }; + + res + } else if matches!(ty.as_ref(), ComplexType::Extern(_)) { + continue; + } else { + todo!() + }; + + stream.append_all(definition); + } + + for (variant, syscall) in &self.syscalls { + stream.append_all(syscall.emit_stub(variant, env)); + } + + stream + } +} + +#[cfg(test)] +mod tests { + use std::{collections::HashMap, rc::Rc}; + + use proc_macro2::Span; + use quote::quote; + use syn::{Ident, Visibility}; + + use crate::{ + abi::ty::{AbiPrimitive, ComplexType, SimpleType, TypeWidth}, + Syscall, TargetEnv, + }; + + use super::Abi; + + #[test] + fn generate_full_simple_abi() { + let target = TargetEnv { + thin_pointer_width: TypeWidth::U64, + fat_pointer_width: TypeWidth::U128, + }; + let fd = ComplexType::transparent(Ident::new("Fd", Span::call_site()), AbiPrimitive::U32); + let read_buf_ty = ComplexType::Simple(SimpleType::Slice { + mutable: true, + element: Rc::new(ComplexType::primitive(AbiPrimitive::U8)), + }); + let write_buf_ty = ComplexType::Simple(SimpleType::Slice { + mutable: false, + element: Rc::new(ComplexType::primitive(AbiPrimitive::U8)), + }); + + let read_syscall = Syscall { + name: Ident::new("read", Span::call_site()), + visibility: Visibility::Inherited, + args: vec![ + (Ident::new("fd", Span::call_site()), fd.clone()), + (Ident::new("buf", Span::call_site()), read_buf_ty), + ], + return_type: Some(ComplexType::SizeResult), + }; + let write_syscall = Syscall { + name: Ident::new("write", Span::call_site()), + visibility: Visibility::Inherited, + args: vec![ + (Ident::new("fd", Span::call_site()), fd.clone()), + (Ident::new("buf", Span::call_site()), write_buf_ty), + ], + return_type: Some(ComplexType::SizeResult), + }; + + let abi = Abi { + types: HashMap::from_iter([("Fd".to_owned(), fd)]), + syscalls: vec![read_syscall, write_syscall], + }; + + let result = abi.generate_user_side(&target); + assert_eq!( + result.to_string(), + quote! { + #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] + #[repr(transparent)] + pub struct Fd(pub(crate) u32); + + impl IntoSyscallArgument for Option { + fn into_syscall_argument(self) -> usize { + match self { + Some(value) => value.0 as usize, + None => usize::MAX + } + } + } + + impl FromSyscallArgument for Option { + fn from_syscall_argument(value: usize) -> Self { + match value { + usize::MAX => None, + _ => Some(Fd(value as _)) + } + } + } + + impl FromSyscallResult for Result { + fn from_syscall_result(value: usize) -> Self { + if (value as isize) < 0 { + todo!() + } else { + Ok(Fd(value as _)) + } + } + } + + unsafe fn read(fd: Fd, buf: &mut [u8],) -> Result { + >::from_syscall_result(syscall!( + SyscallFunction::read, + fd.0 as usize, + buf.as_mut_ptr().expose_addr(), + buf.len(), + )) + } + + unsafe fn write(fd: Fd, buf: &[u8],) -> Result { + >::from_syscall_result(syscall!( + SyscallFunction::write, + fd.0 as usize, + buf.as_ptr().expose_addr(), + buf.len(), + )) + } + } + .to_string() + ); + } +} diff --git a/src/abi/ty/complex.rs b/src/abi/ty/complex.rs new file mode 100644 index 00000000..958b92c6 --- /dev/null +++ b/src/abi/ty/complex.rs @@ -0,0 +1,109 @@ +use std::{fmt, rc::Rc}; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::Ident; + +use crate::{error::Error, TargetEnv}; + +use super::{AbiPrimitive, SimpleType, Type, TypeKind, TypeWidth}; + +#[derive(Clone)] +pub enum ComplexType { + Simple(SimpleType), + MaybeUninit(Rc), + Result(Rc), + Option(Rc), + Extern(syn::Type), +} + +impl ComplexType { + pub fn primitive(val: AbiPrimitive) -> Self { + Self::Simple(SimpleType::Primitive(val)) + } + + pub fn transparent(name: Ident, val: AbiPrimitive) -> Self { + Self::Simple(SimpleType::Transparent { name, inner: val }) + } + + pub fn to_simple_type(&self) -> Result { + todo!() + // if let Self::Simple(simple) = self { + // Ok(simple) + // } else { + // todo!("{:?}", self) + // } + } +} + +impl Type for ComplexType { + fn width(&self, env: &TargetEnv) -> TypeWidth { + match self { + Self::Simple(ty) => ty.width(env), + Self::Option(ty) | Self::Result(ty) => match ty.kind() { + TypeKind::Other => ty.width(env).double(), + // Some(&[1, 2, 3]) -> (ptr, len), where ptr and len are non-zero + // None -> (null, 0) + TypeKind::FatPointer => env.fat_pointer_width, + // Some(&x) -> ptr, where ptr is non-zero + // None -> null + TypeKind::ThinPointer => env.thin_pointer_width, + }, + Self::Extern(_) => TypeWidth::Unknown, + // MaybeUninit has the same layout as its underlying data + Self::MaybeUninit(ty) => ty.width(env), + } + } + + fn kind(&self) -> TypeKind { + match self { + Self::Simple(ty) => ty.kind(), + Self::MaybeUninit(ty) => ty.kind(), + _ => TypeKind::Other, + } + } + + fn as_rust_type(&self) -> TokenStream { + match self { + Self::Simple(ty) => ty.as_rust_type(), + Self::Option(ty) => { + let ty = ty.as_rust_type(); + quote!(Option<#ty>) + } + Self::Result(ty) => { + let ty = ty.as_rust_type(); + // TODO take from TargetEnv + quote!(Result<#ty, Error>) + } + Self::Extern(ty) => quote!(#ty), + Self::MaybeUninit(ty) => { + let ty = ty.as_rust_type(); + quote!(core::mem::MaybeUninit<#ty>) + } + } + } + + fn emit_to_syscall_arguments(&self, env: &TargetEnv, value: &Ident) -> TokenStream { + match self { + Self::Simple(ty) => ty.emit_to_syscall_arguments(env, value), + Self::Option(ty) + if ty.width(env) < env.thin_pointer_width || ty.kind() == TypeKind::ThinPointer => + { + quote!(#value.into_syscall_argument()) + } + _ => todo!(), + } + } +} + +impl fmt::Debug for ComplexType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Simple(ty) => f.debug_tuple("Simple").field(&ty).finish(), + Self::Result(ty) => f.debug_tuple("Result").field(&ty).finish(), + Self::Option(ty) => f.debug_tuple("Option").field(&ty).finish(), + Self::Extern(ty) => f.debug_tuple("Extern").field("e!(#ty)).finish(), + Self::MaybeUninit(ty) => f.debug_tuple("MaybeUninit").field(&ty).finish(), + } + } +} diff --git a/src/abi/ty/mod.rs b/src/abi/ty/mod.rs new file mode 100644 index 00000000..04a76bce --- /dev/null +++ b/src/abi/ty/mod.rs @@ -0,0 +1,161 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::Ident; + +use crate::TargetEnv; + +pub mod complex; +pub mod primitive; +pub mod simple; + +pub use complex::ComplexType; +pub use primitive::AbiPrimitive; +pub use simple::SimpleType; + +#[derive(PartialEq, Eq)] +pub enum TypeKind { + ThinPointer, + FatPointer, + Other, +} + +pub trait Type { + fn width(&self, env: &TargetEnv) -> TypeWidth; + fn kind(&self) -> TypeKind; + fn as_rust_type(&self) -> TokenStream; + + fn emit_to_syscall_arguments(&self, env: &TargetEnv, value: &Ident) -> TokenStream; + + fn as_rust_ref_type(&self, mutable: bool) -> TokenStream { + let inner = self.as_rust_type(); + match mutable { + true => quote!(&mut #inner), + false => quote!(& #inner), + } + } + + fn as_rust_slice_type(&self, mutable: bool) -> TokenStream { + let inner = self.as_rust_type(); + match mutable { + true => quote!(&mut [#inner]), + false => quote!(&[#inner]), + } + } + + fn as_rust_array_type(&self, mutable: bool, length: usize) -> TokenStream { + let inner = self.as_rust_type(); + match mutable { + true => quote!(&mut [#inner; #length]), + false => quote!(&[#inner; #length]), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] +pub enum TypeWidth { + Zero, + U8, + U16, + U32, + U64, + U128, + Unknown, +} + +impl TypeWidth { + pub fn double(&self) -> Self { + match self { + Self::Zero => todo!(), + Self::U8 => Self::U16, + Self::U16 => Self::U32, + Self::U32 => Self::U64, + Self::U64 => Self::U128, + Self::U128 => todo!(), + Self::Unknown => Self::Unknown, + } + } +} + +#[cfg(test)] +mod tests { + use std::rc::Rc; + + use proc_macro2::Span; + use quote::quote; + use syn::Ident; + + use crate::abi::ty::Type; + + use super::{AbiPrimitive, ComplexType, SimpleType, TargetEnv, TypeWidth}; + + const TARGET_64: TargetEnv = TargetEnv { + thin_pointer_width: TypeWidth::U64, + fat_pointer_width: TypeWidth::U128, + }; + + #[test] + fn type_width() { + let ty = SimpleType::Reference { + mutable: false, + pointee: Rc::new(ComplexType::Simple(SimpleType::Primitive( + AbiPrimitive::U32, + ))), + }; + assert_eq!(ty.width(&TARGET_64), TypeWidth::U64); + + let ty = SimpleType::Primitive(AbiPrimitive::U64); + assert_eq!(ty.width(&TARGET_64), TypeWidth::U64); + } + + #[test] + fn option_width() { + let ty = ComplexType::Option(Rc::new(ComplexType::primitive(AbiPrimitive::U64))); + assert_eq!(ty.width(&TARGET_64), TypeWidth::U128); + + let ty = ComplexType::Option(Rc::new(ComplexType::Simple(SimpleType::Reference { + mutable: true, + pointee: Rc::new(ComplexType::Simple(SimpleType::Primitive( + AbiPrimitive::U64, + ))), + }))); + assert_eq!(ty.width(&TARGET_64), TypeWidth::U64); + } + + #[test] + fn simple_rust_types() { + let ty = ComplexType::Option(Rc::new(ComplexType::primitive(AbiPrimitive::U32))); + assert_eq!( + quote!(Option).to_string(), + ty.as_rust_type().to_string() + ); + + let ty = SimpleType::Reference { + mutable: true, + pointee: Rc::new(ComplexType::Simple(SimpleType::Primitive( + AbiPrimitive::U64, + ))), + }; + assert_eq!(quote!(&mut u64).to_string(), ty.as_rust_type().to_string()); + + let ty = SimpleType::Slice { + mutable: false, + element: Rc::new(ComplexType::Simple(SimpleType::Primitive(AbiPrimitive::U8))), + }; + assert_eq!(quote!(&[u8]).to_string(), ty.as_rust_type().to_string()); + } + + #[test] + fn emit_to_syscall_arguments() { + let ty = SimpleType::Reference { + mutable: false, + pointee: Rc::new(ComplexType::Simple(SimpleType::Primitive( + AbiPrimitive::U64, + ))), + }; + let val = Ident::new("val0", Span::call_site()); + assert_eq!( + ty.emit_to_syscall_arguments(&TARGET_64, &val).to_string(), + quote!((val0 as *const _).expose_addr()).to_string() + ); + } +} diff --git a/src/abi/ty/primitive.rs b/src/abi/ty/primitive.rs new file mode 100644 index 00000000..804d8c68 --- /dev/null +++ b/src/abi/ty/primitive.rs @@ -0,0 +1,83 @@ +use std::str::FromStr; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::Ident; + +use crate::TargetEnv; + +use super::{Type, TypeKind, TypeWidth}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum AbiPrimitive { + Unit, + Bool, + U8, + I8, + U16, + I16, + U32, + I32, + U64, + I64, + USize, +} + +impl Type for AbiPrimitive { + fn width(&self, env: &TargetEnv) -> TypeWidth { + match self { + Self::Unit => TypeWidth::Zero, + Self::U8 | Self::I8 => TypeWidth::U8, + Self::U16 | Self::I16 => TypeWidth::U16, + Self::U32 | Self::I32 => TypeWidth::U32, + Self::U64 | Self::I64 => TypeWidth::U64, + Self::USize => env.thin_pointer_width, + // ??? + Self::Bool => TypeWidth::U32, + } + } + + fn kind(&self) -> TypeKind { + TypeKind::Other + } + + fn as_rust_type(&self) -> TokenStream { + match self { + Self::Unit => quote!(()), + Self::U8 => quote!(u8), + Self::I8 => quote!(i8), + Self::U16 => quote!(u16), + Self::I16 => quote!(i16), + Self::U32 => quote!(u32), + Self::I32 => quote!(i32), + Self::U64 => quote!(u64), + Self::I64 => quote!(i64), + Self::USize => quote!(usize), + Self::Bool => quote!(bool), + } + } + + fn emit_to_syscall_arguments(&self, _env: &TargetEnv, value: &Ident) -> TokenStream { + quote!(#value as usize) + } +} + +impl FromStr for AbiPrimitive { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "i8" => Ok(Self::I8), + "u8" => Ok(Self::U8), + "i16" => Ok(Self::I16), + "u16" => Ok(Self::U16), + "i32" => Ok(Self::I32), + "u32" => Ok(Self::U32), + "i64" => Ok(Self::I64), + "u64" => Ok(Self::U64), + "bool" => Ok(Self::Bool), + "usize" => Ok(Self::USize), + _ => Err(()), + } + } +} diff --git a/src/abi/ty/simple.rs b/src/abi/ty/simple.rs new file mode 100644 index 00000000..c664802e --- /dev/null +++ b/src/abi/ty/simple.rs @@ -0,0 +1,104 @@ +use std::rc::Rc; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::Ident; + +use crate::TargetEnv; + +use super::{AbiPrimitive, ComplexType, Type, TypeKind, TypeWidth}; + +#[derive(Debug, Clone)] +pub enum SimpleType { + Primitive(AbiPrimitive), + Str, + NonZeroUsize, + Never, + Transparent { + name: Ident, + inner: AbiPrimitive, + }, + Reference { + mutable: bool, + pointee: Rc, + }, + Slice { + mutable: bool, + element: Rc, + }, + Array { + mutable: bool, + element: Rc, + length: usize, + }, +} + +impl Type for SimpleType { + fn width(&self, env: &TargetEnv) -> TypeWidth { + match self { + Self::Str => env.fat_pointer_width, + Self::NonZeroUsize => env.thin_pointer_width, + Self::Reference { .. } => env.thin_pointer_width, + Self::Array { .. } => env.thin_pointer_width, + Self::Slice { .. } => env.fat_pointer_width, + Self::Primitive(ty) => ty.width(env), + Self::Transparent { inner, .. } => inner.width(env), + Self::Never => TypeWidth::Zero, + } + } + + fn kind(&self) -> TypeKind { + match self { + Self::Str => TypeKind::FatPointer, + Self::NonZeroUsize => TypeKind::ThinPointer, + Self::Primitive(ty) => ty.kind(), + Self::Transparent { inner, .. } => inner.kind(), + Self::Reference { .. } => TypeKind::ThinPointer, + Self::Array { .. } => TypeKind::ThinPointer, + Self::Slice { .. } => TypeKind::FatPointer, + Self::Never => TypeKind::Other, + } + } + + fn as_rust_type(&self) -> TokenStream { + match self { + Self::Str => quote!(&str), + Self::NonZeroUsize => quote!(core::num::NonZeroUsize), + Self::Reference { mutable, pointee } => pointee.as_rust_ref_type(*mutable), + Self::Array { + mutable, + element, + length, + } => element.as_rust_array_type(*mutable, *length), + Self::Slice { mutable, element } => element.as_rust_slice_type(*mutable), + Self::Primitive(ty) => ty.as_rust_type(), + Self::Transparent { name, .. } => quote!(#name), + Self::Never => quote!(!), + } + } + + fn emit_to_syscall_arguments(&self, env: &TargetEnv, value: &Ident) -> TokenStream { + match self { + Self::NonZeroUsize => quote!(#value.into()), + Self::Primitive(ty) => ty.emit_to_syscall_arguments(env, value), + Self::Transparent { .. } => quote!(#value.0 as usize), + Self::Str => quote!(#value.as_ptr().expose_addr(), #value.len()), + Self::Never => todo!("Prevent Never type from being a syscall argument"), + Self::Reference { mutable, pointee } => { + let pointee = pointee.as_rust_type(); + match mutable { + true => quote!((#value as *mut #pointee).expose_addr()), + false => quote!((#value as *const #pointee).expose_addr()), + } + } + Self::Array { mutable, .. } => match mutable { + true => quote!(#value.as_mut_ptr().expose_addr()), + false => quote!(#value.as_ptr().expose_addr()), + }, + Self::Slice { mutable, .. } => match mutable { + true => quote!(#value.as_mut_ptr().expose_addr(), #value.len()), + false => quote!(#value.as_ptr().expose_addr(), #value.len()), + }, + } + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..cf615516 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,22 @@ +use std::io; + +use proc_macro2::Span; +use syn::Ident; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}")] + IoError(#[from] io::Error), + + #[error("{1:?}: Incorrectly constructed type: {0}")] + TypeError(String, Span), + #[error("{0:?}: Unhandled type name")] + UnhandledTypeNameError(Ident), + #[error("{0:?}: Only primitive types are allowed inside newtype X(Y) constructs")] + UnhandledNonPrimitive(Span), + + #[error("{0}")] + LexError(#[from] proc_macro2::LexError), + #[error("{0}")] + ParseError(#[from] syn::parse::Error), +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..ac45a39a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,141 @@ +#![feature(if_let_guard, extend_one, proc_macro_span)] + +use std::{fmt, rc::Rc}; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::Ident; + +use crate::abi::ty::{ComplexType, SimpleType, Type, TypeWidth}; + +pub mod abi; +pub mod error; +pub mod parse; + +pub struct Syscall { + pub name: Ident, + pub args: Vec<(Ident, Rc)>, + pub return_type: Option>, +} + +impl fmt::Debug for Syscall { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Syscall") + .field("name", &self.name) + .field("args", &self.args) + .field("return_type", &self.return_type) + .finish_non_exhaustive() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct TargetEnv { + pub thin_pointer_width: TypeWidth, + pub fat_pointer_width: TypeWidth, +} + +impl Syscall { + pub fn emit_stub(&self, enum_variant: &syn::Ident, env: &TargetEnv) -> TokenStream { + let Self { + name, + args, + return_type, + } = self; + + let syscall_args = args + .iter() + .map(|(name, ty)| { + let arg = ty.emit_to_syscall_arguments(env, name); + quote!(#arg,) + }) + .collect::(); + let args = args + .iter() + .map(|(name, ty)| { + let ty = ty.as_rust_type(); + quote!(#name: #ty,) + }) + .collect::(); + let return_type_arrow = return_type.as_ref().map(|ty| { + let ty = ty.as_rust_type(); + quote!(-> #ty) + }); + + let sys_call = quote!(syscall!(SyscallFunction::#enum_variant, #syscall_args)); + + let wrapped = match return_type.as_deref() { + Some(ComplexType::Simple(SimpleType::Never)) => { + quote! { + #sys_call; + unreachable!() + } + } + Some(ty) => { + let ty = ty.as_rust_type(); + quote!(<#ty>::from_syscall_result(#sys_call)) + } + None => quote!(#sys_call;), + }; + + let v = quote! { + pub unsafe fn #name(#args) #return_type_arrow { + #wrapped + } + }; + println!("{}", v); + v + } +} + +#[cfg(test)] +mod tests { + use std::rc::Rc; + + use proc_macro2::Span; + use quote::quote; + use syn::Ident; + + use crate::{ + abi::ty::{AbiPrimitive, ComplexType, SimpleType, TypeWidth}, + Syscall, TargetEnv, + }; + + #[test] + fn emit_syscall_stub() { + let target = TargetEnv { + thin_pointer_width: TypeWidth::U64, + fat_pointer_width: TypeWidth::U128, + }; + let syscall = Syscall { + name: Ident::new("read", Span::call_site()), + args: vec![ + ( + Ident::new("fd", Span::call_site()), + ComplexType::Simple(SimpleType::Primitive(AbiPrimitive::U32)), + ), + ( + Ident::new("buffer", Span::call_site()), + ComplexType::Simple(SimpleType::Slice { + mutable: true, + element: Rc::new(ComplexType::Simple(SimpleType::Primitive( + AbiPrimitive::U8, + ))), + }), + ), + ], + return_type: Some(ComplexType::Result(SimpleType::Primitive( + AbiPrimitive::USize, + ))), + }; + let res = syscall.emit_stub(&target); + assert_eq!( + res.to_string(), + quote! { + unsafe fn read(fd: u32, buffer: &mut [u8],) -> Result { + >::from_syscall_result(syscall!(SyscallFunction::read, fd as usize, buffer.as_mut_ptr().expose_addr(), buffer.len(),)) + } + } + .to_string() + ); + } +} diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 00000000..734ff60f --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,622 @@ +use std::{collections::HashMap, fmt, path::Path, rc::Rc, str::FromStr}; + +use proc_macro2::Span; +use quote::quote; +use syn::{ + braced, parenthesized, parse::ParseStream, punctuated::Punctuated, spanned::Spanned, token, + Expr, ExprLit, Ident, Lit, PathSegment, Token, +}; + +use crate::{ + abi::{ + syscall_enum_variant_identifier, + ty::{AbiPrimitive, ComplexType, SimpleType}, + Abi, + }, + Syscall, +}; + +/* +Document syntax: + +newtype SomeType = u32; +newtype Fd(u32); + +syscall read(fd: Fd, buf: &mut [u8]) -> SizeResult; +syscall write(fd: Fd, buf: &[u8]) -> SizeResult; + + */ + +pub enum SyntaxError { + UnhandledType(syn::Type), + UndefinedIdentifier(syn::Ident), +} + +pub enum ParseError { + HardError(syn::parse::Error), + IoError(std::io::Error), + SyntaxError(Vec), +} + +pub trait UnwrapFancy { + fn unwrap_fancy>(self, context: P) -> T; +} + +impl From for ParseError { + fn from(value: std::io::Error) -> Self { + Self::IoError(value) + } +} + +impl From for ParseError { + fn from(value: syn::parse::Error) -> Self { + Self::HardError(value) + } +} + +impl From for ParseError { + fn from(value: SyntaxError) -> Self { + Self::SyntaxError(vec![value]) + } +} + +impl SyntaxError { + pub fn span(&self) -> Span { + match self { + Self::UndefinedIdentifier(id) => id.span(), + Self::UnhandledType(ty) => ty.span(), + } + } + + pub fn describe(&self) -> String { + match self { + Self::UnhandledType(_ty) => "Unhandled type".into(), + Self::UndefinedIdentifier(id) => format!("Undefined identifier '{}'", id), + } + } +} + +impl UnwrapFancy for Result { + fn unwrap_fancy>(self, path: P) -> T { + match self { + Self::Ok(value) => value, + Self::Err(err) => { + match err { + ParseError::IoError(err) => { + eprintln!("{}: {}", path.as_ref().display(), err); + } + ParseError::HardError(err) => { + eprintln!("{}: {}", path.as_ref().display(), err); + } + ParseError::SyntaxError(errs) => { + eprintln!("{}:", path.as_ref().display()); + for err in errs { + eprintln!("* ...: {}", err.describe()); + } + } + } + panic!("Compilation aborted"); + } + } + } +} + +impl ParseError { + pub fn syntax1(e: SyntaxError) -> Self { + Self::SyntaxError(vec![e]) + } + + pub fn fold>, Q: Extend>( + mut acc: Q, + it: I, + ) -> Result { + let mut errors = vec![]; + for item in it { + match item { + Ok(val) => acc.extend_one(val), + Err(ParseError::SyntaxError(err)) => errors.extend(err), + Err(err) => return Err(err), + } + } + if errors.is_empty() { + Ok(acc) + } else { + Err(ParseError::SyntaxError(errors)) + } + } +} + +#[derive(Debug)] +pub struct Document { + newtypes: Vec, + syscalls: Vec, +} + +enum NewTypeDefinition { + Alias(syn::Type), + Transparent { + repr: syn::Type, + #[allow(dead_code)] + sentinel: Option, + }, + Extern(syn::Type), +} + +struct SyscallDefinition { + name: Ident, + arguments: Punctuated, + return_type: Option, +} + +struct Argument { + name: Ident, + ty: syn::Type, +} + +#[derive(Debug)] +struct NewType { + name: Ident, + definition: NewTypeDefinition, +} + +struct ExternTypeItem { + name: Ident, + ty: syn::Type, +} + +enum TopLevel { + NewType(NewType), + ExternBlock(Punctuated), + Syscall(SyscallDefinition), +} + +impl syn::parse::Parse for SyscallDefinition { + fn parse(input: ParseStream) -> syn::Result { + let arguments; + + let name = input.parse()?; + parenthesized!(arguments in input); + let arguments = arguments.parse_terminated(Argument::parse, Token![,])?; + + let return_type = if input.peek(Token![->]) { + input.parse::]>()?; + let ty = input.parse()?; + Some(ty) + } else { + None + }; + + Ok(Self { + name, + arguments, + return_type, + }) + } +} + +impl syn::parse::Parse for Argument { + fn parse(input: ParseStream) -> syn::Result { + let name = input.parse()?; + input.parse::()?; + let ty = input.parse()?; + + Ok(Self { name, ty }) + } +} + +impl syn::parse::Parse for NewType { + fn parse(input: ParseStream) -> syn::Result { + let name = input.parse()?; + if input.peek(Token![=]) { + let _token: Token![=] = input.parse()?; + let ty = input.parse()?; + + Ok(Self { + name, + definition: NewTypeDefinition::Alias(ty), + }) + } else { + // Parenthesized definition + let content; + parenthesized!(content in input); + + // First value has to be a type + let ty = content.parse()?; + + Ok(Self { + name, + definition: NewTypeDefinition::Transparent { + repr: ty, + sentinel: None, + }, + }) + } + } +} + +impl syn::parse::Parse for ExternTypeItem { + fn parse(input: ParseStream) -> syn::Result { + input.parse::()?; + let name: syn::Ident = input.parse()?; + + let ty = if input.peek(Token![=]) { + input.parse::()?; + input.parse()? + } else { + syn::Type::Path(syn::TypePath { + qself: None, + path: syn::Path { + leading_colon: None, + segments: Punctuated::from_iter([syn::PathSegment { + arguments: syn::PathArguments::None, + ident: name.clone(), + }]), + }, + }) + }; + + Ok(Self { name, ty }) + } +} + +impl syn::parse::Parse for TopLevel { + fn parse(input: ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + if lookahead.peek(Ident) { + let ident: Ident = input.parse()?; + match ident.to_string().as_str() { + "newtype" => return NewType::parse(input).map(Self::NewType), + "syscall" => return SyscallDefinition::parse(input).map(Self::Syscall), + _ => (), + } + } else if lookahead.peek(token::Extern) { + input.parse::()?; + let lookahead = input.lookahead1(); + if lookahead.peek(Token![type]) { + input.parse::()?; + let name = input.parse()?; + input.parse::()?; + let ty = input.parse()?; + return Ok(Self::NewType(NewType { + name, + definition: NewTypeDefinition::Extern(ty), + })); + } else if lookahead.peek(token::Brace) { + let content; + braced!(content in input); + let items = content.parse_terminated(ExternTypeItem::parse, Token![;])?; + return Ok(Self::ExternBlock(items)); + } else { + return Err(input.error(&format!( + "Expected 'extern type' or 'extern {{ ... }}': {}", + lookahead.error() + ))); + } + } + + Err(input.error(&format!( + "Expected 'extern', 'newtype' or 'syscall', got '{}'", + lookahead.error() + ))) + } +} + +impl syn::parse::Parse for Document { + fn parse(input: ParseStream) -> syn::Result { + let mut newtypes = vec![]; + let mut syscalls = vec![]; + + loop { + if input.is_empty() { + break; + } + + let toplevel = TopLevel::parse(input)?; + input.parse::()?; + + match toplevel { + TopLevel::NewType(ty) => newtypes.push(ty), + TopLevel::ExternBlock(extys) => { + for exty in extys { + newtypes.push(NewType { + name: exty.name, + definition: NewTypeDefinition::Extern(exty.ty), + }); + } + } + TopLevel::Syscall(syscall) => syscalls.push(syscall), + } + } + + Ok(Self { newtypes, syscalls }) + } +} + +impl fmt::Debug for Argument { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let ty = &self.ty; + f.debug_tuple("Argument") + .field(&self.name) + .field("e!(#ty)) + .finish() + } +} + +impl fmt::Debug for SyscallDefinition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let args: Vec<_> = self.arguments.iter().collect(); + let return_type = self.return_type.as_ref().map(|ty| quote!(#ty)); + + f.debug_struct("SyscallDefinition") + .field("name", &self.name) + .field("args", &args) + .field("return_type", &return_type) + .finish() + } +} + +impl fmt::Debug for NewTypeDefinition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Transparent { repr, .. } => f + .debug_struct("NewTypeDefinition::Transparent") + .field("repr", "e!(#repr)) + .finish(), + Self::Alias(ty) => f + .debug_tuple("NewTypeDefinition::Alias") + .field("e!(#ty)) + .finish(), + Self::Extern(ty) => f + .debug_tuple("NewTypeDefinition::Extern") + .field("e!(#ty)) + .finish(), + } + } +} + +pub fn parse_abi_document(input: &str) -> Result { + let document: Document = syn::parse_str(input)?; + Ok(document) +} + +fn as_single_segment_path(path: &syn::TypePath) -> Option<&PathSegment> { + if path.qself.is_some() || path.path.leading_colon.is_some() || path.path.segments.len() != 1 { + return None; + } + + Some(&path.path.segments[0]) +} + +fn as_single_type_argument(args: &syn::PathArguments) -> Option<&syn::Type> { + let syn::PathArguments::AngleBracketed(args) = args else { + return None; + }; + if args.args.len() != 1 { + return None; + } + let syn::GenericArgument::Type(ty) = &args.args[0] else { + return None; + }; + + Some(ty) +} + +fn as_const_array_len(len: &Expr) -> Option { + let Expr::Lit(ExprLit { + attrs, + lit: Lit::Int(lit), + }) = len + else { + return None; + }; + + if !attrs.is_empty() { + todo!() + } + + lit.base10_parse().ok() +} + +fn process_type( + t: &syn::Type, + known_types: &HashMap>, +) -> Result, ParseError> { + match t { + syn::Type::Path(path) => { + let segment = as_single_segment_path(path).expect("TODO: more complex paths"); + + match segment.ident.to_string().as_str() { + seg if let Ok(ty) = AbiPrimitive::from_str(seg) => { + Ok(Rc::new(ComplexType::primitive(ty))) + } + "NonZeroUsize" => Ok(Rc::new(ComplexType::Simple(SimpleType::NonZeroUsize))), + "Result" if let Some(ty) = as_single_type_argument(&segment.arguments) => { + let ty = process_type(ty, known_types)?; + Ok(Rc::new(ComplexType::Result(ty))) + } + "Option" if let Some(ty) = as_single_type_argument(&segment.arguments) => { + let ty = process_type(ty, known_types)?; + Ok(Rc::new(ComplexType::Option(ty))) + } + "MaybeUninit" if let Some(ty) = as_single_type_argument(&segment.arguments) => { + let ty = process_type(ty, known_types)?; + Ok(Rc::new(ComplexType::MaybeUninit(ty))) + } + name if let Some(ty) = known_types.get(name) => Ok(ty.clone()), + _ => Err(ParseError::syntax1(SyntaxError::UndefinedIdentifier( + segment.ident.clone(), + ))), + } + } + syn::Type::Never(_) => Ok(Rc::new(ComplexType::Simple(SimpleType::Never))), + syn::Type::Reference(r) => { + // TODO lifetimes + if r.lifetime.is_some() { + todo!() + } + + let mutable = r.mutability.is_some(); + + match r.elem.as_ref() { + syn::Type::Slice(ty) => { + let element = process_type(&ty.elem, known_types)?; + Ok(Rc::new(ComplexType::Simple(SimpleType::Slice { + mutable, + element, + }))) + } + syn::Type::Path(path) + if !mutable + && as_single_segment_path(path) + .map(|seg| seg.ident.to_string() == "str" && seg.arguments.is_empty()) + .unwrap_or(false) => + { + Ok(Rc::new(ComplexType::Simple(SimpleType::Str))) + } + syn::Type::Array(array) if let Some(length) = as_const_array_len(&array.len) => { + let element = process_type(&array.elem, known_types)?; + Ok(Rc::new(ComplexType::Simple(SimpleType::Array { + mutable, + element, + length, + }))) + } + _ => { + let pointee = process_type(&r.elem, known_types)?; + Ok(Rc::new(ComplexType::Simple(SimpleType::Reference { + mutable, + pointee, + }))) + } + } + } + syn::Type::Tuple(t) if t.elems.is_empty() => { + Ok(Rc::new(ComplexType::primitive(AbiPrimitive::Unit))) + } + _ => todo!(), + } +} + +fn process_newtype( + t: &NewType, + known_types: &HashMap>, +) -> Result<(Ident, Rc), ParseError> { + let ty = match &t.definition { + NewTypeDefinition::Transparent { repr, .. } => { + let ty = process_type(repr, known_types)?; + if let ComplexType::Simple(SimpleType::Primitive(ty)) = ty.as_ref() { + Rc::new(ComplexType::Simple(SimpleType::Transparent { + name: t.name.clone(), + inner: *ty, + })) + } else { + todo!() + } + } + NewTypeDefinition::Extern(ty) => Rc::new(ComplexType::Extern(ty.clone())), + NewTypeDefinition::Alias(ty) => process_type(ty, known_types)?, + }; + Ok((t.name.clone(), ty)) +} + +pub fn parse_abi(input: &str) -> Result { + let doc = parse_abi_document(input)?; + let mut abi = Abi { + types: HashMap::new(), + syscalls: Vec::new(), + }; + + ParseError::fold( + (), + doc.newtypes.iter().map(|nt| { + let (name, value) = process_newtype(nt, &abi.types)?; + abi.types.insert(name.to_string(), value); + Ok(()) + }), + )?; + + ParseError::fold( + (), + doc.syscalls.iter().map(|syscall| { + let args = syscall + .arguments + .iter() + .map(|arg| { + let ty = process_type(&arg.ty, &abi.types)?; + Ok((arg.name.clone(), ty)) + }) + .collect::, ParseError>>()?; + let return_type = syscall + .return_type + .as_ref() + .map(|ty| process_type(ty, &abi.types)) + .transpose()?; + + let variant = syscall_enum_variant_identifier(&syscall.name); + let def = Syscall { + name: syscall.name.clone(), + args, + return_type, + }; + + abi.syscalls.push((variant, def)); + + Ok(()) + }), + )?; + + Ok(abi) +} + +#[cfg(test)] +mod tests { + use quote::quote; + + use crate::{abi::ty::TypeWidth, parse::UnwrapFancy, TargetEnv}; + + use super::parse_abi; + + const TARGET_64: TargetEnv = TargetEnv { + thin_pointer_width: TypeWidth::U64, + fat_pointer_width: TypeWidth::U128, + }; + + #[test] + fn emit_basic_syscall() { + let abi = parse_abi( + r#" + extern type MyStruct = some_lib::MyStruct; + newtype Fd(u32); + + syscall read(fd: Fd, buf: &mut [u8]) -> SizeResult; + syscall get_file_info(data: &mut MyStruct); + "#, + ) + .unwrap_fancy(""); + + assert_eq!( + abi.syscalls[0].emit_stub(&TARGET_64).to_string(), + quote! { + unsafe fn read(fd: Fd, buf: &mut [u8],) -> Result { + >::from_syscall_result(syscall!( + SyscallFunction::read, + fd.0 as usize, + buf.as_mut_ptr().expose_addr(), + buf.len(), + )) + } + } + .to_string() + ); + + assert_eq!( + abi.syscalls[1].emit_stub(&TARGET_64).to_string(), + quote! { + unsafe fn get_file_info(data: &mut some_lib::MyStruct,) { + syscall!(SyscallFunction::get_file_info, (data as *mut _).expose_addr(),); + } + } + .to_string() + ); + } +}