Static allocations

This commit is contained in:
2023-11-22 23:40:25 -06:00
parent 640ef91306
commit 33c28ceb4a
9 changed files with 86 additions and 12 deletions
+1
View File
@@ -173,6 +173,7 @@ pub enum Expr {
Address(Lvalue),
Call(Call),
New(BoxExpr),
Static(BoxExpr),
Subscript(BoxExpr, BoxExpr),
Infix(BoxExpr, Operator, BoxExpr),
String(String),
+1 -1
View File
@@ -44,7 +44,7 @@ pub fn eval_const(expr: Expr, scope: &Scope) -> Result<i32, CompileError> {
// (currently) parsed as a special case of const. The only things that will hit this are
// const expressions that include strings, like '"foo"[2]' or something. Those can't be
// (generally) calculated at compile time, so, they're an error.
Expr::Address(_) | Expr::Deref(_) | Expr::String(_) => Err(CompileError(
Expr::Address(_) | Expr::Deref(_) | Expr::String(_) | Expr::Static(_) => Err(CompileError(
0,
0,
String::from("Addresses are not known at compile time"),
+22
View File
@@ -91,6 +91,9 @@ impl Compilable for Expr {
compile_alloc(&mut sig);
Ok(())
},
Expr::Static(size_expr) => {
compile_static(size_expr, state, sig)
}
Expr::Subscript(array, index) => {
array.0.process(state, Some(sig), loc)?;
index.0.process(state, Some(sig), loc)?;
@@ -174,6 +177,13 @@ fn compile_alloc(sig: &mut CompiledFn) {
sig.emit("pushr");
}
fn compile_static(expr: BoxExpr, state: &mut State, sig: &mut CompiledFn) -> Result<(), CompileError> {
let size = eval_const(*expr.0, &state.global_scope)? as usize;
let label = state.add_buffer(size * 3);
sig.emit_arg("push", label);
Ok(())
}
#[cfg(test)]
mod test {
use crate::compiler::test_utils::*;
@@ -282,4 +292,16 @@ mod test {
].join("\n")
)
}
#[test]
fn test_static() {
assert_eq!(
test_body(state_for("fn test() { var x = static(1); }")),
vec![
"push _forge_gensym_3", // Address of a 3-byte buffer
"peekr",
"storew",
].join("\n")
)
}
}
@@ -19,6 +19,7 @@ impl Compilable for Lvalue {
Expr::Address(_) |
Expr::Call(_) |
Expr::New(_) |
Expr::Static(_) |
Expr::Infix(_, _, _) |
Expr::String(_) => {
Err(CompileError(0, 0, String::from("Not a valid lvalue")))
+19 -9
View File
@@ -40,22 +40,32 @@ pub fn build_boot(src: &str) -> Result<Vec<String>, CompileError> {
// When main returns, just hlt:
asm.push("hlt".into());
// Now start dumping compiled objects into there:
// Now start dumping compiled objects into there. First functions:
for (_, val) in state.functions.iter_mut() {
asm.push(format!("{}:", val.label));
asm.append(val.preamble.as_mut());
asm.append(val.body.as_mut());
asm.append(val.outro.as_mut());
}
// Strings:
for (label, val) in state.strings.iter() {
asm.push(format!("{}: .db \"{}\\0\"", label, val))
}
for (label, val) in state.global_scope.iter() {
// Global vars:
for (_, val) in state.global_scope.iter() {
// All global variables are indirect labels; anything in scope that's not that is a const,
// fn, or string, which we'll emit elsewhere.
if let Variable::IndirectLabel(label) = val {
asm.push(format!("{}: .db 0", label))
}
}
for (_, val) in state.functions.iter_mut() {
asm.push(format!("{}:", val.label));
asm.append(val.preamble.as_mut());
asm.append(val.body.as_mut());
asm.append(val.outro.as_mut());
// Static buffers:
for(label, size) in state.buffers {
asm.push(format!("{}: .db 0", label));
asm.push(format!(".org {} + {}", label, size));
}
// Final thing is to place a label for the stack:
@@ -105,7 +115,6 @@ mod test {
"push stack",
"call _forge_gensym_2",
"hlt",
"_forge_gensym_1: .db \"blah\\0\"",
"_forge_gensym_2:",
"dup",
"pushr",
@@ -119,6 +128,7 @@ mod test {
"popr",
"pop",
"ret",
"_forge_gensym_1: .db \"blah\\0\"",
"stack: .db 0",
].join("\n"))
}
@@ -132,7 +142,6 @@ mod test {
"push stack",
"call _forge_gensym_2",
"hlt",
"_forge_gensym_1: .db 0", // The global var
"_forge_gensym_2:", // main()
"dup", "pushr", "pushr", // The frame is zero length; that's a global we're assigning to
"push 3", // rvalue
@@ -141,6 +150,7 @@ mod test {
"push 0",
"_forge_gensym_3:",
"popr", "pop", "popr", "pop", "ret",
"_forge_gensym_1: .db 0", // The global var
"stack: .db 0",
].join("\n"))
}
+8
View File
@@ -15,6 +15,8 @@ pub(crate) struct State {
pub functions: BTreeMap<String, CompiledFn>,
/// The string table
pub strings: Vec<(Label, String)>,
/// The statically-allocated buffers (size in bytes)
pub buffers: Vec<(Label, usize)>,
/// The functions that have been prototyped but not yet defined
pub prototypes: Scope,
}
@@ -52,6 +54,12 @@ impl State {
sym
}
pub(crate) fn add_buffer(&mut self, size: usize) -> Label {
let sym = self.gensym();
self.buffers.push((sym.clone(), size));
sym
}
pub(crate) fn declare_function(&mut self, name: &str, _args: Vec<String>) -> Result<(), CompileError> {
// If it's not already prototyped, gensym a label and put it in the list. If it is, just
// ignore this (we don't check arity so it's not like the arglist being different matters)
+6
View File
@@ -19,6 +19,7 @@ impl AstNode for Expr {
.map_primary(|term| match term.as_rule() {
PestRule::number => Expr::Number(term.into_number()),
PestRule::alloc => Expr::New(Expr::from_pair(term.first()).into()),
PestRule::static_alloc => Expr::Static(Expr::from_pair(term.first()).into()),
PestRule::name => Expr::Name(String::from(term.as_str())),
PestRule::expr => Expr::from_pair(term),
PestRule::string => Self::String(term.into_quoted_string()),
@@ -275,4 +276,9 @@ mod test {
fn alloc() {
assert_eq!(Expr::from_str("new(8)"), Ok(Expr::New(8.into())));
}
#[test]
fn static_alloc() {
assert_eq!(Expr::from_str("static(8)"), Ok(Expr::Static(8.into())));
}
}
+2 -2
View File
@@ -63,9 +63,9 @@ operator = _{
}
expr = { prefix* ~ term ~ suffix* ~ (operator ~ prefix* ~ term ~ suffix*)* }
term = _{ number | alloc | name | "(" ~ expr ~ ")" | string }
term = _{ number | alloc | static_alloc | name | "(" ~ expr ~ ")" | string }
alloc = { "new" ~ "(" ~ expr ~ ")" }
static_alloc = { "static" ~ "(" ~ expr ~ ")" }
suffix = _{ subscript | arglist }
subscript = { "[" ~ expr ~ "]" }