From 6148c599b824b4f27b22dea68161527174d1b54a Mon Sep 17 00:00:00 2001 From: NotAFile Date: Tue, 15 Feb 2022 21:32:55 +0100 Subject: [PATCH] start interning types --- src/builtin_cells.rs | 8 ++- src/frontend.rs | 118 ++++++++++++++++++++----------- src/frontend/callable.rs | 8 +-- src/frontend/typed_ir.rs | 41 +++++++---- src/frontend/types.rs | 145 ++++++++++++++------------------------- 5 files changed, 165 insertions(+), 155 deletions(-) diff --git a/src/builtin_cells.rs b/src/builtin_cells.rs index 34fb0e3..ee3ab2b 100644 --- a/src/builtin_cells.rs +++ b/src/builtin_cells.rs @@ -39,7 +39,8 @@ fn instantiate_binop(celltype: &str, id: &str, args: &[SigSpec], ret: &SigSpec) cell } -fn make_binop_callable<'ctx>(name: &str, _celltype: &'static str) -> Callable<'ctx> { +/* +fn make_binop_callable<'ctx>(name: &str, _celltype: &'static str) -> Callable { // FIXME: CRIMES CRIMES CRIMES let logic_type: &'static TypeStruct = Box::leak(Box::new(TypeStruct::logic_infer())); let args = vec![ @@ -53,7 +54,7 @@ fn make_binop_callable<'ctx>(name: &str, _celltype: &'static str) -> Callable<'c } } -fn make_unnop_callable<'ctx>(name: &str, _celltype: &'static str) -> Callable<'ctx> { +fn make_unnop_callable<'ctx>(name: &str, _celltype: &'static str) -> Callable { // FIXME: CRIMES CRIMES CRIMES let logic_type: &'static TypeStruct = Box::leak(Box::new(TypeStruct::logic_infer())); let args = vec![(Some("A".to_owned()), logic_type)]; @@ -64,7 +65,7 @@ fn make_unnop_callable<'ctx>(name: &str, _celltype: &'static str) -> Callable<'c } } -pub fn get_builtins<'ctx>() -> Vec> { +pub fn get_builtins<'ctx>() -> Vec { vec![ make_binop_callable("and", "$and"), make_binop_callable("or", "$or"), @@ -74,3 +75,4 @@ pub fn get_builtins<'ctx>() -> Vec> { make_unnop_callable("reduce_or", "$reduce_or"), ] } +*/ diff --git a/src/frontend.rs b/src/frontend.rs index 13d9add..7e1be26 100644 --- a/src/frontend.rs +++ b/src/frontend.rs @@ -1,11 +1,10 @@ -use std::borrow::Borrow; use std::cell::Cell; use std::collections::BTreeMap; use super::parser; use crate::rtlil; pub use callable::Callable; -pub use types::{make_primitives, Type, TypeStruct}; +pub use types::{Type, TypeStruct, TypingContext}; mod callable; #[cfg(never)] @@ -13,6 +12,7 @@ pub mod lowering; pub mod typed_ir; pub mod types; +#[cfg(never)] use crate::builtin_cells::get_builtins; // pub use lowering::lower_module; @@ -29,6 +29,7 @@ pub enum CompileErrorKind { UndefinedReference(String), BadArgCount { received: usize, expected: usize }, TodoError(String), + TypeError { expected: Type, found: Type }, } #[derive(Debug)] @@ -43,30 +44,31 @@ impl CompileError { } /// A user-defined signal -pub struct Signal<'ctx> { +pub struct Signal { /// the user-visible name of the signal pub name: String, /// the id of the signal in RTLIL pub il_id: String, /// the type of the signal - pub typ: Type<'ctx>, + pub typ: Type, // unique ID of the signal // pub uid: u64, } -impl<'ctx> Signal<'ctx> { +impl Signal { fn sigspec(&self) -> rtlil::SigSpec { rtlil::SigSpec::Wire(self.il_id.to_owned()) } } -pub struct Context<'ctx> { +pub struct Context { /// map callable name to callable - callables: BTreeMap>, - /// types - types: BTreeMap>, + callables: BTreeMap, + /// type names + typenames: BTreeMap, + types: TypingContext, /// map signal name to Signal - signals: BTreeMap>, + signals: BTreeMap, /// incrementing counter for unique IDs ids: Counter, } @@ -84,50 +86,81 @@ impl Counter { } } -impl<'ctx> Context<'ctx> { +impl Context { pub fn new() -> Self { + let tcx = TypingContext::new(); Context { - callables: get_builtins() - .into_iter() - .map(|clb| (clb.name().to_owned(), clb)) - .collect(), + callables: BTreeMap::new(), signals: BTreeMap::new(), - types: make_primitives().into_iter().collect(), + types: TypingContext::new(), + typenames: [("Logic".to_string(), tcx.primitives.logic)].into(), ids: Counter::new(), } } - fn get_signal(&self, signame: &str) -> Option<&typed_ir::Signal> { - self.signals.get(signame) - } fn try_get_signal(&self, signame: &str) -> Result<&typed_ir::Signal, CompileError> { - self.get_signal(signame).ok_or_else(|| { + self.signals.get(signame).ok_or_else(|| { CompileError::new(CompileErrorKind::UndefinedReference(signame.to_owned())) }) } - fn try_get_type(&'ctx self, typename: &str) -> Result, CompileError> { - self.types.get(typename).ok_or_else(|| { + fn try_get_type(&self, typename: &str) -> Result { + self.typenames.get(typename).map(|t| *t).ok_or_else(|| { CompileError::new(CompileErrorKind::UndefinedReference(typename.to_owned())) }) } + fn try_get_callable(&self, callname: &str) -> Result<&Callable, CompileError> { + self.callables.get(callname).map(|t| t).ok_or_else(|| { + CompileError::new(CompileErrorKind::UndefinedReference(callname.to_owned())) + }) + } + fn type_expression( &self, expr: &parser::expression::Expression, ) -> Result { - let typ = match expr { - parser::expression::Expression::Path(name) => self.try_get_signal(name)?.typ, - parser::expression::Expression::Literal(_) => todo!(), - parser::expression::Expression::UnOp(op) => self.type_expression(&op.a)?.typ, - parser::expression::Expression::BinOp(_) => todo!(), - parser::expression::Expression::Call(call) => todo!(), + use parser::expression::Expression; + let id = typed_ir::ExprId(self.ids.next() as u32); + let t_expr = match expr { + Expression::Path(name) => { + let signal = self.try_get_signal(name)?; + typed_ir::Expr { + id, + kind: typed_ir::ExprKind::Path(signal.id), + typ: signal.typ, + } + } + Expression::Literal(_) => todo!(), + Expression::UnOp(op) => self.type_expression(&op.a)?, + Expression::BinOp(op) => { + let (a, b) = (self.type_expression(&op.a)?, self.type_expression(&op.b)?); + typed_ir::Expr { + id, + kind: typed_ir::ExprKind::Call { + called: typed_ir::DefId(99), + args: vec![a, b], + }, + typ: self.types.primitives.elabnum, + } + } + Expression::Call(call) => { + let args_resolved = call + .args + .iter() + .map(|expr| self.type_expression(expr)) + .collect::, _>>()?; + typed_ir::Expr { + id, + kind: typed_ir::ExprKind::Call { + called: typed_ir::DefId(99), + args: args_resolved, + }, + typ: self.types.primitives.elabnum, + } + } }; - Ok(typed_ir::Expr { - id: 0, - inputs: vec![], - typ: typ, - }) + Ok(t_expr) } fn type_comb( @@ -141,10 +174,10 @@ impl<'ctx> Context<'ctx> { let sig_typename = &port.net.typ; let sig_type = self.try_get_type(sig_typename.name.fragment())?; let sig = typed_ir::Signal { - id: sig_id as u32, + id: typed_ir::DefId(sig_id as u32), typ: sig_type, }; - signals.push(sig); + signals.push(sig.clone()); self.signals.insert(port.net.name.to_string(), sig); } @@ -153,16 +186,23 @@ impl<'ctx> Context<'ctx> { let root_expr = self.type_expression(&comb.expr)?; + // TODO: more sophisticated type compat check + if root_expr.typ != ret_type { + let expected = ret_type; + let found = root_expr.typ; + return Err(CompileError::new(CompileErrorKind::TypeError { + expected, + found, + })); + } + Ok(typed_ir::Block { signals, expr: root_expr, }) } - pub fn type_module( - &'ctx mut self, - module: parser::Module, - ) -> Result, CompileError> { + pub fn type_module(&mut self, module: parser::Module) -> Result { for item in module.items { let block = match &item { parser::ModuleItem::Comb(comb) => self.type_comb(comb)?, diff --git a/src/frontend/callable.rs b/src/frontend/callable.rs index e7ebbbe..3f13138 100644 --- a/src/frontend/callable.rs +++ b/src/frontend/callable.rs @@ -1,12 +1,12 @@ use super::types::Type; -pub struct Callable<'ty> { +pub struct Callable { pub name: String, - pub args: Vec<(Option, Type<'ty>)>, - pub ret_type: Option>, + pub args: Vec<(Option, Type)>, + pub ret_type: Option, } -impl<'ty> Callable<'ty> { +impl<'ty> Callable { pub fn name(&self) -> &str { &self.name } diff --git a/src/frontend/typed_ir.rs b/src/frontend/typed_ir.rs index c3c61d8..8284bbb 100644 --- a/src/frontend/typed_ir.rs +++ b/src/frontend/typed_ir.rs @@ -1,22 +1,35 @@ -use super::{types::Type, Context}; +use super::types::Type; + +/// ID of a definition (e.g. variable, block, function) +#[derive(Debug, Clone, Copy)] +pub struct DefId(pub u32); +#[derive(Debug, Clone, Copy)] +pub struct ExprId(pub u32); /// an abstract element that performs some kind of computation on inputs -#[derive(Debug)] -pub struct Expr<'ty> { - pub id: u32, - pub inputs: Vec>, - pub typ: Type<'ty>, +#[derive(Debug, Clone)] +pub struct Expr { + pub id: ExprId, + pub kind: ExprKind, + pub typ: Type, } -#[derive(Debug)] -pub struct Signal<'ty> { - pub id: u32, - pub typ: Type<'ty>, +#[derive(Debug, Clone)] +pub enum ExprKind { + Literal, + Path(DefId), + Call { called: DefId, args: Vec }, +} + +#[derive(Debug, Clone)] +pub struct Signal { + pub id: DefId, + pub typ: Type, } /// A block of HDL code, e.g. comb block -#[derive(Debug)] -pub struct Block<'ty> { - pub signals: Vec>, - pub expr: Expr<'ty>, +#[derive(Debug, Clone)] +pub struct Block { + pub signals: Vec, + pub expr: Expr, } diff --git a/src/frontend/types.rs b/src/frontend/types.rs index 698b7d7..d9ae3f3 100644 --- a/src/frontend/types.rs +++ b/src/frontend/types.rs @@ -1,42 +1,46 @@ +use std::fmt::Debug; /// Alias for &TypeStruct to reduce repetition /// and make futura migration to interning /// easier -pub type Type<'ty> = &'ty TypeStruct<'ty>; +pub type Type = InternedType; -pub struct TypeStruct<'ty> { - kind: TypeKind<'ty>, +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct InternedType(usize); + +pub struct TypeStruct { + kind: TypeKind, } #[derive(Debug)] -enum TypeKind<'ty> { +enum TypeKind { /// Elaboration-time types ElabType(ElabKind), /// Signal/Wire of generic width - Logic(ElabData<'ty>), + Logic(ElabData), /// UInt of generic width - UInt(ElabData<'ty>), + UInt(ElabData), /// Callable Callable, } #[derive(Debug)] -struct ElabData<'ty> { - typ: Type<'ty>, - value: ElabValue<'ty>, +struct ElabData { + typ: Type, + value: ElabValue, } #[derive(Debug)] -enum ElabValue<'ty> { +enum ElabValue { /// the value is not given and has to be inferred Infer, /// the value is given as some byte representation - Concrete(ElabValueData<'ty>), + Concrete(ElabValueData), } #[derive(Debug)] -enum ElabValueData<'ty> { +enum ElabValueData { U32(u32), - Bytes(&'ty [u8]), + Bytes(Vec), } /// Types that are only valid during Elaboration @@ -46,98 +50,49 @@ enum ElabKind { Num, } -/// Helper functions to create primitive types -impl<'ty> TypeStruct<'ty> { - /// a logic signal with inferred width - pub fn logic_infer() -> Self { +pub struct PrimitiveTypes { + pub elabnum: Type, + pub logic: Type, +} + +pub struct TypingContext { + types: Vec, + pub primitives: PrimitiveTypes, +} + +impl TypingContext { + pub fn new() -> Self { + let primitives = PrimitiveTypes { + elabnum: InternedType(0), + logic: InternedType(1), + }; Self { - kind: TypeKind::Logic(ElabData { - typ: &TypeStruct { - kind: TypeKind::ElabType(ElabKind::Num), - }, - value: ElabValue::Infer, - }), + types: vec![TypeStruct { + kind: TypeKind::Logic(ElabData { + typ: primitives.elabnum, + value: ElabValue::Infer, + }), + }], + primitives, } } - /// a logic signal with known width - pub fn logic_width(width: u32) -> Self { - Self { - kind: TypeKind::Logic(ElabData::from_u32(width)), - } + pub fn add(&mut self, typ: TypeStruct) -> Type { + let id = self.types.len(); + self.types.push(typ); + InternedType(id) } - /// return an elaboration number type - pub fn elab_num() -> Self { - Self { - kind: TypeKind::ElabType(ElabKind::Num), - } + pub fn get(&self, typ: Type) -> &TypeStruct { + &self.types[typ.0] } - pub fn bit_width(&self) -> Option { - match &self.kind { - // elab types are not representable in hardware - TypeKind::ElabType(_) => None, - TypeKind::Logic(data) => data.try_u32(), + pub fn pretty_type(&self, w: &mut dyn std::fmt::Write, typ: Type) -> std::fmt::Result { + match &self.get(typ).kind { + TypeKind::ElabType(val) => write!(w, "{{{:?}}}", val), + TypeKind::Logic(_) => todo!(), TypeKind::UInt(_) => todo!(), - // callables are not representable in hardware - TypeKind::Callable => None, - } - } - - pub fn genparam_count(&self) -> u32 { - match self.kind { - TypeKind::ElabType(_) => todo!(), - TypeKind::Logic(_) => 1, - TypeKind::UInt(_) => 1, TypeKind::Callable => todo!(), } } } - -impl std::fmt::Debug for TypeStruct<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &self.kind { - TypeKind::Logic(width) => { - if let Some(num) = width.try_u32() { - write!(f, "Logic<{}>", num) - } else { - write!(f, "Logic") - } - } - _ => f - .debug_struct("TypeStruct") - .field("kind", &self.kind) - .finish(), - } - } -} - -/// Helper functions to create primitive elaboration values -impl<'ty> ElabData<'ty> { - /// an integer - pub fn from_u32(val: u32) -> Self { - Self { - typ: &TypeStruct { - kind: TypeKind::ElabType(ElabKind::Num), - }, - value: ElabValue::Concrete(ElabValueData::U32(val)), - } - } - - /// return Some(u32) if this is a number - pub fn try_u32(&self) -> Option { - // TODO: assert this is actually a number - match &self.value { - ElabValue::Infer => None, - ElabValue::Concrete(val) => match val { - ElabValueData::U32(num) => Some(*num), - ElabValueData::Bytes(_) => todo!(), - }, - } - } -} - -pub fn make_primitives() -> Vec<(String, TypeStruct<'static>)> { - vec![("Logic".to_string(), TypeStruct::logic_infer())] -}