From 8635e4e2b28ed6f1b3130544079ce833ba0781da Mon Sep 17 00:00:00 2001 From: Lennart Van Hirtum Date: Thu, 4 Jan 2024 20:07:29 +0100 Subject: [PATCH] Perform Type Resolution separately after flatten --- src/ast.rs | 12 +- src/codegen_fallback.rs | 7 +- src/flattening.rs | 232 ++++++++++++++++++++++++++------------- src/instantiation/mod.rs | 42 ++++--- src/linker.rs | 45 ++++---- src/parser.rs | 4 +- src/typing.rs | 7 +- 7 files changed, 217 insertions(+), 132 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index fd9ffc4..6b80d4c 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -2,7 +2,7 @@ use crate::{tokenizer::{TokenTypeIdx, get_token_type_name}, linker::{NamedUUID, FileUUID, Linker}, flattening::{FlattenedModule, FlattenedInterface}, arena_alloc::{UUIDMarker, UUID, FlatAlloc}, instantiation::InstantiationList, value::Value}; use core::ops::Range; -use std::{fmt::Display, ops::Deref}; +use std::{fmt::Display, ops::Deref, cell::RefCell}; // Token span. Indices are INCLUSIVE #[derive(Clone,Copy,Debug,PartialEq,Eq)] @@ -146,8 +146,7 @@ pub struct Module { pub declarations : FlatAlloc, pub code : CodeBlock, - pub interface : FlattenedInterface, - pub flattened : FlattenedModule, + pub flattened : RefCell, pub instantiations : InstantiationList } @@ -155,14 +154,15 @@ pub struct Module { impl Module { pub fn print_flattened_module(&self, linker : &Linker) { println!("Interface:"); - for (port_idx, port) in self.interface.interface_wires.iter().enumerate() { - let port_direction = if port_idx < self.interface.outputs_start {"input"} else {"output"}; + let flattened_borrow = self.flattened.borrow(); + for (port_idx, port) in flattened_borrow.interface.interface_wires.iter().enumerate() { + let port_direction = if port_idx < flattened_borrow.interface.outputs_start {"input"} else {"output"}; let port_type = port.typ.to_string(linker); let port_name = &port.port_name; println!(" {port_direction} {port_type} {port_name} -> {:?}", port.wire_id); } println!("Instantiations:"); - for (id, inst) in &self.flattened.instantiations { + for (id, inst) in &flattened_borrow.instantiations { println!(" {:?}: {:?}", id, inst); } } diff --git a/src/codegen_fallback.rs b/src/codegen_fallback.rs index 80aec94..8be48a7 100644 --- a/src/codegen_fallback.rs +++ b/src/codegen_fallback.rs @@ -56,9 +56,10 @@ pub fn gen_verilog_code(md : &Module, instance : &InstantiatedModule) -> String assert!(!instance.errors.did_error(), "Module cannot have experienced an error"); let mut program_text : String = format!("module {}(\n\tinput clk, \n", md.link_info.name); let submodule_interface = instance.interface.as_ref().unwrap(); - for (port_idx, (port, real_port)) in zip(md.interface.interface_wires.iter(), submodule_interface).enumerate() { + let flattened_borrow = md.flattened.borrow(); + for (port_idx, (port, real_port)) in zip(flattened_borrow.interface.interface_wires.iter(), submodule_interface).enumerate() { let wire = &instance.wires[*real_port]; - program_text.push_str(if port_idx < md.interface.outputs_start {"\tinput"} else {"\toutput /*mux_wire*/ reg"}); + program_text.push_str(if port_idx < flattened_borrow.interface.outputs_start {"\tinput"} else {"\toutput /*mux_wire*/ reg"}); program_text.push_str(&typ_to_verilog_array(&wire.typ)); program_text.push(' '); program_text.push_str(&wire.name); @@ -67,7 +68,7 @@ pub fn gen_verilog_code(md : &Module, instance : &InstantiatedModule) -> String program_text.push_str(");\n"); for (_id, w) in &instance.wires { - if let Instantiation::WireDeclaration(wire_decl) = &md.flattened.instantiations[w.original_wire] { + if let Instantiation::WireDeclaration(wire_decl) = &flattened_borrow.instantiations[w.original_wire] { // Don't print named inputs and outputs, already did that in interface match wire_decl.identifier_type { IdentifierType::Input | IdentifierType::Output => {continue;} diff --git a/src/flattening.rs b/src/flattening.rs index dc4871c..a176891 100644 --- a/src/flattening.rs +++ b/src/flattening.rs @@ -1,4 +1,4 @@ -use std::{ops::{Deref, Range}, iter::zip}; +use std::{ops::{Deref, Range}, iter::zip, collections::VecDeque}; use crate::{ ast::{Span, Module, Expression, SpanExpression, LocalOrGlobal, Operator, AssignableExpression, SpanAssignableExpression, Statement, CodeBlock, IdentifierType, GlobalReference, TypeExpression, DeclIDMarker}, @@ -71,7 +71,7 @@ pub struct WireInstance { pub typ : Type, pub is_compiletime : bool, pub span : Span, - pub inst : WireSource + pub source : WireSource } #[derive(Debug)] @@ -131,10 +131,10 @@ struct FlatteningContext<'l, 'm, 'fl> { } impl<'l, 'm, 'fl> FlatteningContext<'l, 'm, 'fl> { - fn typecheck(&self, wire_id : FlatID, expected : &Type, context : &str) -> Option<()> { + /*fn typecheck(&self, wire_id : FlatID, expected : &Type, context : &str) -> Option<()> { let wire = self.instantiations[wire_id].extract_wire(); typecheck(&wire.typ, wire.span, expected, context, self.linker, &self.errors) - } + }*/ pub fn map_to_type(&self, type_expr : &TypeExpression, global_references : &[GlobalReference]) -> Type { match type_expr { TypeExpression::Named(n) => { @@ -148,7 +148,7 @@ impl<'l, 'm, 'fl> FlatteningContext<'l, 'm, 'fl> { let (array_type_expr, array_size_expr) = b.deref(); let array_element_type = self.map_to_type(&array_type_expr.0, global_references); if let Some(array_size_wire) = self.flatten_single_expr(array_size_expr, None) { - self.typecheck(array_size_wire, &Type::Named(get_builtin_uuid("int")), "array size"); + //self.typecheck(array_size_wire, &Type::Named(get_builtin_uuid("int")), "array size"); Type::Array(Box::new((array_element_type, array_size_wire))) } else { Type::Error @@ -157,7 +157,7 @@ impl<'l, 'm, 'fl> FlatteningContext<'l, 'm, 'fl> { } } // May also error, for example when array accesses happen on non-array types - fn get_connectionwrite_type(&self, cw : &ConnectionWrite) -> Option<&Type> { + /*fn get_connectionwrite_type(&self, cw : &ConnectionWrite) -> Option<&Type> { let mut current_type = &self.instantiations[cw.root].extract_wire_declaration().typ; for p in &cw.path { match p { @@ -186,20 +186,21 @@ impl<'l, 'm, 'fl> FlatteningContext<'l, 'm, 'fl> { self.instantiations.alloc(Instantiation::Connection(connection)); Some(()) - } + }*/ fn alloc_module_interface(&self, name : Box, module : &Module, module_uuid : NamedUUID, typ_span : Span) -> Instantiation { - let local_wires : Vec = module.interface.interface_wires.iter().enumerate().map(|(port_idx, port)| { + let flattened_borrow = module.flattened.borrow(); + let local_wires : Vec = flattened_borrow.interface.interface_wires.iter().enumerate().map(|(port_idx, port)| { self.instantiations.alloc(Instantiation::WireDeclaration(WireDeclaration{ typ: port.typ.clone(), typ_span, - read_only : port_idx >= module.interface.outputs_start, + read_only : port_idx >= flattened_borrow.interface.outputs_start, identifier_type : IdentifierType::Local, name : format!("{}_{}", &name, &port.port_name).into_boxed_str(), name_token : None })) }).collect(); - Instantiation::SubModule(SubModuleInstance{name, module_uuid, typ_span, outputs_start : module.interface.outputs_start, local_wires : local_wires.into_boxed_slice()}) + Instantiation::SubModule(SubModuleInstance{name, module_uuid, typ_span, outputs_start : flattened_borrow.interface.outputs_start, local_wires : local_wires.into_boxed_slice()}) } fn desugar_func_call(&self, func_and_args : &[SpanExpression], closing_bracket_pos : usize, condition : Option) -> Option<(&Module, &[FlatID])> { let (name_expr, name_expr_span) = &func_and_args[0]; // Function name is always there @@ -222,7 +223,7 @@ impl<'l, 'm, 'fl> FlatteningContext<'l, 'm, 'fl> { let func_instantiation = &self.instantiations[func_instantiation_id]; let Instantiation::SubModule(SubModuleInstance{module_uuid, name : _, typ_span : _, outputs_start:_, local_wires}) = func_instantiation else {unreachable!("It should be proven {func_instantiation:?} was a Module!");}; let Named::Module(md) = &self.linker.links.globals[*module_uuid] else {unreachable!("UUID Should be a module!");}; - let (inputs, output_range) = md.interface.func_call_syntax_interface(); + let (inputs, output_range) = md.flattened.borrow().interface.func_call_syntax_interface(); let mut args = &func_and_args[1..]; @@ -244,85 +245,77 @@ impl<'l, 'm, 'fl> FlatteningContext<'l, 'm, 'fl> { for (field, arg_expr) in zip(inputs, args) { if let Some(arg_read_side) = self.flatten_single_expr(arg_expr, condition) { - if self.typecheck(arg_read_side, &md.interface.interface_wires[field].typ, "submodule output") == None { + /*if self.typecheck(arg_read_side, &md.interface.interface_wires[field].typ, "submodule output") == None { continue; - } + }*/ let func_input_port = &local_wires[field]; - self.create_connection(Connection { num_regs: 0, from: arg_read_side, to: ConnectionWrite::simple(*func_input_port, *name_expr_span), condition }); + self.instantiations.alloc(Instantiation::Connection(Connection { num_regs: 0, from: arg_read_side, to: ConnectionWrite::simple(*func_input_port, *name_expr_span), condition })); } } Some((md, &local_wires[output_range])) } fn flatten_single_expr(&self, (expr, expr_span) : &SpanExpression, condition : Option) -> Option { - let span = *expr_span; // for more compact constructors - let single_connection_side = match expr { + let (is_compiletime, source) = match expr { Expression::Named(LocalOrGlobal::Local(l)) => { let from_wire = self.decl_to_flat_map[*l].unwrap(); - let Instantiation::WireDeclaration(WireDeclaration { typ, typ_span:_, read_only:_, identifier_type, name:_, name_token:_ }) = &self.instantiations[from_wire] else {unreachable!("Reference to a wire MUST refer to a WireDeclaration, found {:?} instead", &self.instantiations[from_wire])}; - let is_compiletime = *identifier_type == IdentifierType::Generative; - self.instantiations.alloc(Instantiation::Wire(WireInstance{typ : typ.clone(), is_compiletime, span, inst : WireSource::WireRead{from_wire}})) + let WireDeclaration { typ, typ_span:_, read_only:_, identifier_type, name:_, name_token:_ } = self.instantiations[from_wire].extract_wire_declaration(); + (*identifier_type == IdentifierType::Generative, WireSource::WireRead{from_wire}) } Expression::Named(LocalOrGlobal::Global(g)) => { let r = self.module.link_info.global_references[*g]; let cst = self.linker.try_get_constant(r, &self.errors)?; - self.instantiations.alloc(Instantiation::Wire(WireInstance{typ : cst.get_type_of_constant(), is_compiletime : true, span, inst : WireSource::Constant{value : cst}})) + (true, WireSource::Constant{value : cst}) } Expression::Constant(cst) => { - self.instantiations.alloc(Instantiation::Wire(WireInstance{typ : cst.get_type_of_constant(), is_compiletime : true, span, inst : WireSource::Constant{value : cst.clone()}})) + (true, WireSource::Constant{value : cst.clone()}) } Expression::UnaryOp(op_box) => { let (op, _op_pos, operate_on) = op_box.deref(); let right = self.flatten_single_expr(operate_on, condition)?; let right_wire = self.instantiations[right].extract_wire(); - let output_type = typecheck_unary_operator(*op, &right_wire.typ, right_wire.span, self.linker, &self.errors); - self.instantiations.alloc(Instantiation::Wire(WireInstance{typ : output_type, is_compiletime : right_wire.is_compiletime, span, inst : WireSource::UnaryOp{op : *op, right}})) + (right_wire.is_compiletime, WireSource::UnaryOp{op : *op, right}) } Expression::BinOp(binop_box) => { let (left_expr, op, _op_pos, right_expr) = binop_box.deref(); let left = self.flatten_single_expr(left_expr, condition)?; let right = self.flatten_single_expr(right_expr, condition)?; - let ((input_left_type, input_right_type), output_type) = get_binary_operator_types(*op); - self.typecheck(left, &input_left_type, &format!("{op} left"))?; - self.typecheck(right, &input_right_type, &format!("{op} right"))?; - let is_constant = self.instantiations[left].extract_wire().is_compiletime && self.instantiations[right].extract_wire().is_compiletime; - self.instantiations.alloc(Instantiation::Wire(WireInstance{typ : output_type, is_compiletime: is_constant, span, inst : WireSource::BinaryOp{op : *op, left, right}})) + let left_wire = self.instantiations[left].extract_wire(); + let right_wire = self.instantiations[right].extract_wire(); + let is_compiletime = left_wire.is_compiletime && right_wire.is_compiletime; + (is_compiletime, WireSource::BinaryOp{op : *op, left, right}) } Expression::Array(arr_box) => { let (left, right, _bracket_span) = arr_box.deref(); let arr = self.flatten_single_expr(left, condition)?; let arr_idx = self.flatten_single_expr(right, condition)?; - - let index_was_int = self.typecheck(arr_idx, &Type::Named(get_builtin_uuid("int")), "array index"); let arr_wire = self.instantiations[arr].extract_wire(); let arr_idx_wire = self.instantiations[arr_idx].extract_wire(); - let typ = typecheck_is_array_indexer(&arr_wire.typ, arr_wire.span, self.linker, &self.errors)?.clone(); - index_was_int?; // Do both for better typechecking diagnostics - let is_constant = arr_wire.is_compiletime && arr_idx_wire.is_compiletime; - self.instantiations.alloc(Instantiation::Wire(WireInstance{typ, is_compiletime: is_constant, span, inst : WireSource::ArrayAccess{arr, arr_idx}})) + (arr_wire.is_compiletime && arr_idx_wire.is_compiletime, WireSource::ArrayAccess{arr, arr_idx}) } Expression::FuncCall(func_and_args) => { let (md, outputs) = self.desugar_func_call(func_and_args, expr_span.1, condition)?; if outputs.len() != 1 { let info = error_info(md.link_info.span, md.link_info.file, "Module Defined here"); - self.errors.error_with_info(span, "A function called in this context may only return one result. Split this function call into a separate line instead.", vec![info]); + self.errors.error_with_info(*expr_span, "A function called in this context may only return one result. Split this function call into a separate line instead.", vec![info]); return None; } - outputs[0] + return Some(outputs[0]) } }; - Some(single_connection_side) + + Some(self.instantiations.alloc(Instantiation::Wire(WireInstance{typ : Type::Unknown, span : *expr_span, is_compiletime, source}))) } fn flatten_assignable_expr(&self, (expr, span) : &SpanAssignableExpression, condition : Option) -> Option { Some(match expr { AssignableExpression::Named{local_idx} => { let root = self.decl_to_flat_map[*local_idx].unwrap(); - let Instantiation::WireDeclaration(WireDeclaration{read_only, identifier_type : _, name : _, name_token : _, typ : _, typ_span : _}) = &self.instantiations[root] else { - unreachable!("Attempting to assign to a Instantiation::PlainWire") - }; - if *read_only { + if let Instantiation::Error = &self.instantiations[root] {return None}; // TODO, remove Error variant. Just a quick fix so it works again + let decl = self.instantiations[root].extract_wire_declaration(); + + if decl.read_only { let decl_info = error_info(self.module.declarations[*local_idx].span, self.errors.file, "Declared here"); self.errors.error_with_info(*span, "Cannot Assign to Read-Only value", vec![decl_info]); return None @@ -349,8 +342,7 @@ impl<'l, 'm, 'fl> FlatteningContext<'l, 'm, 'fl> { let prev_condition_wire = self.instantiations[condition].extract_wire(); let additional_condition_wire = self.instantiations[condition].extract_wire(); assert!(!prev_condition_wire.is_compiletime); // Conditions are only used for runtime conditions. Compile time ifs are handled at instantiation time - assert!(prev_condition_wire.typ == bool_typ); - self.instantiations.alloc(Instantiation::Wire(WireInstance{typ : bool_typ, is_compiletime : false, span : additional_condition_wire.span, inst : WireSource::BinaryOp{op: Operator{op_typ : kw("&")}, left : condition, right : additional_condition}})) + self.instantiations.alloc(Instantiation::Wire(WireInstance{typ : bool_typ, is_compiletime : false, span : additional_condition_wire.span, source : WireSource::BinaryOp{op: Operator{op_typ : kw("&")}, left : condition, right : additional_condition}})) } else { additional_condition } @@ -407,12 +399,12 @@ impl<'l, 'm, 'fl> FlatteningContext<'l, 'm, 'fl> { println!("TODO generative if statements"); } - let bool_typ = Type::Named(get_builtin_uuid("bool")); - if self.typecheck(if_statement_condition, &bool_typ, "if statement condition") == None {continue;} + //let bool_typ = Type::Named(get_builtin_uuid("bool")); + //if self.typecheck(if_statement_condition, &bool_typ, "if statement condition") == None {continue;} let then_condition = self.extend_condition(condition, if_statement_condition); self.flatten_code(then, Some(then_condition)); if let Some(e) = els { - let else_condition_bool = self.instantiations.alloc(Instantiation::Wire(WireInstance{typ : bool_typ, is_compiletime : false/* Generative If */, span : condition_expr.1, inst : WireSource::UnaryOp{op : Operator{op_typ : kw("!")}, right : if_statement_condition}})); + let else_condition_bool = self.instantiations.alloc(Instantiation::Wire(WireInstance{typ : Type::Unknown, is_compiletime : false/* Generative If */, span : condition_expr.1, source : WireSource::UnaryOp{op : Operator{op_typ : kw("!")}, right : if_statement_condition}})); let else_condition = self.extend_condition(condition, else_condition_bool); self.flatten_code(e, Some(else_condition)); } @@ -439,8 +431,8 @@ impl<'l, 'm, 'fl> FlatteningContext<'l, 'm, 'fl> { // temporary let module_port_wire_decl = self.instantiations[*field].extract_wire_declaration(); - let module_port_proxy = self.instantiations.alloc(Instantiation::Wire(WireInstance{typ : module_port_wire_decl.typ.clone(), is_compiletime : module_port_wire_decl.identifier_type == IdentifierType::Generative, span : *func_span, inst : WireSource::WireRead { from_wire: *field }})); - self.create_connection(Connection{num_regs : to_i.num_regs, from: module_port_proxy, to: write_side, condition}); + let module_port_proxy = self.instantiations.alloc(Instantiation::Wire(WireInstance{typ : module_port_wire_decl.typ.clone(), is_compiletime : module_port_wire_decl.identifier_type == IdentifierType::Generative, span : *func_span, source : WireSource::WireRead { from_wire: *field }})); + self.instantiations.alloc(Instantiation::Connection(Connection{num_regs : to_i.num_regs, from: module_port_proxy, to: write_side, condition})); } }, Statement::Assign{to, expr : non_func_expr, eq_sign_position : _} => { @@ -448,7 +440,7 @@ impl<'l, 'm, 'fl> FlatteningContext<'l, 'm, 'fl> { let Some(read_side) = self.flatten_single_expr(non_func_expr, condition) else {return;}; let t = &to[0]; let Some(write_side) = self.flatten_assignable_expr(&t.expr, condition) else {return;}; - self.create_connection(Connection{num_regs : t.num_regs, from: read_side, to: write_side, condition}); + self.instantiations.alloc(Instantiation::Connection(Connection{num_regs : t.num_regs, from: read_side, to: write_side, condition})); } else { self.errors.error_basic(*stmt_span, format!("Non-function assignments must only output exactly 1 instead of {}", to.len())); } @@ -495,14 +487,16 @@ impl FlattenedInterface { #[derive(Debug)] pub struct FlattenedModule { pub instantiations : ListAllocator, - pub errors : ErrorCollector + pub errors : ErrorCollector, + pub interface : FlattenedInterface } impl FlattenedModule { pub fn empty(file : FileUUID) -> FlattenedModule { FlattenedModule { instantiations : ListAllocator::new(), - errors : ErrorCollector::new(file) + errors : ErrorCollector::new(file), + interface : FlattenedInterface::new() } } /* @@ -510,16 +504,20 @@ impl FlattenedModule { Produces an initial FlattenedModule, in which the interface types have already been resolved. Must be further processed by flatten, but this requires all modules to have been Initial Flattened for dependency resolution */ - pub fn initialize_interfaces(linker : &Linker, module : &Module) -> (FlattenedInterface, FlattenedModule, FlatAlloc, DeclIDMarker>) { - let flat_mod = FlattenedModule { - instantiations: ListAllocator::new(), - errors: ErrorCollector::new(module.link_info.file) - }; + /* + This method flattens all given code into a simple set of assignments, operators and submodules. + It already does basic type checking and assigns a type to every wire. + The Generating Structure of the code is not yet executed. + It is template-preserving + */ + pub fn initialize(linker : &Linker, module : &Module) -> FlattenedModule { + let instantiations = ListAllocator::new(); + let errors = ErrorCollector::new(module.link_info.file); let mut context = FlatteningContext{ decl_to_flat_map: module.declarations.iter().map(|_| None).collect(), - instantiations: &flat_mod.instantiations, - errors: &flat_mod.errors, + instantiations: &instantiations, + errors: &errors, linker, module, }; @@ -558,28 +556,106 @@ impl FlattenedModule { inputs.append(&mut outputs); let interface = FlattenedInterface{interface_wires: inputs.into_boxed_slice(), outputs_start}; - let decl_to_flat_map = context.decl_to_flat_map; - (interface, flat_mod, decl_to_flat_map) - } + context.flatten_code(&module.code, None); - /* - This method flattens all given code into a simple set of assignments, operators and submodules. - It already does basic type checking and assigns a type to every wire. - The Generating Structure of the code is not yet executed. - It is template-preserving - */ - pub fn flatten(&self, module : &Module, linker : &Linker, decl_to_flat_map : FlatAlloc, DeclIDMarker>) { - let mut context = FlatteningContext { - decl_to_flat_map : decl_to_flat_map, - instantiations : &self.instantiations, - errors : &self.errors, - module, - linker, + let flat_mod = FlattenedModule { + instantiations, + errors, + interface }; - context.flatten_code(&module.code, None); + flat_mod } - pub fn find_unused_variables(&self, md : &Module) { + /* Type Checking */ + fn typecheck_wire_is_of_type(&self, wire : &WireInstance, expected : &Type, context : &str, linker : &Linker) { + typecheck(&wire.typ, wire.span, expected, context, linker, &self.errors); + } + + pub fn typecheck(&mut self, linker : &Linker) { + let look_at_queue : Vec = self.instantiations.iter().map(|(id,_)| id).collect(); + + for elem_id in look_at_queue { + match &self.instantiations[elem_id] { + Instantiation::SubModule(_) => {} + Instantiation::WireDeclaration(_) => {}, + Instantiation::Wire(w) => { + let result_typ = match &w.source { + &WireSource::WireRead{from_wire} => { + self.instantiations[from_wire].extract_wire_declaration().typ.clone() + } + &WireSource::UnaryOp{op, right} => { + let right_wire = self.instantiations[right].extract_wire(); + typecheck_unary_operator(op, &right_wire.typ, right_wire.span, linker, &self.errors) + } + &WireSource::BinaryOp{op, left, right} => { + let left_wire = self.instantiations[left].extract_wire(); + let right_wire = self.instantiations[right].extract_wire(); + let ((input_left_type, input_right_type), output_type) = get_binary_operator_types(op); + self.typecheck_wire_is_of_type(left_wire, &input_left_type, &format!("{op} left"), linker); + self.typecheck_wire_is_of_type(right_wire, &input_right_type, &format!("{op} right"), linker); + output_type + } + &WireSource::ArrayAccess{arr, arr_idx} => { + let arr_wire = self.instantiations[arr].extract_wire(); + let arr_idx_wire = self.instantiations[arr_idx].extract_wire(); + + self.typecheck_wire_is_of_type(arr_idx_wire, &Type::Named(get_builtin_uuid("int")), "array index", linker); + if let Some(typ) = typecheck_is_array_indexer(&arr_wire.typ, arr_wire.span, linker, &self.errors) { + typ.clone() + } else { + Type::Error + } + } + WireSource::Constant{value} => { + value.get_type_of_constant() + } + }; + let Instantiation::Wire(w) = &mut self.instantiations[elem_id] else {unreachable!()}; + w.typ = result_typ; + } + Instantiation::Connection(conn) => { + + // Typecheck digging down into write side + let conn_root = self.instantiations[conn.to.root].extract_wire_declaration(); + let mut write_to_type = Some(&conn_root.typ); + for p in &conn.to.path { + match p { + &ConnectionWritePathElement::ArrayIdx{idx, idx_span} => { + let idx_wire = self.instantiations[idx].extract_wire(); + self.typecheck_wire_is_of_type(idx_wire, &Type::Named(get_builtin_uuid("int")), "array index", linker); + if let Some(wr) = write_to_type { + write_to_type = typecheck_is_array_indexer(wr, idx_span, linker, &self.errors); + } + } + } + } + + // Typecheck compile-time ness + let from_wire = self.instantiations[conn.from].extract_wire(); + if conn_root.identifier_type == IdentifierType::Generative && !from_wire.is_compiletime { + let decl_info = error_info(conn_root.get_full_decl_span(), self.errors.file, "Declared here"); + self.errors.error_with_info(from_wire.span, "Assignments to compile-time variables must themselves be known at compile time", vec![decl_info]); + } + + // Typecheck the value with where it is stored + if let Some(target_type) = write_to_type { + self.typecheck_wire_is_of_type(from_wire, &target_type, "connection", linker); + } + + // Typecheck condition is bool + if let Some(condition) = conn.condition { + let condition_wire = self.instantiations[condition].extract_wire(); + self.typecheck_wire_is_of_type(condition_wire, &Type::Named(get_builtin_uuid("bool")), "assignment condition", linker); + } + } + Instantiation::Error => {} + } + } + } + + + /* Additional Warnings */ + pub fn find_unused_variables(&self) { // Setup Wire Fanouts List for faster processing let mut connection_fanin : FlatAlloc, FlatIDMarker> = self.instantiations.iter().map(|_| Vec::new()).collect(); @@ -596,7 +672,7 @@ impl FlattenedModule { let mut wire_to_explore_queue : Vec = Vec::new(); - for port in md.interface.outputs() { + for port in self.interface.outputs() { is_instance_used_map[port.wire_id] = true; wire_to_explore_queue.push(port.wire_id); } @@ -616,7 +692,7 @@ impl FlattenedModule { match &self.instantiations[item] { Instantiation::WireDeclaration(_) => {} Instantiation::Wire(wire) => { - wire.inst.for_each_input_wire(&mut func); + wire.source.for_each_input_wire(&mut func); } Instantiation::SubModule(submodule) => { for (port_id, port) in submodule.local_wires.iter().enumerate() { diff --git a/src/instantiation/mod.rs b/src/instantiation/mod.rs index a0f0c18..ab61108 100644 --- a/src/instantiation/mod.rs +++ b/src/instantiation/mod.rs @@ -2,7 +2,7 @@ use std::{rc::Rc, ops::Deref, cell::RefCell}; use num::BigInt; -use crate::{arena_alloc::{UUID, UUIDMarker, FlatAlloc}, ast::{Operator, Module, IdentifierType, Span}, typing::{ConcreteType, Type}, flattening::{FlatID, Instantiation, FlatIDMarker, ConnectionWritePathElement, WireSource, WireInstance, Connection, ConnectionWritePathElementComputed, WireDeclaration, SubModuleInstance}, errors::ErrorCollector, linker::{Linker, get_builtin_uuid}, value::{Value, compute_unary_op, compute_binary_op}}; +use crate::{arena_alloc::{UUID, UUIDMarker, FlatAlloc}, ast::{Operator, Module, IdentifierType, Span}, typing::{ConcreteType, Type}, flattening::{FlatID, Instantiation, FlatIDMarker, ConnectionWritePathElement, WireSource, WireInstance, Connection, ConnectionWritePathElementComputed, WireDeclaration, SubModuleInstance, FlattenedModule}, errors::ErrorCollector, linker::{Linker, get_builtin_uuid}, value::{Value, compute_unary_op, compute_binary_op}}; pub mod latency; @@ -154,7 +154,7 @@ struct InstantiationContext<'fl, 'l> { submodules : FlatAlloc, errors : ErrorCollector, - module : &'fl Module, + flattened : &'fl FlattenedModule, linker : &'l Linker, } @@ -172,7 +172,11 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { fn concretize_type(&self, typ : &Type, span : Span) -> Option { match typ { Type::Error => { - self.errors.error_basic(span, "Type is invalid".to_owned()); + self.errors.error_basic(span, "Type is {error}".to_owned()); + None + } + Type::Unknown => { + self.errors.error_basic(span, "Type is {unknown}".to_owned()); None } Type::Named(n) => { @@ -217,7 +221,8 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { let found_typ = &self.wires[from.from].typ; if write_to_typ != found_typ { - todo!(); + // todo!(); + //TODO } let RealWireDataSource::Multiplexer{is_state : _, sources} = &mut self.wires[wire_id].source else {unreachable!("Should only be a writeable wire here")}; @@ -273,7 +278,7 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { if let SubModuleOrWire::CompileTimeValue(vv) = &self.generation_state[v] { Some(vv) } else { - self.errors.error_basic(self.module.flattened.instantiations[v].extract_wire().span, "This variable is not set at this point!"); + self.errors.error_basic(self.flattened.instantiations[v].extract_wire().span, "This variable is not set at this point!"); None } } @@ -294,7 +299,7 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { &WireSource::ArrayAccess{arr, arr_idx} => { let Value::Array(arr_val) = self.get_generation_value(arr)? else {return None}; let arr_idx_val = self.get_generation_value(arr_idx)?; - let arr_idx_wire = self.module.flattened.instantiations[arr_idx].extract_wire(); + let arr_idx_wire = self.flattened.instantiations[arr_idx].extract_wire(); let idx : usize = self.extract_integer_from_value(arr_idx_val, arr_idx_wire.span)?; if let Some(item) = arr_val.get(idx) { item.clone() @@ -316,7 +321,7 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { SubModuleOrWire::Wire(w) => Some(*w), SubModuleOrWire::CompileTimeValue(v) => { let value = v.clone(); - let Instantiation::Wire(WireInstance{typ, inst : _, is_compiletime : _, span}) = &self.module.flattened.instantiations[flat_id] else {unreachable!()}; + let Instantiation::Wire(WireInstance{typ, source : _, is_compiletime : _, span}) = &self.flattened.instantiations[flat_id] else {unreachable!()}; let typ = self.concretize_type(typ, *span)?; let name = self.get_unique_name(); Some(self.wires.alloc(RealWire{source : RealWireDataSource::Constant{value}, original_wire : flat_id, typ, name})) @@ -324,9 +329,9 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { } } fn wire_to_real_wire(&mut self, w: &WireInstance, typ : ConcreteType, original_wire : FlatID) -> Option { - let source = match &w.inst { + let source = match &w.source { &WireSource::WireRead{from_wire} => { - /*Assert*/ self.module.flattened.instantiations[from_wire].extract_wire_declaration(); // WireReads must point to a NamedWire! + /*Assert*/ self.flattened.instantiations[from_wire].extract_wire_declaration(); // WireReads must point to a NamedWire! return Some(self.generation_state[from_wire].extract_wire()) } &WireSource::UnaryOp{op, right} => { @@ -347,7 +352,7 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { RealWireDataSource::ArrayAccess{arr, arr_idx: *w} } SubModuleOrWire::CompileTimeValue(v) => { - let arr_idx_wire = self.module.flattened.instantiations[arr_idx].extract_wire(); + let arr_idx_wire = self.flattened.instantiations[arr_idx].extract_wire(); let arr_idx = self.extract_integer_from_value(v, arr_idx_wire.span)?; RealWireDataSource::ConstArrayAccess{arr, arr_idx} } @@ -361,7 +366,7 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { Some(self.wires.alloc(RealWire{ name, typ, original_wire, source})) } fn instantiate_flattened_module(&mut self) { - for (original_wire, inst) in &self.module.flattened.instantiations { + for (original_wire, inst) in &self.flattened.instantiations { let instance_to_add : SubModuleOrWire = match inst { Instantiation::SubModule(SubModuleInstance{module_uuid, name, typ_span, outputs_start, local_wires}) => { let instance = self.linker.instantiate(*module_uuid); @@ -398,7 +403,7 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { return; // Exit early, do not produce invalid wires in InstantiatedModule }; if w.is_compiletime { - let Some(value_computed) = self.compute_compile_time(&w.inst) else {return}; + let Some(value_computed) = self.compute_compile_time(&w.source) else {return}; assert!(value_computed.is_of_type(&typ)); SubModuleOrWire::CompileTimeValue(value_computed) } else { @@ -410,7 +415,7 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { self.process_connection(conn, original_wire); continue; } - Instantiation::Error => {unreachable!()}, + Instantiation::Error => {continue}, // TODO Remove Instantiation::Error }; self.generation_state[original_wire] = instance_to_add; } @@ -419,8 +424,8 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { // Returns a proper interface if all ports involved did not produce an error. If a port did produce an error then returns None. fn make_interface(&self) -> Option> { let mut result = Vec::new(); - result.reserve(self.module.interface.interface_wires.len()); - for port in self.module.interface.interface_wires.iter() { + result.reserve(self.flattened.interface.interface_wires.len()); + for port in self.flattened.interface.interface_wires.iter() { match &self.generation_state[port.wire_id] { SubModuleOrWire::Wire(w) => { result.push(*w) @@ -450,15 +455,16 @@ impl InstantiationList { pub fn instantiate(&self, module : &Module, linker : &Linker) -> Rc { let mut cache_borrow = self.cache.borrow_mut(); + let flattened = module.flattened.borrow(); // Temporary, no template arguments yet if cache_borrow.is_empty() { let mut context = InstantiationContext{ - generation_state : module.flattened.instantiations.iter().map(|(_, _)| SubModuleOrWire::Unnasigned).collect(), + generation_state : flattened.instantiations.iter().map(|(_, _)| SubModuleOrWire::Unnasigned).collect(), wires : FlatAlloc::new(), submodules : FlatAlloc::new(), - module : module, + flattened : &flattened, linker : linker, - errors : ErrorCollector::new(module.flattened.errors.file) + errors : ErrorCollector::new(flattened.errors.file) }; context.instantiate_flattened_module(); diff --git a/src/linker.rs b/src/linker.rs index 5523c14..8aee4a7 100644 --- a/src/linker.rs +++ b/src/linker.rs @@ -342,7 +342,7 @@ impl Linker { fn get_flattening_errors(&self, file_uuid : FileUUID, errors : &ErrorCollector) { for v in &self.files[file_uuid].associated_values { if let Named::Module(md) = &self.links.globals[*v] { - errors.ingest(&md.flattened.errors); + errors.ingest(&md.flattened.borrow().errors); md.instantiations.collect_errors(errors); } } @@ -514,36 +514,33 @@ impl Linker { pub fn recompile_all(&mut self) { // First create initial flattening for everything, to produce the necessary interfaces - let mut interface_vec : Vec<(NamedUUID, FlattenedInterface, FlattenedModule)> = Vec::new(); - let mut initial_flat_vec : Vec<(NamedUUID, FlatAlloc, DeclIDMarker>)> = Vec::new(); - for (id, named_object) in &self.links.globals { - println!("Initializing Interface for {}", named_object.get_name()); - if let Named::Module(md) = named_object { - // Do initial flattening for ALL modules, regardless of linking errors, to get proper interface - // if !md.link_info.is_fully_linked {continue;} - let (interface, flattened, decl_to_flat_map) = FlattenedModule::initialize_interfaces(&self, md); - interface_vec.push((id, interface, flattened)); - initial_flat_vec.push((id, decl_to_flat_map)); + let module_ids : Vec = self.links.globals.iter().filter_map(|(id,v)| { + if let Named::Module(_) = v { + Some(id) + } else { + None } - } + }).collect(); + for id in &module_ids { + let Named::Module(md) = &self.links.globals[*id] else {unreachable!()}; + + println!("Flattening {}", md.link_info.name); - for (id, interface, flattened) in interface_vec { - let Named::Module(md) = &mut self.links.globals[id] else {unreachable!()}; + let flattened = FlattenedModule::initialize(&self, md); - md.interface = interface; - md.flattened = flattened; + let Named::Module(md) = &mut self.links.globals[*id] else {unreachable!()}; + *md.flattened.get_mut() = flattened; md.instantiations.clear_instances(); } // Then do proper flattening on every module - for (id, decl_to_flat_map) in initial_flat_vec { - let Named::Module(md) = &self.links.globals[id] else {unreachable!()}; - - println!("Flattening {}", &md.link_info.name); - // Do check for linking errors when generating code, as this could cause the compiler to error - if !md.link_info.is_fully_linked {continue;} - md.flattened.flatten(md, &self, decl_to_flat_map); - md.flattened.find_unused_variables(md); + for id in &module_ids { + let Named::Module(md) = &self.links.globals[*id] else {unreachable!()}; + println!("Typechecking {}", &md.link_info.name); + + let mut flattened_mut_borrow = md.flattened.borrow_mut(); + flattened_mut_borrow.typecheck(self); + flattened_mut_borrow.find_unused_variables(); } // Can't merge these loops diff --git a/src/parser.rs b/src/parser.rs index c8c0c89..ac6de38 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,7 +3,7 @@ use num::BigInt; use crate::{tokenizer::*, errors::*, ast::*, linker::FileUUID, flattening::{FlattenedModule, FlattenedInterface}, arena_alloc::FlatAlloc, instantiation::InstantiationList, value::Value}; -use std::{iter::Peekable, str::FromStr, ops::Range}; +use std::{iter::Peekable, str::FromStr, ops::Range, cell::RefCell}; use core::slice::Iter; use std::mem::replace; @@ -720,7 +720,7 @@ impl<'g, 'file> ASTParserContext<'g, 'file> { global_references : replace(&mut self.global_references, Vec::new()), is_fully_linked : false }; - Some(Module{declarations, code, link_info, interface : FlattenedInterface::new(), flattened : FlattenedModule::empty(self.errors.file), instantiations : InstantiationList::new()}) + Some(Module{declarations, code, link_info, flattened : RefCell::new(FlattenedModule::empty(self.errors.file)), instantiations : InstantiationList::new()}) } fn parse_ast(mut self, outer_token_iter : &mut TokenStream) -> ASTRoot { diff --git a/src/typing.rs b/src/typing.rs index 4961006..89242f2 100644 --- a/src/typing.rs +++ b/src/typing.rs @@ -6,6 +6,7 @@ use crate::{ast::{Operator, Span}, linker::{get_builtin_uuid, NamedUUID, Linker, #[derive(Debug, Clone)] pub enum Type { Error, + Unknown, Named(NamedUUID), /*Contains a wireID pointing to a constant expression for the array size, but doesn't actually take size into account for type checking as that would @@ -29,6 +30,9 @@ impl Type { pub fn to_string(&self, linker : &Linker) -> String { match self { Type::Error => { + "{error}".to_owned() + } + Type::Unknown => { "{unknown}".to_owned() } Type::Named(n) => { @@ -39,7 +43,8 @@ impl Type { } pub fn get_root(&self) -> Option { match self { - Type::Error => {None} + Type::Error => None, + Type::Unknown => None, Type::Named(name) => Some(*name), Type::Array(sub) => sub.0.get_root(), }