diff --git a/tool/abi-generator/.gitignore b/tool/abi-generator/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/tool/abi-generator/.gitignore @@ -0,0 +1 @@ +/target diff --git a/tool/abi-generator/Cargo.lock b/tool/abi-generator/Cargo.lock new file mode 100644 index 00000000..b0682bde --- /dev/null +++ b/tool/abi-generator/Cargo.lock @@ -0,0 +1,110 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "abi-generator" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "thiserror", +] + +[[package]] +name = "abi-lib" +version = "0.1.0" +dependencies = [ + "compiler_builtins", + "rustc-std-workspace-core", +] + +[[package]] +name = "compiler_builtins" +version = "0.1.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68bc55329711cd719c2687bb147bc06211b0521f97ef398280108ccb23227e9" +dependencies = [ + "rustc-std-workspace-core", +] + +[[package]] +name = "example-abi" +version = "0.1.0" +dependencies = [ + "abi-generator", + "abi-lib", + "prettyplease", +] + +[[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 = "rustc-std-workspace-core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1956f5517128a2b6f23ab2dadf1a976f4f5b27962e7724c2bf3d45e539ec098c" + +[[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 = "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/tool/abi-generator/Cargo.toml b/tool/abi-generator/Cargo.toml new file mode 100644 index 00000000..d4e50df6 --- /dev/null +++ b/tool/abi-generator/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "abi-generator" +version = "0.1.0" +edition = "2021" + +[dependencies] +proc-macro2 = "^1.0.63" +quote = "^1.0" +syn = { version = "2.0.32", features = ["full"] } +thiserror = "1.0.47" + +[workspace] +members = ["abi-lib", "example-abi"] diff --git a/tool/abi-generator/abi-lib/Cargo.toml b/tool/abi-generator/abi-lib/Cargo.toml new file mode 100644 index 00000000..4845c848 --- /dev/null +++ b/tool/abi-generator/abi-lib/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "abi-lib" +version = "0.1.0" +edition = "2021" + +[dependencies] +core = { version = "1.0.0", optional = true, package = "rustc-std-workspace-core" } +compiler_builtins = { version = "0.1", optional = true } + +[features] +default = [] +rustc-dep-of-std = [ + "core", + "compiler_builtins/rustc-dep-of-std", +] diff --git a/tool/abi-generator/abi-lib/src/lib.rs b/tool/abi-generator/abi-lib/src/lib.rs new file mode 100644 index 00000000..e38307c7 --- /dev/null +++ b/tool/abi-generator/abi-lib/src/lib.rs @@ -0,0 +1,19 @@ +#![feature(never_type)] +#![no_std] + +mod primitive_impls; +mod wrapper_impls; + +pub trait SyscallRegister { + fn into_syscall_register(self) -> usize; + fn from_syscall_register(value: usize) -> Self; +} + +pub trait SyscallFatRegister { + fn as_syscall_meta(&self) -> usize; + fn as_syscall_data(&self) -> usize; + + fn from_syscall_registers(meta: usize, data: usize) -> Self; +} + +pub unsafe trait ThinWrapped: SyscallRegister {} diff --git a/tool/abi-generator/abi-lib/src/primitive_impls.rs b/tool/abi-generator/abi-lib/src/primitive_impls.rs new file mode 100644 index 00000000..a2918a49 --- /dev/null +++ b/tool/abi-generator/abi-lib/src/primitive_impls.rs @@ -0,0 +1,56 @@ +use core::num::NonZeroUsize; + +use crate::{SyscallRegister, ThinWrapped}; + +macro_rules! numeric_impl { + ($($ty:ident),+ $(,)?) => { + $( + unsafe impl ThinWrapped for $ty {} + + impl SyscallRegister for $ty { + fn into_syscall_register(self) -> usize { + self as _ + } + + fn from_syscall_register(value: usize) -> Self { + value as _ + } + } + )+ + }; +} + +numeric_impl!(u8, u16, u32, u64); +numeric_impl!(i8, i16, i32, i64); + +unsafe impl ThinWrapped for () {} + +impl SyscallRegister for NonZeroUsize { + fn from_syscall_register(value: usize) -> Self { + unsafe { Self::new_unchecked(value) } + } + + fn into_syscall_register(self) -> usize { + self.into() + } +} + +impl SyscallRegister for () { + fn into_syscall_register(self) -> usize { + 0 + } + + fn from_syscall_register(_value: usize) -> Self { + () + } +} + +impl SyscallRegister for ! { + fn from_syscall_register(_value: usize) -> Self { + unreachable!() + } + + fn into_syscall_register(self) -> usize { + self + } +} diff --git a/tool/abi-generator/abi-lib/src/wrapper_impls.rs b/tool/abi-generator/abi-lib/src/wrapper_impls.rs new file mode 100644 index 00000000..860a8b52 --- /dev/null +++ b/tool/abi-generator/abi-lib/src/wrapper_impls.rs @@ -0,0 +1,95 @@ +use core::num::NonZeroUsize; + +use crate::{SyscallRegister, ThinWrapped}; + +pub fn make_error(e: E) -> usize { + (-(e.into_syscall_register() as isize)) as usize +} + +pub fn get_error(value: usize) -> E { + E::from_syscall_register((-(value as isize)) as usize) +} + +// Result is special: usize is not thinly-wrapped, but is represented in the ABI similar +// to Unix ssize_t-returning system calls +impl SyscallRegister for Result { + fn into_syscall_register(self) -> usize { + match self { + Ok(value) => value, + Err(err) => make_error(err), + } + } + + fn from_syscall_register(value: usize) -> Self { + if (value as isize) < 0 { + Err(get_error(value)) + } else { + Ok(value as _) + } + } +} + +impl SyscallRegister for Option { + fn into_syscall_register(self) -> usize { + match self { + None => usize::MAX, + Some(value) => value.into_syscall_register(), + } + } + + fn from_syscall_register(value: usize) -> Self { + match value { + usize::MAX => None, + _ => Some(T::from_syscall_register(value)), + } + } +} + +impl SyscallRegister for Result { + fn into_syscall_register(self) -> usize { + match self { + Ok(value) => value.into_syscall_register(), + Err(err) => make_error(err), + } + } + + fn from_syscall_register(value: usize) -> Self { + if (value as isize) < 0 { + Err(get_error(value)) + } else { + Ok(T::from_syscall_register(value)) + } + } +} + +impl SyscallRegister for Option { + fn into_syscall_register(self) -> usize { + match self { + Some(value) => value.into(), + None => 0, + } + } + + fn from_syscall_register(value: usize) -> Self { + NonZeroUsize::new(value) + } +} + +impl SyscallRegister for Result { + fn into_syscall_register(self) -> usize { + match self { + Ok(value) => value.into(), + Err(err) => make_error(err), + } + } + + fn from_syscall_register(value: usize) -> Self { + if (value as isize) < 0 { + Err(get_error(value)) + } else if value != 0 { + Ok(unsafe { NonZeroUsize::new_unchecked(value) }) + } else { + unreachable!() + } + } +} diff --git a/tool/abi-generator/example-abi/Cargo.toml b/tool/abi-generator/example-abi/Cargo.toml new file mode 100644 index 00000000..fd163c29 --- /dev/null +++ b/tool/abi-generator/example-abi/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "example-abi" +version = "0.1.0" +edition = "2021" + +[dependencies] +abi-lib = { path = "../abi-lib" } + +[build-dependencies] +abi-generator = { path = ".." } +prettyplease = "0.2.15" diff --git a/tool/abi-generator/example-abi/build.rs b/tool/abi-generator/example-abi/build.rs new file mode 100644 index 00000000..1f11464d --- /dev/null +++ b/tool/abi-generator/example-abi/build.rs @@ -0,0 +1,34 @@ +use std::{env, path::Path}; + +use abi_generator::{ + abi::{ty::TypeWidth, AbiBuilder}, + syntax::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"); + + let types_file = prettyplease::unparse( + &abi.emit_file(true, false) + .unwrap_fancy("Could not emit generated types"), + ); + let syscalls_file = prettyplease::unparse( + &abi.emit_file(false, true) + .unwrap_fancy("Could not emit generated syscalls"), + ); + + std::fs::write(output.join("generated_types.rs"), types_file).unwrap(); + std::fs::write(output.join("generated_syscalls.rs"), syscalls_file).unwrap(); +} diff --git a/tool/abi-generator/example-abi/src/main.rs b/tool/abi-generator/example-abi/src/main.rs new file mode 100644 index 00000000..a25c508c --- /dev/null +++ b/tool/abi-generator/example-abi/src/main.rs @@ -0,0 +1,152 @@ +#![feature(exposed_provenance)] + +use std::sync::Mutex; + +mod types { + use std::marker::PhantomData; + + use abi_lib::{SyscallFatRegister, SyscallRegister}; + + pub enum MappingSource {} + pub struct MountOptions; + pub struct UnmountOptions; + pub struct DirectoryEntry; + pub struct FileAttr; + pub struct SpawnOptions<'a> { + _a: PhantomData<&'a ()>, + } + 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 {} + + pub enum ExitCode { + Exited(u32), + Killed(u32), + } + + pub enum SeekFrom { + Start(u64), + Current(i64), + End(i64), + } + + impl SyscallRegister for ExitCode { + fn into_syscall_register(self) -> usize { + match self { + Self::Killed(value) => (value as usize) | (1 << u32::BITS), + Self::Exited(value) => value as usize, + } + } + + fn from_syscall_register(value: usize) -> Self { + if value & (1 << u32::BITS) != 0 { + Self::Killed(value as _) + } else { + Self::Exited(value as _) + } + } + } + + impl SyscallFatRegister for SeekFrom { + fn as_syscall_meta(&self) -> usize { + match self { + Self::Start(_) => 0, + Self::Current(_) => 1, + Self::End(_) => 2, + } + } + + fn as_syscall_data(&self) -> usize { + match self { + &Self::Start(value) => value as _, + &Self::Current(value) | &Self::End(value) => value as _, + } + } + + fn from_syscall_registers(_meta: usize, _data: usize) -> Self { + todo!() + } + } + + include!(concat!(env!("OUT_DIR"), "/generated_types.rs")); +} + +mod calls { + use super::types::*; + + macro_rules! syscall { + ($f:expr $(,)?) => { + $crate::syscall0($f) + }; + ($f:expr, $a0:expr $(,)?) => { + $crate::syscall1($f, $a0) + }; + ($f:expr, $a0:expr, $a1:expr $(,)?) => { + $crate::syscall2($f, $a0, $a1) + }; + ($f:expr, $a0:expr, $a1:expr, $a2:expr $(,)?) => { + $crate::syscall3($f, $a0, $a1, $a2) + }; + ($f:expr, $a0:expr, $a1:expr, $a2:expr, $a3:expr $(,)?) => { + $crate::syscall4($f, $a0, $a1, $a2, $a3) + }; + ($f:expr, $a0:expr, $a1:expr, $a2:expr, $a3:expr, $a4:expr $(,)?) => { + $crate::syscall5($f, $a0, $a1, $a2, $a3, $a4) + }; + } + + include!(concat!(env!("OUT_DIR"), "/generated_syscalls.rs")); +} + +pub use calls::*; +pub use types::*; + +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 +} + +fn main() {} diff --git a/tool/abi-generator/example-abi/syscall.abi b/tool/abi-generator/example-abi/syscall.abi new file mode 100644 index 00000000..44fadaa5 --- /dev/null +++ b/tool/abi-generator/example-abi/syscall.abi @@ -0,0 +1,201 @@ +// 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; + + #[thin] + type ExitCode; + #[fat] + type SeekFrom; +} + +newtype Fd(u32); +newtype ProcessId(u32); +newtype ThreadId(u32); +newtype ProcessGroupId(u32); + +// TODO define these as enums +newtype MessageDestination(u32); + +bitfield OpenOptions(u32) { + /// Open the file with read capability + READ: 0, + /// Open the file with write capability + WRITE: 1, + + /// Position at the end of the opened file + APPEND: 2, + /// Truncate the file to zero bytes when opening + TRUNCATE: 3, + + /// Create a new file if it does not exist + CREATE: 4, +} + +bitfield FileMode(u32) { + /// Other user execute access + OTHER_EXEC: 0, + /// Other user write access + OTHER_WRITE: 1, + /// Other user read access + OTHER_READ: 2, + + /// Group execute access + GROUP_EXEC: 0, + /// Group write access + GROUP_WRITE: 1, + /// Group read access + GROUP_READ: 2, + + /// Owner execute access + USER_EXEC: 0, + /// Owner write access + USER_WRITE: 1, + /// Owner read access + USER_READ: 2, +} + +enum Signal(u32) { + MemoryAccessViolation = 1, + Aborted = 2, + Killed = 3, + Interrupted = 4 +} + +enum PollControl(u32) { + AddFd = 1 +} + +enum SocketType(u32) { + RawPacket = 1, + TcpStream = 2, + UdpPacket = 3 +} + +enum Error(u32) { + Dummy = 1, + UndefinedSyscall = 2, + InvalidArgument = 3, +} + +syscall test_syscall(a: &(u32, u64)); +// +// // 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/tool/abi-generator/src/abi/mod.rs b/tool/abi-generator/src/abi/mod.rs new file mode 100644 index 00000000..480cb558 --- /dev/null +++ b/tool/abi-generator/src/abi/mod.rs @@ -0,0 +1,513 @@ +use std::{collections::HashMap, path::Path, rc::Rc}; + +use proc_macro2::{Span, TokenStream}; +use quote::{quote, TokenStreamExt}; +use syn::{punctuated::Punctuated, spanned::Spanned, token, Token}; + +pub mod ty; + +use crate::{ + abi::ty::ComplexType, + syntax::{ + parse_abi, Attributes, BitfieldRange, BitfieldType, BitfieldTypeField, + DocumentTypeDefinition, EnumType, EnumTypeVariant, NewType, ParseError, TypeRepr, + }, + TargetEnv, +}; + +use self::ty::{complex::ExternKind, Type}; + +mod syscall; + +pub use syscall::Syscall; + +pub trait GenerateTypeDefinition { + fn generate_type_definition(&self, abi_type: &ComplexType, env: &TargetEnv) -> TokenStream; +} + +pub struct TypeEnv { + pub locals: HashMap, DocumentTypeDefinition)>, + pub externs: HashMap, +} + +impl TypeEnv { + pub fn new() -> Self { + Self { + locals: HashMap::new(), + externs: HashMap::new(), + } + } + + pub fn lookup_local(&self, name: &str) -> Option<&(Rc, DocumentTypeDefinition)> { + self.locals.get(name) + } + + pub fn lookup_extern(&self, name: &str) -> Option<&(syn::Type, ExternKind)> { + self.externs.get(name) + } + + pub fn insert_local( + &mut self, + name: String, + abi_type: Rc, + def: DocumentTypeDefinition, + ) { + self.locals.insert(name, (abi_type, def)); + } + + pub fn insert_extern(&mut self, name: String, def: syn::Type, kind: ExternKind) { + self.externs.insert(name, (def, kind)); + } +} + +pub struct Abi { + pub types: TypeEnv, + // Contains syscalls and their corresponding SyscallFunction enum variants + pub syscalls: Vec<(syn::Ident, Syscall)>, +} + +pub struct AbiBuilder { + abi: Abi, + target_env: TargetEnv, +} + +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_file(path)?; + Ok(Self { abi, target_env }) + } + + pub fn from_string>(data: S, target_env: TargetEnv) -> Result { + let abi = Abi::read_string(data)?; + Ok(Self { abi, target_env }) + } + + pub fn emit_syscall_dispatcher( + &self, + dispatcher_fn: &str, + impl_mod: &str, + ) -> Result { + let dispatcher_fn = syn::Ident::new(dispatcher_fn, Span::call_site()); + let impl_mod = syn::Ident::new(impl_mod, Span::call_site()); + + let raw_fn = syn::Ident::new("__fn", Span::call_site()); + let raw_args = syn::Ident::new("__args", Span::call_site()); + + let mut branches = Punctuated::<_, Token![,]>::new(); + + for (enum_variant, syscall) in self.abi.syscalls.iter() { + let named_args = (0..syscall.args.len()) + .map(|i| syn::Ident::new(&format!("__a{}", i), Span::call_site())) + .collect::>(); + let call_args = named_args + .iter() + .cloned() + .collect::>(); + let syscall_name = &syscall.name; + + let handler_fn = quote! { #impl_mod::#syscall_name }; + + let body = quote! { + Ok((#handler_fn(#call_args)).into_syscall_register()) + }; + + let match_arm = syscall.emit_handler_branch( + &named_args, + &raw_args, + body, + enum_variant, + &self.target_env, + ); + + branches.push(match_arm); + } + + let dispatcher_fn = quote! { + pub(crate) fn #dispatcher_fn(#raw_fn: SyscallFunction, #raw_args: &[usize]) -> Result { + match #raw_fn { + #branches + } + } + }; + + let file = syn::parse_file(&dispatcher_fn.to_string())?; + Ok(file) + } + + pub fn emit_file(&self, emit_types: bool, emit_calls: bool) -> Result { + let mut stream = TokenStream::new(); + + if emit_types { + stream.extend(self.emit_syscall_enum()); + stream.extend(self.emit_type_definitions()); + } + + if emit_calls { + // Preamble + stream.extend(quote! { + #[allow(unused_imports)] + use abi_lib::{SyscallRegister, SyscallFatRegister}; + }); + stream.extend(self.emit_syscalls()); + } + + let file = syn::parse_file(&stream.to_string())?; + Ok(file) + } + + fn emit_syscall_enum(&self) -> TokenStream { + let syscall_discriminants = self + .abi + .syscalls + .iter() + .enumerate() + .map(|(index, (variant, _))| { + let index = index + 1; + quote! { #variant = #index } + }) + .collect::>(); + + let syscall_try_from_branches = self + .abi + .syscalls + .iter() + .enumerate() + .map(|(index, (variant, _))| { + let index = index + 1; + quote! { #index => Ok(Self::#variant) } + }) + .collect::>(); + + quote! { + #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)] + #[repr(usize)] + pub enum SyscallFunction { + #syscall_discriminants + } + + impl From for usize { + fn from(value: SyscallFunction) -> usize { + value as usize + } + } + + impl TryFrom for SyscallFunction { + type Error = Error; + + fn try_from(value: usize) -> Result { + match value { + #syscall_try_from_branches, + _ => Err(Error::UndefinedSyscall) + } + } + } + } + } + + fn emit_type_definitions(&self) -> TokenStream { + let mut stream = TokenStream::new(); + + for (_, (ty, def)) in &self.abi.types.locals { + stream.extend(def.generate_type_definition(ty, &self.target_env)); + } + + stream + } + + fn emit_syscalls(&self) -> TokenStream { + let mut stream = TokenStream::new(); + + for (variant, syscall) in &self.abi.syscalls { + stream.append_all(syscall.emit_stub(variant, &self.target_env)); + } + + stream + } +} + +impl Abi { + pub fn read_file>(path: P) -> Result { + let data = std::fs::read_to_string(path)?; + parse_abi(&data) + } + + pub fn read_string>(data: S) -> Result { + parse_abi(data.as_ref()) + } +} + +impl GenerateTypeDefinition for EnumType { + fn generate_type_definition(&self, abi_type: &ComplexType, env: &TargetEnv) -> TokenStream { + let Self { + attributes, + name, + repr, + variants, + } = self; + let TypeRepr(repr) = &repr; + + // TODO auto-assign numbers to enum variants + let try_from_branches = variants + .iter() + .map(|variant| { + let EnumTypeVariant { + discriminant, + value, + .. + } = variant; + quote! { #value => Ok(Self::#discriminant) } + }) + .collect::>(); + + let direct = quote! { + #attributes + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] + #[repr(#repr)] + pub enum #name { + #variants + } + + impl #name { + pub const fn into_raw(self) -> #repr { + self as #repr + } + + pub const unsafe fn from_raw(value: #repr) -> Self { + core::mem::transmute(value) + } + } + + impl From<#name> for #repr { + #[inline] + fn from(value: #name) -> #repr { + value as #repr + } + } + + impl TryFrom<#repr> for #name { + type Error = Error; + + fn try_from(value: #repr) -> Result<#name, Error> { + match value { + #try_from_branches, + _ => Err(Error::InvalidArgument) + } + } + } + + impl abi_lib::SyscallRegister for #name { + fn into_syscall_register(self) -> usize { + self as #repr as usize + } + + fn from_syscall_register(value: usize) -> Self { + unsafe { core::mem::transmute(value as #repr) } + } + } + }; + + let wrapped = if abi_type.width(env) < env.fat_pointer_width { + quote! { + #direct + + unsafe impl abi_lib::ThinWrapped for #name {} + } + } else { + direct + }; + + wrapped + } +} + +fn generate_transparent_struct( + attributes: &Attributes, + name: &syn::Ident, + repr: &TypeRepr, + is_thin: bool, +) -> TokenStream { + let TypeRepr(repr) = &repr; + + let direct = quote! { + #attributes + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] + pub struct #name(pub(crate) #repr); + + impl #name { + pub const fn into_raw(self) -> #repr { + self.0 + } + + pub const fn bits(self) -> #repr { + self.0 + } + + pub const unsafe fn from_raw(value: #repr) -> Self { + Self(value) + } + } + + impl From<#name> for #repr { + #[inline] + fn from(value: #name) -> #repr { + value.0 + } + } + + impl abi_lib::SyscallRegister for #name { + fn into_syscall_register(self) -> usize { + self.0 as _ + } + + fn from_syscall_register(value: usize) -> Self { + Self(value as _) + } + } + }; + + let wrapped = if is_thin { + quote! { + #direct + + unsafe impl abi_lib::ThinWrapped for #name {} + } + } else { + direct + }; + + wrapped +} + +impl GenerateTypeDefinition for NewType { + fn generate_type_definition(&self, abi_type: &ComplexType, env: &TargetEnv) -> TokenStream { + let Self { + attributes, + name, + repr, + } = self; + + generate_transparent_struct( + attributes, + name, + repr, + abi_type.width(env) < env.fat_pointer_width, + ) + } +} + +impl GenerateTypeDefinition for BitfieldType { + fn generate_type_definition(&self, abi_type: &ComplexType, env: &TargetEnv) -> TokenStream { + let Self { + attributes, + name, + repr, + fields, + } = self; + + let base = generate_transparent_struct( + attributes, + name, + repr, + abi_type.width(env) < env.fat_pointer_width, + ); + + let mut field_impls = TokenStream::new(); + for BitfieldTypeField { + attributes, + name, + range, + } in fields + { + match range { + BitfieldRange::Single(offset) => { + field_impls.extend(quote! { + #attributes + pub const #name: Self = Self(1 << #offset); + }); + } + BitfieldRange::Range(_start, _end) => todo!(), + } + } + + let base = quote! { + #base + + impl #name { + #field_impls + + pub const fn empty() -> Self { + Self(0) + } + + pub const fn contains(&self, value: Self) -> bool { + self.0 & value.0 == value.0 + } + } + + impl core::ops::BitOrAssign for #name { + fn bitor_assign(&mut self, other: Self) { + self.0 |= other.0; + } + } + + impl core::ops::BitAndAssign for #name { + fn bitand_assign(&mut self, other: Self) { + self.0 &= other.0; + } + } + + impl core::ops::BitAnd for #name { + type Output = #name; + + fn bitand(self, other: Self) -> Self { + Self(self.0 & other.0) + } + } + + impl core::ops::BitOr for #name { + type Output = #name; + + fn bitor(self, other: Self) -> Self { + Self(self.0 | other.0) + } + } + + impl core::ops::Not for #name { + type Output = #name; + + fn not(self) -> Self { + Self(!self.0) + } + } + }; + + base + } +} + +impl GenerateTypeDefinition for DocumentTypeDefinition { + fn generate_type_definition(&self, abi_type: &ComplexType, env: &TargetEnv) -> TokenStream { + match self { + Self::Enum(inner) => inner.generate_type_definition(abi_type, env), + Self::Bitfield(inner) => inner.generate_type_definition(abi_type, env), + Self::NewType(inner) => inner.generate_type_definition(abi_type, env), + // Extern types require no definition + Self::ExternType(_) => TokenStream::new(), + } + } +} diff --git a/tool/abi-generator/src/abi/syscall.rs b/tool/abi-generator/src/abi/syscall.rs new file mode 100644 index 00000000..2306426c --- /dev/null +++ b/tool/abi-generator/src/abi/syscall.rs @@ -0,0 +1,124 @@ +use core::fmt; +use std::rc::Rc; + +use proc_macro2::TokenStream; +use quote::quote; + +use crate::{ + abi::ty::{SimpleType, Type}, + TargetEnv, +}; + +use super::ty::ComplexType; + +pub struct Syscall { + pub name: syn::Ident, + pub args: Vec<(syn::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() + } +} + +impl Syscall { + // Syntax: + // + // syscall_handler!("abi-document.abi", pub syscall_handler { + // read(fd, buffer) => ..., + // write(fd, buffer) => ..., + // }) + pub fn emit_handler_branch( + &self, + named_args: &[syn::Ident], + raw_args: &syn::Ident, + body: TokenStream, + enum_variant: &syn::Ident, + env: &TargetEnv, + ) -> TokenStream { + if self.args.len() != named_args.len() { + todo!( + "Argument length mismatch: got {} expectd {}", + named_args.len(), + self.args.len() + ) + } + + let mut extracted_arguments = TokenStream::new(); + let mut index = 0; + + for ((_, arg), name) in std::iter::zip(self.args.iter(), named_args) { + let (arg_expr, arg_width) = arg.emit_from_syscall_arguments(env, raw_args, index); + let arg_assignment = quote! { + let #name = #arg_expr; + }; + index += arg_width; + + extracted_arguments.extend(arg_assignment); + } + + quote! { + SyscallFunction::#enum_variant => { + #extracted_arguments + + #body + } + } + } + + 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_register(#sys_call)) + } + None => quote!(#sys_call;), + }; + + let v = quote! { + pub unsafe fn #name(#args) #return_type_arrow { + #wrapped + } + }; + v + } +} diff --git a/tool/abi-generator/src/abi/ty/complex.rs b/tool/abi-generator/src/abi/ty/complex.rs new file mode 100644 index 00000000..a019fa19 --- /dev/null +++ b/tool/abi-generator/src/abi/ty/complex.rs @@ -0,0 +1,181 @@ +use std::{fmt, rc::Rc}; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{punctuated::Punctuated, Ident, Token}; + +use crate::TargetEnv; + +use super::{AbiPrimitive, SimpleType, Type, TypeKind, TypeWidth}; + +#[derive(Clone, Copy)] +pub enum ExternKind { + None, + Thin, + Fat, +} + +#[derive(Clone)] +pub enum ComplexType { + Simple(SimpleType), + MaybeUninit(Rc), + Result(Rc), + Option(Rc), + Tuple(Vec>), + Extern { + kind: ExternKind, + alias: syn::Type, + arguments: syn::PathArguments, + }, +} + +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 as_primitive(&self) -> Option { + if let Self::Simple(SimpleType::Primitive(ty)) = self { + Some(*ty) + } else { + None + } + } +} + +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::Tuple(_) => TypeWidth::Unknown, + 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::Tuple(elems) => { + let elems = elems + .iter() + .map(|e| e.as_rust_type()) + .collect::>(); + quote!((#elems)) + } + Self::Result(ty) => { + let ty = ty.as_rust_type(); + // TODO take from TargetEnv + quote!(Result<#ty, Error>) + } + Self::Extern { + alias, arguments, .. + } => { + quote!(#alias #arguments) + } + 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 { + // TODO: Option/Result + 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_register()) + } + Self::Extern { + kind: ExternKind::Thin, + .. + } => { + quote!(#value.into_syscall_register()) + } + Self::Extern { + kind: ExternKind::Fat, + .. + } => { + quote!(#value.as_syscall_meta(), #value.as_syscall_data()) + } + _ => todo!("???"), + } + } + + fn emit_from_syscall_arguments( + &self, + env: &TargetEnv, + args: &Ident, + index: usize, + ) -> (TokenStream, usize) { + match self { + Self::Simple(ty) => ty.emit_from_syscall_arguments(env, args, index), + Self::Option(ty) + if ty.width(env) < env.thin_pointer_width || ty.kind() == TypeKind::ThinPointer => + { + let ty = ty.as_rust_type(); + ( + quote!(>::from_syscall_register(#args[#index])), + 1, + ) + } + Self::Result(_) => todo!("Result"), + Self::Extern { + kind: ExternKind::Thin, + alias, + .. + } => (quote!(<#alias>::from_syscall_register(#args[#index])), 1), + Self::MaybeUninit(_) => todo!("MaybeUninit"), + Self::Tuple(_) => unimplemented!(), + _ => todo!("{:?}", self), + } + } +} + +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::Tuple(elems) => { + let mut r = f.debug_tuple("Tuple"); + for elem in elems { + r.field(elem); + } + r.finish() + } + Self::Extern { alias, .. } => f.debug_tuple("Extern").field("e!(#alias)).finish(), + Self::MaybeUninit(ty) => f.debug_tuple("MaybeUninit").field(&ty).finish(), + } + } +} diff --git a/tool/abi-generator/src/abi/ty/mod.rs b/tool/abi-generator/src/abi/ty/mod.rs new file mode 100644 index 00000000..5225aa6b --- /dev/null +++ b/tool/abi-generator/src/abi/ty/mod.rs @@ -0,0 +1,167 @@ +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(Clone, Copy, 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 emit_from_syscall_arguments( + &self, + env: &TargetEnv, + args: &Ident, + index: usize, + ) -> (TokenStream, usize); + + 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/tool/abi-generator/src/abi/ty/primitive.rs b/tool/abi-generator/src/abi/ty/primitive.rs new file mode 100644 index 00000000..7a2805bc --- /dev/null +++ b/tool/abi-generator/src/abi/ty/primitive.rs @@ -0,0 +1,98 @@ +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) + } + + fn emit_from_syscall_arguments( + &self, + _env: &TargetEnv, + args: &Ident, + index: usize, + ) -> (TokenStream, usize) { + match self { + Self::Bool => (quote!(#args[#index] != 0), 1), + _ => { + let ty = self.as_rust_type(); + (quote!(#args[#index] as #ty), 1) + } + } + } +} + +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/tool/abi-generator/src/abi/ty/simple.rs b/tool/abi-generator/src/abi/ty/simple.rs new file mode 100644 index 00000000..bba2df7c --- /dev/null +++ b/tool/abi-generator/src/abi/ty/simple.rs @@ -0,0 +1,158 @@ +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.into_syscall_register()), + 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()), + }, + } + } + + fn emit_from_syscall_arguments( + &self, + env: &TargetEnv, + args: &Ident, + index: usize, + ) -> (TokenStream, usize) { + match self { + Self::Str => (quote!(arg::str_ref(#args[#index], #args[#index + 1])?), 2), + Self::Never => todo!("!"), + Self::Slice { mutable, element } => { + let element = element.as_rust_type(); + match mutable { + true => ( + quote!(arg::slice_mut::<#element>(#args[#index], #args[#index + 1])?), + 2, + ), + false => ( + quote!(arg::slice_ref::<#element>(#args[#index], #args[#index + 1])?), + 2, + ), + } + } + Self::Reference { mutable, pointee } => { + let pointee = pointee.as_rust_type(); + match mutable { + true => (quote!(arg::ref_mut::<#pointee>(#args[#index])?), 1), + false => (quote!(arg::ref_const::<#pointee>(#args[#index])?), 1), + } + } + Self::Array { + mutable, + element, + length, + } => { + let element = element.as_rust_type(); + match mutable { + true => ( + quote!(arg::ref_mut::<[#element; #length]>(#args[#index])?), + 1, + ), + false => ( + quote!(arg::ref_const::<[#element; #length]>(#args[#index])?), + 1, + ), + } + } + Self::NonZeroUsize => todo!("NonZeroUsize"), + Self::Primitive(ty) => ty.emit_from_syscall_arguments(env, args, index), + Self::Transparent { name, .. } => { + (quote!(<#name>::from_syscall_register(#args[#index])), 1) + } + } + } +} diff --git a/tool/abi-generator/src/error.rs b/tool/abi-generator/src/error.rs new file mode 100644 index 00000000..cf615516 --- /dev/null +++ b/tool/abi-generator/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/tool/abi-generator/src/lib.rs b/tool/abi-generator/src/lib.rs new file mode 100644 index 00000000..bc49ae63 --- /dev/null +++ b/tool/abi-generator/src/lib.rs @@ -0,0 +1,13 @@ +#![feature(if_let_guard, extend_one, proc_macro_span)] + +use crate::abi::ty::TypeWidth; + +pub mod abi; +pub mod error; +pub mod syntax; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct TargetEnv { + pub thin_pointer_width: TypeWidth, + pub fat_pointer_width: TypeWidth, +} diff --git a/tool/abi-generator/src/syntax/bitfield_type.rs b/tool/abi-generator/src/syntax/bitfield_type.rs new file mode 100644 index 00000000..da3288ef --- /dev/null +++ b/tool/abi-generator/src/syntax/bitfield_type.rs @@ -0,0 +1,120 @@ +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{braced, punctuated::Punctuated, Token}; + +use super::{ + common::{Attributes, TypeRepr}, + document::DocumentItemAttributes, +}; + +#[derive(Clone)] +pub enum BitfieldRange { + Single(syn::LitInt), + Range(syn::LitInt, syn::LitInt), +} + +#[derive(Clone)] +pub struct BitfieldTypeField { + pub attributes: Attributes, + pub name: syn::Ident, + pub range: BitfieldRange, +} + +#[derive(Clone)] +pub struct BitfieldType { + pub attributes: Attributes, + pub name: syn::Ident, + pub repr: TypeRepr, + pub fields: Punctuated, +} + +impl DocumentItemAttributes for BitfieldType { + fn extend_attributes>(&mut self, attrs: I) { + self.attributes.extend(attrs); + } +} + +impl syn::parse::Parse for BitfieldRange { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let start = input.parse()?; + if input.peek(Token![..]) { + input.parse::()?; + let end = input.parse()?; + Ok(Self::Range(start, end)) + } else { + Ok(Self::Single(start)) + } + } +} + +impl syn::parse::Parse for BitfieldTypeField { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let attributes = input.parse()?; + let name = input.parse()?; + input.parse::()?; + let range = input.parse()?; + Ok(Self { + attributes, + name, + range, + }) + } +} + +impl syn::parse::Parse for BitfieldType { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let fields; + let attributes = input.parse()?; + let name = input.parse()?; + let repr = input.parse()?; + braced!(fields in input); + let fields = fields.parse_terminated(BitfieldTypeField::parse, Token![,])?; + + Ok(Self { + attributes, + name, + repr, + fields, + }) + } +} + +impl ToTokens for BitfieldRange { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Range(start, end) => tokens.extend(quote!(#start..#end)), + Self::Single(offset) => tokens.extend(quote!(#offset)), + } + } +} + +impl ToTokens for BitfieldType { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + attributes, + name, + repr, + fields, + } = self; + tokens.extend(quote! { + #attributes + bitfield #name #repr { + #fields + } + }) + } +} + +impl ToTokens for BitfieldTypeField { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + attributes, + name, + range, + } = self; + tokens.extend(quote! { + #attributes + #name: #range + }) + } +} diff --git a/tool/abi-generator/src/syntax/common.rs b/tool/abi-generator/src/syntax/common.rs new file mode 100644 index 00000000..06d7ff39 --- /dev/null +++ b/tool/abi-generator/src/syntax/common.rs @@ -0,0 +1,66 @@ +use std::ops::{Deref, DerefMut}; + +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{parenthesized, Token}; + +#[derive(Clone)] +pub struct TypeRepr(pub syn::Ident); +#[derive(Clone)] +pub struct Attributes(pub Vec); + +impl Attributes { + pub fn new() -> Self { + Self(vec![]) + } +} + +impl Deref for Attributes { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Attributes { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl syn::parse::Parse for Attributes { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + syn::Attribute::parse_outer(input).map(Self) + } +} + +impl syn::parse::Parse for TypeRepr { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let content; + parenthesized!(content in input); + let content = content.parse_terminated(syn::Ident::parse, Token![,])?; + let mut content = content.into_iter(); + let ty = content.next().expect("TODO insert error here"); + if content.len() != 0 { + todo!("TODO insert error here"); + } + Ok(Self(ty)) + } +} + +impl ToTokens for TypeRepr { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self(repr) = self; + tokens.extend(quote!((#repr))) + } +} + +impl ToTokens for Attributes { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self(attrs) = self; + for attr in attrs { + attr.to_tokens(tokens); + } + } +} diff --git a/tool/abi-generator/src/syntax/document.rs b/tool/abi-generator/src/syntax/document.rs new file mode 100644 index 00000000..71cee8ea --- /dev/null +++ b/tool/abi-generator/src/syntax/document.rs @@ -0,0 +1,135 @@ +use quote::ToTokens; +use syn::{parse::ParseStream, Token}; + +use super::{ + bitfield_type::BitfieldType, + enum_type::EnumType, + extern_block::{ExternType, ExternTypeBlock}, + newtype::NewType, + syscall::SyscallDefinition, +}; + +#[derive(Clone)] +pub enum DocumentTypeDefinition { + NewType(NewType), + Bitfield(BitfieldType), + Enum(EnumType), + ExternType(ExternType), +} + +pub struct Document { + pub types: Vec, + pub syscalls: Vec, +} + +pub trait DocumentItemAttributes { + fn extend_attributes>(&mut self, attrs: I); +} + +enum DocumentItem { + ExternTypeBlock(ExternTypeBlock), + + // Type definitions + NewType(NewType), + Bitfield(BitfieldType), + Enum(EnumType), + + // Syscall definitions + SyscallDefinition(SyscallDefinition), +} + +impl syn::parse::Parse for DocumentItem { + fn parse(input: ParseStream<'_>) -> syn::Result { + let lookahead = input.lookahead1(); + + if lookahead.peek(Token![extern]) { + input.parse::()?; + return input.parse().map(Self::ExternTypeBlock); + } + + let item_attributes = syn::Attribute::parse_outer(input)?; + + let lookahead = input.lookahead1(); + + let mut item = if lookahead.peek(syn::Ident) { + // TODO parse attributes + let keyword: syn::Ident = input.parse()?; + + match keyword.to_string().as_str() { + "newtype" => NewType::parse(input).map(Self::NewType), + "bitfield" => BitfieldType::parse(input).map(Self::Bitfield), + "syscall" => SyscallDefinition::parse(input).map(Self::SyscallDefinition), + _ => todo!(), + } + } else if lookahead.peek(Token![enum]) { + input.parse::()?; + EnumType::parse(input).map(Self::Enum) + } else { + todo!() + }?; + + item.extend_attributes(item_attributes); + + Ok(item) + } +} + +impl DocumentItemAttributes for DocumentItem { + fn extend_attributes>(&mut self, attrs: I) { + match self { + Self::NewType(inner) => inner.extend_attributes(attrs), + Self::Enum(inner) => inner.extend_attributes(attrs), + Self::Bitfield(inner) => inner.extend_attributes(attrs), + Self::SyscallDefinition(inner) => inner.extend_attributes(attrs), + Self::ExternTypeBlock(_) => unreachable!(), + } + } +} + +impl syn::parse::Parse for Document { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut types = vec![]; + let mut syscalls = vec![]; + + loop { + if input.is_empty() { + break; + } + + let toplevel = DocumentItem::parse(input)?; + + match toplevel { + DocumentItem::Enum(inner) => types.push(DocumentTypeDefinition::Enum(inner)), + DocumentItem::NewType(inner) => types.push(DocumentTypeDefinition::NewType(inner)), + DocumentItem::Bitfield(inner) => { + types.push(DocumentTypeDefinition::Bitfield(inner)) + } + DocumentItem::ExternTypeBlock(inner) => { + types.extend( + inner + .types + .into_iter() + .map(DocumentTypeDefinition::ExternType), + ); + } + DocumentItem::SyscallDefinition(inner) => { + syscalls.push(inner); + } + } + } + + Ok(Self { types, syscalls }) + } +} + +impl ToTokens for DocumentItem { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + match self { + Self::Enum(inner) => inner.to_tokens(tokens), + Self::Bitfield(inner) => inner.to_tokens(tokens), + Self::NewType(inner) => inner.to_tokens(tokens), + Self::SyscallDefinition(inner) => inner.to_tokens(tokens), + Self::ExternTypeBlock(inner) => inner.to_tokens(tokens), + } + } +} diff --git a/tool/abi-generator/src/syntax/enum_type.rs b/tool/abi-generator/src/syntax/enum_type.rs new file mode 100644 index 00000000..21657c5b --- /dev/null +++ b/tool/abi-generator/src/syntax/enum_type.rs @@ -0,0 +1,99 @@ +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{braced, punctuated::Punctuated, Token}; + +use super::{ + common::{Attributes, TypeRepr}, + document::DocumentItemAttributes, +}; + +#[derive(Clone)] +pub struct EnumTypeVariant { + pub attributes: Attributes, + pub discriminant: syn::Ident, + pub value: syn::LitInt, +} + +#[derive(Clone)] +pub struct EnumType { + pub attributes: Attributes, + pub name: syn::Ident, + pub repr: TypeRepr, + pub variants: Punctuated, +} + +impl syn::parse::Parse for EnumTypeVariant { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let attributes = input.parse()?; + let discriminant = input.parse()?; + input.parse::()?; + let value = input.parse()?; + // let value = if input.peek(Token![=]) { + // input.parse::()?; + // let value = input.parse()?; + // Some(value) + // } else { + // None + // }; + Ok(Self { + attributes, + discriminant, + value, + }) + } +} + +impl syn::parse::Parse for EnumType { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let variants; + let attributes = input.parse()?; + let name = input.parse()?; + let repr = input.parse()?; + braced!(variants in input); + let variants = variants.parse_terminated(EnumTypeVariant::parse, Token![,])?; + Ok(Self { + attributes, + name, + repr, + variants, + }) + } +} + +impl DocumentItemAttributes for EnumType { + fn extend_attributes>(&mut self, attrs: I) { + self.attributes.extend(attrs); + } +} + +impl ToTokens for EnumTypeVariant { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + attributes, + discriminant, + value, + } = self; + // let value = value.as_ref().map(|val| quote!(= #val)); + tokens.extend(quote! { + #attributes + #discriminant = #value + }) + } +} + +impl ToTokens for EnumType { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + attributes, + name, + repr, + variants, + } = self; + tokens.extend(quote! { + #attributes + enum #name #repr { + #variants + } + }) + } +} diff --git a/tool/abi-generator/src/syntax/error.rs b/tool/abi-generator/src/syntax/error.rs new file mode 100644 index 00000000..bb75d505 --- /dev/null +++ b/tool/abi-generator/src/syntax/error.rs @@ -0,0 +1,103 @@ +use std::path::Path; + +use proc_macro2::Span; +use syn::spanned::Spanned; + +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)) + } + } +} diff --git a/tool/abi-generator/src/syntax/extern_block.rs b/tool/abi-generator/src/syntax/extern_block.rs new file mode 100644 index 00000000..6c204572 --- /dev/null +++ b/tool/abi-generator/src/syntax/extern_block.rs @@ -0,0 +1,70 @@ +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{braced, punctuated::Punctuated, Token}; + +use super::Attributes; + +#[derive(Clone)] +pub struct ExternType { + pub attributes: Attributes, + pub name: syn::Ident, + pub alias: Option, +} + +pub struct ExternTypeBlock { + pub types: Punctuated, +} + +impl syn::parse::Parse for ExternType { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let attributes = input.parse()?; + input.parse::()?; + let name = input.parse()?; + let alias = if input.peek(Token![=]) { + input.parse::()?; + Some(input.parse()?) + } else { + None + }; + Ok(Self { + attributes, + name, + alias, + }) + } +} + +impl syn::parse::Parse for ExternTypeBlock { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let types; + braced!(types in input); + let types = types.parse_terminated(ExternType::parse, Token![;])?; + Ok(Self { types }) + } +} + +impl ToTokens for ExternType { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + attributes, + name, + alias, + } = self; + let alias = alias.as_ref().map(|ty| quote!(= #ty)); + tokens.extend(quote! { + #attributes + type #name #alias + }); + } +} + +impl ToTokens for ExternTypeBlock { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { types } = self; + tokens.extend(quote! { + extern { + #types + } + }) + } +} diff --git a/tool/abi-generator/src/syntax/mod.rs b/tool/abi-generator/src/syntax/mod.rs new file mode 100644 index 00000000..f9df25e4 --- /dev/null +++ b/tool/abi-generator/src/syntax/mod.rs @@ -0,0 +1,329 @@ +use std::{rc::Rc, str::FromStr}; + +use syn::{punctuated::Punctuated, Expr, ExprLit, Lit, PathSegment}; + +use crate::abi::{ + syscall_enum_variant_identifier, + ty::{complex::ExternKind, AbiPrimitive, ComplexType, SimpleType}, + Abi, Syscall, TypeEnv, +}; + +mod common; + +// Types +mod bitfield_type; +mod enum_type; +mod extern_block; +mod newtype; + +mod document; + +mod error; +mod syscall; + +pub use bitfield_type::{BitfieldRange, BitfieldType, BitfieldTypeField}; +pub use common::{Attributes, TypeRepr}; +pub use document::{Document, DocumentTypeDefinition}; +pub use enum_type::{EnumType, EnumTypeVariant}; +pub use error::{ParseError, SyntaxError, UnwrapFancy}; +pub use extern_block::{ExternType, ExternTypeBlock}; +pub use newtype::NewType; + +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: &TypeEnv) -> 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.lookup_local(name) => Ok(ty.clone()), + name if let Some((ty, kind)) = known_types.lookup_extern(name) => { + Ok(Rc::new(ComplexType::Extern { + kind: *kind, + alias: ty.clone(), + arguments: segment.arguments.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))) + } else { + let elems = t + .elems + .iter() + .map(|e| process_type(e, known_types)) + .collect::>()?; + Ok(Rc::new(ComplexType::Tuple(elems))) + } + } + _ => todo!(), + } +} + +fn require_primitive_repr(repr: &TypeRepr) -> Result { + let TypeRepr(repr) = repr; + Ok(AbiPrimitive::from_str(repr.to_string().as_str()).expect("TODO proper error message")) +} + +fn make_single_ident_type(ident: syn::Ident) -> syn::Type { + syn::Type::Path(syn::TypePath { + qself: None, + path: syn::Path { + leading_colon: None, + segments: Punctuated::from_iter([syn::PathSegment { + arguments: syn::PathArguments::None, + ident, + }]), + }, + }) +} + +pub enum ProcessedType { + Extern(syn::Type, ExternKind), + Local(Rc, DocumentTypeDefinition), +} + +fn process_extern_type( + def: &ExternType, + _known_types: &TypeEnv, +) -> Result<(syn::Ident, syn::Type, ExternKind), ParseError> { + // TODO allow mixing arguments from + // extern { type X = ::A }; + // and use sites like + // syscall my_call(arg: X); + + // Process extern attributes + let mut kind = ExternKind::None; + for attr in def.attributes.iter() { + if attr.path().is_ident("thin") { + kind = ExternKind::Thin; + } else if attr.path().is_ident("fat") { + kind = ExternKind::Fat; + } + } + + let alias = def + .alias + .clone() + .unwrap_or_else(|| make_single_ident_type(def.name.clone())); + Ok((def.name.clone(), alias, kind)) +} + +fn process_bitfield_type( + def: &BitfieldType, + _known_types: &TypeEnv, +) -> Result<(syn::Ident, Rc), ParseError> { + let repr = require_primitive_repr(&def.repr)?; + Ok(( + def.name.clone(), + Rc::new(ComplexType::Simple(SimpleType::Transparent { + name: def.name.clone(), + inner: repr, + })), + )) +} + +fn process_enum_type( + def: &EnumType, + _known_types: &TypeEnv, +) -> Result<(syn::Ident, Rc), ParseError> { + let repr = require_primitive_repr(&def.repr)?; + Ok(( + def.name.clone(), + Rc::new(ComplexType::Simple(SimpleType::Transparent { + name: def.name.clone(), + inner: repr, + })), + )) +} + +fn process_newtype( + def: &NewType, + _known_types: &TypeEnv, +) -> Result<(syn::Ident, Rc), ParseError> { + let repr = require_primitive_repr(&def.repr)?; + Ok(( + def.name.clone(), + Rc::new(ComplexType::Simple(SimpleType::Transparent { + name: def.name.clone(), + inner: repr, + })), + )) +} + +fn process_type_definition( + def: &DocumentTypeDefinition, + known_types: &TypeEnv, +) -> Result<(syn::Ident, ProcessedType), ParseError> { + match def { + DocumentTypeDefinition::ExternType(inner) => process_extern_type(inner, known_types) + .map(|(ident, def_ty, kind)| (ident, ProcessedType::Extern(def_ty, kind))), + DocumentTypeDefinition::Enum(inner) => process_enum_type(inner, known_types) + .map(|(ident, def_ty)| (ident, ProcessedType::Local(def_ty, def.clone()))), + DocumentTypeDefinition::Bitfield(inner) => process_bitfield_type(inner, known_types) + .map(|(ident, def_ty)| (ident, ProcessedType::Local(def_ty, def.clone()))), + DocumentTypeDefinition::NewType(inner) => process_newtype(inner, known_types) + .map(|(ident, def_ty)| (ident, ProcessedType::Local(def_ty, def.clone()))), + } +} + +pub fn parse_abi(input: &str) -> Result { + let doc = parse_abi_document(input)?; + let mut abi = Abi { + types: TypeEnv::new(), + syscalls: Vec::new(), + }; + + ParseError::fold( + (), + doc.types.iter().map(|nt| { + let (name, value) = process_type_definition(nt, &abi.types)?; + match value { + ProcessedType::Local(abi_ty, def_ty) => { + abi.types.insert_local(name.to_string(), abi_ty, def_ty) + } + ProcessedType::Extern(def_ty, kind) => { + abi.types.insert_extern(name.to_string(), def_ty, kind) + } + } + 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) +} diff --git a/tool/abi-generator/src/syntax/newtype.rs b/tool/abi-generator/src/syntax/newtype.rs new file mode 100644 index 00000000..40242182 --- /dev/null +++ b/tool/abi-generator/src/syntax/newtype.rs @@ -0,0 +1,50 @@ +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::Token; + +use super::{ + common::{Attributes, TypeRepr}, + document::DocumentItemAttributes, +}; + +#[derive(Clone)] +pub struct NewType { + pub attributes: Attributes, + pub name: syn::Ident, + // No need for a full type, since only certain primitives are allowed + pub repr: TypeRepr, +} + +impl DocumentItemAttributes for NewType { + fn extend_attributes>(&mut self, attrs: I) { + self.attributes.extend(attrs); + } +} + +impl syn::parse::Parse for NewType { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let attributes = input.parse()?; + let name = input.parse()?; + let repr = input.parse()?; + input.parse::()?; + Ok(Self { + attributes, + name, + repr, + }) + } +} + +impl ToTokens for NewType { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + attributes, + name, + repr, + } = self; + tokens.extend(quote! { + #attributes + newtype #name #repr; + }) + } +} diff --git a/tool/abi-generator/src/syntax/syscall.rs b/tool/abi-generator/src/syntax/syscall.rs new file mode 100644 index 00000000..5c6caf56 --- /dev/null +++ b/tool/abi-generator/src/syntax/syscall.rs @@ -0,0 +1,82 @@ +use quote::{quote, ToTokens}; +use syn::{parenthesized, parse::ParseStream, punctuated::Punctuated, Ident, Token}; + +use super::{common::Attributes, document::DocumentItemAttributes}; + +pub struct SyscallDefinition { + pub attributes: Attributes, + pub name: Ident, + pub arguments: Punctuated, + pub return_type: Option, +} + +pub struct SyscallArgument { + pub name: syn::Ident, + pub ty: syn::Type, +} + +impl DocumentItemAttributes for SyscallDefinition { + fn extend_attributes>(&mut self, attrs: I) { + self.attributes.extend(attrs); + } +} + +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(SyscallArgument::parse, Token![,])?; + + let return_type = if input.peek(Token![->]) { + input.parse::]>()?; + let ty = input.parse()?; + Some(ty) + } else { + None + }; + + input.parse::()?; + + Ok(Self { + attributes: Attributes::new(), + name, + arguments, + return_type, + }) + } +} + +impl syn::parse::Parse for SyscallArgument { + fn parse(input: ParseStream<'_>) -> syn::Result { + let name = input.parse()?; + input.parse::()?; + let ty = input.parse()?; + + Ok(Self { name, ty }) + } +} + +impl ToTokens for SyscallArgument { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let Self { name, ty } = self; + tokens.extend(quote!(#name: #ty)) + } +} + +impl ToTokens for SyscallDefinition { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let Self { + attributes, + name, + arguments, + return_type, + } = self; + let return_type = return_type.as_ref().map(|ty| quote!(-> #ty)); + tokens.extend(quote! { + #attributes + syscall #name (#arguments) #return_type; + }); + } +}