A bunch of prerequisites before we can do arrays, part 3, the pool pointer
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
Reference in New Issue
Block a user