Initial commit

This commit is contained in:
Mark Poliakov 2024-03-08 22:16:50 +02:00
commit 2fd9cf1128
15 changed files with 1939 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

86
Cargo.lock generated Normal file
View File

@ -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"

14
Cargo.toml Normal file
View File

@ -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"]

7
example-abi/Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "example-abi"
version = "0.1.0"
edition = "2021"
[build-dependencies]
syscall-generator = { path = ".." }

25
example-abi/build.rs Normal file
View File

@ -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");
}

118
example-abi/src/main.rs Normal file
View File

@ -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<usize, Error> {
fn from_syscall_result(value: usize) -> Self {
if (value as isize) < 0 {
todo!()
} else {
Ok(value as _)
}
}
}
impl IntoSyscallArgument for Option<NonZeroUsize> {
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<Vec<(SyscallFunction, Vec<usize>)>> = 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() {}

141
example-abi/syscall.abi Normal file
View File

@ -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<Fd>;
// Memory management
syscall map_memory(hint: Option<NonZeroUsize>, len: usize, source: &MappingSource) -> Result<usize>;
syscall unmap_memory(virt: usize, len: usize) -> Result<()>;
// Process/thread management
syscall spawn_process(options: &SpawnOptions) -> Result<ProcessId>;
syscall spawn_thread(options: &ThreadSpawnOptions) -> Result<ThreadId>;
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<ProcessGroupId>) -> Result<ProcessGroupId>;
syscall get_process_info(what: &mut ProcessInfoElement) -> Result<()>;
syscall set_process_info(what: &ProcessInfoElement) -> Result<()>;
// C compat
syscall fork() -> Result<ProcessId>;
syscall execve(opts: &ExecveOptions) -> Result<()>;
// I/O
syscall write(fd: Fd, data: &[u8]) -> Result<usize>;
syscall read(fd: Fd, data: &mut [u8]) -> Result<usize>;
syscall open(at: Option<Fd>, path: &str, opts: OpenOptions, mode: FileMode) -> Result<Fd>;
syscall close(fd: Fd) -> Result<()>;
syscall mount(options: &MountOptions) -> Result<()>;
syscall unmount(options: &UnmountOptions) -> Result<()>;
syscall open_directory(at: Option<Fd>, path: &str) -> Result<Fd>;
syscall read_directory_entries(fd: Fd, buffer: &mut [MaybeUninit<DirectoryEntry>]) -> Result<usize>;
syscall create_directory(at: Option<Fd>, path: &str, mode: FileMode) -> Result<()>;
syscall remove(at: Option<Fd>, path: &str) -> Result<()>;
syscall remove_directory(at: Option<Fd>, path: &str) -> Result<()>;
syscall get_metadata(at: Option<Fd>, path: &str, metadata: &mut FileAttr, follow: bool) -> Result<()>;
// TODO this one is broken
syscall seek(fd: Fd, pos: SeekFrom) -> Result<usize>;
syscall device_request(fd: Fd, req: &mut DeviceRequest) -> Result<()>;
syscall create_pipe(ends: &mut [MaybeUninit<Fd>; 2]) -> Result<()>;
syscall create_poll_channel() -> Result<Fd>;
syscall poll_channel_control(poll_fd: Fd, ctl: PollControl, fd: Fd) -> Result<()>;
syscall poll_channel_wait(poll_fd: Fd, timeout: &Option<Duration>, out: &mut Option<Fd>) -> Result<()>;
syscall open_channel(name: &str, subscribe: bool) -> Result<Fd>;
syscall send_message(fd: Fd, message: &SentMessage, destination: MessageDestination) -> Result<()>;
syscall receive_message(
fd: Fd,
metadata: &mut MaybeUninit<ReceivedMessageMetadata>,
buffer: &mut [u8],
from: &mut MaybeUninit<u32>
) -> Result<usize>;
syscall create_shared_memory(size: usize) -> Result<Fd>;
syscall create_pty(options: &TerminalOptions, size: &TerminalSize, fds: &mut [MaybeUninit<Fd>; 2]) -> Result<()>;
syscall clone_fd(source: Fd, target: Option<Fd>) -> Result<Fd>;
syscall update_metadata(at: Option<Fd>, path: &str, update: &FileMetadataUpdate) -> Result<()>;
syscall create_timer(repeat: bool) -> Result<Fd>;
// Network
syscall bind_socket(listen_address: &SocketAddr, ty: SocketType) -> Result<Fd>;
syscall connect_socket(
socket_fd: Option<Fd>,
remote_address: &SocketAddr,
ty: SocketType,
local_address: &mut MaybeUninit<SocketAddr>
) -> Result<Fd>;
syscall send_to(socket_fd: Fd, data: &[u8], recepient: &Option<SocketAddr>) -> Result<usize>;
syscall receive_from(socket_fd: Fd, buffer: &mut [u8], sender: &mut MaybeUninit<SocketAddr>) -> Result<usize>;
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<SocketAddr>) -> Result<Fd>;

305
src/abi/mod.rs Normal file
View File

@ -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<String, Rc<ComplexType>>,
// 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<P: AsRef<Path>>(path: P, target_env: TargetEnv) -> Result<Self, ParseError> {
let abi = Abi::read(path)?;
Ok(Self {
abi,
target_env,
emit_syscall_traits: true,
})
}
pub fn write<P: AsRef<Path>>(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::<Punctuated<_, token::Comma>>();
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<P: AsRef<Path>>(path: P) -> Result<Self, ParseError> {
let data = std::fs::read_to_string(path)?;
parse_abi(&data)
}
pub fn write<P: AsRef<Path>>(&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<Fd> {
fn into_syscall_argument(self) -> usize {
match self {
Some(value) => value.0 as usize,
None => usize::MAX
}
}
}
impl FromSyscallArgument for Option<Fd> {
fn from_syscall_argument(value: usize) -> Self {
match value {
usize::MAX => None,
_ => Some(Fd(value as _))
}
}
}
impl FromSyscallResult for Result<Fd, Error> {
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<usize, Error> {
<Result<usize, Error> >::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<usize, Error> {
<Result<usize, Error> >::from_syscall_result(syscall!(
SyscallFunction::write,
fd.0 as usize,
buf.as_ptr().expose_addr(),
buf.len(),
))
}
}
.to_string()
);
}
}

109
src/abi/ty/complex.rs Normal file
View File

@ -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<ComplexType>),
Result(Rc<ComplexType>),
Option(Rc<ComplexType>),
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<SimpleType, Error> {
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(&quote!(#ty)).finish(),
Self::MaybeUninit(ty) => f.debug_tuple("MaybeUninit").field(&ty).finish(),
}
}
}

161
src/abi/ty/mod.rs Normal file
View File

@ -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<u32>).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()
);
}
}

83
src/abi/ty/primitive.rs Normal file
View File

@ -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<Self, Self::Err> {
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(()),
}
}
}

104
src/abi/ty/simple.rs Normal file
View File

@ -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<ComplexType>,
},
Slice {
mutable: bool,
element: Rc<ComplexType>,
},
Array {
mutable: bool,
element: Rc<ComplexType>,
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()),
},
}
}
}

22
src/error.rs Normal file
View File

@ -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),
}

141
src/lib.rs Normal file
View File

@ -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<ComplexType>)>,
pub return_type: Option<Rc<ComplexType>>,
}
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::<TokenStream>();
let args = args
.iter()
.map(|(name, ty)| {
let ty = ty.as_rust_type();
quote!(#name: #ty,)
})
.collect::<TokenStream>();
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<usize, Error> {
<Result<usize, Error> >::from_syscall_result(syscall!(SyscallFunction::read, fd as usize, buffer.as_mut_ptr().expose_addr(), buffer.len(),))
}
}
.to_string()
);
}
}

622
src/parse.rs Normal file
View File

@ -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<SyntaxError>),
}
pub trait UnwrapFancy<T> {
fn unwrap_fancy<P: AsRef<Path>>(self, context: P) -> T;
}
impl From<std::io::Error> for ParseError {
fn from(value: std::io::Error) -> Self {
Self::IoError(value)
}
}
impl From<syn::parse::Error> for ParseError {
fn from(value: syn::parse::Error) -> Self {
Self::HardError(value)
}
}
impl From<SyntaxError> 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<T> UnwrapFancy<T> for Result<T, ParseError> {
fn unwrap_fancy<P: AsRef<Path>>(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<T, I: IntoIterator<Item = Result<T, Self>>, Q: Extend<T>>(
mut acc: Q,
it: I,
) -> Result<Q, Self> {
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<NewType>,
syscalls: Vec<SyscallDefinition>,
}
enum NewTypeDefinition {
Alias(syn::Type),
Transparent {
repr: syn::Type,
#[allow(dead_code)]
sentinel: Option<syn::LitInt>,
},
Extern(syn::Type),
}
struct SyscallDefinition {
name: Ident,
arguments: Punctuated<Argument, Token![,]>,
return_type: Option<syn::Type>,
}
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<ExternTypeItem, Token![;]>),
Syscall(SyscallDefinition),
}
impl syn::parse::Parse for SyscallDefinition {
fn parse(input: ParseStream) -> syn::Result<Self> {
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::<Token![->]>()?;
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<Self> {
let name = input.parse()?;
input.parse::<Token![:]>()?;
let ty = input.parse()?;
Ok(Self { name, ty })
}
}
impl syn::parse::Parse for NewType {
fn parse(input: ParseStream) -> syn::Result<Self> {
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<Self> {
input.parse::<Token![type]>()?;
let name: syn::Ident = input.parse()?;
let ty = if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
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<Self> {
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::<Token![extern]>()?;
let lookahead = input.lookahead1();
if lookahead.peek(Token![type]) {
input.parse::<token::Type>()?;
let name = input.parse()?;
input.parse::<Token![=]>()?;
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<Self> {
let mut newtypes = vec![];
let mut syscalls = vec![];
loop {
if input.is_empty() {
break;
}
let toplevel = TopLevel::parse(input)?;
input.parse::<Token![;]>()?;
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(&quote!(#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", &quote!(#repr))
.finish(),
Self::Alias(ty) => f
.debug_tuple("NewTypeDefinition::Alias")
.field(&quote!(#ty))
.finish(),
Self::Extern(ty) => f
.debug_tuple("NewTypeDefinition::Extern")
.field(&quote!(#ty))
.finish(),
}
}
}
pub fn parse_abi_document(input: &str) -> Result<Document, ParseError> {
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<usize> {
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<String, Rc<ComplexType>>,
) -> Result<Rc<ComplexType>, 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<String, Rc<ComplexType>>,
) -> Result<(Ident, Rc<ComplexType>), 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<Abi, ParseError> {
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::<Result<Vec<_>, 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<usize, Error> {
<Result<usize, Error> >::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()
);
}
}