Created Text type, initial values for globals
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user