Arrays (local) now work!

This commit is contained in:
2023-11-13 12:10:57 -06:00
parent 749b11adfa
commit dbdafbfd4b
6 changed files with 185 additions and 12 deletions
@@ -15,3 +15,42 @@ impl Compilable for Assignment {
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::compiler::test_utils::*;
#[test]
fn test_subscripts() {
assert_eq!(
test_body(state_for("fn test(x) { x[3] = 5; }")),
vec![
"push 5", // rvalue
"peekr", "loadw", // Base of x
"push 3", // index
"mul 3", // as byte offset
"add", // addr of x[3]
"storew"
].join("\n")
)
}
#[test]
fn test_zero_subscripts() {
assert_eq!(
test_body(state_for("fn test() { var x = new(5); x[0] = 5; }")),
vec![
"push 5", // size of array
"mul 3","popr","swap","popr","swap","pick 1","add","pushr","swap","pushr", // alloc call
"peekr","storew", // store addr in x
"push 5", // rvalue
"peekr", "loadw", // Base of x
"push 0", // index
"mul 3", // as byte offset
"add", // addr of x[0]
"storew"
].join("\n")
)
}
}
+64 -2
View File
@@ -86,8 +86,19 @@ impl Compilable for Expr {
Ok(())
}
Expr::Call(call) => call.process(state, Some(sig), loc),
Expr::New(_size) => todo!("new not yet supported"),
Expr::Subscript(_, _) => todo!("Structs and arrays are not yet supported"),
Expr::New(size_expr) => {
size_expr.0.process(state, Some(sig), loc)?;
compile_alloc(&mut sig);
Ok(())
},
Expr::Subscript(array, index) => {
array.0.process(state, Some(sig), loc)?;
index.0.process(state, Some(sig), loc)?;
sig.emit_arg("mul", 3); // Indices are in words, convert to byte offset
sig.emit("add"); // Add offset
sig.emit("loadw");
Ok(())
},
Expr::Infix(lhs, op, rhs) => {
// Recurse on expressions, handling operators
(*lhs.0).process(state, Some(&mut sig), loc)?;
@@ -141,6 +152,28 @@ impl Compilable for Expr {
}
}
/// Compile the code to run an alloc. This is just generated in-place for now.
fn compile_alloc(sig: &mut CompiledFn) {
// We have the size we want to alloc on top of the stack, in words. So first turn it to bytes:
sig.emit_arg("mul", 3);
// We need to pull off the frame ptr and pool ptr, so the stack looks like ( frame pool size )
sig.emit("popr");
sig.emit("swap");
sig.emit("popr");
sig.emit("swap");
// We need to leave the pool pointer (as it was at the start) on top of the stack. So, copy it
// and move it forward, so the stack is ( frame old-pool new-pool )
sig.emit_arg("pick", 1);
sig.emit("add");
// Put the new pool in place and the frame on top of it, so the stack is just ( old-pool ):
sig.emit("pushr");
sig.emit("swap");
sig.emit("pushr");
}
#[cfg(test)]
mod test {
use crate::compiler::test_utils::*;
@@ -220,4 +253,33 @@ mod test {
.join("\n")
)
}
#[test]
fn test_alloc() {
assert_eq!(
test_body(state_for("fn test() { var x = new(10); return x[2]; }")),
vec![
"push 10", // eval the size
"mul 3", // turn to bytes
"popr", // take out the frame / old-pool
"swap",
"popr",
"swap",
"pick 1", // construct new-pool
"add",
"pushr", // put new-pool in place
"swap", // put frame back in place
"pushr",
"peekr", // store new-pool in x
"storew",
"peekr", // Get x
"loadw",
"push 2", // Index...
"mul 3", // ...to byte offset...
"add", // ..added to base...
"loadw", // ...And loaded
"jmpr @_forge_gensym_2", // (and returned)
].join("\n")
)
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ use crate::compiler::utils::Variable;
impl Compilable for Global {
fn process(self, state: &mut State, _: Option<&mut CompiledFn>, _loc: Location) -> Result<(), CompileError> {
if self.size.is_some() {
todo!("Arrays are not yet supported")
todo!("Global arrays are not yet supported")
}
state.add_global(&self.name, |s| Variable::IndirectLabel(s.gensym()))
}
+6 -2
View File
@@ -57,8 +57,12 @@ impl Compilable for Lvalue {
Expr::Deref(BoxExpr(expr)) => {
(*expr).process(state, Some(sig), loc)
}
Expr::Subscript(_, _) => {
Err(CompileError(0, 0, String::from("Arrays are not yet supported")))
Expr::Subscript(array, index) => {
array.0.process(state, Some(sig), loc)?;
index.0.process(state, Some(sig), loc)?;
sig.emit_arg("mul", 3); // Indices are in words, convert to byte offset
sig.emit("add"); // Add offset
Ok(())
}
}
}
+51 -7
View File
@@ -31,11 +31,6 @@ use crate::compiler::utils::{Label, Scope, Variable};
/// memory for any reason (like, new(), or calling a fn and giving it a valid frame ptr), this is
/// what we pass.
///
/// todo: the allocator problem has (probably) been solved! Make an alloca() that increases the
/// current frame pointer by some size. To dynamically allocate memory, just put it in the stack
/// frame of the current fn. All fns return one word and all params are one word long (structs
/// get passed around by reference)
///
/// todo: more of a global todo. Add a register that stores an offset that's added implicitly to
/// all absolute addresses. This makes it a lot simpler to make relocatable code
///
@@ -72,6 +67,7 @@ impl CompiledFn {
let frame = self.frame_size();
if let Vacant(e) = self.local_scope.entry(name.into()) {
e.insert(Variable::Local(frame));
let frame = self.frame_size();
self.max_frame_size = max(frame, self.max_frame_size);
Ok(())
} else {
@@ -139,8 +135,8 @@ impl CompiledFn {
// (the frame ptr) plus a (known) max scope size:
self.preamble_emit("dup");
if self.max_frame_size > 0 {
// Pool ptr is right after the locals, so, max_frame_size + 3
self.preamble_emit_arg("add", self.max_frame_size + 3);
// Pool ptr is right after the locals, so, add max_frame_size
self.preamble_emit_arg("add", self.max_frame_size);
}
self.preamble_emit("pushr");
@@ -212,3 +208,51 @@ impl CompiledFn {
self.reduce_frame_size_to(self.frame_size() - 3);
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::compiler::test_utils::*;
#[test]
fn max_frame_size_args() {
assert_eq!(test_preamble(state_for("fn test(a) { return 5; }")),
vec![
"dup", // ( frame frame )
"add 3", // add max size to frame to get ( frame pool )
"pushr", // save pool ptr
"pushr", // save frame ptr
"peekr", // store param in 'a'
"storew",
].join("\n")
)
}
#[test]
fn max_frame_size_locals() {
assert_eq!(test_preamble(state_for("fn test() { var x; return 5; }")),
vec![
"dup", // ( frame frame )
"add 3", // add max size to frame to get ( frame pool )
"pushr", // save pool ptr
"pushr", // save frame ptr
].join("\n")
)
}
#[test]
fn max_frame_size_block_scope() {
assert_eq!(test_preamble(state_for("fn test(p) { var x; if(3) { var y; } var z; }")),
vec![
"dup", // ( frame frame )
// This is the test: p and x are always in scope; y is created but leaves
// scope before z enters so z can reuse y's slot
"add 9", // add max size to frame to get ( frame pool )
"pushr", // save pool ptr
"pushr", // save frame ptr
"peekr", // Capture param p
"storew",
].join("\n")
)
}
}
+24
View File
@@ -10,6 +10,7 @@ fn run_forge(src: &str) -> CPU {
cpu
}
#[allow(dead_code)]
fn compiler_output(src: &str) -> Vec<String> {
forge_core::compiler::build_boot(src).unwrap()
}
@@ -102,6 +103,29 @@ fn recursion_test() {
)
}
#[test]
fn array_test() {
// Declare an array and do things
assert_eq!(
main_return(
"fn sum(arr) {
var i = 0;
var s = 0;
while (arr[i] != -1) {
s = arr[i] + s;
i = i + 1;
}
return s;
}
fn main() {
var a = new(5);
a[0] = 1; a[1] = 3; a[2] = 5; a[3] = -1; a[4] = 10;
return sum(a);
}"),
9
)
}
//#[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.