From aca20e296baaa29d9c4fbf3cb0e36ff4c5a5c17a Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Thu, 23 Nov 2023 01:37:22 -0600 Subject: [PATCH] Created Text type, initial values for globals --- forge_core/src/compiler/ast_nodes/expr.rs | 2 +- forge_core/src/compiler/ast_nodes/global.rs | 29 +++++++- forge_core/src/compiler/compiled_fn.rs | 81 +++++++++------------ forge_core/src/compiler/mod.rs | 33 ++++++++- forge_core/src/compiler/state.rs | 3 + forge_core/src/compiler/test_utils.rs | 6 +- forge_core/src/compiler/text.rs | 24 ++++++ vtest/src/forge_tests.rs | 18 +++++ 8 files changed, 140 insertions(+), 56 deletions(-) create mode 100644 forge_core/src/compiler/text.rs diff --git a/forge_core/src/compiler/ast_nodes/expr.rs b/forge_core/src/compiler/ast_nodes/expr.rs index cd77f41..a0f4dec 100644 --- a/forge_core/src/compiler/ast_nodes/expr.rs +++ b/forge_core/src/compiler/ast_nodes/expr.rs @@ -24,7 +24,7 @@ impl Compilable for Expr { match self { Expr::Number(n) => { // Numbers are just pushed as literals - sig.body.push(format!("push {}", n)); + sig.emit_arg("push", n); Ok(()) } Expr::Name(name) => { diff --git a/forge_core/src/compiler/ast_nodes/global.rs b/forge_core/src/compiler/ast_nodes/global.rs index 96d9452..05c65e5 100644 --- a/forge_core/src/compiler/ast_nodes/global.rs +++ b/forge_core/src/compiler/ast_nodes/global.rs @@ -6,16 +6,25 @@ use crate::compiler::state::State; use crate::compiler::utils::Variable; impl Compilable for Global { - fn process(self, state: &mut State, _: Option<&mut CompiledFn>, _loc: Location) -> Result<(), CompileError> { - if self.initial.is_some() { - todo!("Globals with initials are not yet supported") + fn process(self, state: &mut State, _: Option<&mut CompiledFn>, loc: Location) -> Result<(), CompileError> { + let label = state.gensym(); + if let Some(expr) = self.initial { + // Let's make a fake CompiledFn to compile this expr in: + let mut f = CompiledFn::default(); + // Process the initial value and add that to the state's init + expr.process(state, Some(&mut f), loc)?; + state.init.append(&mut f.body); + // Now actually store it: + state.init.emit_arg("storew", label.clone()); } - state.add_global(&self.name, |s| Variable::IndirectLabel(s.gensym())) + + state.add_global(&self.name, |s| Variable::IndirectLabel(label.clone())) } } #[cfg(test)] mod test { + use crate::compiler::test_utils::state_for; use super::*; use crate::parser::parse; @@ -40,4 +49,16 @@ mod test { .process(&mut state, None, (0, 0).into()) .is_err()); } + + #[test] + fn test_init() { + let mut state = state_for("global a = 5 + 3;"); + assert_eq!( + state.init.0.join("\n"), + vec![ + "push 8", + "storew _forge_gensym_1" + ].join("\n") + ) + } } \ No newline at end of file diff --git a/forge_core/src/compiler/compiled_fn.rs b/forge_core/src/compiler/compiled_fn.rs index 3b8445f..4265adf 100644 --- a/forge_core/src/compiler/compiled_fn.rs +++ b/forge_core/src/compiler/compiled_fn.rs @@ -2,6 +2,7 @@ use std::cmp::max; use std::collections::btree_map::Entry::Vacant; use std::fmt::Display; use crate::compiler::compile_error::CompileError; +use crate::compiler::text::Text; use crate::compiler::utils::{Label, Scope, Variable}; /// The data associated with a function signature: @@ -24,7 +25,7 @@ use crate::compiler::utils::{Label, Scope, Variable}; /// and put that on the top of the data stack. Functions store their pointer in the top of the /// rstack, and locals can be found by adding some offset from the frame pointer. /// -/// The max_frams_size field is important: this is the most entries that can be in scope at one time. +/// The max_frame_size field is important: this is the most entries that can be in scope at one time. /// The reason that's important is that anything in the stack _after_ that many slots is available /// for use by the allocator pool. The second entry in the rstack is the "pool pointer" which is the /// address of the first byte of the stack that we haven't used yet. When we want to allocate more @@ -54,9 +55,15 @@ pub struct CompiledFn { pub max_frame_size: usize, pub local_scope: Scope, pub arity: usize, - pub preamble: Vec, - pub body: Vec, - pub outro: Vec, + + /// The preamble is the area of the function capturing the args, etc + pub preamble: Text, + + /// The body is the actual body of the function + pub body: Text, + + /// The outro handles restoring the stack / pool pointers and pushing the return value + pub outro: Text, } impl CompiledFn { @@ -75,37 +82,21 @@ impl CompiledFn { } } - /// Emit a string (ideally one instruction, but whatever) to the function body. - /// This doesn't actually emit anything to output, the body will eventually be emitted - /// in a final pass by the compiler once all functions are compiled. + pub(crate) fn text(&self) -> Vec { + let preamble = self.preamble.0.clone(); + let body = self.body.0.clone(); + let outro = self.outro.0.clone(); + + [preamble, body, outro].into_iter().flatten().collect() + } + + /// Emit and emit arg should just be delegated to `body` pub(crate) fn emit(&mut self, opcode: &str) { - self.body.push(String::from(opcode)) + self.body.emit(opcode) } - /// Emit a string (ideally one instruction, but whatever) to the function preamble. - /// This is very similar to `emit` but it appends the line to a section that will be written - /// before the body. If we need to write something that depends on the body but must be run - /// before it, it gets written through here. - pub(crate) fn preamble_emit(&mut self, opcode: &str) { - self.preamble.push(String::from(opcode)) - } - - /// Emit a string (ideally one instruction, but whatever) to the function outro. Just like the - /// body and preamble, the outro is part of the fn, but will be emitted after the fn body. Any - /// cleanup that needs to happen should happen here. - pub(crate) fn outro_emit(&mut self, opcode: &str) { - self.outro.push(String::from(opcode)) - } - - /// A shorthand method to emit something with a `Display` arg, because emitting a - /// single instruction with a variable (numeric or label) arg is very common. pub(crate) fn emit_arg(&mut self, opcode: &str, arg: T) { - self.body.push(format!("{} {}", opcode, arg)) - } - - /// Preamble version of emit_arg - pub(crate) fn preamble_emit_arg(&mut self, opcode: &str, arg: T) { - self.preamble.push(format!("{} {}", opcode, arg)) + self.body.emit_arg(opcode, arg) } /// The (current) size of the local scope in bytes. This increases as variables are declared, @@ -133,15 +124,15 @@ impl CompiledFn { pub(crate) fn generate_preamble_outro(&mut self, args: &Vec) -> Result<(), CompileError> { // First, we need a pool pointer on the rstack. We know this because it's the current top // (the frame ptr) plus a (known) max scope size: - self.preamble_emit("dup"); + self.preamble.emit("dup"); if self.max_frame_size > 0 { // Pool ptr is right after the locals, so, add max_frame_size - self.preamble_emit_arg("add", self.max_frame_size); + self.preamble.emit_arg("add", self.max_frame_size); } - self.preamble_emit("pushr"); + self.preamble.emit("pushr"); // The top argument we were sent is the frame ptr, store that also: - self.preamble_emit("pushr"); + self.preamble.emit("pushr"); // Add each argument as a local let mut arg_names: Vec<_> = args.iter().map(|a| a.clone()).collect(); @@ -152,11 +143,11 @@ impl CompiledFn { for name in arg_names { if let Variable::Local(offset) = self.local_scope[&name] { - self.preamble_emit("peekr"); + self.preamble.emit("peekr"); if offset != 0 { - self.preamble_emit_arg("add", offset); + self.preamble.emit_arg("add", offset); } - self.preamble_emit("storew"); + self.preamble.emit("storew"); } } @@ -164,21 +155,21 @@ impl CompiledFn { // First, we might fall through to here, so, leave a push 0 on the stack. Normally we roll // with an empty stack, or have some data and jmpr here, but if we fall through this will // ensure that the following return returns something: - self.outro_emit("push 0"); + self.outro.emit("push 0"); // Now, the outro label: - self.outro_emit(format!("{}:", self.end_label).as_str()); + self.outro.emit(format!("{}:", self.end_label).as_str()); // The top of the rstack is, of course, the frame ptr and pool ptr. So we need to get rid of // those: - self.outro_emit("popr"); - self.outro_emit("pop"); - self.outro_emit("popr"); - self.outro_emit("pop"); + self.outro.emit("popr"); + self.outro.emit("pop"); + self.outro.emit("popr"); + self.outro.emit("pop"); // We're in the same condition we entered in except that our return value is on the stack // (or a default 0 is) so time to actually return: - self.outro_emit("ret"); + self.outro.emit("ret"); Ok(()) } diff --git a/forge_core/src/compiler/mod.rs b/forge_core/src/compiler/mod.rs index b8e4a77..5b46a64 100644 --- a/forge_core/src/compiler/mod.rs +++ b/forge_core/src/compiler/mod.rs @@ -13,6 +13,7 @@ mod compilable; #[cfg(test)] mod test_utils; +mod text; /////////////////////////////////////////////////////////// @@ -31,6 +32,9 @@ pub fn build_boot(src: &str) -> Result, CompileError> { // Now we start piling stuff into the vec, starting with an org: asm.push(".org 0x400".into()); + // We need to put in any global initialization: + asm.append(state.init.0.as_mut()); + // Main takes no args, but it does take a frame ptr: asm.push("push stack".into()); @@ -43,9 +47,7 @@ pub fn build_boot(src: &str) -> Result, CompileError> { // 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()); + asm.append(val.text().as_mut()); } // Strings: @@ -188,4 +190,29 @@ mod test { "stack: .db 0", ].join("\n")) } + + #[test] + fn test_global_static() { + let asm = build_boot("global a = static(10); fn main() { a[2] = 5; }".into()).unwrap(); + assert_eq!(asm.join("\n"), vec![ + ".org 0x400", + "push _forge_gensym_2", // the addr of the static buffer + "storew _forge_gensym_1", // stored in a + "push stack", + "call _forge_gensym_3", + "hlt", + "_forge_gensym_3:", // fn main() + "dup", "pushr", "pushr", // capture pool / frame ptrs + "push 5", // rvalue + "loadw _forge_gensym_1", "push 2", "mul 3", "add", "storew", // store it in a[2] + "push 0", // Implicit return value + "_forge_gensym_4:", // Outro start + "popr", "pop", "popr", "pop", // Drop frame / pool ptrs + "ret", + "_forge_gensym_1: .db 0", // a itself + "_forge_gensym_2: .db 0", // the buffer + ".org _forge_gensym_2 + 30", + "stack: .db 0", + ].join("\n")) + } } diff --git a/forge_core/src/compiler/state.rs b/forge_core/src/compiler/state.rs index 4e55050..6219d38 100644 --- a/forge_core/src/compiler/state.rs +++ b/forge_core/src/compiler/state.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use crate::ast::Location; use crate::compiler::compile_error::CompileError; use crate::compiler::compiled_fn::CompiledFn; +use crate::compiler::text::Text; use crate::compiler::utils::{Label, Scope, Variable}; /// The compiler state: @@ -19,6 +20,8 @@ pub(crate) struct State { pub buffers: Vec<(Label, usize)>, /// The functions that have been prototyped but not yet defined pub prototypes: Scope, + /// The initialization code for globals + pub init: Text, } impl State { diff --git a/forge_core/src/compiler/test_utils.rs b/forge_core/src/compiler/test_utils.rs index 0786eb0..403082d 100644 --- a/forge_core/src/compiler/test_utils.rs +++ b/forge_core/src/compiler/test_utils.rs @@ -12,13 +12,13 @@ pub(crate) fn state_for(src: &str) -> State { } pub(crate) fn test_body(state: State) -> String { - state.functions.get("test").unwrap().body.join("\n") + state.functions.get("test").unwrap().body.0.join("\n") } pub(crate) fn test_preamble(state: State) -> String { - state.functions.get("test").unwrap().preamble.join("\n") + state.functions.get("test").unwrap().preamble.0.join("\n") } pub(crate) fn test_outro(state: State) -> String { - state.functions.get("test").unwrap().outro.join("\n") + state.functions.get("test").unwrap().outro.0.join("\n") } diff --git a/forge_core/src/compiler/text.rs b/forge_core/src/compiler/text.rs new file mode 100644 index 0000000..16e6c3b --- /dev/null +++ b/forge_core/src/compiler/text.rs @@ -0,0 +1,24 @@ +use std::fmt::Display; + +#[derive(Default, Clone, PartialEq, Debug)] +pub struct Text(pub Vec); + +impl Text { + /// Emit a string (ideally one instruction, but whatever) to the function body. + /// This doesn't actually emit anything to output, the body will eventually be emitted + /// in a final pass by the compiler once all functions are compiled. + pub(crate) fn emit(&mut self, opcode: &str) { + self.0.push(String::from(opcode)) + } + + /// A shorthand method to emit something with a `Display` arg, because emitting a + /// single instruction with a variable (numeric or label) arg is very common. + pub(crate) fn emit_arg(&mut self, opcode: &str, arg: T) { + self.0.push(format!("{} {}", opcode, arg)) + } + + /// Append another `Text` to this one + pub(crate) fn append(&mut self, other: &mut Text) { + self.0.append(&mut other.0) + } +} \ No newline at end of file diff --git a/vtest/src/forge_tests.rs b/vtest/src/forge_tests.rs index a00c8ae..0528254 100644 --- a/vtest/src/forge_tests.rs +++ b/vtest/src/forge_tests.rs @@ -166,6 +166,24 @@ fn static_test() { ); } +#[test] +fn static2_test() { + // An actual global static array + let src = " + global a = static(1); + fn foo() { + 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.