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