From 888556b657803adfe6a050951847648a909fa33b Mon Sep 17 00:00:00 2001 From: javalsai Date: Tue, 2 Dec 2025 01:33:02 +0100 Subject: [PATCH] YOO: start 2025 with a benching rust framework --- 2025/.gitignore | 2 + 2025/01/p1.rs | 24 +++++ 2025/01/p2.rs | 37 +++++++ 2025/Makefile | 7 ++ 2025/README.md | 3 + 2025/tester.rs | 269 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 342 insertions(+) create mode 100644 2025/.gitignore create mode 100644 2025/01/p1.rs create mode 100644 2025/01/p2.rs create mode 100644 2025/Makefile create mode 100644 2025/README.md create mode 100644 2025/tester.rs diff --git a/2025/.gitignore b/2025/.gitignore new file mode 100644 index 0000000..3fcd6cf --- /dev/null +++ b/2025/.gitignore @@ -0,0 +1,2 @@ +*.so +/tester diff --git a/2025/01/p1.rs b/2025/01/p1.rs new file mode 100644 index 0000000..333850e --- /dev/null +++ b/2025/01/p1.rs @@ -0,0 +1,24 @@ +use std::{hint::unreachable_unchecked, io::BufRead}; + +#[unsafe(no_mangle)] +extern "Rust" fn challenge_isize(buf: &[u8]) -> isize { + let mut count = 0; + let mut pos = 50; + + for ln in buf.lines() { + let ln = unsafe { ln.unwrap_unchecked() }; + let (dir, amt) = unsafe { (ln.as_bytes().get_unchecked(0), ln.get_unchecked(1..)) }; + + let amt: i16 = unsafe { amt.parse().unwrap_unchecked() }; + match dir { + b'L' => pos -= amt, + b'R' => pos += amt, + _ => unsafe { unreachable_unchecked() }, + } + + pos %= 100; + count += (pos != 0) as i16; + } + + count.into() +} diff --git a/2025/01/p2.rs b/2025/01/p2.rs new file mode 100644 index 0000000..b534a90 --- /dev/null +++ b/2025/01/p2.rs @@ -0,0 +1,37 @@ +use std::{hint::unreachable_unchecked, io::BufRead}; + +#[unsafe(no_mangle)] +unsafe extern "Rust" fn challenge_isize(buf: &[u8]) -> isize { + let mut count = 0; + let mut pos = 50; + + for ln in buf.lines() { + let ln = unsafe { ln.unwrap_unchecked() }; + let (dir, amt) = (ln.as_bytes()[0], &ln[1..]); + + let amt: isize = unsafe { amt.parse().unwrap_unchecked() }; + if amt == 0 { + continue; + } + let prev_pos = pos; + match dir { + b'L' => pos -= amt, + b'R' => pos += amt, + _ => unsafe { unreachable_unchecked() }, + } + + count += if pos < 0 { + if prev_pos == 0 { + count -= 1; + }; + (pos - 1).div_euclid(100).abs() + } else if pos > 0 { + pos.div_euclid(100) + } else { + 1 + }; + pos = pos.rem_euclid(100); + } + + count +} diff --git a/2025/Makefile b/2025/Makefile new file mode 100644 index 0000000..f1bcf60 --- /dev/null +++ b/2025/Makefile @@ -0,0 +1,7 @@ +tester: tester.rs + rustc --edition 2024 $< \ + -O -Copt-level=3 -Cstrip=symbols \ + -Cdebuginfo=0 -Cdebug-assertions=off \ + -Coverflow-checks=false -Cpanic=abort \ + -Ctarget-cpu=native \ + -Ccodegen-units=1 diff --git a/2025/README.md b/2025/README.md new file mode 100644 index 0000000..f98098a --- /dev/null +++ b/2025/README.md @@ -0,0 +1,3 @@ +All this year is going to be firstly made in rust, I will do performance too. For that I'ma make a general helper that compiles each rust file into an `.so` file with a common exported main function that takes the input byte buffer and returns a solution (hoping all of them will be integers). + +I will make an external rust program that takes the day to run and the input, it will buffer and measure the execution speed. diff --git a/2025/tester.rs b/2025/tester.rs new file mode 100644 index 0000000..31556ea --- /dev/null +++ b/2025/tester.rs @@ -0,0 +1,269 @@ +#![feature(iter_next_chunk, ptr_metadata, fn_ptr_trait)] + +use std::{ + env, + ffi::{CStr, OsStr}, + fs::File, + io::{self, Read}, + path::{Path, PathBuf}, + process::{Command, Stdio}, + time::Instant, +}; + +pub mod dl { + use std::{ + ffi::{CStr, CString}, + fmt::Pointer, + marker::FnPtr, + os::unix::ffi::OsStrExt, + path::Path, + ptr::{NonNull, Pointee}, + str::Utf8Error, + }; + + const RTLD_NOW: i32 = 1; + const RTLD_GLOBAL: i32 = 2; + + #[derive(Debug)] + pub struct Handle(NonNull); + + impl Handle { + /// To get the error call [`ffi::error()`]. + pub fn try_drop(self) -> Result<(), (Self, i32)> { + let err = unsafe { ffi::close(self.0) }; + if err == 0 { Ok(()) } else { Err((self, err)) } + } + + /// # Safety + /// + /// There's no guarantees the pointer is actually of `T` aside from the generic. + pub unsafe fn sym(&self, symbol: &CStr) -> Option> { + NonNull::new(unsafe { ffi::sym(self.0, symbol.as_ptr().cast()).cast() }) + } + + /// # Safety + /// + /// There's no guarantees the pointer is actually of `T` aside from the generic. + pub unsafe fn symfn(&self, symbol: &CStr) -> Option { + let ptr = unsafe { ffi::sym(self.0, symbol.as_ptr().cast()) }; + if ptr.is_null() { + None + } else { + Some(unsafe { std::mem::transmute_copy(&ptr) }) + } + } + } + + impl Drop for Handle { + fn drop(&mut self) { + unsafe { ffi::close(self.0) }; + } + } + + #[derive(Debug)] + pub struct DlOpenError; + + /// To get the error call [`ffi::error()`]. + pub fn open(path: impl AsRef) -> Result { + let osstr = path.as_ref().as_os_str(); + let cstr = + CString::new(osstr.as_bytes()).expect("Failed to make CString from path's OSString"); + + let raw_handle = unsafe { ffi::open(cstr.as_ptr() as *const _, RTLD_NOW | RTLD_GLOBAL) }; + if let Some(raw_handle) = NonNull::new(raw_handle) { + Ok(Handle(raw_handle)) + } else { + Err(DlOpenError) + } + } + + /// # Safety + /// + /// There must be a previous error, will assume [`ffi::error()`] is not null. And the returned + /// slice will only be valid until another call to dl happens. + pub unsafe fn last_error() -> Result<&'static str, Utf8Error> { + let cstr = unsafe { CStr::from_ptr(ffi::error()) }; + cstr.to_str() + } + + pub mod ffi { + use std::{ + ffi::{c_char, c_void}, + ptr::NonNull, + }; + + #[repr(C)] + pub struct Handle(()); + + #[link(name = "dl")] + unsafe extern "C" { + #[link_name = "dlopen"] + pub fn open(path: *const c_char, flags: i32) -> *mut Handle; + #[link_name = "dlclose"] + pub fn close(handle: NonNull) -> i32; + #[link_name = "dlsym"] + pub fn sym(handle: NonNull, symbol: *const c_char) -> *mut c_void; + /// NULL if there was no error, may also return an address to a static buffer so its a + /// MUST to not call it again until this buffer is cloned or freed and no referenced to + /// it remain. + #[link_name = "dlerror"] + pub fn error() -> *mut c_char; + } + } +} + +pub mod loader { + use crate::dl::Handle; + + pub type ChallengeFn = unsafe extern "Rust" fn(&[u8]) -> T; + + pub enum FnVariant { + Isize(ChallengeFn), + Usize(ChallengeFn), + IsizeDuple(ChallengeFn<(isize, isize)>), + UsizeDuple(ChallengeFn<(usize, usize)>), + } + + #[derive(Debug, PartialEq)] + pub enum FnRetVariant { + Isize(isize), + Usize(usize), + IsizeDuple((isize, isize)), + UsizeDuple((usize, usize)), + } + + impl FnVariant { + pub unsafe fn call(&self, buf: &[u8]) -> FnRetVariant { + use FnRetVariant as R; + use FnVariant as V; + + match self { + V::Isize(f) => unsafe { R::Isize((f)(buf)) }, + V::Usize(f) => unsafe { R::Usize((f)(buf)) }, + V::IsizeDuple(f) => unsafe { R::IsizeDuple((f)(buf)) }, + V::UsizeDuple(f) => unsafe { R::UsizeDuple((f)(buf)) }, + } + } + } + + /// # Safety + /// + /// Can return arbitrary fn's depending on the handle's symbols. + pub unsafe fn load_fn_from(handle: &Handle) -> Option { + macro_rules! untry { + ($e:expr) => { + match $e { + Some(v) => return Some(v), + None => (), + } + }; + } + + use ChallengeFn as C; + use FnVariant as V; + + untry!(unsafe { handle.symfn::>(c"challenge_isize") }.map(V::Isize)); + untry!(unsafe { handle.symfn::>(c"challenge_isize") }.map(V::Usize)); + untry!(unsafe { handle.symfn::>(c"challenge_isize") }.map(V::UsizeDuple)); + untry!(unsafe { handle.symfn::>(c"challenge_isize") }.map(V::IsizeDuple)); + + None + } +} + +const EXPORT_NAME: &CStr = c"challenge"; +pub type ChallengeFn = unsafe extern "Rust" fn(&[u8]) -> isize; + +fn main() -> io::Result<()> { + let rustc = env::var("RUSTC_PATH").ok(); + + let Ok([day, input]) = std::env::args().skip(1).next_chunk() else { + eprintln!("\x1b[1;31mUsage: $0 "); + return Ok(()); + }; + + let rs_path = PathBuf::from(&day); + assert_eq!(rs_path.extension(), Some(OsStr::new("rs"))); + let so_path = rs_path.with_extension("so"); + + compile(&rs_path, &so_path, rustc.as_deref())?; + println!("\x1b[33mBuferring input...\x1b[0m"); + let input = buffer_input(&input)?; + + let challenge = dl::open(so_path).expect("Couldn't load dyn library"); + let challenge_main = unsafe { loader::load_fn_from(&challenge) }.expect(concat!( + "Didn't find any appropiate symbol in the compiled .so file. Make sure there is one and is ", + stringify!(unsafe extern "Rust" fn(&[u8]) -> isize) + )); + + let start = Instant::now(); + let result = unsafe { challenge_main.call(&input) }; + + println!( + "done in {:#?} and yielded result {:?}", + start.elapsed(), + result + ); + + Ok(()) +} + +fn compile(rs: &Path, so: &Path, rustc: Option<&str>) -> io::Result<()> { + if env::var("REBUILD").is_err() { + let rsf = File::open(rs)?; + let rs_metadata = rsf.metadata()?; + + if let Ok(f) = File::open(so) { + let so_metadata = f.metadata()?; + so_metadata.modified()?; + + // No need to recompile + if so_metadata.modified()? > rs_metadata.modified()? { + println!("\x1b[32mChallenge already compiled\x1b[0m"); + return Ok(()); + } + } + } + + // Recompile + println!("\x1b[33mCompiling {rs:#?}...\x1b[0m"); + let exit = Command::new(rustc.unwrap_or("rustc")) + .args([ + "--crate-type=cdylib", + "--edition=2024", + "-O", + "-Copt-level=3", + "-Cstrip=symbols", + "-Cdebuginfo=0", + "-Coverflow-checks=false", + "-Cpanic=abort", + "-Ctarget-cpu=native", + "-Ccodegen-units=1", + "-Cdebug-assertions=off", + ]) + .arg(rs) + .arg("-o") + .arg(so) + .stdout(Stdio::inherit()) + .spawn()? + .wait()?; + + if exit.success() { + Ok(()) + } else { + Err(io::Error::other("rustc command failed")) + } +} + +fn buffer_input(input_path: &str) -> io::Result> { + let mut input = Vec::new(); + + if input_path == "-" { + io::stdin().read_to_end(&mut input)?; + Ok(input) + } else { + let mut f = File::open(input_path)?; + f.read_to_end(&mut input)?; + Ok(input) + } +}