Storing the frame pointer in the rstack

This commit is contained in:
2023-11-11 18:08:54 -06:00
parent 89560573ec
commit 45fe27b823
12 changed files with 133 additions and 92 deletions
+10 -6
View File
@@ -65,7 +65,8 @@ mod test {
assert_eq!(
test_body(state_for("fn test() { var x; asm(&x) { swap 12\nstorew } }")),
vec![
"loadw frame", // Push the addr of x
"pushr", // capture frame ptr
"peekr", // Push the addr of x
"swap 12", // The asm body, which swaps 12 behind it and stores it there
"storew",
]
@@ -78,20 +79,21 @@ mod test {
assert_eq!(
test_body(state_for("fn test() { repeat(5) c { 2; } var k = 5; return k; }")),
vec![
"pushr", // capture frame ptr
"push 0", // Create the 'c' var and store 0 in it
"loadw frame",
"peekr",
"storew",
"push 5",
"#while", // Starting the loop:
"dup", // Copy the limit
"loadw frame", // Load c
"peekr", // Load c
"loadw",
"sub", // Subtract c from a
"agt 0", // Are we still positive?
"#do",
"push 2", // Pointless loop body
"pop",
"loadw frame", // Load c as an lvalue
"peekr", // Load c as an lvalue
"dup", // Dup it, load it, add 1
"loadw",
"add 1",
@@ -100,10 +102,12 @@ mod test {
"#end", // End of the loop body!
"pop", // Drop the limit off the top
"push 5", // Push the rvalue we'll put in 'k'
"loadw frame", // THIS IS THE TEST: 'k' should go at frame + 0, because it's
"peekr", // THIS IS THE TEST: 'k' should go at frame + 0, because it's
"storew", // taking the same (now freed) frame slot that c took, because c is
"loadw frame", // now out of scope
"peekr", // now out of scope
"loadw",
"popr", // Blow away old frame ptr
"pop",
"ret",
]
.join("\n")
+20 -24
View File
@@ -9,19 +9,27 @@ impl Compilable for Call {
// Require a function
let sig = sig.expect("lvalue outside a function");
// We need a stack consisting of the arguments (last on top), followed by the address to call
// We need a stack consisting of the arguments (last on top), followed by the new fn's frame
// pointer, followed by the address to call.
// So, first eval the args:
for arg in self.args {
arg.process(state, Some(sig), loc)?;
}
// Now we increment the frame ptr to right after the current frame:
sig.emit("peekr");
let frame_size = sig.frame_size();
if frame_size > 0 {
sig.emit_arg("add", frame_size);
}
// Eval the target
self.target.0.process(state, Some(sig), loc)?;
// Before we actually do the call though, we need to deal with some paperwork around the
// frame pointer. We will store the current frame pointer in the rstack:
sig.emit("loadw frame");
sig.emit("pushr");
//sig.emit("loadw frame");
//sig.emit("pushr");
// todo this makes arrays not work. we can no longer statically compute the frame size, we
// need to have the frame size stored at runtime somehow, probably rstack. This will mean
@@ -37,23 +45,15 @@ impl Compilable for Call {
// todo in fact, a larger refactor would just store the frame size as the first thing in the
// frame, to make vardecls faster
// Now we increment the frame ptr to right after the current frame:
let frame_size = sig.frame_size();
if frame_size > 0 {
sig.emit("loadw frame");
sig.emit_arg("add", frame_size);
sig.emit("storew frame");
}
// Frame is now pointing at a safe place, the top of stack is the target, do the call:
// Do the call:
sig.emit("call");
// And this is where we'll return to. The function has popped its args and left a word on
// the stack as a return value, which someone else will deal with (this is the word that
// this expr::call will end up evaluating to). But before we're done, we need to restore
// our frame pointerS
sig.emit("popr");
sig.emit("storew frame");
// our frame pointer:
//sig.emit("popr");
//sig.emit("storew frame");
// And we're finished!
Ok(())
@@ -69,22 +69,18 @@ mod test {
assert_eq!(
test_body(state_for("fn test(a, b) { test(2, 3); }")),
vec![
"loadw frame", // capture arg b
"pushr",
"peekr", // capture arg b
"add 3",
"storew",
"loadw frame", // capture arg a
"peekr", // capture arg a
"storew",
"push 2", // evaluating args, in order
"push 3",
"push _forge_gensym_1", // evaluating target (this fn)
"loadw frame", // Store the frame ptr
"pushr",
"loadw frame", // Increment the frame ptr
"peekr", // The new fn's frame ptr:
"add 6",
"storew frame",
"push _forge_gensym_1", // evaluating target (this fn)
"call", // Actually make the call
"popr", // Restore the frame ptr
"storew frame",
"pop", // expr-as-statement drops the evaluated value
]
.join("\n")
@@ -31,16 +31,17 @@ mod test {
assert_eq!(
test_body(state_for("fn test() { var x = 3; if (x > 2) { x = 1; } }")),
vec![
"pushr",
"push 3",
"loadw frame",
"peekr",
"storew", // x = 3
"loadw frame",
"peekr",
"loadw",
"push 2",
"agt", // The condition, x > 2
"#if", // The if itself
"push 1",
"loadw frame",
"peekr",
"storew", // The branch, x = 1
"#end"]
.join("\n")
@@ -52,20 +53,21 @@ mod test {
assert_eq!(
test_body(state_for("fn test() { var x = 3; if (x > 2) { x = 1; } else { x = 7; } }")),
vec![
"pushr",
"push 3",
"loadw frame",
"peekr",
"storew", // x = 3
"loadw frame",
"peekr",
"loadw",
"push 2",
"agt", // The condition, x > 2
"#if", // The if itself
"push 1",
"loadw frame",
"peekr",
"storew", // The affirmative branch, x = 1
"#else", // The alternative
"push 7",
"loadw frame",
"peekr",
"storew", // x = 7
"#end"]
.join("\n")
+14 -10
View File
@@ -28,7 +28,6 @@ impl Compilable for Expr {
Ok(())
}
Expr::Name(name) => {
// todo split into new file
match lookup(&name, global_scope, &sig.local_scope) {
// Names are treated differently depending on what they are
Some(Variable::Literal(val)) => {
@@ -51,7 +50,8 @@ impl Compilable for Expr {
Some(Variable::Local(offset)) => {
// Names of locals are added from the frame pointer
let offset = *offset;
sig.emit("loadw frame");
sig.emit("peekr"); // Frame ptr is top of rstack
if offset > 0 {
sig.emit_arg("add", offset);
}
@@ -158,11 +158,12 @@ mod test {
assert_eq!(
test_body(state),
vec![
"pushr",
"push _forge_gensym_3",
"loadw frame",
"peekr",
"storew", // the assignment for x
"push _forge_gensym_4",
"loadw frame",
"peekr",
"add 3", // the address of y (frame + 3) and put gensym_4 in it
"storew",
]
@@ -175,11 +176,12 @@ mod test {
assert_eq!(
test_body(state_for("fn test() { var x = 3; var y = &x; }")),
vec![
"pushr",
"push 3",
"loadw frame",
"peekr",
"storew", // the assignment for x
"loadw frame", // The addr of x
"loadw frame",
"peekr", // The addr of x
"peekr",
"add 3", // the address of y (frame + 3) and put the addr of x (frame) in it
"storew",
]
@@ -192,10 +194,11 @@ mod test {
assert_eq!(
test_body(state_for("fn test() { var x = 3; *1000 = *x; }")),
vec![
"pushr",
"push 3",
"loadw frame",
"peekr",
"storew", // the assignment for x
"loadw frame", // Now we're compiling *x, so load x's value, which is 3
"peekr", // Now we're compiling *x, so load x's value, which is 3
"loadw",
"loadw", // Then load the value at 3
"push 1000", // Push the addr 1000, for the lvalue
@@ -210,10 +213,11 @@ mod test {
assert_eq!(
test_body(state_for("const foo = \"foo\"; fn test() { var x = \"bar\" + 3; }")),
vec![
"pushr",
"push _forge_gensym_3", // 1 is the label in the string table for "foo", 2 for "blah,"
"push 3", // so 3 is the string "bar"
"add", // Add 3 to that address
"loadw frame", // Store it in the first var
"peekr", // Store it in the first var
"storew",
]
.join("\n")
@@ -45,16 +45,17 @@ mod test {
assert_eq!(
test_body(state_for("fn test(a, b) { b = 17 + a; }")),
vec![
"loadw frame", // Capture var b
"pushr", // Store frame ptr
"peekr", // Capture var b
"add 3",
"storew",
"loadw frame", // Capture var a
"peekr", // Capture var a
"storew",
"push 17", // Start calculating the rvalue, push the literal
"loadw frame", // This is looking up the "a" arg, at frame + 0
"peekr", // This is looking up the "a" arg, at frame + 0
"loadw",
"add", // 17 + a
"loadw frame", // Calculate the lvalue
"peekr", // Calculate the lvalue
"add 3", // "b" arg is frame + 3
"storew", // Finally store
]
+1 -1
View File
@@ -41,7 +41,7 @@ impl Compilable for Lvalue {
}
Variable::Local(offset) => {
let offset = *offset;
sig.emit_arg("loadw", "frame");
sig.emit("peekr");
if offset > 0 {
sig.emit_arg("add", offset);
}
@@ -86,37 +86,38 @@ mod test {
assert_eq!(
test_body(state_for("fn test(a) { var x = 0; repeat(a) c { x = x + c; } return x; }")),
vec![
"loadw frame", // capture arg a
"pushr",
"peekr", // capture arg a
"storew",
"push 0", // Create the 'x' var and store 0 in it
"loadw frame",
"peekr",
"add 3",
"storew",
"push 0", // Create the 'c' var and store 0 in it
"loadw frame",
"peekr",
"add 6",
"storew",
"loadw frame", // Load 'a' from args
"peekr", // Load 'a' from args
"loadw",
"#while", // Starting the loop:
"dup", // Copy the limit
"loadw frame", // Load c
"peekr", // Load c
"add 6",
"loadw",
"sub", // Subtract c from a
"agt 0", // Are we still positive?
"#do",
"loadw frame", // Load x
"peekr", // Load x
"add 3",
"loadw",
"loadw frame", // Load c
"peekr", // Load c
"add 6",
"loadw",
"add", // Add c to x
"loadw frame", // Load x as an lvalue
"peekr", // Load x as an lvalue
"add 3",
"storew", // Store c + x into it
"loadw frame", // Load c as an lvalue
"peekr", // Load c as an lvalue
"add 6",
"dup", // Dup it, load it, add 1
"loadw",
@@ -125,9 +126,11 @@ mod test {
"storew",
"#end", // End of the loop body!
"pop", // Drop the limit off the top
"loadw frame", // Load x so we can return it
"peekr", // Load x so we can return it
"add 3",
"loadw",
"popr", // toss old frame ptr
"pop",
"ret",
]
.join("\n")
@@ -140,32 +143,35 @@ mod test {
assert_eq!(
test_body(state_for("fn test(a) { var x = 1; repeat(a) { x = x * 2; } return x; }")),
vec![
"loadw frame", // capture arg a
"pushr",
"peekr", // capture arg a
"storew",
"push 1", // Create the 'x' var and store 1 in it
"loadw frame",
"peekr",
"add 3",
"storew",
"loadw frame", // Load 'a' from args
"peekr", // Load 'a' from args
"loadw",
"#while", // Starting the loop:
"dup", // Copy the limit
"agt 0", // Are we still positive?
"#do",
"loadw frame", // Load x
"peekr", // Load x
"add 3",
"loadw",
"push 2", // double it
"mul",
"loadw frame", // Load x as an lvalue
"peekr", // Load x as an lvalue
"add 3",
"storew", // Store 2x into it
"sub 1", // decrement the counter
"#end", // End of the loop body!
"pop", // Drop the limit off the top
"loadw frame", // Load x so we can return it
"peekr", // Load x so we can return it
"add 3",
"loadw",
"popr", // Toss old frame ptr
"pop",
"ret",
].join("\n")
);
+19 -4
View File
@@ -11,11 +11,20 @@ impl Compilable for Return {
match self {
Return(None) => {
// Returning nothing, so just default to returning a 0:
// (but pop our frame pointer first)
sig.emit("popr");
sig.emit("pop");
sig.emit_arg("ret", 0)
}
Return(Some(expr)) => {
// Eval the expr and emit a ret for it
expr.process(state, Some(sig), loc)?;
// Now that we're done with it, blow away the frame ptr:
sig.emit("popr");
sig.emit("pop");
// Finally ret
sig.emit("ret")
}
}
@@ -33,12 +42,15 @@ mod test {
assert_eq!(
test_body(state_for("fn test(a) { return a + 3; }")),
vec![
"loadw frame", // capture arg a
"pushr",
"peekr", // capture arg a
"storew",
"loadw frame", // Load a
"peekr", // Load a
"loadw",
"push 3", // Add 3
"add",
"popr", // Toss frame ptr
"pop",
"ret", // Return that
]
.join("\n")
@@ -50,13 +62,16 @@ mod test {
assert_eq!(
test_body(state_for("fn test(a) { if (a > 0) { return; } }")),
vec![
"loadw frame", // capture arg a
"pushr",
"peekr", // capture arg a
"storew",
"loadw frame", // Load a
"peekr", // Load a
"loadw",
"push 0", // Compare to 0
"agt",
"#if", // If statement
"popr", // Toss frame ptr
"pop",
"ret 0", // Default return value, for an expr-less return
"#end",
]
@@ -36,16 +36,17 @@ mod test {
assert_eq!(
test_body(state_for("fn test() { var a; var b = 7; a = b * 2; }")),
vec![
"pushr",
"push 7", // Start calculating the rvalue, push the literal
"loadw frame", // "b" is the second local var at frame + 3
"peekr", // "b" is the second local var at frame + 3
"add 3",
"storew", // Do the initialization
"loadw frame", // Now we're evaluating b * 2
"peekr", // Now we're evaluating b * 2
"add 3", // b is at frame + 3...
"loadw", // load it to the stack
"push 2",
"mul", // b * 2 evaluated
"loadw frame", // Loading "a" as an lvalue
"peekr", // Loading "a" as an lvalue
"storew", // doing the assignment
]
.join("\n")
@@ -27,37 +27,38 @@ mod test {
assert_eq!(
test_body(state_for("fn test() { var x = 0; var c = 0; while (c < 10) { x = x + c; c = c + 1; } }")),
vec![
"pushr",
"push 0",
"loadw frame",
"peekr",
"storew", // var x = 0
"push 0",
"loadw frame",
"peekr",
"add 3",
"storew", // var c = 0
"#while", // Start the loop
"loadw frame",
"peekr",
"add 3",
"loadw",
"push 10",
"alt", // c > 10
"#do", // Start loop body
"loadw frame",
"peekr",
"loadw",
"loadw frame",
"peekr",
"add 3",
"loadw",
"add",
"loadw frame",
"peekr",
"storew", // x = x + c
"loadw frame",
"peekr",
"add 3",
"loadw",
"push 1",
"add",
"loadw frame",
"peekr",
"add 3",
"storew", // c = c + 1
"#end", // End the loop body
"#end" // End the loop body
]
.join("\n")
);
+5 -2
View File
@@ -84,12 +84,15 @@ impl CompiledFn {
self.local_scope.len() * 3
}
/// Emit the code to add the arguments' names to the local scopr, and move the arguments off
/// Emit the code to add the arguments' names to the local scope, and move the arguments off
/// the stack into the frame. Should be run before the body is compiled, because this code
/// has to be the first thing in the body
pub(crate) fn handle_args(&mut self, function: &Function) -> Result<(), CompileError> {
let mut arg_names: Vec<&str> = Vec::new();
// Top argument is the frame ptr; copy it to the rstack:
self.emit("pushr");
// Add each argument as a local
for name in function.args.iter() {
self.add_local(name.as_str())?;
@@ -103,7 +106,7 @@ impl CompiledFn {
for name in arg_names {
if let Variable::Local(offset) = self.local_scope[name] {
self.emit("loadw frame");
self.emit("peekr");
if offset != 0 {
self.emit_arg("add", offset);
}
+14 -6
View File
@@ -31,6 +31,9 @@ pub fn build_boot(src: &str) -> Result<Vec<String>, CompileError> {
// Now we start piling stuff into the vec, starting with an org:
asm.push(".org 0x400".into());
// Main takes no args, but it does take a frame ptr:
asm.push("push stack".into());
// jmp into main:
asm.push(format!("call {}", label));
@@ -48,8 +51,7 @@ pub fn build_boot(src: &str) -> Result<Vec<String>, CompileError> {
// Final thing is to place a label for the stack:
// (this is just a cell with the address of the following word)
asm.push("frame: .db $+1".into());
asm.push(".db 0".into());
asm.push("stack: .db 0".into());
Ok(asm)
} else {
Err(CompileError(0, 0, "Function main not defined (or not a function)".into()))
@@ -68,27 +70,33 @@ mod test {
let asm = build_boot("fn main() { return 5; }".into()).unwrap();
assert_eq!(asm.join("\n"), vec![
".org 0x400",
"push stack",
"call _forge_gensym_1",
"hlt",
"_forge_gensym_1:",
"pushr",
"push 5",
"popr",
"pop",
"ret",
"frame: .db $+1",
".db 0",
"stack: .db 0",
].join("\n"));
// Slightly more complicated, with a global str
let asm = build_boot("const str = \"blah\"; fn main() { return str; }".into()).unwrap();
assert_eq!(asm.join("\n"), vec![
".org 0x400",
"push stack",
"call _forge_gensym_2",
"hlt",
"_forge_gensym_1: .db \"blah\\0\"",
"_forge_gensym_2:",
"pushr",
"push _forge_gensym_1",
"popr",
"pop",
"ret",
"frame: .db $+1",
".db 0",
"stack: .db 0",
].join("\n"))
}
}