Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce variable exprs and VM runtime tests #47

Merged
merged 5 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pete/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(),
Expand Down
6 changes: 3 additions & 3 deletions petr-bind/src/binder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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) })
Expand All @@ -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) })
Expand Down
39 changes: 36 additions & 3 deletions petr-ir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -485,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
Expand Down Expand Up @@ -525,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
"#]],
);
}
}
49 changes: 39 additions & 10 deletions petr-typecheck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub struct TypeChecker {
typed_functions: BTreeMap<FunctionId, Function>,
errors: Vec<TypeCheckError>,
resolved: QueryableResolvedItems,
generics_in_scope: Vec<BTreeMap<Identifier, TypeVariable>>,
variable_scope: Vec<BTreeMap<Identifier, TypeVariable>>,
}

pub type TypeVariable = Type<&'static str>;
Expand Down Expand Up @@ -102,29 +102,41 @@ 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
}

fn generic_type(
&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<TypeVariable> {
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() {
Expand Down Expand Up @@ -157,13 +169,24 @@ 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,
) {
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()
}
Expand Down Expand Up @@ -438,11 +461,12 @@ impl TypeCheck for Expr {
ExprKind::Variable { name, ty } => {
// look up variable in scope
// find its expr return type
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 => {
Expand All @@ -455,6 +479,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));
}

Expand Down Expand Up @@ -519,6 +544,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::<Vec<_>>();

for (name, ty) in &params {
ctx.insert_variable(*name, ty.clone());
}

// unify types within the body with the parameter
let body = self.body.type_check(ctx);

Expand Down
6 changes: 6 additions & 0 deletions petr-vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
62 changes: 59 additions & 3 deletions petr-vm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
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::<Vec<_>>();
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<ProgramOffset, IrOpcode>,
Expand All @@ -34,7 +90,7 @@ impl Default for ProgramOffset {
}
}

#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
pub struct Value(u64);

#[derive(Debug, Error)]
Expand Down Expand Up @@ -78,7 +134,7 @@ impl Vm {
}
}

pub fn run(&mut self) -> Result<()> {
pub fn run(mut self) -> Result<Vec<Value>> {
use VmControlFlow::*;
loop {
match self.execute() {
Expand All @@ -87,7 +143,7 @@ impl Vm {
Err(e) => return Err(e),
}
}
Ok(())
Ok(self.state.stack)
}

fn execute(&mut self) -> Result<VmControlFlow> {
Expand Down
Loading