optimizing ast allocation

This commit is contained in:
Jakub Doka 2024-10-10 13:04:17 +02:00
parent c31d1dcb9c
commit a538c0ddb0
No known key found for this signature in database
GPG key ID: C6E9A89936B8C143
7 changed files with 89 additions and 58 deletions

7
Cargo.lock generated
View file

@ -35,12 +35,6 @@ version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
[[package]]
name = "anyhow"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
[[package]]
name = "async-trait"
version = "0.1.83"
@ -153,7 +147,6 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
name = "depell"
version = "0.1.0"
dependencies = [
"anyhow",
"axum",
"getrandom",
"htmlm",

View file

@ -4,7 +4,6 @@ version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.89"
axum = "0.7.7"
getrandom = "0.2.15"
htmlm = "0.3.0"

View file

@ -89,8 +89,11 @@ trait Page: Default {
Html(Self::default().render(&session))
}
async fn page(session: Session) -> Html<String> {
base(Self::default().render(&session), Some(session)).await
async fn page(session: Option<Session>) -> Result<Html<String>, axum::response::Redirect> {
match session {
Some(session) => Ok(base(Self::default().render(&session), Some(session)).await),
None => Err(axum::response::Redirect::permanent("/login")),
}
}
}
@ -409,7 +412,7 @@ impl<S> axum::extract::FromRequestParts<S> for Session {
let (name, expiration) = db::with(|db| {
db.get_session
.query_row((dbg!(id),), |r| Ok((r.get::<_, String>(0)?, r.get::<_, u64>(1)?)))
.query_row((id,), |r| Ok((r.get::<_, String>(0)?, r.get::<_, u64>(1)?)))
.inspect_err(|e| log::error!("{e}"))
.map_err(|_| err)
})?;

View file

@ -117,7 +117,8 @@ unsafe extern "C" fn fmt() {
let code = core::str::from_raw_parts(core::ptr::addr_of!(INPUT).cast(), INPUT_LEN);
let arena = hblang::parser::Arena::default();
let arena =
hblang::parser::Arena::with_capacity(code.len() * hblang::parser::SOURCE_TO_AST_FACTOR);
let mut ctx = ParserCtx::default();
let exprs = hblang::parser::Parser::parse(&mut ctx, code, "source.hb", &|_, _| Ok(0), &arena);

View file

@ -635,7 +635,7 @@ struct Pool {
arg_locs: Vec<Loc>,
}
const VM_STACK_SIZE: usize = 1024 * 1024 * 2;
const VM_STACK_SIZE: usize = 1024 * 64;
struct Comptime {
vm: hbvm::Vm<LoggedMem, { 1024 * 10 }>,

View file

@ -450,6 +450,15 @@ pub mod test {
minned.truncate(len);
let ast = parser::Ast::new(ident, minned, &mut ParserCtx::default(), &|_, _| Ok(0));
log::error!(
"{} / {} = {} | {} / {} = {}",
ast.mem.size(),
input.len(),
ast.mem.size() as f32 / input.len() as f32,
ast.mem.size(),
ast.file.len(),
ast.mem.size() as f32 / ast.file.len() as f32
);
let mut output = String::new();
write!(output, "{ast}").unwrap();

View file

@ -6,6 +6,7 @@ use {
},
alloc::{boxed::Box, string::String, vec::Vec},
core::{
alloc::Layout,
cell::UnsafeCell,
fmt::{self},
intrinsics::unlikely,
@ -24,6 +25,8 @@ pub type IdentIndex = u16;
pub type LoaderError = String;
pub type Loader<'a> = &'a (dyn Fn(&str, &str) -> Result<FileId, LoaderError> + 'a);
pub const SOURCE_TO_AST_FACTOR: usize = 7 * (core::mem::size_of::<usize>() / 4) + 1;
pub mod idfl {
use super::*;
@ -637,6 +640,15 @@ macro_rules! generate_expr {
)*}
impl<$lt> $name<$lt> {
pub fn used_bytes(&self) -> usize {
match self {
$(Self::$variant { $($field),* } => {
0 $(.max($field as *const _ as usize - self as *const _ as usize
+ core::mem::size_of::<$ty>()))*
})*
}
}
pub fn pos(&self) -> Pos {
#[allow(unused_variables)]
match self {
@ -653,7 +665,7 @@ macro_rules! generate_expr {
(@last ($($last:tt)*),) => { $($last)* };
}
#[repr(u32)]
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Radix {
Hex = 16,
@ -954,7 +966,7 @@ pub struct ParserCtx {
#[repr(C)]
pub struct AstInner<T: ?Sized> {
ref_count: AtomicUsize,
mem: ArenaChunk,
pub mem: ArenaChunk,
exprs: *const [Expr<'static>],
pub path: Box<str>,
@ -971,7 +983,7 @@ impl AstInner<[Symbol]> {
}
fn new(file: Box<str>, path: &str, ctx: &mut ParserCtx, loader: Loader) -> NonNull<Self> {
let arena = Arena::default();
let arena = Arena::with_capacity(file.len() * SOURCE_TO_AST_FACTOR);
let exprs =
unsafe { core::mem::transmute(Parser::parse(ctx, &file, path, loader, &arena)) };
@ -1070,7 +1082,7 @@ impl ExprRef {
}
pub fn get<'a>(&self, from: &'a Ast) -> Option<&'a Expr<'a>> {
ArenaChunk::contains(from.mem.base, self.0.as_ptr() as _).then_some(())?;
from.mem.contains(self.0.as_ptr() as _).then_some(())?;
// SAFETY: the pointer is or was a valid reference in the past, if it points within one of
// arenas regions, it muts be walid, since arena does not give invalid pointers to its
// allocations
@ -1197,12 +1209,22 @@ pub struct Arena {
}
impl Arena {
pub fn with_capacity(cap: usize) -> Arena {
Self { chunk: UnsafeCell::new(ArenaChunk::new(cap, ArenaChunk::default())) }
}
pub fn alloc<'a>(&'a self, expr: Expr<'a>) -> &'a Expr<'a> {
let layout = core::alloc::Layout::new::<Expr<'a>>();
let layout = core::alloc::Layout::from_size_align(
expr.used_bytes(),
core::mem::align_of::<Expr<'a>>(),
)
.unwrap();
let ptr = self.alloc_low(layout);
unsafe {
ptr.cast::<u64>()
.copy_from_nonoverlapping(NonNull::from(&expr).cast(), layout.size() / 8)
ptr.cast::<usize>().copy_from_nonoverlapping(
NonNull::from(&expr).cast(),
layout.size() / core::mem::size_of::<usize>(),
)
};
unsafe { ptr.cast::<Expr<'a>>().as_ref() }
}
@ -1219,9 +1241,6 @@ impl Arena {
}
fn alloc_low(&self, layout: core::alloc::Layout) -> NonNull<u8> {
assert!(layout.align() <= ArenaChunk::ALIGN);
assert!(layout.size() <= ArenaChunk::CHUNK_SIZE);
let chunk = unsafe { &mut *self.chunk.get() };
if let Some(ptr) = chunk.alloc(layout) {
@ -1229,44 +1248,46 @@ impl Arena {
}
unsafe {
core::ptr::write(chunk, ArenaChunk::new(chunk.base));
core::ptr::write(
chunk,
ArenaChunk::new(
1024 * 4 - core::mem::size_of::<ArenaChunk>(),
core::ptr::read(chunk),
),
);
}
chunk.alloc(layout).unwrap()
}
}
struct ArenaChunk {
pub struct ArenaChunk {
base: *mut u8,
end: *mut u8,
size: usize,
}
impl Default for ArenaChunk {
fn default() -> Self {
Self { base: core::ptr::null_mut(), end: core::ptr::null_mut() }
Self {
base: core::mem::size_of::<Self>() as _,
end: core::mem::size_of::<Self>() as _,
size: 0,
}
}
}
impl ArenaChunk {
const ALIGN: usize = 16;
const CHUNK_SIZE: usize = 1 << 16;
const LAYOUT: core::alloc::Layout =
unsafe { core::alloc::Layout::from_size_align_unchecked(Self::CHUNK_SIZE, Self::ALIGN) };
const NEXT_OFFSET: usize = Self::CHUNK_SIZE - core::mem::size_of::<*mut u8>();
fn new(next: *mut u8) -> Self {
let base = unsafe { alloc::alloc::alloc(Self::LAYOUT) };
let end = unsafe { base.add(Self::NEXT_OFFSET) };
Self::set_next(base, next);
Self { base, end }
fn layout(size: usize) -> Layout {
Layout::new::<Self>().extend(Layout::array::<u8>(size).unwrap()).unwrap().0
}
fn set_next(curr: *mut u8, next: *mut u8) {
unsafe { core::ptr::write(curr.add(Self::NEXT_OFFSET) as *mut _, next) };
}
fn next(curr: *mut u8) -> *mut u8 {
unsafe { core::ptr::read(curr.add(Self::NEXT_OFFSET) as *mut _) }
fn new(size: usize, next: Self) -> Self {
let mut base = unsafe { alloc::alloc::alloc(Self::layout(size)) };
let end = unsafe { base.add(size) };
unsafe { core::ptr::write(base.cast(), next) };
base = unsafe { base.add(core::mem::size_of::<Self>()) };
Self { base, end, size }
}
fn alloc(&mut self, layout: core::alloc::Layout) -> Option<NonNull<u8>> {
@ -1279,26 +1300,31 @@ impl ArenaChunk {
unsafe { Some(NonNull::new_unchecked(self.end)) }
}
fn contains(base: *mut u8, arg: *mut u8) -> bool {
!base.is_null()
&& ((unsafe { base.add(Self::CHUNK_SIZE) } > arg && base <= arg)
|| Self::contains(Self::next(base), arg))
fn next(&self) -> Option<&Self> {
unsafe { self.base.cast::<Self>().sub(1).as_ref() }
}
fn contains(&self, arg: *mut u8) -> bool {
(self.base <= arg && unsafe { self.base.add(self.size) } > arg)
|| self.next().map_or(false, |s| s.contains(arg))
}
pub fn size(&self) -> usize {
self.base as usize + self.size - self.end as usize + self.next().map_or(0, Self::size)
}
}
impl Drop for ArenaChunk {
fn drop(&mut self) {
//log::inf!(
// "dropping chunk of size: {}",
// (Self::LAYOUT.size() - (self.end as usize - self.base as usize))
// * !self.end.is_null() as usize
//);
let mut current = self.base;
while !current.is_null() {
let next = Self::next(current);
unsafe { alloc::alloc::dealloc(current, Self::LAYOUT) };
current = next;
//log::dbg!("deallocating full chunk");
if self.size == 0 {
return;
}
_ = self.next().map(|r| unsafe { core::ptr::read(r) });
unsafe {
alloc::alloc::dealloc(
self.base.sub(core::mem::size_of::<Self>()),
Self::layout(self.size),
)
}
}
}