microfetch_lib/
syscall.rs

1//! Incredibly fast syscall wrappers for using inline assembly. Serves the
2//! purposes of completely bypassing Rust's standard library in favor of
3//! handwritten Assembly. Is this a good idea? No. Is it fast? Yeah, but only
4//! marginally. Either way it serves a purpose and I will NOT accept criticism.
5//! What do you mean I wasted two whole hours to make the program only 100µs
6//! faster?
7//!
8//! Supports `x86_64` and `aarch64` architectures. Riscv support will be
9//! implemented when and ONLY WHEN I can be bothered to work on it.
10
11use std::io;
12
13/// Direct syscall to open a file
14///
15/// # Returns
16///
17/// File descriptor or -1 on error
18///
19/// # Safety
20///
21/// The caller must ensure:
22///
23/// - `path` points to a valid null-terminated C string
24/// - The pointer remains valid for the duration of the syscall
25#[inline]
26#[must_use]
27pub unsafe fn sys_open(path: *const u8, flags: i32) -> i32 {
28  #[cfg(target_arch = "x86_64")]
29  unsafe {
30    let fd: i64;
31    std::arch::asm!(
32      "syscall",
33      in("rax") 2i64,  // SYS_open
34      in("rdi") path,
35      in("rsi") flags,
36      in("rdx") 0i32,  // mode (not used for reading)
37      lateout("rax") fd,
38      lateout("rcx") _,
39      lateout("r11") _,
40      options(nostack)
41    );
42    #[allow(clippy::cast_possible_truncation)]
43    {
44      fd as i32
45    }
46  }
47  #[cfg(target_arch = "aarch64")]
48  unsafe {
49    let fd: i64;
50    std::arch::asm!(
51      "svc #0",
52      in("x8") 56i64,  // SYS_openat
53      in("x0") -100i32,  // AT_FDCWD
54      in("x1") path,
55      in("x2") flags,
56      in("x3") 0i32,  // mode
57      lateout("x0") fd,
58      options(nostack)
59    );
60    #[allow(clippy::cast_possible_truncation)]
61    {
62      fd as i32
63    }
64  }
65  #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
66  {
67    compile_error!("Unsupported architecture for inline assembly syscalls");
68  }
69}
70
71/// Direct syscall to read from a file descriptor
72///
73/// # Returns n
74///
75/// Number of bytes read or -1 on error
76///
77/// # Safety
78///
79/// The caller must ensure:
80/// - `buf` points to a valid writable buffer of at least `count` bytes
81/// - `fd` is a valid open file descriptor
82#[inline]
83pub unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize {
84  #[cfg(target_arch = "x86_64")]
85  unsafe {
86    let ret: i64;
87    std::arch::asm!(
88      "syscall",
89      in("rax") 0i64,  // SYS_read
90      in("rdi") fd,
91      in("rsi") buf,
92      in("rdx") count,
93      lateout("rax") ret,
94      lateout("rcx") _,
95      lateout("r11") _,
96      options(nostack)
97    );
98    #[allow(clippy::cast_possible_truncation)]
99    {
100      ret as isize
101    }
102  }
103  #[cfg(target_arch = "aarch64")]
104  unsafe {
105    let ret: i64;
106    std::arch::asm!(
107      "svc #0",
108      in("x8") 63i64,  // SYS_read
109      in("x0") fd,
110      in("x1") buf,
111      in("x2") count,
112      lateout("x0") ret,
113      options(nostack)
114    );
115    #[allow(clippy::cast_possible_truncation)]
116    {
117      ret as isize
118    }
119  }
120  #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
121  {
122    compile_error!("Unsupported architecture for inline assembly syscalls");
123  }
124}
125
126/// Direct syscall to close a file descriptor
127///
128/// # Safety
129///
130/// The caller must ensure `fd` is a valid open file descriptor
131#[inline]
132#[must_use]
133pub unsafe fn sys_close(fd: i32) -> i32 {
134  #[cfg(target_arch = "x86_64")]
135  unsafe {
136    let ret: i64;
137    std::arch::asm!(
138      "syscall",
139      in("rax") 3i64,  // SYS_close
140      in("rdi") fd,
141      lateout("rax") ret,
142      lateout("rcx") _,
143      lateout("r11") _,
144      options(nostack)
145    );
146    #[allow(clippy::cast_possible_truncation)]
147    {
148      ret as i32
149    }
150  }
151  #[cfg(target_arch = "aarch64")]
152  unsafe {
153    let ret: i64;
154    std::arch::asm!(
155      "svc #0",
156      in("x8") 57i64,  // SYS_close
157      in("x0") fd,
158      lateout("x0") ret,
159      options(nostack)
160    );
161    #[allow(clippy::cast_possible_truncation)]
162    {
163      ret as i32
164    }
165  }
166  #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
167  {
168    compile_error!("Unsupported architecture for inline assembly syscalls");
169  }
170}
171
172/// Read entire file using direct syscalls. This avoids libc overhead and can be
173/// significantly faster for small files.
174///
175/// # Errors
176///
177/// Returns an error if the file cannot be opened or read
178#[inline]
179pub fn read_file_fast(path: &str, buffer: &mut [u8]) -> io::Result<usize> {
180  const O_RDONLY: i32 = 0;
181
182  // Use stack-allocated buffer for null-terminated path (max 256 bytes)
183  let path_bytes = path.as_bytes();
184  if path_bytes.len() >= 256 {
185    return Err(io::Error::new(io::ErrorKind::InvalidInput, "Path too long"));
186  }
187
188  let mut path_buf = [0u8; 256];
189  path_buf[..path_bytes.len()].copy_from_slice(path_bytes);
190  // XXX: Already zero-terminated since array is initialized to zeros
191
192  unsafe {
193    let fd = sys_open(path_buf.as_ptr(), O_RDONLY);
194    if fd < 0 {
195      return Err(io::Error::last_os_error());
196    }
197
198    let bytes_read = sys_read(fd, buffer.as_mut_ptr(), buffer.len());
199    let _ = sys_close(fd);
200
201    if bytes_read < 0 {
202      return Err(io::Error::last_os_error());
203    }
204
205    #[allow(clippy::cast_sign_loss)]
206    {
207      Ok(bytes_read as usize)
208    }
209  }
210}