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> {
|
fn process(self, state: &mut State, sig: Option<&mut CompiledFn>, _loc: Location) -> Result<(), CompileError> {
|
||||||
let sig = sig.expect("Block outside function");
|
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:
|
// Compile each statement:
|
||||||
for stmt in self.0 {
|
for stmt in self.0 {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ impl Compilable for Function {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::compiler::test_utils::{state_for, test_body, test_preamble};
|
use crate::compiler::test_utils::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic_fns() {
|
fn test_basic_fns() {
|
||||||
@@ -70,6 +70,9 @@ mod test {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
test_preamble(state_for("fn test(a, b) { b = 17 + a; }")),
|
test_preamble(state_for("fn test(a, b) { b = 17 + a; }")),
|
||||||
vec![
|
vec![
|
||||||
|
"dup", // Create pool ptr
|
||||||
|
"add 6",
|
||||||
|
"pushr", // Store pool ptr
|
||||||
"pushr", // Store frame ptr
|
"pushr", // Store frame ptr
|
||||||
"peekr", // Capture var b
|
"peekr", // Capture var b
|
||||||
"add 3",
|
"add 3",
|
||||||
@@ -80,4 +83,19 @@ mod test {
|
|||||||
.join("\n")
|
.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::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;
|
||||||
@@ -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
|
/// 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 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
|
/// 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
|
/// 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
|
/// 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 struct CompiledFn {
|
||||||
pub label: Label,
|
pub label: Label,
|
||||||
pub end_label: Label,
|
pub end_label: Label,
|
||||||
pub frame_size: usize,
|
pub max_frame_size: usize,
|
||||||
pub local_scope: Scope,
|
pub local_scope: Scope,
|
||||||
pub arity: usize,
|
pub arity: usize,
|
||||||
pub alloc: bool,
|
|
||||||
pub preamble: Vec<String>,
|
pub preamble: Vec<String>,
|
||||||
pub body: Vec<String>,
|
pub body: Vec<String>,
|
||||||
pub outro: Vec<String>,
|
pub outro: Vec<String>,
|
||||||
@@ -62,9 +69,10 @@ impl CompiledFn {
|
|||||||
/// - Increases the size of the stack frame by that much
|
/// - Increases the size of the stack frame by that much
|
||||||
/// - Records the offset into the local stack frame where that variable is stored
|
/// - Records the offset into the local stack frame where that variable is stored
|
||||||
pub(crate) fn add_local(&mut self, name: &str) -> Result<(), CompileError> {
|
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()) {
|
if let Vacant(e) = self.local_scope.entry(name.into()) {
|
||||||
e.insert(Variable::Local(self.frame_size));
|
e.insert(Variable::Local(frame));
|
||||||
self.frame_size += 3;
|
self.max_frame_size = max(frame, self.max_frame_size);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(CompileError(0, 0, format!("Duplicate name {}", name)))
|
Err(CompileError(0, 0, format!("Duplicate name {}", name)))
|
||||||
@@ -104,8 +112,8 @@ impl CompiledFn {
|
|||||||
self.preamble.push(format!("{} {}", opcode, arg))
|
self.preamble.push(format!("{} {}", opcode, arg))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The 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,
|
||||||
/// todo: this needs to change for arrays; it won't like having vars that aren't 3 bytes long
|
/// decreases as they leave scope
|
||||||
pub(crate) fn frame_size(&self) -> usize {
|
pub(crate) fn frame_size(&self) -> usize {
|
||||||
self.local_scope.len() * 3
|
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
|
/// depends on knowledge of the body of the fn, but what this produces will be emitted to
|
||||||
/// the final listing before the fn body
|
/// the final listing before the fn body
|
||||||
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> {
|
||||||
// Does our fn make any allocations? If so we need to save the old alloc pool pointer:
|
// First, we need a pool pointer on the rstack. We know this because it's the current top
|
||||||
if self.alloc {
|
// (the frame ptr) plus a (known) max scope size:
|
||||||
todo!("alloc pool not actually implemented");
|
self.preamble_emit("dup");
|
||||||
//self.preamble_emit("loadw pool");
|
if self.max_frame_size > 0 {
|
||||||
//self.preamble_emit("pushr");
|
// 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");
|
self.preamble_emit("pushr");
|
||||||
|
|
||||||
// Add each argument as a local
|
// Add each argument as a local
|
||||||
@@ -163,14 +173,12 @@ impl CompiledFn {
|
|||||||
// 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. 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("popr");
|
||||||
self.outro_emit("pop");
|
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
|
// 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:
|
||||||
@@ -183,11 +191,10 @@ impl CompiledFn {
|
|||||||
/// beyond that frame length: used when compiling blocks to de-scope names that should only
|
/// beyond that frame length: used when compiling blocks to de-scope names that should only
|
||||||
/// be visible in the block
|
/// be visible in the block
|
||||||
pub(crate) fn reduce_frame_size_to(&mut self, new_size: usize) {
|
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();
|
let mut to_remove: Vec<String> = Vec::new();
|
||||||
for (name, var) in self.local_scope.iter() {
|
for (name, var) in self.local_scope.iter() {
|
||||||
if let Variable::Local(offset) = var {
|
if let Variable::Local(offset) = var {
|
||||||
if *offset >= self.frame_size {
|
if *offset >= new_size {
|
||||||
to_remove.push(name.clone());
|
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
|
/// 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.
|
/// should only be visible in the block anyway.
|
||||||
pub(crate) fn forget_last_local(&mut self) {
|
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",
|
"call _forge_gensym_1",
|
||||||
"hlt",
|
"hlt",
|
||||||
"_forge_gensym_1:",
|
"_forge_gensym_1:",
|
||||||
|
"dup",
|
||||||
|
"pushr",
|
||||||
"pushr",
|
"pushr",
|
||||||
"push 5",
|
"push 5",
|
||||||
"jmpr @_forge_gensym_2",
|
"jmpr @_forge_gensym_2",
|
||||||
@@ -83,6 +85,8 @@ mod test {
|
|||||||
"_forge_gensym_2:",
|
"_forge_gensym_2:",
|
||||||
"popr",
|
"popr",
|
||||||
"pop",
|
"pop",
|
||||||
|
"popr",
|
||||||
|
"pop",
|
||||||
"ret",
|
"ret",
|
||||||
"stack: .db 0",
|
"stack: .db 0",
|
||||||
].join("\n"));
|
].join("\n"));
|
||||||
@@ -96,6 +100,8 @@ mod test {
|
|||||||
"hlt",
|
"hlt",
|
||||||
"_forge_gensym_1: .db \"blah\\0\"",
|
"_forge_gensym_1: .db \"blah\\0\"",
|
||||||
"_forge_gensym_2:",
|
"_forge_gensym_2:",
|
||||||
|
"dup",
|
||||||
|
"pushr",
|
||||||
"pushr",
|
"pushr",
|
||||||
"push _forge_gensym_1", // Push the str
|
"push _forge_gensym_1", // Push the str
|
||||||
"jmpr @_forge_gensym_3", // Return
|
"jmpr @_forge_gensym_3", // Return
|
||||||
@@ -103,6 +109,8 @@ mod test {
|
|||||||
"_forge_gensym_3:", // outro
|
"_forge_gensym_3:", // outro
|
||||||
"popr",
|
"popr",
|
||||||
"pop",
|
"pop",
|
||||||
|
"popr",
|
||||||
|
"pop",
|
||||||
"ret",
|
"ret",
|
||||||
"stack: .db 0",
|
"stack: .db 0",
|
||||||
].join("\n"))
|
].join("\n"))
|
||||||
@@ -122,13 +130,15 @@ mod test {
|
|||||||
"call _forge_gensym_3",
|
"call _forge_gensym_3",
|
||||||
"hlt",
|
"hlt",
|
||||||
"_forge_gensym_1:", // fn foo()
|
"_forge_gensym_1:", // fn foo()
|
||||||
|
"dup", "pushr", // capture pool ptr
|
||||||
"pushr", // capture frame ptr
|
"pushr", // capture frame ptr
|
||||||
"push 0", // implicit return
|
"push 0", // implicit return
|
||||||
"_forge_gensym_2:",
|
"_forge_gensym_2:",
|
||||||
"popr",
|
"popr", "pop",
|
||||||
"pop",
|
"popr", "pop",
|
||||||
"ret",
|
"ret",
|
||||||
"_forge_gensym_3:", // fn main()
|
"_forge_gensym_3:", // fn main()
|
||||||
|
"dup", "pushr", // capture pool ptr
|
||||||
"pushr", // capture frame ptr
|
"pushr", // capture frame ptr
|
||||||
"peekr", // prep frame ptr to send to foo
|
"peekr", // prep frame ptr to send to foo
|
||||||
"push _forge_gensym_1", // load foo
|
"push _forge_gensym_1", // load foo
|
||||||
@@ -136,7 +146,9 @@ mod test {
|
|||||||
"pop", // Throw away its return value
|
"pop", // Throw away its return value
|
||||||
"push 0", // Implicit return value
|
"push 0", // Implicit return value
|
||||||
"_forge_gensym_4:", // Outro start
|
"_forge_gensym_4:", // Outro start
|
||||||
"popr", // Restore frame ptr
|
"popr", // Drop frame ptr
|
||||||
|
"pop",
|
||||||
|
"popr", // Drop pool ptr
|
||||||
"pop",
|
"pop",
|
||||||
"ret",
|
"ret",
|
||||||
"stack: .db 0",
|
"stack: .db 0",
|
||||||
|
|||||||
@@ -18,3 +18,7 @@ pub(crate) fn test_body(state: State) -> String {
|
|||||||
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.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",
|
"call _forge_gensym_3",
|
||||||
"hlt",
|
"hlt",
|
||||||
"_forge_gensym_1:", // foo()
|
"_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()
|
"_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 0", "peekr", "storew", // repeat counter var (n)
|
||||||
"push 2", // repeat limit
|
"push 2", // repeat limit
|
||||||
"#while",
|
"#while",
|
||||||
@@ -136,7 +136,7 @@ fn cursed_call_test() {
|
|||||||
"pop", // Drop the loop limit off
|
"pop", // Drop the loop limit off
|
||||||
"push 0", // Implicit return value
|
"push 0", // Implicit return value
|
||||||
"_forge_gensym_4:",
|
"_forge_gensym_4:",
|
||||||
"popr","pop","ret", // Standard outro
|
"popr","pop","popr","pop","ret", // Standard outro
|
||||||
"stack: .db 0", // The stack
|
"stack: .db 0", // The stack
|
||||||
].join("\n"))
|
].join("\n"))
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user