From 0e766aebcf80277a4fef94d5bd67f63ccb2a3bfd Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Sun, 12 Nov 2023 23:50:25 -0600 Subject: [PATCH] A bunch of prerequisites before we can do arrays, part 2 --- forge_core/src/compiler/ast_nodes/block.rs | 6 +-- forge_core/src/compiler/ast_nodes/call.rs | 1 - .../src/compiler/ast_nodes/conditional.rs | 2 - forge_core/src/compiler/ast_nodes/expr.rs | 16 +++---- forge_core/src/compiler/ast_nodes/function.rs | 10 ++--- .../src/compiler/ast_nodes/repeat_loop.rs | 10 +---- forge_core/src/compiler/ast_nodes/return.rs | 26 +++-------- forge_core/src/compiler/ast_nodes/var_decl.rs | 1 - .../src/compiler/ast_nodes/while_loop.rs | 1 - forge_core/src/compiler/compiled_fn.rs | 43 ++++++++++++++----- forge_core/src/compiler/mod.rs | 26 +++++++---- vtest/src/forge_tests.rs | 10 +++-- 12 files changed, 75 insertions(+), 77 deletions(-) diff --git a/forge_core/src/compiler/ast_nodes/block.rs b/forge_core/src/compiler/ast_nodes/block.rs index 67f9e81..bfb7d1e 100644 --- a/forge_core/src/compiler/ast_nodes/block.rs +++ b/forge_core/src/compiler/ast_nodes/block.rs @@ -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") ); diff --git a/forge_core/src/compiler/ast_nodes/call.rs b/forge_core/src/compiler/ast_nodes/call.rs index bf3350c..18ff5ac 100644 --- a/forge_core/src/compiler/ast_nodes/call.rs +++ b/forge_core/src/compiler/ast_nodes/call.rs @@ -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") ); diff --git a/forge_core/src/compiler/ast_nodes/conditional.rs b/forge_core/src/compiler/ast_nodes/conditional.rs index d386156..c0ab873 100644 --- a/forge_core/src/compiler/ast_nodes/conditional.rs +++ b/forge_core/src/compiler/ast_nodes/conditional.rs @@ -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") ) diff --git a/forge_core/src/compiler/ast_nodes/expr.rs b/forge_core/src/compiler/ast_nodes/expr.rs index 439b858..98a36eb 100644 --- a/forge_core/src/compiler/ast_nodes/expr.rs +++ b/forge_core/src/compiler/ast_nodes/expr.rs @@ -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") ) diff --git a/forge_core/src/compiler/ast_nodes/function.rs b/forge_core/src/compiler/ast_nodes/function.rs index 68b6bae..052849d 100644 --- a/forge_core/src/compiler/ast_nodes/function.rs +++ b/forge_core/src/compiler/ast_nodes/function.rs @@ -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") ) diff --git a/forge_core/src/compiler/ast_nodes/repeat_loop.rs b/forge_core/src/compiler/ast_nodes/repeat_loop.rs index ed9944f..4bde2d2 100644 --- a/forge_core/src/compiler/ast_nodes/repeat_loop.rs +++ b/forge_core/src/compiler/ast_nodes/repeat_loop.rs @@ -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") ); } diff --git a/forge_core/src/compiler/ast_nodes/return.rs b/forge_core/src/compiler/ast_nodes/return.rs index 9c1cea4..b839987 100644 --- a/forge_core/src/compiler/ast_nodes/return.rs +++ b/forge_core/src/compiler/ast_nodes/return.rs @@ -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") ); diff --git a/forge_core/src/compiler/ast_nodes/var_decl.rs b/forge_core/src/compiler/ast_nodes/var_decl.rs index ba3a0a6..3481385 100644 --- a/forge_core/src/compiler/ast_nodes/var_decl.rs +++ b/forge_core/src/compiler/ast_nodes/var_decl.rs @@ -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") ) diff --git a/forge_core/src/compiler/ast_nodes/while_loop.rs b/forge_core/src/compiler/ast_nodes/while_loop.rs index 6346495..4e3a2fc 100644 --- a/forge_core/src/compiler/ast_nodes/while_loop.rs +++ b/forge_core/src/compiler/ast_nodes/while_loop.rs @@ -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") ); diff --git a/forge_core/src/compiler/compiled_fn.rs b/forge_core/src/compiler/compiled_fn.rs index e0309d7..2dc5510 100644 --- a/forge_core/src/compiler/compiled_fn.rs +++ b/forge_core/src/compiler/compiled_fn.rs @@ -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) -> 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(()) } diff --git a/forge_core/src/compiler/mod.rs b/forge_core/src/compiler/mod.rs index a4943cb..f0f1f20 100644 --- a/forge_core/src/compiler/mod.rs +++ b/forge_core/src/compiler/mod.rs @@ -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")) } diff --git a/vtest/src/forge_tests.rs b/vtest/src/forge_tests.rs index 4381fe7..003ba7d 100644 --- a/vtest/src/forge_tests.rs +++ b/vtest/src/forge_tests.rs @@ -105,11 +105,11 @@ fn cursed_call_test() { assert_eq!(compiler_output(src).join("\n"), vec![ ".org 0x400", "push stack", - "call _forge_gensym_2", + "call _forge_gensym_3", "hlt", "_forge_gensym_1:", // foo() - "pushr","push 2","popr","pop","ret","popr","pop","ret 0", - "_forge_gensym_2:", // main() + "pushr","push 2","jmpr @_forge_gensym_2","push 0","_forge_gensym_2:","popr","pop","ret", + "_forge_gensym_3:", // main() "pushr", // preamble (no args) "push 0", "peekr", "storew", // repeat counter var (n) "push 2", // repeat limit @@ -134,7 +134,9 @@ fn cursed_call_test() { "#end", "peekr","dup","loadw","add 1","swap","storew","#end", // inc n, end the loop "pop", // Drop the loop limit off - "popr","pop","ret 0", // Implicit return 0 to end main + "push 0", // Implicit return value + "_forge_gensym_4:", + "popr","pop","ret", // Standard outro "stack: .db 0", // The stack ].join("\n")) } \ No newline at end of file