From 33c28ceb4a40ef349d76f07a9b23991b0a66fc7a Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Wed, 22 Nov 2023 23:40:25 -0600 Subject: [PATCH] Static allocations --- forge_core/src/ast.rs | 1 + forge_core/src/compiler/ast_nodes/const.rs | 2 +- forge_core/src/compiler/ast_nodes/expr.rs | 22 ++++++++++++++++ forge_core/src/compiler/ast_nodes/lvalue.rs | 1 + forge_core/src/compiler/mod.rs | 28 ++++++++++++++------- forge_core/src/compiler/state.rs | 8 ++++++ forge_core/src/parser/ast_nodes/expr.rs | 6 +++++ forge_core/src/parser/forge.pest | 4 +-- vtest/src/forge_tests.rs | 26 +++++++++++++++++++ 9 files changed, 86 insertions(+), 12 deletions(-) diff --git a/forge_core/src/ast.rs b/forge_core/src/ast.rs index f6911a3..6f79a01 100644 --- a/forge_core/src/ast.rs +++ b/forge_core/src/ast.rs @@ -173,6 +173,7 @@ pub enum Expr { Address(Lvalue), Call(Call), New(BoxExpr), + Static(BoxExpr), Subscript(BoxExpr, BoxExpr), Infix(BoxExpr, Operator, BoxExpr), String(String), diff --git a/forge_core/src/compiler/ast_nodes/const.rs b/forge_core/src/compiler/ast_nodes/const.rs index b7f2e12..258c900 100644 --- a/forge_core/src/compiler/ast_nodes/const.rs +++ b/forge_core/src/compiler/ast_nodes/const.rs @@ -44,7 +44,7 @@ pub fn eval_const(expr: Expr, scope: &Scope) -> Result { // (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"), diff --git a/forge_core/src/compiler/ast_nodes/expr.rs b/forge_core/src/compiler/ast_nodes/expr.rs index 5c56a09..cd77f41 100644 --- a/forge_core/src/compiler/ast_nodes/expr.rs +++ b/forge_core/src/compiler/ast_nodes/expr.rs @@ -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") + ) + } } \ No newline at end of file diff --git a/forge_core/src/compiler/ast_nodes/lvalue.rs b/forge_core/src/compiler/ast_nodes/lvalue.rs index 9c2c49a..d8795d0 100644 --- a/forge_core/src/compiler/ast_nodes/lvalue.rs +++ b/forge_core/src/compiler/ast_nodes/lvalue.rs @@ -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"))) diff --git a/forge_core/src/compiler/mod.rs b/forge_core/src/compiler/mod.rs index f0a7fe1..b8e4a77 100644 --- a/forge_core/src/compiler/mod.rs +++ b/forge_core/src/compiler/mod.rs @@ -40,22 +40,32 @@ pub fn build_boot(src: &str) -> Result, 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")) } diff --git a/forge_core/src/compiler/state.rs b/forge_core/src/compiler/state.rs index adb5d61..4e55050 100644 --- a/forge_core/src/compiler/state.rs +++ b/forge_core/src/compiler/state.rs @@ -15,6 +15,8 @@ pub(crate) struct State { pub functions: BTreeMap, /// 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) -> 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) diff --git a/forge_core/src/parser/ast_nodes/expr.rs b/forge_core/src/parser/ast_nodes/expr.rs index 67d09d6..73d0061 100644 --- a/forge_core/src/parser/ast_nodes/expr.rs +++ b/forge_core/src/parser/ast_nodes/expr.rs @@ -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()))); + } } \ No newline at end of file diff --git a/forge_core/src/parser/forge.pest b/forge_core/src/parser/forge.pest index af200f9..4211571 100644 --- a/forge_core/src/parser/forge.pest +++ b/forge_core/src/parser/forge.pest @@ -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 ~ "]" } diff --git a/vtest/src/forge_tests.rs b/vtest/src/forge_tests.rs index 381ef2a..a00c8ae 100644 --- a/vtest/src/forge_tests.rs +++ b/vtest/src/forge_tests.rs @@ -15,6 +15,12 @@ fn compiler_output(src: &str) -> Vec { forge_core::compiler::build_boot(src).unwrap() } +#[allow(dead_code)] +fn assembler_output(src: &str) -> Vec { + let asm = compiler_output(src); + assemble_snippet(asm).unwrap() +} + fn main_return(src: &str) -> i32 { let mut cpu = run_forge(src); cpu.pop_data().into() @@ -140,6 +146,26 @@ fn global_test() { ) } +#[test] +fn static_test() { + // Because the static(1) returns the same address each time, a[0] is the same variable + // no matter how many times foo is called + let src = " + global a; + fn foo() { + a = static(1); + a[0] = a[0] + 1; + } + fn main() { + foo(); foo(); foo(); + return a[0]; + }"; + assert_eq!( + main_return(src), + 3 + ); +} + //#[test] // This is no longer cursed, or a test. The revised calling convention with the pool pointer makes // it now perfectly sane. Left here for posterity.