A bunch of prerequisites before we can do arrays, part 2

This commit is contained in:
2023-11-12 23:50:25 -06:00
parent 21af891574
commit 0e766aebcf
12 changed files with 75 additions and 77 deletions
+1 -5
View File
@@ -68,7 +68,6 @@ mod test {
"peekr", // Push the addr of x
"swap 12", // The asm body, which swaps 12 behind it and stores it there
"storew",
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
)
@@ -105,10 +104,7 @@ mod test {
"storew", // taking the same (now freed) frame slot that c took, because c is
"peekr", // now out of scope
"loadw",
"popr", // Blow away old frame ptr
"pop",
"ret",
"popr", "pop", "ret 0" // Implicit void return
"jmpr @_forge_gensym_2",
]
.join("\n")
);
@@ -56,7 +56,6 @@ mod test {
"push _forge_gensym_1", // evaluating target (this fn)
"call", // Actually make the call
"pop", // expr-as-statement drops the evaluated value
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
);
@@ -43,7 +43,6 @@ mod test {
"peekr",
"storew", // The branch, x = 1
"#end",
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
);
@@ -70,7 +69,6 @@ mod test {
"peekr",
"storew", // x = 7
"#end",
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
)
+6 -10
View File
@@ -86,7 +86,7 @@ impl Compilable for Expr {
Ok(())
}
Expr::Call(call) => call.process(state, Some(sig), loc),
Expr::New(size) => todo!("new not yet supported"),
Expr::New(_size) => todo!("new not yet supported"),
Expr::Subscript(_, _) => todo!("Structs and arrays are not yet supported"),
Expr::Infix(lhs, op, rhs) => {
// Recurse on expressions, handling operators
@@ -152,21 +152,20 @@ mod test {
state.strings,
vec![
("_forge_gensym_1".into(), "foo".into()),
("_forge_gensym_3".into(), "bar".into()), // gensym 2 is the entrypoint of blah()
("_forge_gensym_4".into(), "norp".into()),
("_forge_gensym_4".into(), "bar".into()), // gensym 2 and 3 are the entry and outro of blah()
("_forge_gensym_5".into(), "norp".into()),
]
);
assert_eq!(
test_body(state),
vec![
"push _forge_gensym_3",
"push _forge_gensym_4",
"peekr",
"storew", // the assignment for x
"push _forge_gensym_4",
"push _forge_gensym_5",
"peekr",
"add 3", // the address of y (frame + 3) and put gensym_4 in it
"storew",
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
)
@@ -184,7 +183,6 @@ mod test {
"peekr",
"add 3", // the address of y (frame + 3) and put the addr of x (frame) in it
"storew",
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
)
@@ -203,7 +201,6 @@ mod test {
"loadw", // Then load the value at 3
"push 1000", // Push the addr 1000, for the lvalue
"storew", // Store whatever's at 3 to 1000
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
)
@@ -214,12 +211,11 @@ mod test {
assert_eq!(
test_body(state_for("const foo = \"foo\"; fn test() { var x = \"bar\" + 3; }")),
vec![
"push _forge_gensym_3", // 1 is the label in the string table for "foo", 2 for "blah,"
"push _forge_gensym_4", // 1 is the label in the string table for "foo", 2 for "blah," 3 for blah's outro
"push 3", // so 3 is the string "bar"
"add", // Add 3 to that address
"peekr", // Store it in the first var
"storew",
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
)
@@ -1,4 +1,4 @@
use crate::ast::{Function, Location, Return};
use crate::ast::{Function, Location};
use crate::compiler::compilable::Compilable;
use crate::compiler::compiled_fn::CompiledFn;
use crate::compiler::CompileError;
@@ -7,10 +7,12 @@ use crate::compiler::state::State;
impl Compilable for Function {
fn process(self, state: &mut State, _: Option<&mut CompiledFn>, loc: Location) -> Result<(), CompileError> {
let label = state.find_or_declare_function(self.name.as_str(), loc)?;
let end_label = state.gensym(); // The label of the outro of the fn
// The CompiledFn for this function, which will eventually get stuff populated into it:
let mut sig = CompiledFn {
label,
end_label,
..Default::default()
};
@@ -32,11 +34,6 @@ impl Compilable for Function {
// created here will be emitted before (preamble) and after (outro) the body
sig.generate_preamble_outro(&self.args)?;
// This fn probably has a return statement... but it's not required. As a final catch just
// in case we fall through to this point, we'll generate a void return and compile it:
// TODO this can die soon because the outro is implicitly a return so we'd just fall into that
Return(None).process(state, Some(&mut sig), loc)?;
// This can't fail because if it were a dupe name, adding the global would have failed
state.functions.insert(self.name.clone(), sig);
@@ -63,7 +60,6 @@ mod test {
"peekr", // Calculate the lvalue
"add 3", // "b" arg is frame + 3
"storew", // Finally store
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
)
@@ -126,10 +126,7 @@ mod test {
"peekr", // Load x so we can return it
"add 3",
"loadw",
"popr", // toss old frame ptr
"pop",
"ret",
"popr", "pop", "ret 0" // Implicit void return
"jmpr @_forge_gensym_2",
]
.join("\n")
);
@@ -165,10 +162,7 @@ mod test {
"peekr", // Load x so we can return it
"add 3",
"loadw",
"popr", // Toss old frame ptr
"pop",
"ret",
"popr", "pop", "ret 0" // Implicit void return
"jmpr @_forge_gensym_2",
].join("\n")
);
}
+7 -19
View File
@@ -11,24 +11,17 @@ 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)
sig.emit_arg("push", 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")
}
}
// The return value is on the stack so jmpr to the outro to do the actual return
sig.emit_arg("jmpr", format!("@{}",sig.end_label));
Ok(())
}
}
@@ -46,10 +39,7 @@ mod test {
"loadw",
"push 3", // Add 3
"add",
"popr", // Toss frame ptr
"pop",
"ret", // Return that
"popr", "pop", "ret 0" // Implicit void return
"jmpr @_forge_gensym_2", // Return that
]
.join("\n")
);
@@ -65,11 +55,9 @@ mod test {
"push 0", // Compare to 0
"agt",
"#if", // If statement
"popr", // Toss frame ptr
"pop",
"ret 0", // Default return value, for an expr-less return
"push 0", // Default return value, for an expr-less return
"jmpr @_forge_gensym_2",
"#end",
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
);
@@ -47,7 +47,6 @@ mod test {
"mul", // b * 2 evaluated
"peekr", // Loading "a" as an lvalue
"storew", // doing the assignment
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
)
@@ -58,7 +58,6 @@ mod test {
"add 3",
"storew", // c = c + 1
"#end", // End the loop body
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
);
+33 -10
View File
@@ -1,6 +1,5 @@
use std::collections::btree_map::Entry::Vacant;
use std::fmt::Display;
use crate::ast::Function;
use crate::compiler::compile_error::CompileError;
use crate::compiler::utils::{Label, Scope, Variable};
@@ -12,14 +11,17 @@ use crate::compiler::utils::{Label, Scope, Variable};
///
/// A complete function implementation consists of:
/// - A label for the entrypoint
/// - The function body (including preamble code to set up the stack frame)
/// - A label for the outro (returns jmpr here)
/// - The function body
/// - The function preamble (code to set up the stack frame, capture args)
/// - The function outro (tear down the stack frame)
///
/// The stack frame is managed by the function through a global pointer called "frame". When
/// the function is called, it can assume that all memory after "frame" is free for use (this
/// The stack frame is managed by the function through a pointer passed to it. When
/// the function is called, it can assume that all memory after that pointer is free for use (this
/// isn't actually true because you can blow out the stack, but within reason it is). So when
/// you make a call to another function, you need to increment frame by the current frame size,
/// and then after the other function has returned, decrement it back so that frame again points
/// at your stack frame. Locals can be found by adding some offset from the frame pointer.
/// 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.
///
/// 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
@@ -45,6 +47,7 @@ use crate::compiler::utils::{Label, Scope, Variable};
#[derive(Clone, PartialEq, Debug, Default)]
pub struct CompiledFn {
pub label: Label,
pub end_label: Label,
pub frame_size: usize,
pub local_scope: Scope,
pub arity: usize,
@@ -124,13 +127,11 @@ 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> {
let mut arg_names: Vec<&str> = Vec::new();
// 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");
//self.preamble_emit("loadw pool");
//self.preamble_emit("pushr");
}
// Top argument is the frame ptr; copy it to the rstack:
@@ -153,6 +154,28 @@ impl CompiledFn {
}
}
// Create the outro:
// First, we might fall through to here, so, leave a push 0 on the stack. Normally we roll
// with an empty stack, or have some data and jmpr here, but if we fall through this will
// ensure that the following return returns something:
self.outro_emit("push 0");
// 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:
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:
self.outro_emit("ret");
Ok(())
}
+17 -9
View File
@@ -78,10 +78,12 @@ mod test {
"_forge_gensym_1:",
"pushr",
"push 5",
"jmpr @_forge_gensym_2",
"push 0",
"_forge_gensym_2:",
"popr",
"pop",
"ret",
"popr", "pop", "ret 0", // Implicit void return
"stack: .db 0",
].join("\n"));
@@ -95,11 +97,13 @@ mod test {
"_forge_gensym_1: .db \"blah\\0\"",
"_forge_gensym_2:",
"pushr",
"push _forge_gensym_1",
"push _forge_gensym_1", // Push the str
"jmpr @_forge_gensym_3", // Return
"push 0", // Implicit return val
"_forge_gensym_3:", // outro
"popr",
"pop",
"ret",
"popr", "pop", "ret 0", // Implicit void return
"stack: .db 0",
].join("\n"))
}
@@ -115,22 +119,26 @@ mod test {
assert_eq!(asm.join("\n"), vec![
".org 0x400",
"push stack",
"call _forge_gensym_2",
"call _forge_gensym_3",
"hlt",
"_forge_gensym_1:", // fn foo()
"pushr", // capture frame ptr
"popr", // drop it and ret
"push 0", // implicit return
"_forge_gensym_2:",
"popr",
"pop",
"ret 0",
"_forge_gensym_2:", // fn main()
"ret",
"_forge_gensym_3:", // fn main()
"pushr", // capture frame ptr
"peekr", // prep frame ptr to send to foo
"push _forge_gensym_1", // load foo
"call", // call it
"pop", // Throw away its return value
"popr", // drop frame ptr and ret
"push 0", // Implicit return value
"_forge_gensym_4:", // Outro start
"popr", // Restore frame ptr
"pop",
"ret 0",
"ret",
"stack: .db 0",
].join("\n"))
}