From 01798236eca233bc6bf06379fe4d82e00e6262dd Mon Sep 17 00:00:00 2001 From: John Doty Date: Tue, 16 Jan 2024 09:26:05 -0800 Subject: [PATCH] [fine] Compile iteratively instead of recursively --- fine/src/compiler.rs | 74 ++++++++++++++++---------- fine/tests/expression/argument.fine | 12 ++--- fine/tests/expression/arithmetic.fine | 10 ++-- fine/tests/expression/block.fine | 4 +- fine/tests/expression/boolean.fine | 10 ++-- fine/tests/expression/conditional.fine | 10 ++-- 6 files changed, 68 insertions(+), 52 deletions(-) diff --git a/fine/src/compiler.rs b/fine/src/compiler.rs index c1c55aba..9003796b 100644 --- a/fine/src/compiler.rs +++ b/fine/src/compiler.rs @@ -120,19 +120,17 @@ impl std::fmt::Debug for Function { } } +#[derive(Eq, PartialEq, Hash, Clone)] +struct FunctionKey { + tree: TreeRef, +} + struct Compiler<'a> { semantics: &'a Semantics<'a>, syntax: &'a SyntaxTree<'a>, - // TODO: generic functions will actually be keyed by treeref and concrete - // types - function_bindings: HashMap, - - // We need to hold a space in the function array while we're compiling - // the function, but the Module functions are not Option<>. Here we just - // make a space that *is* Option<> so that we have a place to hold things - // while we compile. This will get spilled into module.functions at the - // end. + function_bindings: HashMap, + pending_functions: Vec<(FunctionKey, usize, Function)>, temp_functions: Vec>>, module: Module, @@ -223,25 +221,34 @@ pub fn compile(semantics: &Semantics) -> Rc { semantics, syntax: semantics.tree(), function_bindings: HashMap::new(), + pending_functions: Vec::new(), temp_functions: Vec::new(), + module: Module::new(), function: Function::new("<< module >>", 0), }; if let Some(t) = semantics.tree().root() { + compiler.temp_functions.push(None); file(&mut compiler, t); + compiler.temp_functions[0] = Some(Rc::new(compiler.function)); + compiler.module.init = 0; + } + + while let Some((fk, idx, func)) = compiler.pending_functions.pop() { + if idx >= compiler.temp_functions.len() { + compiler.temp_functions.resize(idx + 1, None); + } + compiler.function = func; + compile_function(&mut compiler, fk.tree); + compiler.temp_functions[idx] = Some(Rc::new(compiler.function)); } let mut module = compiler.module; - for f in compiler.temp_functions { module.functions.push(f.unwrap()); } - let index = module.functions.len(); - module.functions.push(Rc::new(compiler.function)); - module.init = index; - Rc::new(module) } @@ -465,14 +472,15 @@ fn compile_identifier_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> O } } Declaration::Function { declaration, .. } => { - let index = match c.function_bindings.get(declaration) { + let key = FunctionKey { tree: *declaration }; + let index = match c.function_bindings.get(&key) { Some(index) => *index, None => { let tree = &c.syntax[*declaration]; compiler_assert_eq!(c, t, tree.kind, TreeKind::FunctionDecl); compile_function_declaration(c, t, tree, false)?; *c.function_bindings - .get(declaration) + .get(&key) .expect("did not compile the function!") } }; @@ -608,27 +616,25 @@ fn compile_let_statement(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: b fn compile_function_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: bool) -> CR { // Only compile a given function once. - // TODO: This should actually be compiled on access! How is this going to work?? - if !c.function_bindings.contains_key(&t) { + // + // TODO: When it's time for generics, this should only actually compile + // if we have no unbound type variables. + let fk = FunctionKey { tree: t }; + if !c.function_bindings.contains_key(&fk) { let name = tree.nth_token(1)?; - let block = tree.child_of_kind(c.syntax, TreeKind::Block)?; let param_list = tree.child_tree_of_kind(c.syntax, TreeKind::ParamList)?; let param_count = param_list.children.len() - 2; let function_index = c.temp_functions.len(); c.temp_functions.push(None); - c.function_bindings.insert(t, function_index); - // Now compile the function. - let mut prev = Function::new(name.as_str(), param_count); - std::mem::swap(&mut c.function, &mut prev); - - compile_expression(c, block); - c.push(Instruction::Return); - - std::mem::swap(&mut c.function, &mut prev); - c.temp_functions[function_index] = Some(Rc::new(prev)); + c.pending_functions.push(( + fk.clone(), + function_index, + Function::new(name.as_str(), param_count), + )); + c.function_bindings.insert(fk, function_index); c.module .exports .insert(name.to_string(), Export::Function(function_index)); @@ -641,6 +647,16 @@ fn compile_function_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_v OK } +fn compile_function(c: &mut Compiler, t: TreeRef) -> CR { + let tree = &c.syntax[t]; + let block = tree.child_of_kind(c.syntax, TreeKind::Block)?; + + compile_expression(c, block); + c.push(Instruction::Return); + + OK +} + fn compile_block_statement(c: &mut Compiler, t: TreeRef, gen_value: bool) -> CR { compile_expression(c, t); if !gen_value { diff --git a/fine/tests/expression/argument.fine b/fine/tests/expression/argument.fine index 78f9d5c7..2c8164a6 100644 --- a/fine/tests/expression/argument.fine +++ b/fine/tests/expression/argument.fine @@ -61,6 +61,11 @@ fun test() -> f64 { // | RightBrace:'"}"' // | // @compiles-to: +// | function << module >> (0 args, 0 locals): +// | strings (0): +// | code (2): +// | 0: PushNothing +// | 1: Return // | function foo (1 args, 0 locals): // | strings (0): // | code (4): @@ -72,12 +77,7 @@ fun test() -> f64 { // | strings (0): // | code (4): // | 0: PushFloat(1.0) -// | 1: LoadFunction(0) +// | 1: LoadFunction(1) // | 2: Call(1) // | 3: Return -// | function << module >> (0 args, 0 locals): -// | strings (0): -// | code (2): -// | 0: PushNothing -// | 1: Return // | diff --git a/fine/tests/expression/arithmetic.fine b/fine/tests/expression/arithmetic.fine index 700ac5e8..395ba11b 100644 --- a/fine/tests/expression/arithmetic.fine +++ b/fine/tests/expression/arithmetic.fine @@ -39,6 +39,11 @@ fun test() -> f64 { // | RightBrace:'"}"' // | // @compiles-to: +// | function << module >> (0 args, 0 locals): +// | strings (0): +// | code (2): +// | 0: PushNothing +// | 1: Return // | function test (0 args, 0 locals): // | strings (0): // | code (10): @@ -52,9 +57,4 @@ fun test() -> f64 { // | 7: FloatMultiply // | 8: FloatAdd // | 9: Return -// | function << module >> (0 args, 0 locals): -// | strings (0): -// | code (2): -// | 0: PushNothing -// | 1: Return // | diff --git a/fine/tests/expression/block.fine b/fine/tests/expression/block.fine index 1d6b0c14..6b9f348d 100644 --- a/fine/tests/expression/block.fine +++ b/fine/tests/expression/block.fine @@ -4,12 +4,12 @@ fun test() { // @no-errors // @compiles-to: -// | function test (0 args, 0 locals): +// | function << module >> (0 args, 0 locals): // | strings (0): // | code (2): // | 0: PushNothing // | 1: Return -// | function << module >> (0 args, 0 locals): +// | function test (0 args, 0 locals): // | strings (0): // | code (2): // | 0: PushNothing diff --git a/fine/tests/expression/boolean.fine b/fine/tests/expression/boolean.fine index 3c95008f..5ed756f0 100644 --- a/fine/tests/expression/boolean.fine +++ b/fine/tests/expression/boolean.fine @@ -4,6 +4,11 @@ fun test() -> bool { // @no-errors // @compiles-to: +// | function << module >> (0 args, 0 locals): +// | strings (0): +// | code (2): +// | 0: PushNothing +// | 1: Return // | function test (0 args, 0 locals): // | strings (0): // | code (15): @@ -22,11 +27,6 @@ fun test() -> bool { // | 12: PushTrue // | 13: BoolNot // | 14: Return -// | function << module >> (0 args, 0 locals): -// | strings (0): -// | code (2): -// | 0: PushNothing -// | 1: Return // | // @eval: Bool(false) // @type: 15 bool diff --git a/fine/tests/expression/conditional.fine b/fine/tests/expression/conditional.fine index 128f0fb3..425f6684 100644 --- a/fine/tests/expression/conditional.fine +++ b/fine/tests/expression/conditional.fine @@ -57,6 +57,11 @@ fun test() -> f64 { // | RightBrace:'"}"' // // @compiles-to: +// | function << module >> (0 args, 0 locals): +// | strings (0): +// | code (2): +// | 0: PushNothing +// | 1: Return // | function test (0 args, 0 locals): // | strings (1): // | 0: "discarded" @@ -69,10 +74,5 @@ fun test() -> f64 { // | 5: Jump(7) // | 6: PushFloat(45.0) // | 7: Return -// | function << module >> (0 args, 0 locals): -// | strings (0): -// | code (2): -// | 0: PushNothing -// | 1: Return // | // @eval: Float(23.0)