Created Text type, initial values for globals
This commit is contained in:
@@ -24,7 +24,7 @@ impl Compilable for Expr {
|
|||||||
match self {
|
match self {
|
||||||
Expr::Number(n) => {
|
Expr::Number(n) => {
|
||||||
// Numbers are just pushed as literals
|
// Numbers are just pushed as literals
|
||||||
sig.body.push(format!("push {}", n));
|
sig.emit_arg("push", n);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Expr::Name(name) => {
|
Expr::Name(name) => {
|
||||||
|
|||||||
@@ -6,16 +6,25 @@ use crate::compiler::state::State;
|
|||||||
use crate::compiler::utils::Variable;
|
use crate::compiler::utils::Variable;
|
||||||
|
|
||||||
impl Compilable for Global {
|
impl Compilable for Global {
|
||||||
fn process(self, state: &mut State, _: Option<&mut CompiledFn>, _loc: Location) -> Result<(), CompileError> {
|
fn process(self, state: &mut State, _: Option<&mut CompiledFn>, loc: Location) -> Result<(), CompileError> {
|
||||||
if self.initial.is_some() {
|
let label = state.gensym();
|
||||||
todo!("Globals with initials are not yet supported")
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use crate::compiler::test_utils::state_for;
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::parser::parse;
|
use crate::parser::parse;
|
||||||
|
|
||||||
@@ -40,4 +49,16 @@ mod test {
|
|||||||
.process(&mut state, None, (0, 0).into())
|
.process(&mut state, None, (0, 0).into())
|
||||||
.is_err());
|
.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::collections::btree_map::Entry::Vacant;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use crate::compiler::compile_error::CompileError;
|
use crate::compiler::compile_error::CompileError;
|
||||||
|
use crate::compiler::text::Text;
|
||||||
use crate::compiler::utils::{Label, Scope, Variable};
|
use crate::compiler::utils::{Label, Scope, Variable};
|
||||||
|
|
||||||
/// The data associated with a function signature:
|
/// 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
|
/// 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.
|
/// 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
|
/// 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
|
/// 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
|
/// 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 max_frame_size: usize,
|
||||||
pub local_scope: Scope,
|
pub local_scope: Scope,
|
||||||
pub arity: usize,
|
pub arity: usize,
|
||||||
pub preamble: Vec<String>,
|
|
||||||
pub body: Vec<String>,
|
/// The preamble is the area of the function capturing the args, etc
|
||||||
pub outro: Vec<String>,
|
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 {
|
impl CompiledFn {
|
||||||
@@ -75,37 +82,21 @@ impl CompiledFn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emit a string (ideally one instruction, but whatever) to the function body.
|
pub(crate) fn text(&self) -> Vec<String> {
|
||||||
/// This doesn't actually emit anything to output, the body will eventually be emitted
|
let preamble = self.preamble.0.clone();
|
||||||
/// in a final pass by the compiler once all functions are compiled.
|
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) {
|
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) {
|
pub(crate) fn emit_arg<T: Display>(&mut self, opcode: &str, arg: T) {
|
||||||
self.body.push(format!("{} {}", opcode, arg))
|
self.body.emit_arg(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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The (current) size of the local scope in bytes. This increases as variables are declared,
|
/// 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> {
|
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
|
// 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:
|
// (the frame ptr) plus a (known) max scope size:
|
||||||
self.preamble_emit("dup");
|
self.preamble.emit("dup");
|
||||||
if self.max_frame_size > 0 {
|
if self.max_frame_size > 0 {
|
||||||
// Pool ptr is right after the locals, so, add max_frame_size
|
// 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:
|
// 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
|
// Add each argument as a local
|
||||||
let mut arg_names: Vec<_> = args.iter().map(|a| a.clone()).collect();
|
let mut arg_names: Vec<_> = args.iter().map(|a| a.clone()).collect();
|
||||||
@@ -152,11 +143,11 @@ impl CompiledFn {
|
|||||||
|
|
||||||
for name in arg_names {
|
for name in arg_names {
|
||||||
if let Variable::Local(offset) = self.local_scope[&name] {
|
if let Variable::Local(offset) = self.local_scope[&name] {
|
||||||
self.preamble_emit("peekr");
|
self.preamble.emit("peekr");
|
||||||
if offset != 0 {
|
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
|
// 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
|
// 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:
|
// ensure that the following return returns something:
|
||||||
self.outro_emit("push 0");
|
self.outro.emit("push 0");
|
||||||
|
|
||||||
// Now, the outro label:
|
// 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
|
// The top of the rstack is, of course, the frame ptr and pool ptr. So we need to get rid of
|
||||||
// those:
|
// those:
|
||||||
self.outro_emit("popr");
|
self.outro.emit("popr");
|
||||||
self.outro_emit("pop");
|
self.outro.emit("pop");
|
||||||
self.outro_emit("popr");
|
self.outro.emit("popr");
|
||||||
self.outro_emit("pop");
|
self.outro.emit("pop");
|
||||||
|
|
||||||
// We're in the same condition we entered in except that our return value is on the stack
|
// 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:
|
// (or a default 0 is) so time to actually return:
|
||||||
self.outro_emit("ret");
|
self.outro.emit("ret");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ mod compilable;
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_utils;
|
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:
|
// Now we start piling stuff into the vec, starting with an org:
|
||||||
asm.push(".org 0x400".into());
|
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:
|
// Main takes no args, but it does take a frame ptr:
|
||||||
asm.push("push stack".into());
|
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:
|
// Now start dumping compiled objects into there. First functions:
|
||||||
for (_, val) in state.functions.iter_mut() {
|
for (_, val) in state.functions.iter_mut() {
|
||||||
asm.push(format!("{}:", val.label));
|
asm.push(format!("{}:", val.label));
|
||||||
asm.append(val.preamble.as_mut());
|
asm.append(val.text().as_mut());
|
||||||
asm.append(val.body.as_mut());
|
|
||||||
asm.append(val.outro.as_mut());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strings:
|
// Strings:
|
||||||
@@ -188,4 +190,29 @@ mod test {
|
|||||||
"stack: .db 0",
|
"stack: .db 0",
|
||||||
].join("\n"))
|
].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::ast::Location;
|
||||||
use crate::compiler::compile_error::CompileError;
|
use crate::compiler::compile_error::CompileError;
|
||||||
use crate::compiler::compiled_fn::CompiledFn;
|
use crate::compiler::compiled_fn::CompiledFn;
|
||||||
|
use crate::compiler::text::Text;
|
||||||
use crate::compiler::utils::{Label, Scope, Variable};
|
use crate::compiler::utils::{Label, Scope, Variable};
|
||||||
|
|
||||||
/// The compiler state:
|
/// The compiler state:
|
||||||
@@ -19,6 +20,8 @@ pub(crate) struct State {
|
|||||||
pub buffers: Vec<(Label, usize)>,
|
pub buffers: Vec<(Label, usize)>,
|
||||||
/// The functions that have been prototyped but not yet defined
|
/// The functions that have been prototyped but not yet defined
|
||||||
pub prototypes: Scope,
|
pub prototypes: Scope,
|
||||||
|
/// The initialization code for globals
|
||||||
|
pub init: Text,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ pub(crate) fn state_for(src: &str) -> State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn test_body(state: State) -> String {
|
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 {
|
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 {
|
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]
|
//#[test]
|
||||||
// This is no longer cursed, or a test. The revised calling convention with the pool pointer makes
|
// 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.
|
// it now perfectly sane. Left here for posterity.
|
||||||
|
|||||||
Reference in New Issue
Block a user