A bunch of prerequisites before we can do arrays, part 3, the pool pointer

This commit is contained in:
2023-11-13 00:32:57 -06:00
parent 0e766aebcf
commit 325288d7ba
6 changed files with 70 additions and 29 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ impl Compilable for Block {
fn process(self, state: &mut State, sig: Option<&mut CompiledFn>, _loc: Location) -> Result<(), CompileError> {
let sig = sig.expect("Block outside function");
let frame_size_before_block = sig.frame_size;
let frame_size_before_block = sig.frame_size();
// Compile each statement:
for stmt in self.0 {
+19 -1
View File
@@ -46,7 +46,7 @@ impl Compilable for Function {
#[cfg(test)]
mod test {
use crate::compiler::test_utils::{state_for, test_body, test_preamble};
use crate::compiler::test_utils::*;
#[test]
fn test_basic_fns() {
@@ -70,6 +70,9 @@ mod test {
assert_eq!(
test_preamble(state_for("fn test(a, b) { b = 17 + a; }")),
vec![
"dup", // Create pool ptr
"add 6",
"pushr", // Store pool ptr
"pushr", // Store frame ptr
"peekr", // Capture var b
"add 3",
@@ -80,4 +83,19 @@ mod test {
.join("\n")
)
}
#[test]
fn test_args_outro() {
assert_eq!(
test_outro(state_for("fn test(a, b) { b = 17 + a; }")),
vec![
"push 0", // Default return value if we fall through
"_forge_gensym_2:", // outro label
"popr", "pop", // Toss the frame ptr
"popr", "pop", // Toss the pool ptr
"ret", // Actually return
]
.join("\n")
)
}
}
+28 -21
View File
@@ -1,3 +1,4 @@
use std::cmp::max;
use std::collections::btree_map::Entry::Vacant;
use std::fmt::Display;
use crate::compiler::compile_error::CompileError;
@@ -23,6 +24,13 @@ 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 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
/// 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
@@ -48,10 +56,9 @@ use crate::compiler::utils::{Label, Scope, Variable};
pub struct CompiledFn {
pub label: Label,
pub end_label: Label,
pub frame_size: usize,
pub max_frame_size: usize,
pub local_scope: Scope,
pub arity: usize,
pub alloc: bool,
pub preamble: Vec<String>,
pub body: Vec<String>,
pub outro: Vec<String>,
@@ -62,9 +69,10 @@ impl CompiledFn {
/// - Increases the size of the stack frame by that much
/// - Records the offset into the local stack frame where that variable is stored
pub(crate) fn add_local(&mut self, name: &str) -> Result<(), CompileError> {
let frame = self.frame_size();
if let Vacant(e) = self.local_scope.entry(name.into()) {
e.insert(Variable::Local(self.frame_size));
self.frame_size += 3;
e.insert(Variable::Local(frame));
self.max_frame_size = max(frame, self.max_frame_size);
Ok(())
} else {
Err(CompileError(0, 0, format!("Duplicate name {}", name)))
@@ -104,8 +112,8 @@ impl CompiledFn {
self.preamble.push(format!("{} {}", opcode, arg))
}
/// The size of the local scope in bytes. This increases as variables are declared
/// todo: this needs to change for arrays; it won't like having vars that aren't 3 bytes long
/// The (current) size of the local scope in bytes. This increases as variables are declared,
/// decreases as they leave scope
pub(crate) fn frame_size(&self) -> usize {
self.local_scope.len() * 3
}
@@ -127,14 +135,16 @@ impl CompiledFn {
/// depends on knowledge of the body of the fn, but what this produces will be emitted to
/// the final listing before the fn body
pub(crate) fn generate_preamble_outro(&mut self, args: &Vec<String>) -> Result<(), CompileError> {
// Does our fn make any allocations? If so we need to save the old alloc pool pointer:
if self.alloc {
todo!("alloc pool not actually implemented");
//self.preamble_emit("loadw pool");
//self.preamble_emit("pushr");
// 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");
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);
}
self.preamble_emit("pushr");
// Top argument is the frame ptr; copy it to the rstack:
// The top argument we were sent is the frame ptr, store that also:
self.preamble_emit("pushr");
// Add each argument as a local
@@ -163,14 +173,12 @@ impl CompiledFn {
// Now, the outro label:
self.outro_emit(format!("{}:", self.end_label).as_str());
// The top of the rstack is, of course, the frame ptr. So we need to get rid of that:
// 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");
// If we alloced anything, we need to drain that pool:
if self.alloc {
todo!("alloc pool not implemented yet");
}
// 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:
@@ -183,11 +191,10 @@ impl CompiledFn {
/// beyond that frame length: used when compiling blocks to de-scope names that should only
/// be visible in the block
pub(crate) fn reduce_frame_size_to(&mut self, new_size: usize) {
self.frame_size = new_size;
let mut to_remove: Vec<String> = Vec::new();
for (name, var) in self.local_scope.iter() {
if let Variable::Local(offset) = var {
if *offset >= self.frame_size {
if *offset >= new_size {
to_remove.push(name.clone());
}
}
@@ -202,6 +209,6 @@ impl CompiledFn {
/// Used for things like repeat loops where there's a name declared outside a block but which
/// should only be visible in the block anyway.
pub(crate) fn forget_last_local(&mut self) {
self.reduce_frame_size_to(self.frame_size - 3);
self.reduce_frame_size_to(self.frame_size() - 3);
}
}
+15 -3
View File
@@ -76,6 +76,8 @@ mod test {
"call _forge_gensym_1",
"hlt",
"_forge_gensym_1:",
"dup",
"pushr",
"pushr",
"push 5",
"jmpr @_forge_gensym_2",
@@ -83,6 +85,8 @@ mod test {
"_forge_gensym_2:",
"popr",
"pop",
"popr",
"pop",
"ret",
"stack: .db 0",
].join("\n"));
@@ -96,6 +100,8 @@ mod test {
"hlt",
"_forge_gensym_1: .db \"blah\\0\"",
"_forge_gensym_2:",
"dup",
"pushr",
"pushr",
"push _forge_gensym_1", // Push the str
"jmpr @_forge_gensym_3", // Return
@@ -103,6 +109,8 @@ mod test {
"_forge_gensym_3:", // outro
"popr",
"pop",
"popr",
"pop",
"ret",
"stack: .db 0",
].join("\n"))
@@ -122,13 +130,15 @@ mod test {
"call _forge_gensym_3",
"hlt",
"_forge_gensym_1:", // fn foo()
"dup", "pushr", // capture pool ptr
"pushr", // capture frame ptr
"push 0", // implicit return
"_forge_gensym_2:",
"popr",
"pop",
"popr", "pop",
"popr", "pop",
"ret",
"_forge_gensym_3:", // fn main()
"dup", "pushr", // capture pool ptr
"pushr", // capture frame ptr
"peekr", // prep frame ptr to send to foo
"push _forge_gensym_1", // load foo
@@ -136,7 +146,9 @@ mod test {
"pop", // Throw away its return value
"push 0", // Implicit return value
"_forge_gensym_4:", // Outro start
"popr", // Restore frame ptr
"popr", // Drop frame ptr
"pop",
"popr", // Drop pool ptr
"pop",
"ret",
"stack: .db 0",
+4
View File
@@ -18,3 +18,7 @@ pub(crate) fn test_body(state: State) -> String {
pub(crate) fn test_preamble(state: State) -> String {
state.functions.get("test").unwrap().preamble.join("\n")
}
pub(crate) fn test_outro(state: State) -> String {
state.functions.get("test").unwrap().outro.join("\n")
}
+3 -3
View File
@@ -108,9 +108,9 @@ fn cursed_call_test() {
"call _forge_gensym_3",
"hlt",
"_forge_gensym_1:", // foo()
"pushr","push 2","jmpr @_forge_gensym_2","push 0","_forge_gensym_2:","popr","pop","ret",
"dup", "pushr", "pushr","push 2","jmpr @_forge_gensym_2","push 0","_forge_gensym_2:","popr","pop", "popr","pop","ret",
"_forge_gensym_3:", // main()
"pushr", // preamble (no args)
"dup", "add 6", "pushr", "pushr", // preamble (no args, but 3 locals)
"push 0", "peekr", "storew", // repeat counter var (n)
"push 2", // repeat limit
"#while",
@@ -136,7 +136,7 @@ fn cursed_call_test() {
"pop", // Drop the loop limit off
"push 0", // Implicit return value
"_forge_gensym_4:",
"popr","pop","ret", // Standard outro
"popr","pop","popr","pop","ret", // Standard outro
"stack: .db 0", // The stack
].join("\n"))
}