implementing comptime constants

This commit is contained in:
mlokr 2024-05-19 18:20:42 +02:00
parent b922dbd232
commit aef9951bc5
19 changed files with 743 additions and 646 deletions

View file

@ -3,7 +3,7 @@ global_var := 10;
complex_global_var := fib(global_var) - 5; complex_global_var := fib(global_var) - 5;
fib := fn(n: int): int { fib := fn(n: int): int {
if n <= 2 { if 2 > n {
return n; return n;
} }
return fib(n - 1) + fib(n - 2); return fib(n - 1) + fib(n - 2);
@ -12,3 +12,4 @@ fib := fn(n: int): int {
main := fn(): int { main := fn(): int {
return complex_global_var; return complex_global_var;
} }

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,5 @@
use std::simd::cmp::SimdPartialEq;
#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Token { pub struct Token {
pub kind: TokenKind, pub kind: TokenKind,
@ -290,85 +292,117 @@ impl<'a> Lexer<'a> {
} }
} }
pub fn line_col(bytes: &[u8], mut start: u32) -> (usize, usize) { pub fn line_col(bytes: &[u8], pos: u32) -> (usize, usize) {
bytes bytes[..pos as usize]
.split(|&b| b == b'\n') .split(|&b| b == b'\n')
.map(<[u8]>::len)
.enumerate() .enumerate()
.find_map(|(i, line)| { .last()
if start < line.len() as u32 { .map(|(line, col)| (line + 1, col + 1))
return Some((i + 1, start as usize + 1));
}
start -= line.len() as u32 + 1;
None
})
.unwrap_or((1, 1)) .unwrap_or((1, 1))
} }
impl<'a> Iterator for Lexer<'a> { pub struct LineMap {
type Item = Token; lines: Box<[u8]>,
}
fn next(&mut self) -> Option<Self::Item> { impl LineMap {
use TokenKind as T; pub fn line_col(&self, mut pos: u32) -> (usize, usize) {
loop { let mut line = 1;
let mut start = self.pos;
let kind = match self.advance()? { let mut iter = self.lines.iter().copied();
b'\n' | b'\r' | b'\t' | b' ' => continue,
b'0'..=b'9' => { while let Some(mut len) = iter.next() {
while let Some(b'0'..=b'9') = self.peek() { let mut acc = 0;
self.advance(); while len & 0x80 != 0 {
} acc = (acc << 7) | (len & 0x7F) as u32;
T::Number len = iter.next().unwrap();
}
acc += len as u32;
if pos < acc {
break;
}
pos = pos.saturating_sub(acc);
line += 1;
}
(line, pos as usize + 1)
}
pub fn new(input: &str) -> Self {
let bytes = input.as_bytes();
let (start, simd_mid, end) = bytes.as_simd::<16>();
let query = std::simd::u8x16::splat(b'\n');
let nl_count = start.iter().map(|&b| (b == b'\n') as usize).sum::<usize>()
+ simd_mid
.iter()
.map(|s| s.simd_eq(query).to_bitmask().count_ones())
.sum::<u32>() as usize
+ end.iter().map(|&b| (b == b'\n') as usize).sum::<usize>();
let mut lines = Vec::with_capacity(nl_count);
let mut last_nl = 0;
let handle_rem = |offset: usize, bytes: &[u8], last_nl: &mut usize, lines: &mut Vec<u8>| {
bytes
.iter()
.copied()
.enumerate()
.filter_map(|(i, b)| (b == b'\n').then_some(i + offset))
.for_each(|i| {
lines.push((i - *last_nl + 1) as u8);
*last_nl = i + 1;
});
};
handle_rem(0, start, &mut last_nl, &mut lines);
for (i, simd) in simd_mid.iter().enumerate() {
let mask = simd.simd_eq(query);
let mut mask = mask.to_bitmask();
while mask != 0 {
let idx = mask.trailing_zeros() as usize + i * 16 + start.len();
let mut len = idx - last_nl + 1;
while len >= 0x80 {
lines.push((0x80 | (len & 0x7F)) as u8);
len >>= 7;
} }
c @ (b'a'..=b'z' | b'A'..=b'Z' | b'_' | b'@') => { lines.push(len as u8);
while let Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_') = self.peek() { last_nl = idx + 1;
self.advance(); mask &= mask - 1;
} }
}
if c == b'@' { handle_rem(bytes.len() - end.len(), end, &mut last_nl, &mut lines);
start += 1;
T::Driective
} else {
let ident = &self.bytes[start as usize..self.pos as usize];
T::from_ident(ident)
}
}
b':' if self.advance_if(b'=') => T::Decl,
b':' => T::Colon,
b',' => T::Comma,
b'.' if self.advance_if(b'{') => T::Ctor,
b'.' if self.advance_if(b'(') => T::Tupl,
b'.' => T::Dot,
b';' => T::Semi,
b'!' if self.advance_if(b'=') => T::Ne,
b'=' if self.advance_if(b'=') => T::Eq,
b'=' => T::Assign,
b'<' if self.advance_if(b'=') => T::Le,
b'<' => T::Lt,
b'>' if self.advance_if(b'=') => T::Ge,
b'>' => T::Gt,
b'+' => T::Add,
b'-' => T::Sub,
b'*' => T::Mul,
b'/' => T::Div,
b'&' => T::Band,
b'(' => T::LParen,
b')' => T::RParen,
b'{' => T::LBrace,
b'}' => T::RBrace,
_ => T::Error,
};
return Some(Token { Self {
kind, lines: Box::from(lines),
start,
end: self.pos,
});
} }
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
fn map_lines(input: &'static str, _: &mut String) {
let line_map = super::LineMap::new(input);
for i in 0..input.len() {
assert_eq!(
line_map.line_col(i as u32),
//line_map.line_col(i as u32),
super::line_col(input.as_bytes(), i as u32)
);
}
}
crate::run_tests! { map_lines:
empty_file => "";
log_line => " ".repeat(1000).leak();
this_file => &include_str!("parser.rs")[..1000];
}
fn lex(input: &'static str, output: &mut String) { fn lex(input: &'static str, output: &mut String) {
use { use {
super::{Lexer, TokenKind as T}, super::{Lexer, TokenKind as T},

View file

@ -1,4 +1,6 @@
#![feature(if_let_guard)]
#![feature(noop_waker)] #![feature(noop_waker)]
#![feature(portable_simd)]
#![feature(iter_collect_into)] #![feature(iter_collect_into)]
#![feature(macro_metavar_expr)] #![feature(macro_metavar_expr)]
#![feature(let_chains)] #![feature(let_chains)]
@ -7,11 +9,7 @@
#![feature(slice_ptr_get)] #![feature(slice_ptr_get)]
#![allow(dead_code)] #![allow(dead_code)]
use std::{ use std::{collections::VecDeque, sync::Mutex};
collections::{HashSet, VecDeque},
io,
sync::{mpsc, Arc, Mutex},
};
#[macro_export] #[macro_export]
macro_rules! run_tests { macro_rules! run_tests {

View file

@ -14,13 +14,11 @@ fn main() -> io::Result<()> {
.skip(1) .skip(1)
.map(|path| std::fs::read_to_string(&path).map(|src| (path, src))) .map(|path| std::fs::read_to_string(&path).map(|src| (path, src)))
.collect::<io::Result<Vec<_>>>()?; .collect::<io::Result<Vec<_>>>()?;
let mut arena = parser::Arena::default();
let mut codegen = codegen::Codegen::default(); let mut codegen = codegen::Codegen::default();
for (path, content) in files.iter() { for (path, content) in files.iter() {
let mut parser = parser::Parser::new(&arena, &mut codegen.symbols, &parser::no_loader); codegen.files = vec![parser::Ast::new(&path, &content, &parser::no_loader)];
let file = parser.file(&path, content.as_str()); codegen.generate();
codegen.file(path, content.as_bytes(), file);
arena.clear();
} }
codegen.dump(&mut std::io::stdout()) codegen.dump(&mut std::io::stdout())
} }

View file

@ -1,20 +1,17 @@
use std::{ use std::{
cell::{Cell, UnsafeCell}, cell::{Cell, UnsafeCell},
collections::{HashMap, HashSet}, collections::HashMap,
io::{self, Read}, io::{self, Read},
ops::Not, ops::{Deref, Not},
path::{Path, PathBuf}, path::{Path, PathBuf},
ptr::NonNull, ptr::NonNull,
sync::{ sync::{atomic::AtomicUsize, Mutex},
atomic::{AtomicU32, AtomicUsize},
Mutex,
},
}; };
use crate::{ use crate::{
codegen::bt, codegen::bt,
ident::{self, Ident}, ident::{self, Ident},
lexer::{Lexer, Token, TokenKind}, lexer::{Lexer, LineMap, Token, TokenKind},
TaskQueue, TaskQueue,
}; };
@ -22,13 +19,13 @@ pub type Pos = u32;
pub type IdentFlags = u32; pub type IdentFlags = u32;
pub type Symbols = Vec<Symbol>; pub type Symbols = Vec<Symbol>;
pub type FileId = u32; pub type FileId = u32;
pub type Loader<'a> = &'a (dyn Fn(&str, &str) -> io::Result<Option<FileId>> + 'a); pub type Loader<'a> = &'a (dyn Fn(&str, &str) -> io::Result<FileId> + 'a);
pub const MUTABLE: IdentFlags = 1 << std::mem::size_of::<IdentFlags>() * 8 - 1; pub const MUTABLE: IdentFlags = 1 << (std::mem::size_of::<IdentFlags>() * 8 - 1);
pub const REFERENCED: IdentFlags = 1 << std::mem::size_of::<IdentFlags>() * 8 - 2; pub const REFERENCED: IdentFlags = 1 << (std::mem::size_of::<IdentFlags>() * 8 - 2);
const GIT_DEPS_DIR: &str = "git-deps"; const GIT_DEPS_DIR: &str = "git-deps";
pub fn parse_all(root: &str, threads: usize) -> io::Result<Vec<Ast>> { pub fn parse_all(threads: usize) -> io::Result<Vec<Ast>> {
enum ImportPath<'a> { enum ImportPath<'a> {
Root { Root {
path: &'a str, path: &'a str,
@ -86,9 +83,9 @@ pub fn parse_all(root: &str, threads: usize) -> io::Result<Vec<Ast>> {
} }
impl<'a> ImportPath<'a> { impl<'a> ImportPath<'a> {
fn resolve(&self, from: &str, root: &str) -> Result<PathBuf, CantLoadFile> { fn resolve(&self, from: &str) -> Result<PathBuf, CantLoadFile> {
match self { match self {
Self::Root { path } => Ok(PathBuf::from_iter([root, path])), Self::Root { path } => Ok(Path::new(path).to_owned()),
Self::Rel { path } => { Self::Rel { path } => {
let path = PathBuf::from_iter([from, path]); let path = PathBuf::from_iter([from, path]);
match path.canonicalize() { match path.canonicalize() {
@ -98,7 +95,7 @@ pub fn parse_all(root: &str, threads: usize) -> io::Result<Vec<Ast>> {
} }
Self::Git { path, link, .. } => { Self::Git { path, link, .. } => {
let link = preprocess_git(link); let link = preprocess_git(link);
Ok(PathBuf::from_iter([root, GIT_DEPS_DIR, link, path])) Ok(PathBuf::from_iter([GIT_DEPS_DIR, link, path]))
} }
} }
} }
@ -179,17 +176,7 @@ pub fn parse_all(root: &str, threads: usize) -> io::Result<Vec<Ast>> {
} }
} }
enum Task { type Task = (FileId, PathBuf, Option<std::process::Command>);
LoadFile {
id: FileId,
physiscal_path: PathBuf,
},
FetchGit {
id: FileId,
physiscal_path: PathBuf,
command: std::process::Command,
},
}
let seen = Mutex::new(HashMap::<PathBuf, FileId>::new()); let seen = Mutex::new(HashMap::<PathBuf, FileId>::new());
let tasks = TaskQueue::<Task>::new(threads); let tasks = TaskQueue::<Task>::new(threads);
@ -198,14 +185,14 @@ pub fn parse_all(root: &str, threads: usize) -> io::Result<Vec<Ast>> {
let loader = |path: &str, from: &str| { let loader = |path: &str, from: &str| {
let path = ImportPath::try_from(path)?; let path = ImportPath::try_from(path)?;
let physiscal_path = path.resolve(from, root)?; let physiscal_path = path.resolve(from)?;
let id = { let id = {
let mut seen = seen.lock().unwrap(); let mut seen = seen.lock().unwrap();
let len = seen.len(); let len = seen.len();
match seen.entry(physiscal_path.clone()) { match seen.entry(physiscal_path.clone()) {
std::collections::hash_map::Entry::Occupied(entry) => { std::collections::hash_map::Entry::Occupied(entry) => {
return Ok(Some(*entry.get())); return Ok(*entry.get());
} }
std::collections::hash_map::Entry::Vacant(entry) => { std::collections::hash_map::Entry::Vacant(entry) => {
entry.insert(len as _); entry.insert(len as _);
@ -214,91 +201,74 @@ pub fn parse_all(root: &str, threads: usize) -> io::Result<Vec<Ast>> {
} }
}; };
if physiscal_path.exists() { let command = if !physiscal_path.exists() {
tasks.push(Task::LoadFile { id, physiscal_path }); let ImportPath::Git {
return Ok(Some(id)); link,
} branch,
rev,
tag,
..
} = path
else {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("can't find file: {}", physiscal_path.display()),
));
};
let ImportPath::Git { let root = PathBuf::from_iter([GIT_DEPS_DIR, preprocess_git(link)]);
link,
path, let mut command = std::process::Command::new("git");
branch, command
rev, .args(["clone", "--depth", "1"])
tag, .args(branch.map(|b| ["--branch", b]).into_iter().flatten())
} = path .args(tag.map(|t| ["--tag", t]).into_iter().flatten())
else { .args(rev.map(|r| ["--rev", r]).into_iter().flatten())
return Err(io::Error::new( .arg(link)
io::ErrorKind::NotFound, .arg(root);
format!("can't find file: {}", physiscal_path.display()), Some(command)
)); } else {
None
}; };
let root = PathBuf::from_iter([root, GIT_DEPS_DIR, preprocess_git(link)]); tasks.push((id, physiscal_path, command));
Ok(id)
let mut command = std::process::Command::new("git");
command
.args(["clone", "--depth", "1"])
.args(branch.map(|b| ["--branch", b]).into_iter().flatten())
.args(tag.map(|t| ["--tag", t]).into_iter().flatten())
.args(rev.map(|r| ["--rev", r]).into_iter().flatten())
.arg(link)
.arg(root);
tasks.push(Task::FetchGit {
id,
physiscal_path,
command,
});
Ok(Some(id))
}; };
let load_from_path = |path: &Path, buffer: &mut Vec<u8>| -> io::Result<Ast> { let execute_task = |(_, path, command): Task, buffer: &mut Vec<u8>| {
let path = path.to_str().ok_or_else(|| { if let Some(mut command) = command {
io::Error::new( let output = command.output()?;
io::ErrorKind::InvalidData,
"path contains invalid characters",
)
})?;
let mut file = std::fs::File::open(&path)?;
file.read_to_end(buffer)?;
let src = std::str::from_utf8(buffer).map_err(InvalidFileData)?;
Ok(Ast::new(&path, src, &loader))
};
let execute_task = |task: Task, buffer: &mut Vec<u8>| match task {
Task::LoadFile { id, physiscal_path } => (id, load_from_path(&physiscal_path, buffer)),
Task::FetchGit {
id,
physiscal_path,
mut command,
} => {
let output = match command.output() {
Ok(output) => output,
Err(e) => return (id, Err(e)),
};
if !output.status.success() { if !output.status.success() {
let msg = format!( let msg = format!(
"git command failed: {}", "git command failed: {}",
String::from_utf8_lossy(&output.stderr) String::from_utf8_lossy(&output.stderr)
); );
return (id, Err(io::Error::new(io::ErrorKind::Other, msg))); return Err(io::Error::new(io::ErrorKind::Other, msg));
} }
(id, load_from_path(&physiscal_path, buffer))
} }
let path = path.to_str().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("path contains invalid characters: {}", path.display()),
)
})?;
let mut file = std::fs::File::open(path)?;
file.read_to_end(buffer)?;
let src = std::str::from_utf8(buffer).map_err(InvalidFileData)?;
Ok(Ast::new(path, src, &loader))
}; };
let thread = || { let thread = || {
let mut buffer = Vec::new(); let mut buffer = Vec::new();
while let Some(task) = tasks.pop() { while let Some(task @ (indx, ..)) = tasks.pop() {
let (indx, res) = execute_task(task, &mut buffer); let res = execute_task(task, &mut buffer);
buffer.clear();
let mut ast = ast.lock().unwrap(); let mut ast = ast.lock().unwrap();
let len = ast.len().max(indx as usize + 1); let len = ast.len().max(indx as usize + 1);
ast.resize_with(len, || Err(io::ErrorKind::InvalidData.into())); ast.resize_with(len, || Err(io::ErrorKind::InvalidData.into()));
ast[indx as usize] = res; ast[indx as usize] = res;
buffer.clear();
} }
}; };
@ -314,10 +284,11 @@ pub fn ident_flag_index(flag: IdentFlags) -> u32 {
flag & !(MUTABLE | REFERENCED) flag & !(MUTABLE | REFERENCED)
} }
pub fn no_loader(_: &str, _: &str) -> io::Result<Option<FileId>> { pub fn no_loader(_: &str, _: &str) -> io::Result<FileId> {
Ok(None) Err(io::ErrorKind::NotFound.into())
} }
#[derive(Debug)]
pub struct Symbol { pub struct Symbol {
pub name: Ident, pub name: Ident,
pub flags: IdentFlags, pub flags: IdentFlags,
@ -490,6 +461,21 @@ impl<'a, 'b> Parser<'a, 'b> {
let frame = self.idents.len(); let frame = self.idents.len();
let token = self.next(); let token = self.next();
let mut expr = match token.kind { let mut expr = match token.kind {
T::Driective if self.lexer.slice(token.range()) == "use" => {
self.expect_advance(TokenKind::LParen);
let str = self.expect_advance(TokenKind::String);
self.expect_advance(TokenKind::RParen);
let path = self.lexer.slice(str.range()).trim_matches('"');
E::Mod {
pos: token.start,
path: self.arena.alloc_str(path),
id: match (self.loader)(path, self.path) {
Ok(id) => id,
Err(e) => self.report(format_args!("error loading dependency: {e:#?}")),
},
}
}
T::Driective => E::Directive { T::Driective => E::Directive {
pos: token.start, pos: token.start,
name: self.move_str(token), name: self.move_str(token),
@ -562,7 +548,9 @@ impl<'a, 'b> Parser<'a, 'b> {
op: token.kind, op: token.kind,
val: { val: {
let expr = self.ptr_unit_expr(); let expr = self.ptr_unit_expr();
self.flag_idents(*expr, REFERENCED); if token.kind == T::Band {
self.flag_idents(*expr, REFERENCED);
}
expr expr
}, },
}, },
@ -808,6 +796,11 @@ pub enum Expr<'a> {
name: &'a str, name: &'a str,
args: &'a [Self], args: &'a [Self],
}, },
Mod {
pos: Pos,
id: FileId,
path: &'a str,
},
} }
impl<'a> Expr<'a> { impl<'a> Expr<'a> {
@ -816,6 +809,7 @@ impl<'a> Expr<'a> {
Self::Call { func, .. } => func.pos(), Self::Call { func, .. } => func.pos(),
Self::Ident { id, .. } => ident::pos(*id), Self::Ident { id, .. } => ident::pos(*id),
Self::Break { pos } Self::Break { pos }
| Self::Mod { pos, .. }
| Self::Directive { pos, .. } | Self::Directive { pos, .. }
| Self::Continue { pos } | Self::Continue { pos }
| Self::Closure { pos, .. } | Self::Closure { pos, .. }
@ -837,13 +831,13 @@ impl<'a> Expr<'a> {
impl<'a> std::fmt::Display for Expr<'a> { impl<'a> std::fmt::Display for Expr<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
thread_local! { thread_local! {
static INDENT: Cell<usize> = Cell::new(0); static INDENT: Cell<usize> = const { Cell::new(0) };
} }
fn fmt_list<'a, T>( fn fmt_list<T>(
f: &mut std::fmt::Formatter, f: &mut std::fmt::Formatter,
end: &str, end: &str,
list: &'a [T], list: &[T],
fmt: impl Fn(&T, &mut std::fmt::Formatter) -> std::fmt::Result, fmt: impl Fn(&T, &mut std::fmt::Formatter) -> std::fmt::Result,
) -> std::fmt::Result { ) -> std::fmt::Result {
let first = &mut true; let first = &mut true;
@ -881,9 +875,8 @@ impl<'a> std::fmt::Display for Expr<'a> {
} }
match *self { match *self {
Self::Field { target, field } => { Self::Mod { path, .. } => write!(f, "@mod(\"{path}\")"),
write!(f, "{}.{field}", Postfix(target)) Self::Field { target, field } => write!(f, "{}.{field}", Postfix(target)),
}
Self::Directive { name, args, .. } => { Self::Directive { name, args, .. } => {
write!(f, "@{name}(")?; write!(f, "@{name}(")?;
fmt_list(f, ")", args, std::fmt::Display::fmt) fmt_list(f, ")", args, std::fmt::Display::fmt)
@ -980,12 +973,14 @@ impl<'a> std::fmt::Display for Expr<'a> {
} }
#[repr(C)] #[repr(C)]
struct AstInner<T: ?Sized> { pub struct AstInner<T: ?Sized> {
ref_count: AtomicUsize, ref_count: AtomicUsize,
mem: ArenaChunk, mem: ArenaChunk,
exprs: *const [Expr<'static>], exprs: *const [Expr<'static>],
path: String,
symbols: T, pub path: Box<str>,
pub nlines: LineMap,
pub symbols: T,
} }
impl AstInner<[Symbol]> { impl AstInner<[Symbol]> {
@ -1006,19 +1001,25 @@ impl AstInner<[Symbol]> {
let layout = Self::layout(syms.len()); let layout = Self::layout(syms.len());
let ptr = unsafe { std::alloc::alloc(layout) };
let inner: *mut Self = std::ptr::from_raw_parts_mut(ptr as *mut _, syms.len());
unsafe { unsafe {
*(inner as *mut AstInner<()>) = AstInner { let ptr = std::alloc::alloc(layout);
ref_count: AtomicUsize::new(1), let inner: *mut Self = std::ptr::from_raw_parts_mut(ptr as *mut _, syms.len());
mem: ArenaChunk::default(),
exprs, std::ptr::write(
path: path.to_owned(), inner as *mut AstInner<()>,
symbols: (), AstInner {
}; ref_count: AtomicUsize::new(1),
mem: arena.chunk.into_inner(),
exprs,
path: path.into(),
nlines: LineMap::new(content),
symbols: (),
},
);
std::ptr::addr_of_mut!((*inner).symbols) std::ptr::addr_of_mut!((*inner).symbols)
.as_mut_ptr() .as_mut_ptr()
.copy_from_nonoverlapping(syms.as_ptr(), syms.len()); .copy_from_nonoverlapping(syms.as_ptr(), syms.len());
NonNull::new_unchecked(inner) NonNull::new_unchecked(inner)
} }
} }
@ -1036,17 +1037,44 @@ impl Ast {
unsafe { &*self.inner().exprs } unsafe { &*self.inner().exprs }
} }
pub fn symbols(&self) -> &[Symbol] {
&self.inner().symbols
}
pub fn path(&self) -> &str {
&self.inner().path
}
fn inner(&self) -> &AstInner<[Symbol]> { fn inner(&self) -> &AstInner<[Symbol]> {
unsafe { self.0.as_ref() } unsafe { self.0.as_ref() }
} }
pub fn find_decl(&self, id: Result<Ident, &str>) -> Option<&Expr> {
self.exprs().iter().find_map(|expr| match expr {
Expr::BinOp {
left: &Expr::Ident { id: iden, name, .. },
op: TokenKind::Decl,
..
} if Ok(iden) == id || Err(name) == id => Some(expr),
_ => None,
})
}
}
impl Default for Ast {
fn default() -> Self {
Self(AstInner::new("", "", &no_loader))
}
}
#[derive(Clone, Copy)]
#[repr(packed)]
pub struct ExprRef(NonNull<Expr<'static>>);
impl ExprRef {
pub fn new(expr: &Expr) -> Self {
Self(NonNull::from(expr).cast())
}
pub fn get<'a>(&self, from: &'a Ast) -> Option<&'a Expr<'a>> {
// 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
ArenaChunk::contains(from.mem.base, self.0.as_ptr() as _)
.then(|| unsafe { { self.0 }.as_ref() })
}
} }
unsafe impl Send for Ast {} unsafe impl Send for Ast {}
@ -1079,6 +1107,14 @@ impl Drop for Ast {
} }
} }
impl Deref for Ast {
type Target = AstInner<[Symbol]>;
fn deref(&self) -> &Self::Target {
self.inner()
}
}
#[derive(Default)] #[derive(Default)]
pub struct Arena<'a> { pub struct Arena<'a> {
chunk: UnsafeCell<ArenaChunk>, chunk: UnsafeCell<ArenaChunk>,
@ -1092,6 +1128,10 @@ impl<'a> Arena<'a> {
} }
pub fn alloc<T>(&self, value: T) -> &'a mut T { pub fn alloc<T>(&self, value: T) -> &'a mut T {
if std::mem::size_of::<T>() == 0 {
return unsafe { NonNull::dangling().as_mut() };
}
let layout = std::alloc::Layout::new::<T>(); let layout = std::alloc::Layout::new::<T>();
let ptr = self.alloc_low(layout); let ptr = self.alloc_low(layout);
unsafe { ptr.cast::<T>().write(value) }; unsafe { ptr.cast::<T>().write(value) };
@ -1099,6 +1139,10 @@ impl<'a> Arena<'a> {
} }
pub fn alloc_slice<T: Copy>(&self, slice: &[T]) -> &'a mut [T] { pub fn alloc_slice<T: Copy>(&self, slice: &[T]) -> &'a mut [T] {
if slice.is_empty() || std::mem::size_of::<T>() == 0 {
return &mut [];
}
let layout = std::alloc::Layout::array::<T>(slice.len()).unwrap(); let layout = std::alloc::Layout::array::<T>(slice.len()).unwrap();
let ptr = self.alloc_low(layout); let ptr = self.alloc_low(layout);
unsafe { unsafe {
@ -1109,23 +1153,6 @@ impl<'a> Arena<'a> {
unsafe { std::slice::from_raw_parts_mut(ptr.as_ptr() as _, slice.len()) } unsafe { std::slice::from_raw_parts_mut(ptr.as_ptr() as _, slice.len()) }
} }
pub fn clear(&mut self) {
let chunk = self.chunk.get_mut();
if chunk.base.is_null() {
return;
}
loop {
let prev = ArenaChunk::prev(chunk.base);
if prev.is_null() {
break;
}
chunk.base = prev;
}
chunk.end = unsafe { chunk.base.add(ArenaChunk::PREV_OFFSET) };
}
fn alloc_low(&self, layout: std::alloc::Layout) -> NonNull<u8> { fn alloc_low(&self, layout: std::alloc::Layout) -> NonNull<u8> {
assert!(layout.align() <= ArenaChunk::ALIGN); assert!(layout.align() <= ArenaChunk::ALIGN);
assert!(layout.size() <= ArenaChunk::CHUNK_SIZE); assert!(layout.size() <= ArenaChunk::CHUNK_SIZE);
@ -1136,10 +1163,8 @@ impl<'a> Arena<'a> {
return ptr; return ptr;
} }
if let Some(prev) = ArenaChunk::reset(ArenaChunk::prev(chunk.base)) { unsafe {
*chunk = prev; std::ptr::write(chunk, ArenaChunk::new(chunk.base));
} else {
*chunk = ArenaChunk::new(chunk.base);
} }
chunk.alloc(layout).unwrap() chunk.alloc(layout).unwrap()
@ -1164,18 +1189,13 @@ impl ArenaChunk {
const CHUNK_SIZE: usize = 1 << 16; const CHUNK_SIZE: usize = 1 << 16;
const ALIGN: usize = std::mem::align_of::<Self>(); const ALIGN: usize = std::mem::align_of::<Self>();
const NEXT_OFFSET: usize = Self::CHUNK_SIZE - std::mem::size_of::<*mut u8>(); const NEXT_OFFSET: usize = Self::CHUNK_SIZE - std::mem::size_of::<*mut u8>();
const PREV_OFFSET: usize = Self::NEXT_OFFSET - std::mem::size_of::<*mut u8>();
const LAYOUT: std::alloc::Layout = const LAYOUT: std::alloc::Layout =
unsafe { std::alloc::Layout::from_size_align_unchecked(Self::CHUNK_SIZE, Self::ALIGN) }; unsafe { std::alloc::Layout::from_size_align_unchecked(Self::CHUNK_SIZE, Self::ALIGN) };
fn new(next: *mut u8) -> Self { fn new(next: *mut u8) -> Self {
let base = unsafe { std::alloc::alloc(Self::LAYOUT) }; let base = unsafe { std::alloc::alloc(Self::LAYOUT) };
let end = unsafe { base.add(Self::PREV_OFFSET) }; let end = unsafe { base.add(Self::NEXT_OFFSET) };
if !next.is_null() {
Self::set_prev(next, base);
}
Self::set_next(base, next); Self::set_next(base, next);
Self::set_prev(base, std::ptr::null_mut());
Self { base, end } Self { base, end }
} }
@ -1183,21 +1203,10 @@ impl ArenaChunk {
unsafe { std::ptr::write(curr.add(Self::NEXT_OFFSET) as *mut _, next) }; unsafe { std::ptr::write(curr.add(Self::NEXT_OFFSET) as *mut _, next) };
} }
fn set_prev(curr: *mut u8, prev: *mut u8) {
unsafe { std::ptr::write(curr.add(Self::PREV_OFFSET) as *mut _, prev) };
}
fn next(curr: *mut u8) -> *mut u8 { fn next(curr: *mut u8) -> *mut u8 {
unsafe { std::ptr::read(curr.add(Self::NEXT_OFFSET) as *mut _) } unsafe { std::ptr::read(curr.add(Self::NEXT_OFFSET) as *mut _) }
} }
fn prev(curr: *mut u8) -> *mut u8 {
if curr.is_null() {
return std::ptr::null_mut();
}
unsafe { std::ptr::read(curr.add(Self::PREV_OFFSET) as *mut _) }
}
fn reset(prev: *mut u8) -> Option<Self> { fn reset(prev: *mut u8) -> Option<Self> {
if prev.is_null() { if prev.is_null() {
return None; return None;
@ -1218,19 +1227,17 @@ impl ArenaChunk {
unsafe { self.end = self.end.sub(size) }; unsafe { self.end = self.end.sub(size) };
unsafe { Some(NonNull::new_unchecked(self.end)) } 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))
}
} }
impl Drop for ArenaChunk { impl Drop for ArenaChunk {
fn drop(&mut self) { fn drop(&mut self) {
let mut current = self.base; let mut current = self.base;
let mut prev = Self::prev(current);
while !prev.is_null() {
let next = Self::prev(prev);
unsafe { std::alloc::dealloc(prev, Self::LAYOUT) };
prev = next;
}
while !current.is_null() { while !current.is_null() {
let next = Self::next(current); let next = Self::next(current);
unsafe { std::alloc::dealloc(current, Self::LAYOUT) }; unsafe { std::alloc::dealloc(current, Self::LAYOUT) };
@ -1241,16 +1248,14 @@ impl Drop for ArenaChunk {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
fn parse(input: &'static str, output: &mut String) { fn parse(input: &'static str, output: &mut String) {
use std::fmt::Write; use std::fmt::Write;
let mut arena = super::Arena::default(); let arena = super::Arena::default();
let mut symbols = Vec::new(); let mut symbols = Vec::new();
let mut parser = super::Parser::new(&arena, &mut symbols, &super::no_loader); let mut parser = super::Parser::new(&arena, &mut symbols, &super::no_loader);
for expr in parser.file(input, "test") { for expr in parser.file(input, "test") {
writeln!(output, "{}", expr).unwrap(); writeln!(output, "{}", expr).unwrap();
} }
arena.clear();
} }
crate::run_tests! { parse: crate::run_tests! { parse:

View file

@ -1,4 +1,4 @@
ev: Ecall ev: Ecall
code size: 217 code size: 204
ret: 0 ret: 0
status: Ok(()) status: Ok(())

View file

@ -1,3 +1,3 @@
code size: 657 code size: 569
ret: 0 ret: 0
status: Ok(()) status: Ok(())

View file

@ -1,3 +1,3 @@
code size: 308 code size: 314
ret: 44 ret: 33
status: Ok(()) status: Ok(())

View file

@ -0,0 +1,3 @@
code size: 305
ret: 50
status: Ok(())

View file

@ -1,3 +1,3 @@
code size: 281 code size: 287
ret: 16 ret: 55
status: Ok(()) status: Ok(())

View file

@ -1,3 +1,3 @@
code size: 379 code size: 366
ret: 0 ret: 0
status: Ok(()) status: Ok(())

View file

@ -1,3 +1,3 @@
code size: 461 code size: 464
ret: 3 ret: 3
status: Ok(()) status: Ok(())

View file

@ -78,3 +78,37 @@ impl From<StoreError> for crate::VmRunError {
Self::StoreAccessEx(value.0) Self::StoreAccessEx(value.0)
} }
} }
#[derive(Default)]
pub struct HostMemory;
impl Memory for HostMemory {
#[inline]
unsafe fn load(
&mut self,
addr: Address,
target: *mut u8,
count: usize,
) -> Result<(), LoadError> {
unsafe { core::ptr::copy(addr.get() as *const u8, target, count) }
Ok(())
}
#[inline]
unsafe fn store(
&mut self,
addr: Address,
source: *const u8,
count: usize,
) -> Result<(), StoreError> {
debug_assert!(addr.get() != 0);
debug_assert!(source != core::ptr::null());
unsafe { core::ptr::copy(source, addr.get() as *mut u8, count) }
Ok(())
}
#[inline]
unsafe fn prog_read<T: Copy>(&mut self, addr: Address) -> T {
debug_assert!(addr.get() != 0);
unsafe { core::ptr::read(addr.get() as *const T) }
}
}

View file

@ -54,7 +54,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut vm = unsafe { let mut vm = unsafe {
Vm::<_, 0>::new( Vm::<_, 0>::new(
mem::HostMemory, hbvm::mem::HostMemory,
Address::new(mmap.as_ptr().add(stack.len()) as u64), Address::new(mmap.as_ptr().add(stack.len()) as u64),
) )
}; };

View file

@ -1,37 +1,5 @@
use std::alloc::Layout; use std::alloc::Layout;
use hbvm::mem::{Address, LoadError, Memory, StoreError};
pub struct HostMemory;
impl Memory for HostMemory {
#[inline]
unsafe fn load(
&mut self,
addr: Address,
target: *mut u8,
count: usize,
) -> Result<(), LoadError> {
unsafe { core::ptr::copy(addr.get() as *const u8, target, count) }
Ok(())
}
#[inline]
unsafe fn store(
&mut self,
addr: Address,
source: *const u8,
count: usize,
) -> Result<(), StoreError> {
unsafe { core::ptr::copy(source, addr.get() as *mut u8, count) }
Ok(())
}
#[inline]
unsafe fn prog_read<T: Copy>(&mut self, addr: Address) -> T {
unsafe { core::ptr::read(addr.get() as *const T) }
}
}
const STACK_SIZE: usize = 2; // MiB const STACK_SIZE: usize = 2; // MiB
type Stack = [u8; 1024 * 1024 * STACK_SIZE]; type Stack = [u8; 1024 * 1024 * STACK_SIZE];

View file

View file

View file