Created Text type, initial values for globals

This commit is contained in:
2023-11-23 01:37:22 -06:00
parent 33c28ceb4a
commit aca20e296b
8 changed files with 140 additions and 56 deletions
+1 -1
View File
@@ -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) => {
+25 -4
View File
@@ -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")
)
}
}
+36 -45
View File
@@ -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<String>,
pub body: Vec<String>,
pub outro: Vec<String>,
/// 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<String> {
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<T: Display>(&mut self, opcode: &str, arg: T) {
self.body.push(format!("{} {}", opcode, arg))
}
/// Preamble version of emit_arg
pub(crate) fn preamble_emit_arg<T: Display>(&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<String>) -> 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(())
}
+30 -3
View File
@@ -13,6 +13,7 @@ mod compilable;
#[cfg(test)]
mod test_utils;
mod text;
///////////////////////////////////////////////////////////
@@ -31,6 +32,9 @@ pub fn build_boot(src: &str) -> Result<Vec<String>, 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<Vec<String>, 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"))
}
}
+3
View File
@@ -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 {
+3 -3
View File
@@ -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")
}
+24
View File
@@ -0,0 +1,24 @@
use std::fmt::Display;
#[derive(Default, Clone, PartialEq, Debug)]
pub struct Text(pub Vec<String>);
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<T: Display>(&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)
}
}
+18
View File
@@ -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.