From 24482c4ebc4e3520718213f56ec5bf2dabe4f792 Mon Sep 17 00:00:00 2001 From: Ethan Uppal <113849268+ethanuppal@users.noreply.github.com> Date: Wed, 24 Jul 2024 23:40:05 -0400 Subject: [PATCH] All tests but calyx emission pass! --- Cargo.lock | 12 ++ Cargo.toml | 7 +- Makefile | 2 +- data/test.plsr | 1 - pulsar-frontend/src/ast/ty.rs | 21 ++- pulsar-frontend/src/parser.rs | 9 - pulsar-frontend/src/type_inferer.rs | 65 ++++--- pulsar-ir/Cargo.toml | 1 + pulsar-ir/src/cell.rs | 14 +- pulsar-ir/src/component.rs | 10 +- pulsar-ir/src/control.rs | 82 +++++++-- pulsar-ir/src/from_ast.rs | 106 +++++++----- pulsar-ir/src/lib.rs | 40 ++--- pulsar-ir/src/memory.rs | 25 ++- pulsar-ir/src/pass.rs | 84 +++++++-- pulsar-ir/src/pass/canonicalize.rs | 163 ++++++++---------- pulsar-ir/src/pass/cell_alloc.rs | 45 ++++- pulsar-ir/src/pass/collapse_control.rs | 4 +- pulsar-ir/src/pass/copy_prop.rs | 61 ++++--- pulsar-ir/src/pass/dead_code.rs | 76 ++++---- pulsar-ir/src/pass/well_formed.rs | 26 +++ pulsar-ir/src/{operand.rs => port.rs} | 37 ++-- pulsar-ir/src/variable.rs | 11 +- pulsar-ir/src/visitor.rs | 57 ++++-- pulsar-utils/src/error.rs | 8 +- pulsar-utils/src/pool.rs | 6 +- redesign.md | 7 + src/context.rs | 10 +- src/main.rs | 45 +++-- src/utils.rs | 4 +- tests/data/infer10.plsr | 4 +- tests/data/infer11.plsr | 11 +- tests/data/infer12.plsr | 8 - tests/data/infer13.plsr | 7 - tests/data/infer14.plsr | 7 - tests/data/infer15.plsr | 20 --- tests/data/infer16.plsr | 9 - tests/data/infer7.plsr | 8 +- tests/data/infer8.plsr | 2 +- tests/data/infer9.plsr | 2 +- .../test_parser__tests__parser_5.snap | 10 +- .../test_parser__tests__parser_6.snap | 2 +- .../test_parser__tests__parser_7.snap | 2 +- .../test_parser__tests__parser_8.snap | 2 +- ...st_typeinferer__tests__type_inferer_1.snap | 10 ++ ...t_typeinferer__tests__type_inferer_10.snap | 17 ++ ...t_typeinferer__tests__type_inferer_11.snap | 23 +++ ...st_typeinferer__tests__type_inferer_2.snap | 22 +++ ...st_typeinferer__tests__type_inferer_3.snap | 18 ++ ...st_typeinferer__tests__type_inferer_4.snap | 18 ++ ...st_typeinferer__tests__type_inferer_5.snap | 11 ++ ...st_typeinferer__tests__type_inferer_6.snap | 18 ++ ...st_typeinferer__tests__type_inferer_7.snap | 28 +++ ...st_typeinferer__tests__type_inferer_8.snap | 5 + ...st_typeinferer__tests__type_inferer_9.snap | 10 ++ tests/test_calyx_verilog.rs | 48 +++--- tests/test_parser.rs | 1 + tests/test_typeinferer.rs | 46 +++-- 58 files changed, 917 insertions(+), 491 deletions(-) create mode 100644 pulsar-ir/src/pass/well_formed.rs rename pulsar-ir/src/{operand.rs => port.rs} (54%) delete mode 100644 tests/data/infer12.plsr delete mode 100644 tests/data/infer13.plsr delete mode 100644 tests/data/infer14.plsr delete mode 100644 tests/data/infer15.plsr delete mode 100644 tests/data/infer16.plsr create mode 100644 tests/snapshots/test_typeinferer__tests__type_inferer_1.snap create mode 100644 tests/snapshots/test_typeinferer__tests__type_inferer_10.snap create mode 100644 tests/snapshots/test_typeinferer__tests__type_inferer_11.snap create mode 100644 tests/snapshots/test_typeinferer__tests__type_inferer_2.snap create mode 100644 tests/snapshots/test_typeinferer__tests__type_inferer_3.snap create mode 100644 tests/snapshots/test_typeinferer__tests__type_inferer_4.snap create mode 100644 tests/snapshots/test_typeinferer__tests__type_inferer_5.snap create mode 100644 tests/snapshots/test_typeinferer__tests__type_inferer_6.snap create mode 100644 tests/snapshots/test_typeinferer__tests__type_inferer_7.snap create mode 100644 tests/snapshots/test_typeinferer__tests__type_inferer_8.snap create mode 100644 tests/snapshots/test_typeinferer__tests__type_inferer_9.snap diff --git a/Cargo.lock b/Cargo.lock index 3bab993..d76e110 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -475,6 +475,17 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "match_deref" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30dd27efba9ccf9069f76ff0b7b65eb293a844d9918e15a36098de609c9aacd7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "memchr" version = "2.7.2" @@ -641,6 +652,7 @@ dependencies = [ "either", "inform", "log", + "match_deref", "pulsar-frontend", "pulsar-utils", ] diff --git a/Cargo.toml b/Cargo.toml index 35ab28f..b4dffef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ members = [ ] [workspace.package] -name = "pulsar" description = "A high-level programming language for building hardware accelerators" homepage = "https://github.com/ethanuppal/pulsar/tree/main" repository = "https://github.com/ethanuppal/pulsar/tree/main" @@ -37,12 +36,10 @@ inform = "0.1.0" serde = "1.0.204" serde_json = "1.0.120" memmap2 = "0.9.4" -log = "0.4.22" +log = { version = "0.4.22", features = ["release_max_level_off"] } env_logger = "0.11.3" either = "1.13.0" - -[log] -features = ["release_max_level_off"] +match_deref = "0.1.1" [package] name = "pulsar-lang" diff --git a/Makefile b/Makefile index bef6c5a..51590c2 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,7 @@ clean: .PHONY: docs docs: @echo '[INFO] Building and viewing documentation' - @cargo doc -p pulsar-frontend -p pulsar-ir -p pulsar-backend -p pulsar-utils -p pulsar --no-deps + @cargo doc -p pulsar-frontend -p pulsar-ir -p pulsar-backend -p pulsar-utils -p pulsar-lang --no-deps --examples .PHONY: cloc diff --git a/data/test.plsr b/data/test.plsr index d6c37b3..f3efaf3 100644 --- a/data/test.plsr +++ b/data/test.plsr @@ -1,6 +1,5 @@ func main(a: Int[64], b: Int[64]) -> (c: Int[64]) { for i in 0 ..< 64 { - let temp = 4 c[i] = a[i] + b[i] } } diff --git a/pulsar-frontend/src/ast/ty.rs b/pulsar-frontend/src/ast/ty.rs index 7a1b894..9af80ab 100644 --- a/pulsar-frontend/src/ast/ty.rs +++ b/pulsar-frontend/src/ast/ty.rs @@ -12,12 +12,13 @@ use pulsar_utils::{id::Id, pool::Handle}; use std::{ cmp, fmt::{self, Display, Write}, + hash::Hash, mem }; /// This isn't a real liquid type. Notably, the only constraint it can /// express is equality to a given number. -#[derive(PartialEq, Eq, Clone)] +#[derive(PartialEq, Eq, Hash, Clone)] pub enum LiquidTypeValue { Equal(usize), All @@ -33,6 +34,12 @@ impl PartialEq for LiquidType { impl Eq for LiquidType {} +impl Hash for LiquidType { + fn hash(&self, state: &mut H) { + self.value.hash(state) + } +} + impl PartialOrd for LiquidType { fn partial_cmp(&self, other: &Self) -> Option { Some(match (&self.value, &other.value) { @@ -53,8 +60,10 @@ impl PartialOrd for LiquidType { impl PrettyPrint for LiquidType { fn pretty_print(&self, f: &mut IndentFormatter<'_, '_>) -> fmt::Result { match self.value { - LiquidTypeValue::Equal(value) => write!(f, "{}", value), - LiquidTypeValue::All => write!(f, "?") + LiquidTypeValue::Equal(value) => { + write!(f, "{{ x | x = {} }}", value) + } + LiquidTypeValue::All => write!(f, "{{ x | x >= 0 }}") } } } @@ -210,4 +219,10 @@ impl PartialEq for Type { impl Eq for Type {} +impl Hash for Type { + fn hash(&self, state: &mut H) { + self.value.hash(state) + } +} + pub trait AsTypePool: AsNodePool + AsNodePool {} diff --git a/pulsar-frontend/src/parser.rs b/pulsar-frontend/src/parser.rs index 8207d56..902b83d 100644 --- a/pulsar-frontend/src/parser.rs +++ b/pulsar-frontend/src/parser.rs @@ -35,7 +35,6 @@ pub type Block = (Handle, Vec>, Handle); #[derive(Default)] struct ParseErrorContext { loc: String, - fix: Option, refback: Option } @@ -44,11 +43,6 @@ impl ParseErrorContext { Self::default() } - pub fn fix>(mut self, msg: S) -> Self { - self.fix = Some(msg.as_ref().to_string()); - self - } - pub fn refback(mut self, error: Error) -> Self { self.refback = Some(error); self @@ -249,7 +243,6 @@ impl<'ast, 'err, P: AsASTPool> Parser<'ast, 'err, P> { .with_code(ErrorCode::UnexpectedEOF) .span(self.buffer.last().unwrap()) .explain(format!("Unexpected EOF {}", context.loc)) - .maybe_fix(context.fix) .build() ); if let Some(refback) = context.refback { @@ -276,7 +269,6 @@ impl<'ast, 'err, P: AsASTPool> Parser<'ast, 'err, P> { context.loc )) .explain(format!("Received '{}' here", actual.ty.name())) - .maybe_fix(context.fix) .build() ); if let Some(refback) = context.refback { @@ -305,7 +297,6 @@ impl<'ast, 'err, P: AsASTPool> Parser<'ast, 'err, P> { context.loc )) .explain(format!("Received '{}' here", actual.ty.name())) - .maybe_fix(context.fix) .build() ); if let Some(refback) = context.refback { diff --git a/pulsar-frontend/src/type_inferer.rs b/pulsar-frontend/src/type_inferer.rs index bd4a7e0..6eeff8d 100644 --- a/pulsar-frontend/src/type_inferer.rs +++ b/pulsar-frontend/src/type_inferer.rs @@ -25,7 +25,7 @@ use pulsar_utils::{ pool::{AsPool, Handle, HandleArray}, span::SpanProvider }; -use std::{fmt::Display, iter::zip}; +use std::{fmt::Display, hash::Hash, iter::zip}; pub struct UnificationConstraint { expected: Handle, @@ -67,7 +67,7 @@ pub type TypeConstraint = UnificationConstraint; pub type LiquidTypeConstraint = UnificationConstraint; trait Unifier< - T: NodeInterface + Eq + Display, + T: NodeInterface + Eq + Hash + Display, P: AsPool, ()> > { fn type_pool_mut(&mut self) -> &mut P; @@ -79,7 +79,7 @@ trait Unifier< .add(UnificationConstraint::new(expected, actual)); self.constraints().push(handle); - log::info!("encountered constraint: {} = {}", expected, actual); + log::trace!("encountered constraint: {} = {}", expected, actual); } fn derive_constraint( @@ -91,7 +91,7 @@ trait Unifier< .add(UnificationConstraint::derived(expected, actual, source)); self.constraints().push(handle); - log::info!( + log::trace!( "encountered constraint: {} = {} (from {} = {})", expected, actual, @@ -243,21 +243,6 @@ impl<'pool, 'err, P: AsInferencePool> TypeInferer<'pool, 'err, P> { ); } - fn report_called_non_function(&mut self, name: Handle) { - self.report( - ErrorBuilder::new() - .of_style(Style::Primary) - .at_level(Level::Error) - .with_code(ErrorCode::StaticAnalysisIssue) - .span(name) - .message(format!( - "Cannot call non-function value `{}`", - name.value - )) - .build() - ); - } - // fn report_invalid_operation(&mut self, explain: String, ctx: // Handle) { self.report( // ErrorBuilder::new() @@ -271,12 +256,14 @@ impl<'pool, 'err, P: AsInferencePool> TypeInferer<'pool, 'err, P> { // ); // } - fn report_unification_failure( + fn report_unification_failure< + T: SpanProvider + Display + Eq + Hash + Clone + >( &mut self, constraint: Handle>, - fix: Option + dsu: &mut DisjointSets>, fix: Option ) { - let expected = constraint.expected(); - let actual = constraint.actual(); + let expected = dsu.find(constraint.expected()).unwrap(); + let actual = dsu.find(constraint.actual()).unwrap(); let mut builder = ErrorBuilder::new() .of_style(Style::Primary) .at_level(Level::Error) @@ -366,12 +353,17 @@ impl<'pool, 'err, P: AsInferencePool> TypeInferer<'pool, 'err, P> { } else { let element_count = elements.len(); let mut elements = elements.iter(); - let first = elements.next().unwrap(); - let first_type = self.visit_expr(*first)?; - for other in elements { - let other_type = self.visit_expr(*other)?; - self.new_constraint(first_type, other_type); - } + let element_type = if element_count > 0 { + let first = elements.next().unwrap(); + let first_type = self.visit_expr(*first)?; + for other in elements { + let other_type = self.visit_expr(*other)?; + self.new_constraint(first_type, other_type); + } + first_type + } else { + self.new_type_var(expr) + }; let liquid_type = self.pool.generate( if should_continue.is_some() { LiquidTypeValue::All @@ -382,7 +374,7 @@ impl<'pool, 'err, P: AsInferencePool> TypeInferer<'pool, 'err, P> { expr.end_token() ); self.pool.generate( - TypeValue::Array(first_type, liquid_type), + TypeValue::Array(element_type, liquid_type), expr.start_token(), expr.end_token() ) @@ -459,13 +451,18 @@ impl<'pool, 'err, P: AsInferencePool> TypeInferer<'pool, 'err, P> { StmtValue::Divider(_) => todo!(), StmtValue::For { var, - lower: _, - exclusive_upper: _, + lower, + exclusive_upper, body } => { self.env.push(); let loop_var_type = self.pool.generate(TypeValue::Int64, *var, *var); + let lower_type = self.visit_expr(*lower)?; + let upper_type = self.visit_expr(*exclusive_upper)?; + // TODO: figure out better semantic arrangement/source for cnstr + self.new_constraint(loop_var_type, lower_type); + self.new_constraint(loop_var_type, upper_type); self.env.bind(var.value.clone(), loop_var_type); self.env.push(); @@ -570,7 +567,7 @@ impl<'pool, 'err, P: AsInferencePool> Unifier } } _ => { - self.report_unification_failure(constraint, None); + self.report_unification_failure(constraint, dsu, None); return Err(()); } } @@ -622,7 +619,7 @@ impl<'pool, 'err, P: AsInferencePool> Unifier dsu.union(actual, expected, false); } _ => { - self.report_unification_failure(constraint, None); + self.report_unification_failure(constraint, dsu, None); return Err(()); } } diff --git a/pulsar-ir/Cargo.toml b/pulsar-ir/Cargo.toml index 7d475af..1158493 100644 --- a/pulsar-ir/Cargo.toml +++ b/pulsar-ir/Cargo.toml @@ -17,3 +17,4 @@ pulsar-utils.workspace = true inform.workspace = true either.workspace = true log.workspace = true +match_deref.workspace = true diff --git a/pulsar-ir/src/cell.rs b/pulsar-ir/src/cell.rs index 59b0a69..14e54be 100644 --- a/pulsar-ir/src/cell.rs +++ b/pulsar-ir/src/cell.rs @@ -3,10 +3,9 @@ //! License as published by the Free Software Foundation, either version 3 of //! the License, or (at your option) any later version. -use std::fmt::{self, Display}; - use crate::memory::Memory; use pulsar_frontend::ast::ty::{LiquidTypeValue, Type, TypeValue}; +use std::fmt::{self, Display}; /// A hardware element capable of storage. pub enum Cell { @@ -47,14 +46,13 @@ impl Display for Cell { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Cell::Memory(memory) => { - write!( - f, - "Memory(length={}, element={}, bank={})", - memory.length, memory.element, memory.bank - ) + for level in memory.levels() { + write!(f, "[{} bank {}]", level.length, level.bank)?; + } + Cell::Register(memory.element()).fmt(f) } Cell::Register(bit_width) => { - write!(f, "Register(width={})", bit_width) + write!(f, "[0:{}]()", bit_width - 1) } } } diff --git a/pulsar-ir/src/component.rs b/pulsar-ir/src/component.rs index f88800d..3487dc0 100644 --- a/pulsar-ir/src/component.rs +++ b/pulsar-ir/src/component.rs @@ -16,9 +16,9 @@ pub struct Component { label: Label, inputs: Vec<(Variable, Handle)>, outputs: Vec<(Variable, Handle)>, - internal_cells: Vec>, + // internal_cells: Vec>, /// Like reg-alloc but for cells. need better way to represent - cell_alloc: HashMap>, + pub cell_alloc: HashMap>, pub(crate) cfg: Control } @@ -33,7 +33,7 @@ impl Component { label, inputs, outputs, - internal_cells: Vec::new(), + // internal_cells: Vec::new(), cell_alloc: initial_cell_alloc, cfg } @@ -70,7 +70,9 @@ impl PrettyPrint for Component { writeln!(f, "cells {{")?; { f.increase_indent(); - for (var, cell) in &self.cell_alloc { + let mut cell_alloc_list: Vec<_> = self.cell_alloc.iter().collect(); + cell_alloc_list.sort_by(|a, b| a.0.cmp(b.0)); + for (var, cell) in &cell_alloc_list { writeln!(f, "{} = {}", var, cell)?; } f.decrease_indent(); diff --git a/pulsar-ir/src/control.rs b/pulsar-ir/src/control.rs index 351f554..e38840a 100644 --- a/pulsar-ir/src/control.rs +++ b/pulsar-ir/src/control.rs @@ -3,7 +3,7 @@ //! License as published by the Free Software Foundation, either version 3 of //! the License, or (at your option) any later version. -use crate::{operand::Operand, variable::Variable, Ir}; +use crate::{port::Port, variable::Variable, Ir}; use inform::fmt::IndentFormatter; use pulsar_frontend::ast::pretty_print::PrettyPrint; use pulsar_utils::{ @@ -18,24 +18,34 @@ use std::{ pub struct For { pub id: Id, variant: Variable, - lower: Operand, - upper: Operand, + lower: Port, + exclusive_upper: Port, pub(crate) body: Handle } impl For { + /// A for control takes exclusive ownership of its upper and lower bound + /// ports, so no handles need to be created for them. pub fn new( - id: Id, variant: Variable, lower: Operand, upper: Operand, + id: Id, variant: Variable, lower: Port, exclusive_upper: Port, body: Handle ) -> Self { Self { id, variant, lower, - upper, + exclusive_upper, body } } + + pub fn variant(&self) -> Variable { + self.variant + } + + pub fn exclusive_upper_bound(&self) -> &Port { + &self.exclusive_upper + } } impl PrettyPrint for For { @@ -43,7 +53,7 @@ impl PrettyPrint for For { writeln!( f, "for {} in {} ..< {} {{", - self.variant, self.lower, self.upper + self.variant, self.lower, self.exclusive_upper )?; f.increase_indent(); self.body.pretty_print(f)?; @@ -140,14 +150,14 @@ impl Display for Par { pub struct IfElse { pub id: Id, - cond: Operand, + cond: Port, pub(crate) true_branch: Handle, pub(crate) false_branch: Handle } impl IfElse { pub fn new( - id: Id, cond: Operand, true_branch: Handle, + id: Id, cond: Port, true_branch: Handle, false_branch: Handle ) -> Self { Self { @@ -231,14 +241,16 @@ impl From for Control { pub trait AsControlPool: AsPool {} -pub struct SeqParBuilder<'gen, 'pool, P: AsControlPool> { +pub struct ControlBuilder<'gen, 'pool, P: AsControlPool + AsPool> { gen: &'gen mut Gen, pool: &'pool mut P, /// inv: `pars.len() >= 1`. pars: Vec } -impl<'gen, 'pool, P: AsControlPool> SeqParBuilder<'gen, 'pool, P> { +impl<'gen, 'pool, P: AsControlPool + AsPool> + ControlBuilder<'gen, 'pool, P> +{ pub fn new(gen: &'gen mut Gen, pool: &'pool mut P) -> Self { let par = Par::new(gen.next()); Self { @@ -248,6 +260,8 @@ impl<'gen, 'pool, P: AsControlPool> SeqParBuilder<'gen, 'pool, P> { } } + /// Enables `control` in the current logical time step since initialization + /// or [`ControlBuilder::split`]. pub fn push>(&mut self, control: C) { self.pars .last_mut() @@ -255,6 +269,8 @@ impl<'gen, 'pool, P: AsControlPool> SeqParBuilder<'gen, 'pool, P> { .push(self.pool.add(control.into())); } + /// Marks all later [`ControlBuilder::push`]es as occuring in a subsequent + /// logical time step. pub fn split(&mut self) { self.pars.push(Par::new(self.gen.next())); } @@ -268,12 +284,52 @@ impl<'gen, 'pool, P: AsControlPool> SeqParBuilder<'gen, 'pool, P> { pub fn next_id(&mut self) -> Id { self.gen.next() } + + /// [`Ir::Add`] followed by [`ControlBuilder::push`]. + pub fn push_add>( + &mut self, result: R, port: Port, port2: Port + ) { + let ir = Ir::Add( + self.pool.add(result.into()), + self.pool.add(port), + self.pool.add(port2) + ); + self.push(ir); + } + + /// [`Ir::Mul`] followed by [`ControlBuilder::push`]. + pub fn push_mul>( + &mut self, result: R, port: Port, port2: Port + ) { + let ir = Ir::Mul( + self.pool.add(result.into()), + self.pool.add(port), + self.pool.add(port2) + ); + self.push(ir); + } + + /// [`Ir::Assign`] followed by [`ControlBuilder::push`]. + pub fn push_assign>(&mut self, result: R, port: Port) { + let ir = Ir::Assign(self.pool.add(result.into()), self.pool.add(port)); + self.push(ir); + } + + pub fn add_port>( + &mut self, port: PortLike + ) -> Handle { + self.pool.add(port.into()) + } + + pub fn new_const(&mut self, value: i64) -> Handle { + self.pool.add(Port::Constant(value)) + } } -impl<'gen, 'pool, P: AsControlPool> From> - for Control +impl<'gen, 'pool, P: AsControlPool + AsPool> + From> for Control { - fn from(mut value: SeqParBuilder

) -> Self { + fn from(mut value: ControlBuilder

) -> Self { if value.pars.len() == 1 { let mut result = Par::new(0); mem::swap(&mut value.pars[0], &mut result); diff --git a/pulsar-ir/src/from_ast.rs b/pulsar-ir/src/from_ast.rs index 4af2600..c34ddf7 100644 --- a/pulsar-ir/src/from_ast.rs +++ b/pulsar-ir/src/from_ast.rs @@ -5,15 +5,15 @@ use super::{ label::{Label, Name, Visibility}, - operand::Operand, - variable::Variable, - Ir + port::Port, + variable::Variable }; use crate::{ cell::Cell, component::Component, - control::{AsControlPool, Control, For, SeqParBuilder, DEFAULT_CONTROL_ID}, - memory::Memory, + control::{ + AsControlPool, Control, ControlBuilder, For, DEFAULT_CONTROL_ID + }, pass::PassRunner }; use pulsar_frontend::{ @@ -31,13 +31,15 @@ use pulsar_utils::{ pool::{AsPool, Handle} }; -pub trait AsGeneratorPool: AsControlPool + AsPool {} +pub trait AsGeneratorPool: + AsControlPool + AsPool + AsPool { +} #[derive(Default)] pub struct ComponentGenerator { var_gen: Gen, - env: Environment, - cells: Environment + env: Environment + // cells: Environment } pub fn ast_to_ir( @@ -58,8 +60,7 @@ pub fn ast_to_ir( body, &mut pass_runner, pool - ), - _ => panic!("tried to turn not-a-function into a component") + ) }) .collect() } @@ -100,13 +101,13 @@ impl ComponentGenerator { ); let mut control_gen = Gen::new_skipping(DEFAULT_CONTROL_ID); - let mut control = SeqParBuilder::new(&mut control_gen, pool); + let mut builder = ControlBuilder::new(&mut control_gen, pool); for stmt in body { - self.gen_stmt(*stmt, &mut control) + self.gen_stmt(*stmt, &mut builder) } let mut comp = - Component::new(label, input_cells, output_cells, control.into()); + Component::new(label, input_cells, output_cells, builder.into()); pass_runner.run(&mut comp, pool); comp } @@ -116,44 +117,59 @@ impl ComponentGenerator { Variable::from(self.var_gen.next()) } - /// Generates IR for `expr` in `par`, returning its result. - fn gen_expr( - &mut self, expr: Handle, control: &mut SeqParBuilder

- ) -> Operand { + /// Generates IR for `expr` in `par`, returning its result. If `expr` is an + /// lvalue, no additional control is built. + fn gen_expr( + &mut self, expr: Handle, builder: &mut ControlBuilder

+ ) -> Port { match &expr.value { - ExprValue::ConstantInt(value) => Operand::Constant(*value), + ExprValue::ConstantInt(value) => Port::Constant(*value), ExprValue::InfixBop(lhs, op, rhs) => { - let lhs = self.gen_expr(*lhs, control); - let rhs = self.gen_expr(*rhs, control); + let lhs = self.gen_expr(*lhs, builder); + let rhs = self.gen_expr(*rhs, builder); let result = self.new_var(); match op.ty { TokenType::Plus => { - control.push(Ir::Add(result, lhs, rhs)); + builder.push_add(result, lhs, rhs); } TokenType::Times => { - control.push(Ir::Mul(result, lhs, rhs)); + builder.push_mul(result, lhs, rhs); } _ => todo!("haven't implemented all infix bops") } - Operand::Variable(result) + Port::Variable(result) } ExprValue::BoundName(name) => { - Operand::Variable(*self.env.find(name.value.clone()).unwrap_or_else(|| panic!("unbound name `{}` should have been caught in type inference", name.value))) + Port::Variable(*self.env.find(name.value.clone()).unwrap_or_else(|| panic!("unbound name `{}` should have been caught in type inference", name.value))) } ExprValue::PostfixBop(array, op1, index, op2) if op1.ty == TokenType::LeftBracket && op2.ty == TokenType::RightBracket => { - let array_operand = self.gen_expr(*array, control); - let index_operand = self.gen_expr(*index, control); - Operand::PartialAccess(Box::new(array_operand), Box::new(index_operand)) - } + let array_port = self.gen_expr(*array, builder); + let index_port = self.gen_expr(*index, builder); + Port::PartialAccess(builder.add_port(array_port), builder.add_port(index_port)) + }, + ExprValue::ArrayLiteral(elements, _should_continue) => { + let result = Port::Variable(self.new_var()); + let result_handle = builder.add_port(result.clone()); // leakedA + let mut i = 0; + #[allow(clippy::explicit_counter_loop)] // for 2nd loop of zeros + for element in elements { + let index = builder.new_const(i as i64); + let element_port = self.gen_expr(*element, builder); + builder.push_assign(Port::PartialAccess(result_handle, index), element_port); + i += 1 + } + // TODO: figure out a nice way to get the liquid type for array length here + result + }, _ => todo!() } } - fn gen_stmt( - &mut self, stmt: Handle, control: &mut SeqParBuilder

+ fn gen_stmt( + &mut self, stmt: Handle, builder: &mut ControlBuilder

) { match &stmt.value { StmtValue::Let { @@ -161,18 +177,18 @@ impl ComponentGenerator { hint: _, value } => { - let value_operand = self.gen_expr(*value, control); + let value_port = self.gen_expr(*value, builder); let name_var = self.new_var(); self.env.bind(name.value.clone(), name_var); - control.push(Ir::assign(name_var, value_operand)); + builder.push_assign(name_var, value_port); } StmtValue::Assign(lhs, _, rhs) => { - let rhs_operand = self.gen_expr(*rhs, control); - let lhs_operand = self.gen_expr(*lhs, control); - control.push(Ir::assign(lhs_operand, rhs_operand)); + let rhs_port = self.gen_expr(*rhs, builder); + let lhs_port = self.gen_expr(*lhs, builder); + builder.push_assign(lhs_port, rhs_port); } StmtValue::Divider(_) => { - control.split(); + builder.split(); } StmtValue::For { var, @@ -183,10 +199,10 @@ impl ComponentGenerator { self.env.push(); let variant = self.new_var(); self.env.bind(var.value.clone(), variant); - let lower_operand = self.gen_expr(*lower, control); - let upper_operand = self.gen_expr(*exclusive_upper, control); - let body = control.with_inner(|gen, pool| { - let mut builder = SeqParBuilder::new(gen, pool); + let lower_port = self.gen_expr(*lower, builder); + let upper_port = self.gen_expr(*exclusive_upper, builder); + let body = builder.with_inner(|gen, pool| { + let mut builder = ControlBuilder::new(gen, pool); self.env.push(); for stmt in body { self.gen_stmt(*stmt, &mut builder); @@ -196,13 +212,9 @@ impl ComponentGenerator { pool.add(control) }); self.env.pop(); - let id = control.next_id(); - control.push(Control::For(For::new( - id, - variant, - lower_operand, - upper_operand, - body + let id = builder.next_id(); + builder.push(Control::For(For::new( + id, variant, lower_port, upper_port, body ))); } } diff --git a/pulsar-ir/src/lib.rs b/pulsar-ir/src/lib.rs index 90df5e0..a20f7b2 100644 --- a/pulsar-ir/src/lib.rs +++ b/pulsar-ir/src/lib.rs @@ -3,13 +3,9 @@ //! License as published by the Free Software Foundation, either version 3 of //! the License, or (at your option) any later version. -use pulsar_frontend::op::Op; - -use self::{operand::Operand, variable::Variable}; -use std::{ - convert, - fmt::{self, Display} -}; +use self::{port::Port, variable::Variable}; +use pulsar_utils::pool::Handle; +use std::fmt::{self, Display}; pub mod cell; pub mod component; @@ -17,36 +13,32 @@ pub mod control; pub mod from_ast; pub mod label; pub mod memory; -pub mod operand; pub mod pass; +pub mod port; pub mod variable; pub mod visitor; +#[derive(Clone)] pub enum Ir { - Add(Variable, Operand, Operand), - Mul(Variable, Operand, Operand), - Assign(Operand, Operand) + Add(Handle, Handle, Handle), + Mul(Handle, Handle, Handle), + Assign(Handle, Handle) } impl Ir { - pub fn assign>(result: R, operand: Operand) -> Self { - Self::Assign(result.into(), operand) - } - - pub fn kill(&self) -> Operand { + pub fn kill(&self) -> Handle { match self { - Ir::Add(lhs, _, _) | Ir::Mul(lhs, _, _) => Operand::from(*lhs), - Ir::Assign(lhs, _) => lhs.clone() + Ir::Add(lhs, _, _) | Ir::Mul(lhs, _, _) | Ir::Assign(lhs, _) => *lhs } } - pub fn gen(&self) -> Vec<&Operand> { + pub fn gen(&self) -> Vec> { match self { - Ir::Add(_, operand, operand2) | Ir::Mul(_, operand, operand2) => { - vec![operand, operand2] + Ir::Add(_, port, port2) | Ir::Mul(_, port, port2) => { + vec![*port, *port2] } - Ir::Assign(_attr, operand) => { - vec![operand] + Ir::Assign(_, port) => { + vec![*port] } } } @@ -66,7 +58,7 @@ impl Display for Ir { } } -impl From for Operand { +impl From for Port { fn from(value: Variable) -> Self { Self::Variable(value) } diff --git a/pulsar-ir/src/memory.rs b/pulsar-ir/src/memory.rs index e648ea5..f386c6c 100644 --- a/pulsar-ir/src/memory.rs +++ b/pulsar-ir/src/memory.rs @@ -3,19 +3,32 @@ //! License as published by the Free Software Foundation, either version 3 of //! the License, or (at your option) any later version. -/// todo: everything about this, including like multilayering and stuff -pub struct Memory { +pub struct MemoryLevel { pub length: usize, - pub element: usize, pub bank: usize } +pub struct Memory { + levels: Vec, + element: usize +} + +// how to handle multidimensional memories with banking per level and other +// stuff? + impl Memory { pub fn new(length: usize, element: usize, bank: usize) -> Self { Self { - length, - element, - bank + levels: vec![MemoryLevel { length, bank }], + element } } + + pub fn levels(&self) -> &[MemoryLevel] { + &self.levels + } + + pub fn element(&self) -> usize { + self.element + } } diff --git a/pulsar-ir/src/pass.rs b/pulsar-ir/src/pass.rs index 4768dba..1d563a3 100644 --- a/pulsar-ir/src/pass.rs +++ b/pulsar-ir/src/pass.rs @@ -3,50 +3,102 @@ //! License as published by the Free Software Foundation, either version 3 of //! the License, or (at your option) any later version. +use crate::{ + component::Component, from_ast::AsGeneratorPool, visitor::Visitor +}; use canonicalize::Canonicalize; +use cell_alloc::CellAlloc; use collapse_control::CollapseControl; use copy_prop::CopyProp; use dead_code::DeadCode; - -use crate::{ - component::Component, from_ast::AsGeneratorPool, visitor::Visitor -}; +use well_formed::WellFormed; pub mod canonicalize; pub mod cell_alloc; pub mod collapse_control; pub mod copy_prop; pub mod dead_code; +pub mod well_formed; + +enum PassOp { + Boxed(Box>), + Coverge(usize), + EndConverge +} pub struct PassRunner { - passes: Vec>> + passes: Vec> } impl PassRunner

{ - /// This pass runner has no registered passes. Use [`Runner::default`] for - /// default passes to be already registered. - pub fn empty() -> Self { - Self { passes: Vec::new() } + /// The minimal pass runner permitted. + pub fn core() -> Self { + let mut runner = Self { passes: Vec::new() }; + runner.register(WellFormed); + runner.register(Canonicalize); + runner } pub fn register + 'static>(&mut self, pass: V) { - self.passes.push(Box::new(pass)); + self.passes.push(PassOp::Boxed(Box::new(pass))); + } + + pub fn register_converge( + &mut self, iter_limit: usize, f: F + ) { + self.passes.push(PassOp::Coverge(iter_limit)); + f(self); + self.passes.push(PassOp::EndConverge); } pub fn run(&mut self, comp: &mut Component, pool: &mut P) { - for pass in &mut self.passes { - pass.traverse_component(comp, pool) + let mut in_convergence = false; + let mut convergence_iter_limit = 0; + let mut convergence_region = Vec::new(); + + for pass_op in &mut self.passes { + match pass_op { + PassOp::Boxed(pass) => { + if in_convergence { + convergence_region.push(pass); + } else { + pass.traverse_component(comp, pool); + } + } + PassOp::Coverge(iter_limit) => { + in_convergence = true; + convergence_iter_limit = *iter_limit; + } + PassOp::EndConverge => { + if in_convergence { + loop { + let mut did_modify = false; + for pass in &mut convergence_region { + did_modify |= + pass.traverse_component(comp, pool); + } + if !did_modify || convergence_iter_limit == 0 { + break; + } + convergence_iter_limit -= 1; + } + in_convergence = false; + } + } + }; } } } impl Default for PassRunner

{ fn default() -> Self { - let mut runner = Self::empty(); - runner.register(Canonicalize); - runner.register(CopyProp); - runner.register(DeadCode::default()); + let mut runner = Self::core(); + runner.register_converge(10, |runner| { + runner.register(CopyProp); + runner.register(DeadCode::default()); + }); runner.register(CollapseControl); + runner.register(CellAlloc::default()); runner } } diff --git a/pulsar-ir/src/pass/canonicalize.rs b/pulsar-ir/src/pass/canonicalize.rs index b1dead0..b0667d9 100644 --- a/pulsar-ir/src/pass/canonicalize.rs +++ b/pulsar-ir/src/pass/canonicalize.rs @@ -6,15 +6,15 @@ use crate::{ control::{Control, Par, Seq}, from_ast::AsGeneratorPool, - operand::Operand, + port::Port, visitor::{Action, Visitor}, Ir }; -use either::Either::{self, Left, Right}; +use match_deref::match_deref; use pulsar_utils::pool::Handle; -use std::{cmp, mem, ops::Deref}; +use std::ops::Deref; -/// This pass is applied before all other passes. +/// This pass is applied after `WellFormed` and before all other passes. /// /// Incoming invariants: /// - The IR has been freshly created via recursive @@ -24,59 +24,62 @@ use std::{cmp, mem, ops::Deref}; /// Effects: /// - Folds chains of [`Operand::PartialAccess`]s into a single /// [`Operand::Access`] ([`Canonicalize::collapse_partial_accesses`]). -// - Re-orders IR statements in parallel control in the order that their -// left-hand-side variables were generated ([`Canonicalize::finish_par`]). pub struct Canonicalize; impl Canonicalize { /// Either the original control pair returned unchanged on the left or a /// replacement on the right. - fn collapse_partial_access_pair( - &self, first: Control, second: Control - ) -> Either<(Control, Control), Control> { - match (first, second) { - ( - Control::Enable(Ir::Assign( - temp, - Operand::PartialAccess(array, index) - )), - Control::Enable(Ir::Assign( - result, - Operand::PartialAccess(temp_again, index2) - )) - ) if *temp_again == temp => { - let Operand::Variable(array_var) = *array else { - panic!("not well-formed: first partial assign indexes non-variable") - }; - Right(Control::Enable(Ir::Assign( - result, - Operand::Access( - array_var, - vec![*index.clone(), *index2.clone()] - ) - ))) - } - ( - Control::Enable(Ir::Assign( - temp, - Operand::Access(array, mut indices) - )), - Control::Enable(Ir::Assign( - result, - Operand::PartialAccess(temp_again, index2) - )) - ) if *temp_again == temp => { - indices.push(*index2); - Right(Control::Enable(Ir::Assign( - result, - Operand::Access(array.clone(), indices) - ))) + fn collapse_partial_access_pair( + &self, first: &Control, second: &Control, pool: &mut P + ) -> Option { + match_deref! { + match &(first, second) { + ( + Control::Enable(Ir::Assign( + Deref @_temp, + Deref @Port::PartialAccess(Deref @array, index) + )), + Control::Enable(Ir::Assign( + result, + Deref @Port::PartialAccess(Deref @_temp_again, index2) + )) + ) if _temp_again == _temp => { + let Port::Variable(array_var) = *array else { + panic!("not well-formed: first partial assign indexes non-variable") + }; + Some(Control::Enable(Ir::Assign( + *result, + pool.add(Port::Access( + array_var, + vec![*index, *index2] + )) + ))) + } + ( + Control::Enable(Ir::Assign( + Deref @_temp, + Deref @Port::Access(array, indices) + )), + Control::Enable(Ir::Assign( + result, + Deref @Port::PartialAccess(Deref @_temp_again, index2) + )) + ) if _temp_again == _temp => { + let mut indices = indices.to_owned(); + indices.push(*index2); + Some(Control::Enable(Ir::Assign( + *result, + pool.add(Port::Access(*array, indices)) + ))) + } + _ => None } - other => Left(other) } } - fn collapse_partial_accesses(&self, children: &mut Vec>) { + fn collapse_partial_accesses( + &self, children: &mut Vec>, pool: &mut P + ) { // first, take pairs of PartialAccess/PartialAccess and // Access/PartialAccess and fold them up into Access let mut i = 0; @@ -84,19 +87,14 @@ impl Canonicalize { while i < children.len() { advance_i = 1; if i < children.len() - 1 { - match self.collapse_partial_access_pair( - mem::take(&mut children[i]), - mem::take(&mut children[i + 1]) + if let Some(replace) = self.collapse_partial_access_pair( + &children[i], + &children[i + 1], + pool ) { - Left((old_first, old_second)) => { - *children[i] = old_first; - *children[i + 1] = old_second; - } - Right(replace) => { - *children[i] = replace; - children.remove(i + 1); - advance_i = 0; - } + *children[i] = replace; + children.remove(i + 1); + advance_i = 0; } } i += advance_i; @@ -104,45 +102,32 @@ impl Canonicalize { // then, since only individual PartialAccess will remain PartialAccess, // we can just turn them into Access - for i in 0..children.len() - 1 { - if let Control::Enable(Ir::Assign( - result, - Operand::PartialAccess(array, index) - )) = children[i].deref() + for i in 0..(children.len() as isize) - 1 { + if let Control::Enable(Ir::Assign(result, access)) = + children[i as usize].deref() { - let Operand::Variable(array_var) = **array else { - panic!("not well-formed: first partial assign indexes non-variable") - }; - *children[i] = Control::Enable(Ir::assign( - result.clone(), - Operand::Access(array_var, vec![*index.clone()]) - )); + if let Port::PartialAccess(array, index) = access.deref() { + let Port::Variable(array_var) = **array else { + panic!("not well-formed: first partial assign indexes non-variable") + }; + *children[i as usize] = Control::Enable(Ir::Assign( + *result, + pool.add(Port::Access(array_var, vec![*index])) + )); + } } } } } impl Visitor

for Canonicalize { - fn start_seq(&mut self, seq: &mut Seq, _pool: &mut P) -> Action { - self.collapse_partial_accesses(&mut seq.children); + fn start_seq(&mut self, seq: &mut Seq, pool: &mut P) -> Action { + self.collapse_partial_accesses(&mut seq.children, pool); Action::None } - fn start_par(&mut self, par: &mut Par, _pool: &mut P) -> Action { - self.collapse_partial_accesses(&mut par.children); - - // par.children.sort_by(|a, b| match (a.deref(), b.deref()) { - // (Control::Enable(ir_a), Control::Enable(ir_b)) => { - // match (ir_a.kill(), ir_b.kill()) { - // (Operand::Variable(var_a), Operand::Variable(var_b)) => { - // var_a.cmp(&var_b) - // } - // _ => cmp::Ordering::Less - // } - // } - // _ => cmp::Ordering::Less - // }); - + fn start_par(&mut self, par: &mut Par, pool: &mut P) -> Action { + self.collapse_partial_accesses(&mut par.children, pool); Action::None } } diff --git a/pulsar-ir/src/pass/cell_alloc.rs b/pulsar-ir/src/pass/cell_alloc.rs index 78b1b49..70c54ee 100644 --- a/pulsar-ir/src/pass/cell_alloc.rs +++ b/pulsar-ir/src/pass/cell_alloc.rs @@ -3,8 +3,47 @@ //! License as published by the Free Software Foundation, either version 3 of //! the License, or (at your option) any later version. -use crate::{from_ast::AsGeneratorPool, visitor::Visitor}; +use std::mem; -pub struct CellAlloc; +use crate::{ + cell::Cell, + component::Component, + control::For, + from_ast::AsGeneratorPool, + port::Port, + variable::Variable, + visitor::{Action, Visitor}, + Ir +}; -impl Visitor

for CellAlloc {} +#[derive(Default)] +pub struct CellAlloc { + allocs: Vec<(Variable, Cell)> +} + +impl Visitor

for CellAlloc { + fn start_for(&mut self, for_: &mut For, _pool: &mut P) -> Action { + let index_reg_bits = match for_.exclusive_upper_bound() { + Port::Constant(value) => { + 64 - ((value - 1).leading_zeros() as usize) + } + _ => 64 + }; + self.allocs + .push((for_.variant(), Cell::Register(index_reg_bits))); + Action::None + } + + fn start_enable(&mut self, enable: &mut Ir, _pool: &mut P) -> Action { + if let Port::Variable(var) = *enable.kill() { + self.allocs.push((var, Cell::Register(64))); + } + Action::None + } + + fn finish_component(&mut self, comp: &mut Component, pool: &mut P) { + for (var, cell) in mem::take(&mut self.allocs) { + comp.cell_alloc.insert(var, pool.add(cell)); + } + } +} diff --git a/pulsar-ir/src/pass/collapse_control.rs b/pulsar-ir/src/pass/collapse_control.rs index 5b57cd1..13b8b01 100644 --- a/pulsar-ir/src/pass/collapse_control.rs +++ b/pulsar-ir/src/pass/collapse_control.rs @@ -31,11 +31,11 @@ impl CollapseControl { } impl Visitor

for CollapseControl { - fn finish_seq(&mut self, seq: &mut Seq, pool: &mut P) -> Action { + fn finish_seq(&mut self, seq: &mut Seq, _pool: &mut P) -> Action { self.collapse(&mut seq.children) } - fn finish_par(&mut self, par: &mut Par, pool: &mut P) -> Action { + fn finish_par(&mut self, par: &mut Par, _pool: &mut P) -> Action { self.collapse(&mut par.children) } } diff --git a/pulsar-ir/src/pass/copy_prop.rs b/pulsar-ir/src/pass/copy_prop.rs index a0b6cfe..0d5786f 100644 --- a/pulsar-ir/src/pass/copy_prop.rs +++ b/pulsar-ir/src/pass/copy_prop.rs @@ -6,68 +6,77 @@ use crate::{ control::{Control, Par, Seq}, from_ast::AsGeneratorPool, - operand::Operand, - variable::Variable, + port::Port, visitor::{Action, Visitor}, Ir }; use pulsar_utils::pool::Handle; -use std::{ - cmp, - collections::HashMap, - ops::{Deref, DerefMut} -}; +use std::collections::HashMap; pub struct CopyProp; +/// Replaces the port that `ir` assigns to with `new_kill`. +fn replace_kill(ir: &Ir, new_kill: Handle) -> Ir { + match ir { + Ir::Add(_, a, b) => Ir::Add(new_kill, *a, *b), + Ir::Mul(_, a, b) => Ir::Mul(new_kill, *a, *b), + Ir::Assign(_, b) => Ir::Assign(new_kill, *b) + } +} + impl CopyProp { fn copy_prop(&self, seq: &mut [Handle]) { - let mut assigns = HashMap::::new(); + let mut assigns = HashMap::, Ir>::new(); fn replace( - assigns: &HashMap, operand: &Operand - ) -> Operand { - if let Operand::Variable(var) = operand { - assigns.get(var).unwrap_or(operand) - } else { - operand - } - .clone() + assigns: &HashMap, Ir>, port: Handle + ) -> Handle { + *assigns + .get(&port) + .and_then(|ir| { + if let Ir::Assign(_, src) = ir { + Some(src) + } else { + None + } + }) + .unwrap_or(&port) } for child in seq { if let Control::Enable(ir) = &mut **child { - *ir = match ir { + *ir = match *ir { Ir::Add(kill, src1, src2) => Ir::Add( - *kill, + kill, replace(&assigns, src1), replace(&assigns, src2) ), Ir::Mul(kill, src1, src2) => Ir::Mul( - *kill, + kill, replace(&assigns, src1), replace(&assigns, src2) ), Ir::Assign(kill, src) => { - let src = replace(&assigns, src); - if let Operand::Variable(kill) = kill { - assigns.insert(*kill, src.clone()); + if let Some(latest_killer) = assigns.get(&src) { + replace_kill(latest_killer, kill) + } else { + Ir::Assign(kill, src) } - Ir::Assign(kill.clone(), src) } - } + }; + assigns.insert(ir.kill(), ir.clone()); } } } } impl Visitor

for CopyProp { - fn finish_seq(&mut self, seq: &mut Seq, pool: &mut P) -> Action { + fn finish_seq(&mut self, seq: &mut Seq, _pool: &mut P) -> Action { self.copy_prop(&mut seq.children); Action::None } - fn finish_par(&mut self, par: &mut Par, pool: &mut P) -> Action { + fn finish_par(&mut self, par: &mut Par, _pool: &mut P) -> Action { self.copy_prop(&mut par.children); Action::None } diff --git a/pulsar-ir/src/pass/dead_code.rs b/pulsar-ir/src/pass/dead_code.rs index 3febb1b..7532801 100644 --- a/pulsar-ir/src/pass/dead_code.rs +++ b/pulsar-ir/src/pass/dead_code.rs @@ -5,29 +5,31 @@ use std::{ collections::{HashMap, HashSet}, - mem::MaybeUninit, ops::Deref }; -use either::Either::{self, Left, Right}; use pulsar_utils::{id::Id, pool::Handle}; use crate::{ component::Component, - control::{Control, For, Par, Seq}, + control::{Control, For, IfElse, Par, Seq}, from_ast::AsGeneratorPool, - operand::Operand, + port::Port, variable::Variable, visitor::{Action, Visitor} }; -/// Conservative dead-code elimination. If an operand assigned to does not -/// appear within its scope, and it is not a component port, then the assignment -/// in which it appears is eliminated. +/// Conservative dead-code elimination. If an port assigned to does not +/// appear within its scope, and it is not an output port, then the assignment +/// in which it appears is eliminated. This is possible because of the IR +/// invariant that lvalues appear immediately on the kill-side of IR operations. +/// Due to bad design on my part, each IR operation does not get an id, so dead +/// code messes up whenever there are at least two reads of a variable, even if +/// that variable is actually dead. #[derive(Default)] pub struct DeadCode { - comp_ports: Vec, - gen_sets: HashMap> + output_ports: Vec, + gen_sets: HashMap>> } impl DeadCode { @@ -38,12 +40,13 @@ impl DeadCode { fn calculate_gen_set( &mut self, id: Id, children: &mut Vec> ) { - // bad software engineering here lol + // bad software engineering here lol. see main struct doc comment for + // why it's slightly broken let mut gen_set = HashSet::new(); for child in children { if let Control::Enable(ir) = (*child).deref() { - for operand in ir.gen() { - gen_set.insert(operand.clone()); + for port in ir.gen() { + gen_set.insert(port); } } else if !matches!(**child, Control::Empty) { // flatten and remove original child set @@ -68,46 +71,47 @@ impl DeadCode { self.gen_sets.entry(id).or_default().extend(child_gen_set); } - /// Removes all assignments with unused operands. + /// Removes all assignments with unused ports. /// /// Precondition: the gen set exists for the control with id `id` and /// children `children` (aka [`DeadCode::calculate_gen_set`] or /// [`DeadCode::extend_gen_set`]). - fn dead_code(&self, id: Id, children: &mut Vec>) { + fn dead_code(&self, id: Id, children: &mut Vec>) -> Action { let gen_set = self .gen_sets .get(&id) .expect("DeadCode::calculate_gen_set was not called"); + let mut did_modify = false; for i in (0..children.len()).rev() { if let Control::Enable(ir) = &*children[i] { let kill = ir.kill(); - if !gen_set.contains(&kill) && { - if let Some(kill_var) = kill.gen_var() { - !self.comp_ports.contains(&kill_var) - } else { - true - } - } { + if !gen_set.contains(&kill) + && kill + .vars() + .iter() + .all(|kill_var| !self.output_ports.contains(kill_var)) + { children.remove(i); + did_modify = true; } } } + if did_modify { + Action::ModifiedInternally + } else { + Action::None + } } } impl Visitor

for DeadCode { - fn start_component(&mut self, comp: &mut Component) { + fn start_component(&mut self, comp: &mut Component, _pool: &mut P) { log::warn!( "You're using the dead code pass, which is currently unstable" ); - self.comp_ports = comp - .inputs() - .iter() - .chain(comp.outputs()) - .map(|(var, _)| var) - .cloned() - .collect(); + self.output_ports = + comp.outputs().iter().map(|(var, _)| var).cloned().collect(); } fn finish_for(&mut self, for_: &mut For, _pool: &mut P) -> Action { @@ -115,15 +119,21 @@ impl Visitor

for DeadCode { Action::None } + fn finish_if_else( + &mut self, if_else: &mut IfElse, _pool: &mut P + ) -> Action { + self.extend_gen_set(if_else.id, if_else.true_branch.id()); + self.extend_gen_set(if_else.id, if_else.false_branch.id()); + Action::None + } + fn finish_seq(&mut self, seq: &mut Seq, _pool: &mut P) -> Action { self.calculate_gen_set(seq.id, &mut seq.children); - self.dead_code(seq.id, &mut seq.children); - Action::None + self.dead_code(seq.id, &mut seq.children) } fn finish_par(&mut self, par: &mut Par, _pool: &mut P) -> Action { self.calculate_gen_set(par.id, &mut par.children); - self.dead_code(par.id, &mut par.children); - Action::None + self.dead_code(par.id, &mut par.children) } } diff --git a/pulsar-ir/src/pass/well_formed.rs b/pulsar-ir/src/pass/well_formed.rs new file mode 100644 index 0000000..54a809f --- /dev/null +++ b/pulsar-ir/src/pass/well_formed.rs @@ -0,0 +1,26 @@ +//! Copyright (C) 2024 Ethan Uppal. This program is free software: you can +//! redistribute it and/or modify it under the terms of the GNU General Public +//! License as published by the Free Software Foundation, either version 3 of +//! the License, or (at your option) any later version. + +use crate::{ + from_ast::AsGeneratorPool, + port::Port, + visitor::{Action, Visitor}, + Ir +}; + +/// This pass and `Canonicalize` after it are applied before every other pass. +pub struct WellFormed; + +impl Visitor

for WellFormed { + fn start_enable(&mut self, enable: &mut Ir, _pool: &mut P) -> Action { + if let Port::Constant(_) = &*enable.kill() { + panic!( + "port {} on lhs of ir operation is not an lvalue", + enable.kill() + ); + } + Action::None + } +} diff --git a/pulsar-ir/src/operand.rs b/pulsar-ir/src/port.rs similarity index 54% rename from pulsar-ir/src/operand.rs rename to pulsar-ir/src/port.rs index 256ba7a..603000b 100644 --- a/pulsar-ir/src/operand.rs +++ b/pulsar-ir/src/port.rs @@ -3,30 +3,45 @@ //! License as published by the Free Software Foundation, either version 3 of //! the License, or (at your option) any later version. +use pulsar_utils::pool::Handle; + use super::variable::Variable; use std::fmt::{self, Display}; +/// A port represents a constant or an lvalue. #[derive(PartialEq, Eq, Hash, Clone)] -pub enum Operand { +pub enum Port { Constant(i64), Variable(Variable), - /// Non-canonical - PartialAccess(Box, Box), - Access(Variable, Vec) + /// Non-canonical -- the `Canonicalize` collapses these into + /// [`Port::Access`]. + PartialAccess(Handle, Handle), + Access(Variable, Vec>) } -impl Operand { - /// Assumes that - pub fn gen_var(&self) -> Option { +impl Port { + /// The variables this port references. + pub fn vars(&self) -> Vec { match self { - Operand::Variable(var) | Operand::Access(var, _) => Some(*var), - Operand::PartialAccess(operand, _) => operand.gen_var(), - _ => None + Port::Variable(var) => vec![*var], + Port::Access(array_var, indices) => { + let mut result = vec![*array_var]; + for index in indices { + result.extend(index.vars()); + } + result + } + Port::PartialAccess(array, index) => { + let mut result = array.vars(); + result.extend(index.vars()); + result + } + _ => vec![] } } } -impl Display for Operand { +impl Display for Port { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Constant(value) => value.fmt(f), diff --git a/pulsar-ir/src/variable.rs b/pulsar-ir/src/variable.rs index 0d45d66..0f1d9b1 100644 --- a/pulsar-ir/src/variable.rs +++ b/pulsar-ir/src/variable.rs @@ -4,7 +4,10 @@ //! the License, or (at your option) any later version. use pulsar_utils::id::Id; -use std::fmt::{self, Display}; +use std::{ + cmp, + fmt::{self, Display} +}; #[derive(PartialEq, Eq, Hash, Clone, Copy)] pub struct Variable { @@ -24,13 +27,13 @@ impl Display for Variable { } impl PartialOrd for Variable { - fn partial_cmp(&self, other: &Self) -> Option { - self.id.partial_cmp(&other.id) + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) } } impl Ord for Variable { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { + fn cmp(&self, other: &Self) -> cmp::Ordering { self.id.cmp(&other.id) } } diff --git a/pulsar-ir/src/visitor.rs b/pulsar-ir/src/visitor.rs index 3f4e2e6..6953e30 100644 --- a/pulsar-ir/src/visitor.rs +++ b/pulsar-ir/src/visitor.rs @@ -12,33 +12,50 @@ use crate::{ pub enum Action { None, + ModifiedInternally, Remove, Replace(Control) } impl Action { - fn execute(self, control: &mut Control) { + fn execute(self, control: &mut Control, did_modify: &mut bool) { match self { Action::None => {} + Action::ModifiedInternally => { + *did_modify = true; + } Action::Remove => { *control = Control::Empty; + *did_modify = true; } Action::Replace(new_control) => { *control = new_control; + *did_modify = true; } } } } +/// To override traversal behavior, implement `traverse_component` and +/// `traverse_control`, although it is highly unlikely to use anything beside +/// the default implementation of these functions. pub trait Visitor { #[allow(unused_variables)] - fn start_component(&mut self, comp: &mut Component) {} + fn start_component(&mut self, comp: &mut Component, pool: &mut P) {} + #[allow(unused_variables)] + fn finish_component(&mut self, comp: &mut Component, pool: &mut P) {} #[allow(unused_variables)] fn start_for(&mut self, for_: &mut For, pool: &mut P) -> Action { Action::None } #[allow(unused_variables)] + fn start_for_with_comp( + &mut self, for_: &mut For, comp: &mut Component, pool: &mut P + ) -> Action { + Action::None + } + #[allow(unused_variables)] fn start_seq(&mut self, seq: &mut Seq, pool: &mut P) -> Action { Action::None } @@ -72,12 +89,22 @@ pub trait Visitor { Action::None } - fn traverse_component(&mut self, comp: &mut Component, pool: &mut P) { - self.start_component(comp); - self.traverse_control(&mut comp.cfg, pool); + /// Returns whether the traversal had any effect on the component. + fn traverse_component( + &mut self, comp: &mut Component, pool: &mut P + ) -> bool { + self.start_component(comp, pool); + let did_modify = self.traverse_control(&mut comp.cfg, pool); + self.finish_component(comp, pool); + did_modify } - fn traverse_control(&mut self, control: &mut Control, pool: &mut P) { + /// Returns whether the traversal had any effect on the component. + fn traverse_control( + &mut self, control: &mut Control, pool: &mut P + ) -> bool { + let mut did_modify = false; + match control { Control::Empty => Action::None, Control::For(for_) => self.start_for(for_, pool), @@ -86,26 +113,28 @@ pub trait Visitor { Control::IfElse(if_else) => self.start_if_else(if_else, pool), Control::Enable(enable) => self.start_enable(enable, pool) } - .execute(control); + .execute(control, &mut did_modify); match control { Control::Empty => {} Control::For(for_) => { - self.traverse_control(&mut for_.body, pool); + did_modify |= self.traverse_control(&mut for_.body, pool); } Control::Seq(seq) => { for child in &mut seq.children { - self.traverse_control(child, pool); + did_modify |= self.traverse_control(child, pool); } } Control::Par(par) => { for child in &mut par.children { - self.traverse_control(child, pool); + did_modify |= self.traverse_control(child, pool); } } Control::IfElse(if_else) => { - self.traverse_control(&mut if_else.true_branch, pool); - self.traverse_control(&mut if_else.false_branch, pool); + did_modify |= + self.traverse_control(&mut if_else.true_branch, pool); + did_modify |= + self.traverse_control(&mut if_else.false_branch, pool); } Control::Enable(_) => {} } @@ -118,6 +147,8 @@ pub trait Visitor { Control::IfElse(if_else) => self.finish_if_else(if_else, pool), Control::Enable(_) => Action::None } - .execute(control); + .execute(control, &mut did_modify); + + did_modify } } diff --git a/pulsar-utils/src/error.rs b/pulsar-utils/src/error.rs index f9d5615..e4c64e5 100644 --- a/pulsar-utils/src/error.rs +++ b/pulsar-utils/src/error.rs @@ -329,7 +329,13 @@ impl ErrorManager { } } - /// Whether the error manager has recorded an error. + /// Whether the error manager contains any recorded items, not just primary + /// errors. + pub fn has_items(&self) -> bool { + !self.errors.is_empty() + } + + /// Whether the error manager has recorded a primary error. pub fn has_errors(&self) -> bool { self.primary_count > 0 } diff --git a/pulsar-utils/src/pool.rs b/pulsar-utils/src/pool.rs index e7dbd39..e386b90 100644 --- a/pulsar-utils/src/pool.rs +++ b/pulsar-utils/src/pool.rs @@ -81,9 +81,11 @@ impl PartialEq for Handle { impl Eq for Handle {} -impl Hash for Handle { +impl Hash for Handle { fn hash(&self, state: &mut H) { - self.pointer.hash(state); + unsafe { + (*self.pointer).hash(state); + } } } diff --git a/redesign.md b/redesign.md index f1c9260..a639bd6 100644 --- a/redesign.md +++ b/redesign.md @@ -56,3 +56,10 @@ also as a note what I realized obviously is that the agen is also gonna have to currently the IR gen stuff is really messy like the gen function is messy, component is messy, etc + + +ok so here's the plan wrt ports +the main issue is that we need to guarantee that a port can always represent a single lvalue so that we can treat the IR as a standard software assignment IR where order of operands in assignment matters - since otherwise you have to treat it as a graph +so there are two main things w need to do for this +- one is to somehow guarantee that when an expression is an lvalue, the gen_expr function doesn't add any additional assignments. +- the other is to have some sort of way for ports to represent arbitrary extion of something including chaining of . etc which means like we have to probably streict like structs can't contain arrays yeah this is a good idea diff --git a/src/context.rs b/src/context.rs index f16a72f..e482b7b 100644 --- a/src/context.rs +++ b/src/context.rs @@ -18,7 +18,8 @@ use pulsar_frontend::{ use pulsar_ir::{ cell::Cell, control::{AsControlPool, Control}, - from_ast::AsGeneratorPool + from_ast::AsGeneratorPool, + port::Port }; use pulsar_utils::pool::{AsPool, Handle, Pool}; use std::io; @@ -34,7 +35,8 @@ pub struct Context { type_constraints: Pool, liquid_type_constraints: Pool, controls: Pool, - cells: Pool + cells: Pool, + ports: Pool } impl Context { @@ -49,7 +51,8 @@ impl Context { type_constraints: Pool::new()?, liquid_type_constraints: Pool::new()?, controls: Pool::new()?, - cells: Pool::new()? + cells: Pool::new()?, + ports: Pool::new()? }) } } @@ -98,4 +101,5 @@ impl AsInferencePool for Context {} as_pool!(Control, usize, controls); impl AsControlPool for Context {} as_pool!(Cell, (), cells); +as_pool!(Port, (), ports); impl AsGeneratorPool for Context {} diff --git a/src/main.rs b/src/main.rs index 8d16266..0d36299 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,26 +8,22 @@ // Output, PulsarBackend // }; use pulsar_frontend::{ - ast::{expr::Expr, node::AsNodePool}, - lexer::Lexer, - parser::Parser, - type_inferer::TypeInferer + lexer::Lexer, parser::Parser, type_inferer::TypeInferer }; use pulsar_ir::{from_ast, pass::PassRunner}; use pulsar_lang::{context::Context, utils::OptionCheckError}; -use pulsar_utils::{ - error::ErrorManager, - pool::{AsPool, HandleArray}, - span::{Source, SpanProvider} -}; +use pulsar_utils::{error::ErrorManager, span::Source}; use std::env; pub fn main() -> anyhow::Result<()> { env_logger::builder() - .filter_level(log::LevelFilter::Warn) + .filter(None, log::LevelFilter::Info) + .parse_default_env() .format_timestamp(None) .init(); + log::info!("Parsing..."); + let mut args = env::args(); args.next(); // ignore program path let filename = args.next().unwrap_or("data/test.plsr".into()); @@ -45,22 +41,33 @@ pub fn main() -> anyhow::Result<()> { .parse() .check_errors(&mut error_manager)?; + log::info!("Inferring types..."); + let ast = TypeInferer::new(ast, &mut ctx, &mut error_manager) .infer() .check_errors(&mut error_manager)?; + { + use pulsar_frontend::ast::{expr::Expr, node::AsNodePool}; + use pulsar_utils::{ + pool::{AsPool, HandleArray}, + span::SpanProvider + }; + + let exprs: HandleArray = ctx.as_pool_mut().as_array(); + for expr in exprs { + if ctx.get_ty(expr).is_invalid() { + panic!("[{}] {} did not have type resolved", expr.span(), expr); + } + println!("{} {}: {}", expr.span(), expr, ctx.get_ty(expr)); + } + } + // for decl in ast { // println!("{}", decl); // } - // let exprs: HandleArray = ctx.as_pool_mut().as_array(); - // for expr in exprs { - // if ctx.get_ty(expr).is_invalid() { - // println!("{} {}: invalid type", expr.span(), expr); - // } else { - // println!("{} {}: {}", expr.span(), expr, ctx.get_ty(expr)); - // } - // } + log::info!("Optimizing..."); let pass_runner = PassRunner::default(); let comps = from_ast::ast_to_ir(ast, pass_runner, &mut ctx); @@ -69,6 +76,8 @@ pub fn main() -> anyhow::Result<()> { println!("{}", comp); } + log::info!("Emitting calyx accelerator and address generator (TODO)..."); + // let command_output = Command::new("fud") // .args(["c", "global.root"]) // .output() diff --git a/src/utils.rs b/src/utils.rs index 8f2e1e2..5be9730 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,8 +3,6 @@ //! License as published by the Free Software Foundation, either version 3 of //! the License, or (at your option) any later version. -use std::io::stdout; - use anyhow::anyhow; use pulsar_utils::error::ErrorManager; @@ -17,7 +15,7 @@ pub trait OptionCheckError { impl OptionCheckError for Option { /// Unwraps this value into an `Ok(...)` if it's `Some(...)`, otherwise, /// writes the error messages in `error_manager` to [`std::io::stdout()`] - /// and returns an `Err(...)`. + /// and returns an `Err(...)`. Non-error messages are always written. fn check_errors( self, error_manager: &mut ErrorManager ) -> anyhow::Result { diff --git a/tests/data/infer10.plsr b/tests/data/infer10.plsr index 82cadfe..0dfab50 100644 --- a/tests/data/infer10.plsr +++ b/tests/data/infer10.plsr @@ -1,3 +1,3 @@ -func bad() -> Int { - return +func bad() -> (return: Int) { + return = [] } diff --git a/tests/data/infer11.plsr b/tests/data/infer11.plsr index 1abf126..b8128a8 100644 --- a/tests/data/infer11.plsr +++ b/tests/data/infer11.plsr @@ -1,8 +1,9 @@ -func square(value: Int) -> Int { - return value * value +func main() { + let arr = [1] + let first = arr[0] } -func sum_squares(input: Int[4]) -> Int { - let squares = map<1>(square, input) - return 0 +func main2() { + let arr = [[1]] + let first = arr[0] } diff --git a/tests/data/infer12.plsr b/tests/data/infer12.plsr deleted file mode 100644 index 18ce8c9..0000000 --- a/tests/data/infer12.plsr +++ /dev/null @@ -1,8 +0,0 @@ -pure func square(value: Int) -> Int { - return value * value -} - -func sum_squares(input: Int[4]) -> Int { - let squares = map<1>(square, input) - return 0 -} diff --git a/tests/data/infer13.plsr b/tests/data/infer13.plsr deleted file mode 100644 index 674f9b8..0000000 --- a/tests/data/infer13.plsr +++ /dev/null @@ -1,7 +0,0 @@ -pure func multiply(x: Int, y: Int) -> Int { - return x * y -} - -pure func square(value: Int) -> Int { - return multiply(value, value) -} diff --git a/tests/data/infer14.plsr b/tests/data/infer14.plsr deleted file mode 100644 index 33a4978..0000000 --- a/tests/data/infer14.plsr +++ /dev/null @@ -1,7 +0,0 @@ -func multiply(x: Int, y: Int) -> Int { - return x * y -} - -pure func square(value: Int) -> Int { - return multiply(value, value) -} diff --git a/tests/data/infer15.plsr b/tests/data/infer15.plsr deleted file mode 100644 index f3397aa..0000000 --- a/tests/data/infer15.plsr +++ /dev/null @@ -1,20 +0,0 @@ -pure func square(value: Int) -> Int { - return value * value -} - -func sum_squares(input: Int[4]) -> Int { - let squares = map<4>(square, input) - return 0 - let a = 3 - return 0 -} - -pure func square2(value: Int) -> Int { - return value * value -} - -func sum_squares2(input: Int[4]) -> Int { - let squares = map<4>(square2, input) - return 0 - return 0 -} diff --git a/tests/data/infer16.plsr b/tests/data/infer16.plsr deleted file mode 100644 index b8128a8..0000000 --- a/tests/data/infer16.plsr +++ /dev/null @@ -1,9 +0,0 @@ -func main() { - let arr = [1] - let first = arr[0] -} - -func main2() { - let arr = [[1]] - let first = arr[0] -} diff --git a/tests/data/infer7.plsr b/tests/data/infer7.plsr index d0d342b..f234051 100644 --- a/tests/data/infer7.plsr +++ b/tests/data/infer7.plsr @@ -1,9 +1,9 @@ -pure func square(x: Int64) -> Int64 { - return x * x +func square(x: Int64) -> (y: Int64) { + y = x * x } -pure func add(x: Int64, y: Int64) -> Int64 { - return x + y +func add(x: Int64, y: Int64) -> (z: Int64) { + z = x + y } func main() { diff --git a/tests/data/infer8.plsr b/tests/data/infer8.plsr index e52f974..3aad8d7 100644 --- a/tests/data/infer8.plsr +++ b/tests/data/infer8.plsr @@ -1,3 +1,3 @@ -func does_not_return() -> Int { +func does_not_return() -> (return: Int) { } diff --git a/tests/data/infer9.plsr b/tests/data/infer9.plsr index fe6fc1f..47e8cbe 100644 --- a/tests/data/infer9.plsr +++ b/tests/data/infer9.plsr @@ -1,3 +1,3 @@ func main() { - return 1 + return = 1 } diff --git a/tests/snapshots/test_parser__tests__parser_5.snap b/tests/snapshots/test_parser__tests__parser_5.snap index 6fba9eb..876ca8c 100644 --- a/tests/snapshots/test_parser__tests__parser_5.snap +++ b/tests/snapshots/test_parser__tests__parser_5.snap @@ -4,11 +4,11 @@ expression: "parser_output(&format!(\"tests/data/parser{}.plsr\",\n --- func main() -> () { let val: Int64 = 1 - let arr1d: Int64[5] = [1, 2, ...] - let zeros64: Int64[64] = [...] - let arr2d: Int64[5][6] = [...] - let warn: Int64[0] = [...] - let warn2: Int64[0][4] = [...] + let arr1d: Int64[{ x | x = 5 }] = [1, 2, ...] + let zeros64: Int64[{ x | x = 64 }] = [...] + let arr2d: Int64[{ x | x = 5 }][{ x | x = 6 }] = [...] + let warn: Int64[{ x | x = 0 }] = [...] + let warn2: Int64[{ x | x = 0 }][{ x | x = 4 }] = [...] } warning[W0008]: parser5.plsr:6:19: Array size is zero │ diff --git a/tests/snapshots/test_parser__tests__parser_6.snap b/tests/snapshots/test_parser__tests__parser_6.snap index 95f9d42..33e0b33 100644 --- a/tests/snapshots/test_parser__tests__parser_6.snap +++ b/tests/snapshots/test_parser__tests__parser_6.snap @@ -3,6 +3,6 @@ source: tests/test_parser.rs expression: "parser_output(&format!(\"tests/data/parser{}.plsr\",\n 6)).expect(\"failed to parse input\")" --- func main() -> () { - let input: Int64[64] = [...] + let input: Int64[{ x | x = 64 }] = [...] let squares = input } diff --git a/tests/snapshots/test_parser__tests__parser_7.snap b/tests/snapshots/test_parser__tests__parser_7.snap index fed79e7..2093459 100644 --- a/tests/snapshots/test_parser__tests__parser_7.snap +++ b/tests/snapshots/test_parser__tests__parser_7.snap @@ -6,4 +6,4 @@ func param1(a: Int64) -> () {} func param2(b: Int64) -> () {} func param3(a: Int64, b: Int64) -> () {} func param4(a: Int64, b: Int64) -> () {} -func param5(a: Int64, b: Int64[64], my_param40: Int64[40][40]) -> () {} +func param5(a: Int64, b: Int64[{ x | x = 64 }], my_param40: Int64[{ x | x = 40 }][{ x | x = 40 }]) -> () {} diff --git a/tests/snapshots/test_parser__tests__parser_8.snap b/tests/snapshots/test_parser__tests__parser_8.snap index 6aaaaec..dc1eb38 100644 --- a/tests/snapshots/test_parser__tests__parser_8.snap +++ b/tests/snapshots/test_parser__tests__parser_8.snap @@ -4,5 +4,5 @@ expression: "parser_output(&format!(\"tests/data/parser{}.plsr\",\n --- func unit() -> (unit: Unit) {} func int() -> (int: Int64) {} -func array() -> (array: Int64[64]) {} +func array() -> (array: Int64[{ x | x = 64 }]) {} func array(input: MyCustomType1) -> (custom: MyCustomType2) {} diff --git a/tests/snapshots/test_typeinferer__tests__type_inferer_1.snap b/tests/snapshots/test_typeinferer__tests__type_inferer_1.snap new file mode 100644 index 0000000..50ba40b --- /dev/null +++ b/tests/snapshots/test_typeinferer__tests__type_inferer_1.snap @@ -0,0 +1,10 @@ +--- +source: tests/test_typeinferer.rs +expression: "typeinferer_output(&format!(\"tests/data/infer{}.plsr\",\n 1)).expect(\"failed to parse/infer input\")" +--- +error[E0009]: infer1.plsr:2:25: Unbound function or variable `a` + │ + 1 │ func main() { + 2 │ let arr: Int[64] = [a, b, ...] + 3 │ } + │ diff --git a/tests/snapshots/test_typeinferer__tests__type_inferer_10.snap b/tests/snapshots/test_typeinferer__tests__type_inferer_10.snap new file mode 100644 index 0000000..d354635 --- /dev/null +++ b/tests/snapshots/test_typeinferer__tests__type_inferer_10.snap @@ -0,0 +1,17 @@ +--- +source: tests/test_typeinferer.rs +expression: "typeinferer_output(&format!(\"tests/data/infer{}.plsr\",\n 10)).expect(\"failed to parse/infer input\")" +--- +error[E0010]: infer10.plsr:1:24: Failed to unify types `Int64` and `'t0[{ x | x = 0 }]` + │ + 1 │ func bad() -> (return: Int) { + │ └── Expected `Int64` here + 2 │ return = [] + │ + ... + │ + 1 │ func bad() -> (return: Int) { + 2 │ return = [] + │ └─ but received `'t0[{ x | x = 0 }]`. + 3 │ } + │ diff --git a/tests/snapshots/test_typeinferer__tests__type_inferer_11.snap b/tests/snapshots/test_typeinferer__tests__type_inferer_11.snap new file mode 100644 index 0000000..67384e1 --- /dev/null +++ b/tests/snapshots/test_typeinferer__tests__type_inferer_11.snap @@ -0,0 +1,23 @@ +--- +source: tests/test_typeinferer.rs +expression: "typeinferer_output(&format!(\"tests/data/infer{}.plsr\",\n 11)).expect(\"failed to parse/infer input\")" +--- +func main() -> () { + let arr = [1] + let first = (arr[0]) +} +func main2() -> () { + let arr = [[1]] + let first = (arr[0]) +} +[infer11.plsr:2:16, infer11.plsr:2:17) 1: Int64 +[infer11.plsr:2:15, infer11.plsr:2:18) [1]: Int64[{ x | x = 1 }] +[infer11.plsr:3:17, infer11.plsr:3:20) arr: Int64[{ x | x = 1 }] +[infer11.plsr:3:21, infer11.plsr:3:22) 0: Int64 +[infer11.plsr:3:17, infer11.plsr:3:23) (arr[0]): Int64 +[infer11.plsr:7:17, infer11.plsr:7:18) 1: Int64 +[infer11.plsr:7:16, infer11.plsr:7:19) [1]: Int64[{ x | x = 1 }] +[infer11.plsr:7:15, infer11.plsr:7:20) [[1]]: Int64[{ x | x = 1 }][{ x | x >= 0 }] +[infer11.plsr:8:17, infer11.plsr:8:20) arr: Int64[{ x | x = 1 }][{ x | x >= 0 }] +[infer11.plsr:8:21, infer11.plsr:8:22) 0: Int64 +[infer11.plsr:8:17, infer11.plsr:8:23) (arr[0]): Int64[{ x | x = 1 }] diff --git a/tests/snapshots/test_typeinferer__tests__type_inferer_2.snap b/tests/snapshots/test_typeinferer__tests__type_inferer_2.snap new file mode 100644 index 0000000..2501f4a --- /dev/null +++ b/tests/snapshots/test_typeinferer__tests__type_inferer_2.snap @@ -0,0 +1,22 @@ +--- +source: tests/test_typeinferer.rs +expression: "typeinferer_output(&format!(\"tests/data/infer{}.plsr\",\n 2)).expect(\"failed to parse/infer input\")" +--- +func main() -> () { + let a = 1 + let b = 2 + let arr: Int64[{ x | x = 64 }] = [a, b, ...] + let complex = ((1 + (2 * 3)) + 4) +} +[infer2.plsr:2:13, infer2.plsr:2:14) 1: Int64 +[infer2.plsr:3:13, infer2.plsr:3:14) 2: Int64 +[infer2.plsr:4:25, infer2.plsr:4:26) a: Int64 +[infer2.plsr:4:28, infer2.plsr:4:29) b: Int64 +[infer2.plsr:4:24, infer2.plsr:4:35) [a, b, ...]: Int64[{ x | x = 64 }] +[infer2.plsr:5:19, infer2.plsr:5:20) 1: Int64 +[infer2.plsr:5:23, infer2.plsr:5:24) 2: Int64 +[infer2.plsr:5:27, infer2.plsr:5:28) 3: Int64 +[infer2.plsr:5:23, infer2.plsr:5:28) (2 * 3): Int64 +[infer2.plsr:5:19, infer2.plsr:5:28) (1 + (2 * 3)): Int64 +[infer2.plsr:5:31, infer2.plsr:5:32) 4: Int64 +[infer2.plsr:5:19, infer2.plsr:5:32) ((1 + (2 * 3)) + 4): Int64 diff --git a/tests/snapshots/test_typeinferer__tests__type_inferer_3.snap b/tests/snapshots/test_typeinferer__tests__type_inferer_3.snap new file mode 100644 index 0000000..585d90b --- /dev/null +++ b/tests/snapshots/test_typeinferer__tests__type_inferer_3.snap @@ -0,0 +1,18 @@ +--- +source: tests/test_typeinferer.rs +expression: "typeinferer_output(&format!(\"tests/data/infer{}.plsr\",\n 3)).expect(\"failed to parse/infer input\")" +--- +error[E0010]: infer3.plsr:2:14: Failed to unify types `Int64[{ x | x = 10 }]` and `Int64[{ x | x = 1 }]` + │ + 1 │ func main() { + 2 │ let big: Int[10] = [...] + │ └────── Expected `Int64[{ x | x = 10 }]` here + 3 │ let small: Int[1] = big + │ + ... + │ + 2 │ let big: Int[10] = [...] + 3 │ let small: Int[1] = big + │ └───── but received `Int64[{ x | x = 1 }]`. + 4 │ } + │ diff --git a/tests/snapshots/test_typeinferer__tests__type_inferer_4.snap b/tests/snapshots/test_typeinferer__tests__type_inferer_4.snap new file mode 100644 index 0000000..e5fcce2 --- /dev/null +++ b/tests/snapshots/test_typeinferer__tests__type_inferer_4.snap @@ -0,0 +1,18 @@ +--- +source: tests/test_typeinferer.rs +expression: "typeinferer_output(&format!(\"tests/data/infer{}.plsr\",\n 4)).expect(\"failed to parse/infer input\")" +--- +error[E0010]: infer4.plsr:2:13: Failed to unify types `Int64` and `'t0[{ x | x = 0 }]` + │ + 1 │ func main() { + 2 │ let huh: Int = [] + │ └── Expected `Int64` here + 3 │ } + │ + ... + │ + 1 │ func main() { + 2 │ let huh: Int = [] + │ └─ but received `'t0[{ x | x = 0 }]`. + 3 │ } + │ diff --git a/tests/snapshots/test_typeinferer__tests__type_inferer_5.snap b/tests/snapshots/test_typeinferer__tests__type_inferer_5.snap new file mode 100644 index 0000000..62c5966 --- /dev/null +++ b/tests/snapshots/test_typeinferer__tests__type_inferer_5.snap @@ -0,0 +1,11 @@ +--- +source: tests/test_typeinferer.rs +expression: "typeinferer_output(&format!(\"tests/data/infer{}.plsr\",\n 5)).expect(\"failed to parse/infer input\")" +--- +func main() -> () { + let small = [1] + let small2: Int64[{ x | x = 1 }] = small +} +[infer5.plsr:2:18, infer5.plsr:2:19) 1: Int64 +[infer5.plsr:2:17, infer5.plsr:2:20) [1]: Int64[{ x | x = 1 }] +[infer5.plsr:3:26, infer5.plsr:3:31) small: Int64[{ x | x = 1 }] diff --git a/tests/snapshots/test_typeinferer__tests__type_inferer_6.snap b/tests/snapshots/test_typeinferer__tests__type_inferer_6.snap new file mode 100644 index 0000000..19e07d3 --- /dev/null +++ b/tests/snapshots/test_typeinferer__tests__type_inferer_6.snap @@ -0,0 +1,18 @@ +--- +source: tests/test_typeinferer.rs +expression: "typeinferer_output(&format!(\"tests/data/infer{}.plsr\",\n 6)).expect(\"failed to parse/infer input\")" +--- +func main() -> () { + let small = [1] + let small2: Int64[{ x | x = 1 }] = small +} +func foo() -> () { + let small = [1] + let small2 = small +} +[infer6.plsr:2:18, infer6.plsr:2:19) 1: Int64 +[infer6.plsr:2:17, infer6.plsr:2:20) [1]: Int64[{ x | x = 1 }] +[infer6.plsr:3:26, infer6.plsr:3:31) small: Int64[{ x | x = 1 }] +[infer6.plsr:7:18, infer6.plsr:7:19) 1: Int64 +[infer6.plsr:7:17, infer6.plsr:7:20) [1]: Int64[{ x | x = 1 }] +[infer6.plsr:8:18, infer6.plsr:8:23) small: Int64[{ x | x = 1 }] diff --git a/tests/snapshots/test_typeinferer__tests__type_inferer_7.snap b/tests/snapshots/test_typeinferer__tests__type_inferer_7.snap new file mode 100644 index 0000000..17dacd8 --- /dev/null +++ b/tests/snapshots/test_typeinferer__tests__type_inferer_7.snap @@ -0,0 +1,28 @@ +--- +source: tests/test_typeinferer.rs +expression: "typeinferer_output(&format!(\"tests/data/infer{}.plsr\",\n 7)).expect(\"failed to parse/infer input\")" +--- +func square(x: Int64) -> (y: Int64) { + y = (x * x) +} +func add(x: Int64, y: Int64) -> (z: Int64) { + z = (x + y) +} +func main() -> () { + let x = 1 + let y = 2 + let z = (x + y) +} +[infer7.plsr:2:5, infer7.plsr:2:6) y: Int64 +[infer7.plsr:2:9, infer7.plsr:2:10) x: Int64 +[infer7.plsr:2:13, infer7.plsr:2:14) x: Int64 +[infer7.plsr:2:9, infer7.plsr:2:14) (x * x): Int64 +[infer7.plsr:6:5, infer7.plsr:6:6) z: Int64 +[infer7.plsr:6:9, infer7.plsr:6:10) x: Int64 +[infer7.plsr:6:13, infer7.plsr:6:14) y: Int64 +[infer7.plsr:6:9, infer7.plsr:6:14) (x + y): Int64 +[infer7.plsr:10:13, infer7.plsr:10:14) 1: Int64 +[infer7.plsr:11:13, infer7.plsr:11:14) 2: Int64 +[infer7.plsr:12:13, infer7.plsr:12:14) x: Int64 +[infer7.plsr:12:17, infer7.plsr:12:18) y: Int64 +[infer7.plsr:12:13, infer7.plsr:12:18) (x + y): Int64 diff --git a/tests/snapshots/test_typeinferer__tests__type_inferer_8.snap b/tests/snapshots/test_typeinferer__tests__type_inferer_8.snap new file mode 100644 index 0000000..38b30ce --- /dev/null +++ b/tests/snapshots/test_typeinferer__tests__type_inferer_8.snap @@ -0,0 +1,5 @@ +--- +source: tests/test_typeinferer.rs +expression: "typeinferer_output(&format!(\"tests/data/infer{}.plsr\",\n 8)).expect(\"failed to parse/infer input\")" +--- +func does_not_return() -> (return: Int64) {} diff --git a/tests/snapshots/test_typeinferer__tests__type_inferer_9.snap b/tests/snapshots/test_typeinferer__tests__type_inferer_9.snap new file mode 100644 index 0000000..e696232 --- /dev/null +++ b/tests/snapshots/test_typeinferer__tests__type_inferer_9.snap @@ -0,0 +1,10 @@ +--- +source: tests/test_typeinferer.rs +expression: "typeinferer_output(&format!(\"tests/data/infer{}.plsr\",\n 9)).expect(\"failed to parse/infer input\")" +--- +error[E0009]: infer9.plsr:2:5: Unbound function or variable `return` + │ + 1 │ func main() { + 2 │ return = 1 + 3 │ } + │ diff --git a/tests/test_calyx_verilog.rs b/tests/test_calyx_verilog.rs index c0d849c..fee02f9 100644 --- a/tests/test_calyx_verilog.rs +++ b/tests/test_calyx_verilog.rs @@ -31,28 +31,28 @@ mod tests { .expect("Failed to restore the current working directory"); } - #[test] - fn twice() { - run_verilator_test("twice"); - } - - #[test] - fn square() { - run_verilator_test("square"); - } - - #[test] - fn map_single() { - run_verilator_test("map_single"); - } - - #[test] - fn map() { - run_verilator_test("map"); - } - - #[test] - fn math() { - run_verilator_test("math"); - } + // #[test] + // fn twice() { + // run_verilator_test("twice"); + // } + + // #[test] + // fn square() { + // run_verilator_test("square"); + // } + + // #[test] + // fn map_single() { + // run_verilator_test("map_single"); + // } + + // #[test] + // fn map() { + // run_verilator_test("map"); + // } + + // #[test] + // fn math() { + // run_verilator_test("math"); + // } } diff --git a/tests/test_parser.rs b/tests/test_parser.rs index f0efa3c..30962db 100644 --- a/tests/test_parser.rs +++ b/tests/test_parser.rs @@ -28,6 +28,7 @@ mod tests { writeln!(&mut output, "{}", decl).unwrap(); } } + let mut buffer = Vec::new(); error_manager.consume_and_write(&mut buffer)?; output.push_str(&String::from_utf8_lossy(&buffer)); diff --git a/tests/test_typeinferer.rs b/tests/test_typeinferer.rs index 2ec3088..3d0c76d 100644 --- a/tests/test_typeinferer.rs +++ b/tests/test_typeinferer.rs @@ -9,7 +9,8 @@ mod tests { use pulsar_frontend::{ ast::{expr::Expr, node::AsNodePool}, lexer::Lexer, - parser::Parser + parser::Parser, + type_inferer::TypeInferer }; use pulsar_lang::{context::Context, utils::OptionCheckError}; use pulsar_utils::{ @@ -31,22 +32,40 @@ mod tests { .parse() .check_errors(&mut error_manager)?; + let ast = TypeInferer::new(ast, &mut ctx, &mut error_manager).infer(); + let mut output = String::new(); - for decl in ast { - writeln!(&mut output, "{}", decl).unwrap(); + if let Some(ast) = ast { + for decl in ast { + writeln!(&mut output, "{}", decl).unwrap(); + } } - if error_manager.has_errors() { - let mut buffer = Vec::new(); - error_manager.consume_and_write(&mut buffer)?; - output.push_str(&String::from_utf8_lossy(&buffer)); + if !error_manager.has_errors() { + let exprs: HandleArray = ctx.as_pool_mut().as_array(); + for expr in exprs { + if ctx.get_ty(expr).is_invalid() { + panic!( + "[{}] {} did not have type resolved", + expr.span(), + expr + ); + } + writeln!( + &mut output, + "{} {}: {}", + expr.span(), + expr, + ctx.get_ty(expr) + ) + .unwrap(); + } } - let exprs: HandleArray = ctx.as_pool_mut().as_array(); - for expr in exprs { - println!("{} {}: {}", expr.span(), expr, ctx.get_ty(expr)); - } + let mut buffer = Vec::new(); + error_manager.consume_and_write(&mut buffer)?; + output.push_str(&String::from_utf8_lossy(&buffer)); Ok(output) } @@ -77,9 +96,4 @@ mod tests { generate_test!(9); generate_test!(10); generate_test!(11); - generate_test!(12); - generate_test!(13); - generate_test!(14); - generate_test!(15); - generate_test!(16); }