Static allocations
This commit is contained in:
@@ -173,6 +173,7 @@ pub enum Expr {
|
||||
Address(Lvalue),
|
||||
Call(Call),
|
||||
New(BoxExpr),
|
||||
Static(BoxExpr),
|
||||
Subscript(BoxExpr, BoxExpr),
|
||||
Infix(BoxExpr, Operator, BoxExpr),
|
||||
String(String),
|
||||
|
||||
@@ -44,7 +44,7 @@ pub fn eval_const(expr: Expr, scope: &Scope) -> Result<i32, CompileError> {
|
||||
// (currently) parsed as a special case of const. The only things that will hit this are
|
||||
// const expressions that include strings, like '"foo"[2]' or something. Those can't be
|
||||
// (generally) calculated at compile time, so, they're an error.
|
||||
Expr::Address(_) | Expr::Deref(_) | Expr::String(_) => Err(CompileError(
|
||||
Expr::Address(_) | Expr::Deref(_) | Expr::String(_) | Expr::Static(_) => Err(CompileError(
|
||||
0,
|
||||
0,
|
||||
String::from("Addresses are not known at compile time"),
|
||||
|
||||
@@ -91,6 +91,9 @@ impl Compilable for Expr {
|
||||
compile_alloc(&mut sig);
|
||||
Ok(())
|
||||
},
|
||||
Expr::Static(size_expr) => {
|
||||
compile_static(size_expr, state, sig)
|
||||
}
|
||||
Expr::Subscript(array, index) => {
|
||||
array.0.process(state, Some(sig), loc)?;
|
||||
index.0.process(state, Some(sig), loc)?;
|
||||
@@ -174,6 +177,13 @@ fn compile_alloc(sig: &mut CompiledFn) {
|
||||
sig.emit("pushr");
|
||||
}
|
||||
|
||||
fn compile_static(expr: BoxExpr, state: &mut State, sig: &mut CompiledFn) -> Result<(), CompileError> {
|
||||
let size = eval_const(*expr.0, &state.global_scope)? as usize;
|
||||
let label = state.add_buffer(size * 3);
|
||||
sig.emit_arg("push", label);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::compiler::test_utils::*;
|
||||
@@ -282,4 +292,16 @@ mod test {
|
||||
].join("\n")
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static() {
|
||||
assert_eq!(
|
||||
test_body(state_for("fn test() { var x = static(1); }")),
|
||||
vec![
|
||||
"push _forge_gensym_3", // Address of a 3-byte buffer
|
||||
"peekr",
|
||||
"storew",
|
||||
].join("\n")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ impl Compilable for Lvalue {
|
||||
Expr::Address(_) |
|
||||
Expr::Call(_) |
|
||||
Expr::New(_) |
|
||||
Expr::Static(_) |
|
||||
Expr::Infix(_, _, _) |
|
||||
Expr::String(_) => {
|
||||
Err(CompileError(0, 0, String::from("Not a valid lvalue")))
|
||||
|
||||
@@ -40,22 +40,32 @@ pub fn build_boot(src: &str) -> Result<Vec<String>, CompileError> {
|
||||
// When main returns, just hlt:
|
||||
asm.push("hlt".into());
|
||||
|
||||
// Now start dumping compiled objects into there:
|
||||
// Now start dumping compiled objects into there. First functions:
|
||||
for (_, val) in state.functions.iter_mut() {
|
||||
asm.push(format!("{}:", val.label));
|
||||
asm.append(val.preamble.as_mut());
|
||||
asm.append(val.body.as_mut());
|
||||
asm.append(val.outro.as_mut());
|
||||
}
|
||||
|
||||
// Strings:
|
||||
for (label, val) in state.strings.iter() {
|
||||
asm.push(format!("{}: .db \"{}\\0\"", label, val))
|
||||
}
|
||||
for (label, val) in state.global_scope.iter() {
|
||||
|
||||
// Global vars:
|
||||
for (_, val) in state.global_scope.iter() {
|
||||
// All global variables are indirect labels; anything in scope that's not that is a const,
|
||||
// fn, or string, which we'll emit elsewhere.
|
||||
if let Variable::IndirectLabel(label) = val {
|
||||
asm.push(format!("{}: .db 0", label))
|
||||
}
|
||||
}
|
||||
for (_, val) in state.functions.iter_mut() {
|
||||
asm.push(format!("{}:", val.label));
|
||||
asm.append(val.preamble.as_mut());
|
||||
asm.append(val.body.as_mut());
|
||||
asm.append(val.outro.as_mut());
|
||||
|
||||
// Static buffers:
|
||||
for(label, size) in state.buffers {
|
||||
asm.push(format!("{}: .db 0", label));
|
||||
asm.push(format!(".org {} + {}", label, size));
|
||||
}
|
||||
|
||||
// Final thing is to place a label for the stack:
|
||||
@@ -105,7 +115,6 @@ mod test {
|
||||
"push stack",
|
||||
"call _forge_gensym_2",
|
||||
"hlt",
|
||||
"_forge_gensym_1: .db \"blah\\0\"",
|
||||
"_forge_gensym_2:",
|
||||
"dup",
|
||||
"pushr",
|
||||
@@ -119,6 +128,7 @@ mod test {
|
||||
"popr",
|
||||
"pop",
|
||||
"ret",
|
||||
"_forge_gensym_1: .db \"blah\\0\"",
|
||||
"stack: .db 0",
|
||||
].join("\n"))
|
||||
}
|
||||
@@ -132,7 +142,6 @@ mod test {
|
||||
"push stack",
|
||||
"call _forge_gensym_2",
|
||||
"hlt",
|
||||
"_forge_gensym_1: .db 0", // The global var
|
||||
"_forge_gensym_2:", // main()
|
||||
"dup", "pushr", "pushr", // The frame is zero length; that's a global we're assigning to
|
||||
"push 3", // rvalue
|
||||
@@ -141,6 +150,7 @@ mod test {
|
||||
"push 0",
|
||||
"_forge_gensym_3:",
|
||||
"popr", "pop", "popr", "pop", "ret",
|
||||
"_forge_gensym_1: .db 0", // The global var
|
||||
"stack: .db 0",
|
||||
].join("\n"))
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ pub(crate) struct State {
|
||||
pub functions: BTreeMap<String, CompiledFn>,
|
||||
/// The string table
|
||||
pub strings: Vec<(Label, String)>,
|
||||
/// The statically-allocated buffers (size in bytes)
|
||||
pub buffers: Vec<(Label, usize)>,
|
||||
/// The functions that have been prototyped but not yet defined
|
||||
pub prototypes: Scope,
|
||||
}
|
||||
@@ -52,6 +54,12 @@ impl State {
|
||||
sym
|
||||
}
|
||||
|
||||
pub(crate) fn add_buffer(&mut self, size: usize) -> Label {
|
||||
let sym = self.gensym();
|
||||
self.buffers.push((sym.clone(), size));
|
||||
sym
|
||||
}
|
||||
|
||||
pub(crate) fn declare_function(&mut self, name: &str, _args: Vec<String>) -> Result<(), CompileError> {
|
||||
// If it's not already prototyped, gensym a label and put it in the list. If it is, just
|
||||
// ignore this (we don't check arity so it's not like the arglist being different matters)
|
||||
|
||||
@@ -19,6 +19,7 @@ impl AstNode for Expr {
|
||||
.map_primary(|term| match term.as_rule() {
|
||||
PestRule::number => Expr::Number(term.into_number()),
|
||||
PestRule::alloc => Expr::New(Expr::from_pair(term.first()).into()),
|
||||
PestRule::static_alloc => Expr::Static(Expr::from_pair(term.first()).into()),
|
||||
PestRule::name => Expr::Name(String::from(term.as_str())),
|
||||
PestRule::expr => Expr::from_pair(term),
|
||||
PestRule::string => Self::String(term.into_quoted_string()),
|
||||
@@ -275,4 +276,9 @@ mod test {
|
||||
fn alloc() {
|
||||
assert_eq!(Expr::from_str("new(8)"), Ok(Expr::New(8.into())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_alloc() {
|
||||
assert_eq!(Expr::from_str("static(8)"), Ok(Expr::Static(8.into())));
|
||||
}
|
||||
}
|
||||
@@ -63,9 +63,9 @@ operator = _{
|
||||
}
|
||||
|
||||
expr = { prefix* ~ term ~ suffix* ~ (operator ~ prefix* ~ term ~ suffix*)* }
|
||||
term = _{ number | alloc | name | "(" ~ expr ~ ")" | string }
|
||||
term = _{ number | alloc | static_alloc | name | "(" ~ expr ~ ")" | string }
|
||||
alloc = { "new" ~ "(" ~ expr ~ ")" }
|
||||
|
||||
static_alloc = { "static" ~ "(" ~ expr ~ ")" }
|
||||
|
||||
suffix = _{ subscript | arglist }
|
||||
subscript = { "[" ~ expr ~ "]" }
|
||||
|
||||
@@ -15,6 +15,12 @@ fn compiler_output(src: &str) -> Vec<String> {
|
||||
forge_core::compiler::build_boot(src).unwrap()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn assembler_output(src: &str) -> Vec<u8> {
|
||||
let asm = compiler_output(src);
|
||||
assemble_snippet(asm).unwrap()
|
||||
}
|
||||
|
||||
fn main_return(src: &str) -> i32 {
|
||||
let mut cpu = run_forge(src);
|
||||
cpu.pop_data().into()
|
||||
@@ -140,6 +146,26 @@ fn global_test() {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_test() {
|
||||
// Because the static(1) returns the same address each time, a[0] is the same variable
|
||||
// no matter how many times foo is called
|
||||
let src = "
|
||||
global a;
|
||||
fn foo() {
|
||||
a = static(1);
|
||||
a[0] = a[0] + 1;
|
||||
}
|
||||
fn main() {
|
||||
foo(); foo(); foo();
|
||||
return a[0];
|
||||
}";
|
||||
assert_eq!(
|
||||
main_return(src),
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
//#[test]
|
||||
// This is no longer cursed, or a test. The revised calling convention with the pool pointer makes
|
||||
// it now perfectly sane. Left here for posterity.
|
||||
|
||||
Reference in New Issue
Block a user