Functions without explicit return statements

This commit is contained in:
2023-11-11 18:36:49 -06:00
parent 45fe27b823
commit db2fbd7bda
10 changed files with 64 additions and 22 deletions
@@ -69,6 +69,7 @@ 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")
)
@@ -109,6 +110,7 @@ mod test {
"popr", // Blow away old frame ptr
"pop",
"ret",
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
);
+6 -18
View File
@@ -17,6 +17,9 @@ impl Compilable for Call {
}
// Now we increment the frame ptr to right after the current frame:
// todo this messes with recursive calls: we're hard-coding the frame size _right now,_
// which may increase later with later vardecls in the fn, which could be _executed_ before
// this call is. We need to store the top of the frame at runtime somehow.
sig.emit("peekr");
let frame_size = sig.frame_size();
if frame_size > 0 {
@@ -31,29 +34,13 @@ impl Compilable for Call {
//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
// every fn will stick the frame ptr it's given in the rstack in its preamble, and that any
// vardecl will increment that ptr (that the top of the rstack is the end of the current
// frame, essentially). Returns also need to popr/pop before doing the actual ret.
// todo also for that matter, that means that the frame ptr no longer needs to be a global
// symbol: have it be stored in the rstack and passed as the first param in a call. So the
// calling convention is, push the args, push the new fn's frame ptr (current top-of-frame+1),
// call. The receiving fn puts its frame ptr into the rstack, dups it there, puts the args
// (it knows its own arity) into its frame, and then every vardecl grabs the 2nd thing from r,
// increments it by whatever, and puts it back.
// todo in fact, a larger refactor would just store the frame size as the first thing in the
// frame, to make vardecls faster
// 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 pointer:
//sig.emit("popr");
//sig.emit("storew frame");
// this expr::call will end up evaluating to). What's left on our rstack now is our frame ptr,
// which is what we want.
// And we're finished!
Ok(())
@@ -82,6 +69,7 @@ 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,9 @@ mod test {
"push 1",
"peekr",
"storew", // The branch, x = 1
"#end"]
"#end",
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
);
}
@@ -69,7 +71,9 @@ mod test {
"push 7",
"peekr",
"storew", // x = 7
"#end"]
"#end",
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
)
}
@@ -166,6 +166,7 @@ mod test {
"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,6 +185,7 @@ 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,6 +205,7 @@ 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")
)
@@ -219,6 +222,7 @@ mod test {
"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};
use crate::ast::{Function, Location, Return};
use crate::compiler::compilable::Compilable;
use crate::compiler::compiled_fn::CompiledFn;
use crate::compiler::CompileError;
@@ -26,6 +26,10 @@ impl Compilable for Function {
// Compile the body, storing all of it in the CompiledFn we just created / added
self.body.process(state, Some(&mut sig), loc)?;
// 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:
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);
@@ -58,6 +62,7 @@ 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")
)
@@ -132,6 +132,7 @@ mod test {
"popr", // toss old frame ptr
"pop",
"ret",
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
);
@@ -173,6 +174,7 @@ mod test {
"popr", // Toss old frame ptr
"pop",
"ret",
"popr", "pop", "ret 0" // Implicit void return
].join("\n")
);
}
@@ -52,6 +52,7 @@ mod test {
"popr", // Toss frame ptr
"pop",
"ret", // Return that
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
);
@@ -74,6 +75,7 @@ mod test {
"pop",
"ret 0", // Default return value, for an expr-less return
"#end",
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
);
@@ -48,6 +48,7 @@ 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,8 @@ mod test {
"peekr",
"add 3",
"storew", // c = c + 1
"#end" // End the loop body
"#end", // End the loop body
"popr", "pop", "ret 0" // Implicit void return
]
.join("\n")
);
+33
View File
@@ -79,6 +79,7 @@ mod test {
"popr",
"pop",
"ret",
"popr", "pop", "ret 0", // Implicit void return
"stack: .db 0",
].join("\n"));
@@ -96,6 +97,38 @@ mod test {
"popr",
"pop",
"ret",
"popr", "pop", "ret 0", // Implicit void return
"stack: .db 0",
].join("\n"))
}
#[test]
fn test_global_vars() {
}
#[test]
fn test_no_return() {
let asm = build_boot("fn foo() { } fn main() { foo(); }".into()).unwrap();
assert_eq!(asm.join("\n"), vec![
".org 0x400",
"push stack",
"call _forge_gensym_2",
"hlt",
"_forge_gensym_1:", // fn foo()
"pushr", // capture frame ptr
"popr", // drop it and ret
"pop",
"ret 0",
"_forge_gensym_2:", // 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
"pop",
"ret 0",
"stack: .db 0",
].join("\n"))
}