From 188842d85f14353d314161e32498497603f70bdd Mon Sep 17 00:00:00 2001 From: sezna Date: Mon, 24 Jun 2024 21:47:17 -0700 Subject: [PATCH 1/5] wip: why is the program returning the wrong val? --- petr-typecheck/src/lib.rs | 51 +++++++++++++++++++++++++++++++-------- petr-vm/src/lib.rs | 3 ++- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/petr-typecheck/src/lib.rs b/petr-typecheck/src/lib.rs index e78d890..3bd7fec 100644 --- a/petr-typecheck/src/lib.rs +++ b/petr-typecheck/src/lib.rs @@ -54,7 +54,7 @@ pub struct TypeChecker { typed_functions: BTreeMap, errors: Vec, resolved: QueryableResolvedItems, - generics_in_scope: Vec>, + variable_scope: Vec>, } pub type TypeVariable = Type<&'static str>; @@ -102,9 +102,9 @@ impl TypeChecker { &mut self, f: impl FnOnce(&mut Self) -> T, ) -> T { - self.generics_in_scope.push(Default::default()); + self.variable_scope.push(Default::default()); let res = f(self); - self.generics_in_scope.pop(); + self.variable_scope.pop(); res } @@ -112,19 +112,31 @@ impl TypeChecker { &mut self, id: &Identifier, ) -> TypeVariable { - for scope in self.generics_in_scope.iter().rev() { + for scope in self.variable_scope.iter().rev() { if let Some(ty) = scope.get(id) { return ty.clone(); } } let fresh_ty = self.fresh_ty_var(); - self.generics_in_scope + self.variable_scope .last_mut() .expect("looked for generic when no scope existed") .insert(*id, fresh_ty.clone()); fresh_ty } + fn find_variable( + &self, + id: Identifier, + ) -> Option { + for scope in self.variable_scope.iter().rev() { + if let Some(ty) = scope.get(&id) { + return Some(ty.clone()); + } + } + None + } + fn fully_type_check(&mut self) { // TODO collects on these iters is not ideal for (id, _) in self.resolved.types() { @@ -157,13 +169,25 @@ impl TypeChecker { errors: Default::default(), typed_functions: Default::default(), resolved, - generics_in_scope: Default::default(), + variable_scope: Default::default(), }; type_checker.fully_type_check(); type_checker } + pub fn insert_variable( + &mut self, + id: Identifier, + ty: TypeVariable, + ) { + println!("inserting variable {:?} with type {:?}", id, ty); + self.variable_scope + .last_mut() + .expect("inserted variable when no scope existed") + .insert(id, ty); + } + pub fn fresh_ty_var(&mut self) -> TypeVariable { self.ctx.new_variable() } @@ -438,11 +462,13 @@ impl TypeCheck for Expr { ExprKind::Variable { name, ty } => { // look up variable in scope // find its expr return type + dbg!(&ctx.variable_scope); + let var_ty = ctx.find_variable(*name).expect("variable not found in scope"); + let ty = ctx.to_type_var(ty); - TypedExpr::Variable { - ty: ctx.to_type_var(ty), - name: *name, - } + ctx.unify(&var_ty, &ty); + + TypedExpr::Variable { ty, name: *name } }, ExprKind::Intrinsic(intrinsic) => intrinsic.type_check(ctx), ExprKind::TypeConstructor => { @@ -455,6 +481,7 @@ impl TypeCheck for Expr { let mut type_checked_bindings = Vec::with_capacity(bindings.len()); for binding in bindings { let binding_ty = binding.expression.type_check(ctx); + ctx.insert_variable(binding.name, binding_ty.ty()); type_checked_bindings.push((binding.name, binding_ty)); } @@ -519,6 +546,10 @@ impl TypeCheck for petr_resolve::Function { ctx.with_type_scope(|ctx| { let params = self.params.iter().map(|(name, ty)| (*name, ctx.to_type_var(ty))).collect::>(); + for (name, ty) in ¶ms { + ctx.insert_variable(*name, ty.clone()); + } + // unify types within the body with the parameter let body = self.body.type_check(ctx); diff --git a/petr-vm/src/lib.rs b/petr-vm/src/lib.rs index a91609f..7692791 100644 --- a/petr-vm/src/lib.rs +++ b/petr-vm/src/lib.rs @@ -34,7 +34,7 @@ impl Default for ProgramOffset { } } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct Value(u64); #[derive(Debug, Error)] @@ -87,6 +87,7 @@ impl Vm { Err(e) => return Err(e), } } + println!("Program terminated with stack {:?}", self.state.stack); Ok(()) } From 1e0ff9732c6e5b8317ed84eada900c52e825edb6 Mon Sep 17 00:00:00 2001 From: sezna Date: Mon, 24 Jun 2024 22:28:59 -0700 Subject: [PATCH 2/5] Implement variable expressions in codegen --- petr-ir/src/lib.rs | 7 ++++++- petr-typecheck/src/lib.rs | 2 -- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/petr-ir/src/lib.rs b/petr-ir/src/lib.rs index 49c17f3..db2e6ff 100644 --- a/petr-ir/src/lib.rs +++ b/petr-ir/src/lib.rs @@ -103,7 +103,12 @@ impl Lowerer { let mut buf = vec![]; self.with_variable_context(|ctx| -> Result<_, _> { // TODO: func should have type checked types...not just the AST type - for (param_name, param_ty) in &func.params { + // Pop parameters off the stack in reverse order -- the last parameter for the function + // will be the first thing popped off the stack + // When we lower a function call, we push them onto the stack from first to last. Since + // the stack is FILO, we reverse that order here. + + for (param_name, param_ty) in func.params.iter().rev() { // in order, assign parameters to registers if fits_in_reg(param_ty) { // load from stack into register diff --git a/petr-typecheck/src/lib.rs b/petr-typecheck/src/lib.rs index 3bd7fec..1b4f595 100644 --- a/petr-typecheck/src/lib.rs +++ b/petr-typecheck/src/lib.rs @@ -181,7 +181,6 @@ impl TypeChecker { id: Identifier, ty: TypeVariable, ) { - println!("inserting variable {:?} with type {:?}", id, ty); self.variable_scope .last_mut() .expect("inserted variable when no scope existed") @@ -462,7 +461,6 @@ impl TypeCheck for Expr { ExprKind::Variable { name, ty } => { // look up variable in scope // find its expr return type - dbg!(&ctx.variable_scope); let var_ty = ctx.find_variable(*name).expect("variable not found in scope"); let ty = ctx.to_type_var(ty); From cdee511c787a92c74e5bf30f3b5e195a6ed6f0a4 Mon Sep 17 00:00:00 2001 From: sezna Date: Mon, 24 Jun 2024 22:30:18 -0700 Subject: [PATCH 3/5] update tests --- petr-bind/src/binder.rs | 6 +++--- petr-ir/src/lib.rs | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/petr-bind/src/binder.rs b/petr-bind/src/binder.rs index 24ab784..cf8ae56 100644 --- a/petr-bind/src/binder.rs +++ b/petr-bind/src/binder.rs @@ -560,7 +560,7 @@ mod tests { __Scopes__ 0: Root (parent none): test: Module Module { root_scope: ScopeId(1), exports: {} } - 1: Module test (parent none): + 1: Module test (parent scopeid0): trinary_boolean: Type TypeId(0) True: Function FunctionId(0) False: Function FunctionId(1) @@ -579,7 +579,7 @@ mod tests { __Scopes__ 0: Root (parent none): test: Module Module { root_scope: ScopeId(1), exports: {} } - 1: Module test (parent none): + 1: Module test (parent scopeid0): add: Function FunctionId(0) 2: Function (parent scopeid1): a: FunctionParameter Named(Identifier { id: SymbolId(3) }) @@ -596,7 +596,7 @@ mod tests { __Scopes__ 0: Root (parent none): test: Module Module { root_scope: ScopeId(1), exports: {} } - 1: Module test (parent none): + 1: Module test (parent scopeid0): add: Function FunctionId(0) 2: Function (parent scopeid1): a: FunctionParameter Named(Identifier { id: SymbolId(3) }) diff --git a/petr-ir/src/lib.rs b/petr-ir/src/lib.rs index db2e6ff..48cf04a 100644 --- a/petr-ir/src/lib.rs +++ b/petr-ir/src/lib.rs @@ -490,7 +490,7 @@ mod tests { function 0: 0 pop v0 1 pop v1 - 2 push v0 + 2 push v1 3 ret ENTRY: function 1: 4 ld v2 datalabel0 @@ -530,7 +530,35 @@ mod tests { a function main() returns 'int ~hi(1, 2) "#, - expect![[r#""#]], + expect![[r#" + ; DATA_SECTION + 0: Int64(20) + 1: Int64(30) + 2: Int64(42) + 3: Int64(1) + 4: Int64(2) + + ; PROGRAM_SECTION + ENTRY: 1 + function 0: + 0 pop v0 + 1 pop v1 + 2 cp v2 v1 + 3 cp v3 v0 + 4 ld v4 datalabel0 + 5 ld v5 datalabel1 + 6 ld v6 datalabel2 + 7 push v2 + 8 ret + ENTRY: function 1: + 9 ld v7 datalabel3 + 10 push v7 + 11 ld v8 datalabel4 + 12 push v8 + 13 ppc + 14 jumpi functionid0 + 15 ret + "#]], ); } } From b6dc3d85ad1d22122bff09193db2382756c2c7b6 Mon Sep 17 00:00:00 2001 From: sezna Date: Mon, 24 Jun 2024 22:58:30 -0700 Subject: [PATCH 4/5] add a single VM test --- Cargo.lock | 4 +++ petr-vm/Cargo.toml | 6 +++++ petr-vm/src/lib.rs | 61 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf6cb93..1ab8a50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1289,7 +1289,11 @@ dependencies = [ name = "petr-vm" version = "0.1.0" dependencies = [ + "expect-test", "petr-ir", + "petr-parse", + "petr-resolve", + "petr-typecheck", "petr-utils", "thiserror", ] diff --git a/petr-vm/Cargo.toml b/petr-vm/Cargo.toml index 2ce47af..4290061 100644 --- a/petr-vm/Cargo.toml +++ b/petr-vm/Cargo.toml @@ -7,3 +7,9 @@ edition = "2021" petr-ir = { path = "../petr-ir" } petr-utils = { path = "../petr-utils" } thiserror = "1.0.61" + +[dev-dependencies] +petr-parse = { path = "../petr-parse" } +expect-test = "1.5.0" +petr-resolve = { path = "../petr-resolve" } +petr-typecheck = { path = "../petr-typecheck" } diff --git a/petr-vm/src/lib.rs b/petr-vm/src/lib.rs index 7692791..3e695b0 100644 --- a/petr-vm/src/lib.rs +++ b/petr-vm/src/lib.rs @@ -10,6 +10,62 @@ use petr_ir::{DataLabel, DataSectionEntry, Intrinsic, IrOpcode, Reg}; use petr_utils::{idx_map_key, IndexMap}; use thiserror::Error; +#[cfg(test)] +mod tests { + + use expect_test::{expect, Expect}; + use petr_ir::Lowerer; + use petr_resolve::resolve_symbols; + use petr_typecheck::TypeChecker; + use petr_utils::render_error; + + use super::*; + fn check( + input: impl Into, + expect: Expect, + ) { + let input = input.into(); + let parser = petr_parse::Parser::new(vec![("test", input)]); + let (ast, errs, interner, source_map) = parser.into_result(); + if !errs.is_empty() { + errs.into_iter().for_each(|err| eprintln!("{:?}", render_error(&source_map, err))); + panic!("fmt failed: code didn't parse"); + } + let (errs, resolved) = resolve_symbols(ast, interner, Default::default()); + if !errs.is_empty() { + dbg!(&errs); + } + let type_checker = TypeChecker::new(resolved); + let lowerer = Lowerer::new(type_checker); + let (data, ir) = lowerer.finalize(); + let vm = Vm::new(ir, data); + let res = vm.run(); + + let res_as_u64 = res.unwrap().iter().map(|val| val.0).collect::>(); + let res = format!("{res_as_u64:?}"); + + expect.assert_eq(&res); + } + + #[test] + + fn let_bindings() { + check( + r#" +function hi(x in 'int, y in 'int) returns 'int + let a = x, + b = y, + c = 20, + d = 30, + e = 12, + a +function main() returns 'int ~hi(42, 3) +"#, + expect!["[42]"], + ) + } +} + pub struct Vm { state: VmState, instructions: IndexMap, @@ -78,7 +134,7 @@ impl Vm { } } - pub fn run(&mut self) -> Result<()> { + pub fn run(mut self) -> Result> { use VmControlFlow::*; loop { match self.execute() { @@ -87,8 +143,7 @@ impl Vm { Err(e) => return Err(e), } } - println!("Program terminated with stack {:?}", self.state.stack); - Ok(()) + Ok(self.state.stack) } fn execute(&mut self) -> Result { From 6601ae46e49ae214dc2583498be5c641b42e2823 Mon Sep 17 00:00:00 2001 From: sezna Date: Mon, 24 Jun 2024 23:08:27 -0700 Subject: [PATCH 5/5] clippy --- pete/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pete/src/main.rs b/pete/src/main.rs index 5f4f0d8..b81c4da 100644 --- a/pete/src/main.rs +++ b/pete/src/main.rs @@ -93,7 +93,7 @@ fn main() -> Result<(), error::PeteError> { timings.start("execution"); match target.to_lowercase().as_str() { "vm" => { - let mut vm = Vm::new(instructions, data); + let vm = Vm::new(instructions, data); vm.run().expect("Failed to run vm"); }, "native" => todo!(),