From e4f047ad567f8881d0d60088b23bd16a8f856ab2 Mon Sep 17 00:00:00 2001 From: Lennart Van Hirtum Date: Sat, 20 Jan 2024 12:53:07 +0100 Subject: [PATCH] Extract struct InterfacePorts, minor latency work --- src/ast.rs | 50 ++++++- src/codegen_fallback.rs | 14 +- src/flattening.rs | 293 +++++++++++++++++++++------------------ src/instantiation/mod.rs | 61 +++++--- src/linker.rs | 31 +++-- src/parser.rs | 8 +- src/typing.rs | 28 ++-- 7 files changed, 289 insertions(+), 196 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index e45410c..a92a158 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -2,7 +2,7 @@ use crate::{tokenizer::{TokenTypeIdx, get_token_type_name}, linker::FileUUID, flattening::FlattenedModule, arena_alloc::{UUIDMarker, UUID, FlatAlloc}, instantiation::InstantiationList, value::Value, errors::ErrorCollector}; use core::ops::Range; -use std::fmt::Display; +use std::{fmt::Display, iter::zip}; // Token span. Indices are INCLUSIVE #[derive(Clone,Copy,Debug,PartialEq,Eq)] @@ -155,13 +155,49 @@ impl LinkInfo { } } + +#[derive(Debug, Clone)] +pub struct InterfacePorts { + pub outputs_start : usize, + pub ports : Box<[ID]> +} + +impl InterfacePorts { + pub fn empty() -> Self { + InterfacePorts{outputs_start : 0, ports : Box::new([])} + } + + // Todo, just treat all inputs and outputs as function call interface + pub fn func_call_syntax_inputs(&self) -> Range { + 0..self.outputs_start + } + pub fn func_call_syntax_outputs(&self) -> Range { + self.outputs_start..self.ports.len() + } + pub fn inputs(&self) -> &[ID] { + &self.ports[..self.outputs_start] + } + pub fn outputs(&self) -> &[ID] { + &self.ports[self.outputs_start..] + } + + pub fn map OtherID>(&self, f : &mut MapFn) -> InterfacePorts { + InterfacePorts{ + ports : self.ports.iter().enumerate().map(|(idx, v)| f(*v, idx < self.outputs_start)).collect(), + outputs_start : self.outputs_start + } + } + pub fn iter(&self) -> impl Iterator + '_ { + self.ports.iter().enumerate().map(|(idx, v)| (*v, idx < self.outputs_start)) + } +} + #[derive(Debug)] pub struct Module { pub link_info : LinkInfo, pub declarations : FlatAlloc, - pub ports : Box<[DeclID]>, - pub outputs_start : usize, + pub ports : InterfacePorts, pub code : CodeBlock, pub flattened : FlattenedModule, @@ -172,10 +208,10 @@ pub struct Module { impl Module { pub fn print_flattened_module(&self) { println!("Interface:"); - for (port_idx, port) in self.flattened.interface_ports.iter().enumerate() { - let port_direction = if port_idx < self.flattened.outputs_start {"input"} else {"output"}; - let port_name = &self.declarations[self.ports[port_idx]].name; - println!(" {port_direction} {port_name} -> {:?}", *port); + for ((port, is_input), port_decl) in zip(self.flattened.interface_ports.iter(), self.ports.ports.iter()) { + let port_direction = if is_input {"input"} else {"output"}; + let port_name = &self.declarations[*port_decl].name; + println!(" {port_direction} {port_name} -> {:?}", port); } println!("Instantiations:"); for (id, inst) in &self.flattened.instantiations { diff --git a/src/codegen_fallback.rs b/src/codegen_fallback.rs index 35ec4fa..4806a8c 100644 --- a/src/codegen_fallback.rs +++ b/src/codegen_fallback.rs @@ -56,9 +56,9 @@ pub fn gen_verilog_code(md : &Module, instance : &InstantiatedModule) -> String assert!(!instance.errors.did_error.get(), "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, real_port) in submodule_interface.iter().enumerate() { - let wire = &instance.wires[*real_port]; - program_text.push_str(if port_idx < md.flattened.outputs_start {"\tinput"} else {"\toutput /*mux_wire*/ reg"}); + for (real_port, is_input) in submodule_interface.iter() { + let wire = &instance.wires[real_port]; + program_text.push_str(if is_input {"\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); @@ -114,12 +114,12 @@ pub fn gen_verilog_code(md : &Module, instance : &InstantiatedModule) -> String program_text.push(' '); program_text.push_str(&sm.name); program_text.push_str("(\n.clk(clk)"); - let Some(sm_interface) = &sm.instance.interface else {unreachable!()}; // Having an invalid interface in a submodule is an error! This should have been caught before! - for (port, wire) in zip(sm_interface, &sm.wires) { + let sm_interface = sm.instance.interface.as_ref().unwrap(); // Having an invalid interface in a submodule is an error! This should have been caught before! + for (port, wire) in zip(sm_interface.iter(), sm.wires.iter()) { program_text.push_str(",\n."); - program_text.push_str(&sm.instance.wires[*port].name); + program_text.push_str(&sm.instance.wires[port.0].name); program_text.push('('); - program_text.push_str(&instance.wires[*wire].name); + program_text.push_str(&instance.wires[wire.0].name); program_text.push_str(")"); } program_text.push_str("\n);\n"); diff --git a/src/flattening.rs b/src/flattening.rs index d8bbc45..41a827b 100644 --- a/src/flattening.rs +++ b/src/flattening.rs @@ -1,9 +1,9 @@ -use std::{ops::{Deref, Range}, iter::zip, cell::RefCell}; +use std::{ops::Deref, iter::zip, cell::RefCell}; use crate::{ - ast::{Span, Module, Expression, SpanExpression, LocalOrGlobal, Operator, AssignableExpression, SpanAssignableExpression, Statement, CodeBlock, IdentifierType, TypeExpression, DeclIDMarker, DeclID, SpanTypeExpression}, - linker::{Linker, FileUUID, GlobalResolver, ResolvedGlobals, NamedConstant, ConstantUUID, ModuleUUID, NameElem}, - errors::{ErrorCollector, error_info}, arena_alloc::{UUID, UUIDMarker, FlatAlloc, UUIDRange}, typing::{Type, typecheck_unary_operator, get_binary_operator_types, typecheck, typecheck_is_array_indexer, BOOL_TYPE, INT_TYPE}, value::Value + ast::{Span, Module, Expression, SpanExpression, LocalOrGlobal, Operator, AssignableExpression, SpanAssignableExpression, Statement, CodeBlock, IdentifierType, TypeExpression, DeclIDMarker, DeclID, SpanTypeExpression, InterfacePorts}, + linker::{Linker, FileUUID, GlobalResolver, ResolvedGlobals, NamedConstant, ConstantUUID, ModuleUUID, NameElem, NamedType, TypeUUIDMarker}, + errors::{ErrorCollector, error_info, ErrorInfo}, arena_alloc::{UUID, UUIDMarker, FlatAlloc, UUIDRange, ArenaAllocator}, typing::{Type, typecheck_unary_operator, get_binary_operator_types, typecheck, typecheck_is_array_indexer, BOOL_TYPE, INT_TYPE}, value::Value }; #[derive(Debug,Clone,Copy,PartialEq,Eq,Hash)] @@ -13,8 +13,6 @@ pub type FlatID = UUID; pub type FlatIDRange = UUIDRange; -pub type FieldID = usize; - #[derive(Debug)] pub enum ConnectionWritePathElement { ArrayIdx{idx : FlatID, idx_span : Span}, @@ -82,7 +80,7 @@ pub struct WireDeclaration { pub name : Box, pub read_only : bool, pub identifier_type : IdentifierType, - //pub latency_specifier : Option + pub latency_specifier : Option } impl WireDeclaration { pub fn get_full_decl_span(&self) -> Span { @@ -97,16 +95,7 @@ pub struct SubModuleInstance { pub name : Box, pub typ_span : Span, pub is_remote_declaration : bool, - pub outputs_start : usize, - pub local_wires : Box<[FlatID]> -} -impl SubModuleInstance { - pub fn inputs(&self) -> &[FlatID] { - &self.local_wires[..self.outputs_start] - } - pub fn outputs(&self) -> &[FlatID] { - &self.local_wires[self.outputs_start..] - } + pub interface_ports : InterfacePorts } #[derive(Debug)] @@ -187,9 +176,19 @@ struct FlatteningContext<'inst, 'l, 'm, 'resolved> { is_remote_declaration : bool, linker : GlobalResolver<'l, 'resolved>, + pub type_list_for_naming : &'l ArenaAllocator, module : &'m Module, } +fn must_be_compiletime_with_info Vec>(wire : &WireInstance, context : &str, errors : &ErrorCollector, ctx_func : CtxFunc) { + if !wire.is_compiletime { + errors.error_with_info(wire.span, format!("{context} must be compile time"), ctx_func()); + } +} +fn must_be_compiletime(wire : &WireInstance, context : &str, errors : &ErrorCollector) { + must_be_compiletime_with_info(wire, context, errors, || Vec::new()); +} + impl<'inst, 'l, 'm, 'resolved> FlatteningContext<'inst, 'l, 'm, 'resolved> { fn map_to_type(&mut self, type_expr : &SpanTypeExpression) -> Type { match &type_expr.0 { @@ -204,10 +203,7 @@ impl<'inst, 'l, 'm, 'resolved> FlatteningContext<'inst, 'l, 'm, 'resolved> { let (array_type_expr, array_size_expr) = b.deref(); let array_element_type = self.map_to_type(&array_type_expr); if let Some(array_size_wire_id) = self.flatten_expr(array_size_expr) { - let array_size_wire = self.instantiations[array_size_wire_id].extract_wire(); - if !array_size_wire.is_compiletime { - self.errors.error_basic(array_size_expr.1, "Array size must be compile time"); - } + must_be_compiletime(self.instantiations[array_size_wire_id].extract_wire(), "Array size", &self.errors); Type::Array(Box::new((array_element_type, array_size_wire_id))) } else { Type::Error @@ -240,6 +236,17 @@ impl<'inst, 'l, 'm, 'resolved> FlatteningContext<'inst, 'l, 'm, 'resolved> { let typ_span = decl.typ.1; + let latency_specifier = if let Some(lat_expr) = &decl.latency_expr { + if let Some(latency_spec) = self.flatten_expr(lat_expr) { + must_be_compiletime(self.instantiations[latency_spec].extract_wire(), "Latency specifier", &self.errors); + Some(latency_spec) + } else { + None + } + } else { + None + }; + let inst_id = self.instantiations.alloc(Instantiation::WireDeclaration(WireDeclaration{ typ, typ_span, @@ -247,19 +254,19 @@ impl<'inst, 'l, 'm, 'resolved> FlatteningContext<'inst, 'l, 'm, 'resolved> { read_only, identifier_type : decl.identifier_type, name : decl.name.clone(), - name_token : decl.name_token + name_token : decl.name_token, + latency_specifier })); self.decl_to_flat_map[decl_id] = Some(inst_id); inst_id } - fn initialize_interface(&mut self) -> Box<[FlatID]> { - self.module.ports.iter().enumerate().map(|(id, decl_id)|{ - let is_input = id < self.module.outputs_start; + fn initialize_interface(&mut self) -> InterfacePorts { + self.module.ports.map(&mut |id, is_input|{ let read_only = is_input ^ IS_SUBMODULE; - self.flatten_declaration::(*decl_id, read_only) - }).collect() + self.flatten_declaration::(id, read_only) + }) } fn alloc_module_interface(&mut self, name : Box, module : &Module, module_uuid : ModuleUUID, typ_span : Span) -> FlatID { let mut nested_context = FlatteningContext { @@ -268,22 +275,22 @@ impl<'inst, 'l, 'm, 'resolved> FlatteningContext<'inst, 'l, 'm, 'resolved> { errors: ErrorCollector::new(module.link_info.file), // Temporary ErrorCollector, unused is_remote_declaration: true, linker: self.linker.new_sublinker(module.link_info.file), + type_list_for_naming: self.type_list_for_naming, module, }; - let local_wires = nested_context.initialize_interface::(); + let interface_ports = nested_context.initialize_interface::(); self.instantiations.alloc(Instantiation::SubModule(SubModuleInstance{ name, module_uuid, is_remote_declaration : self.is_remote_declaration, typ_span, - outputs_start : module.outputs_start, - local_wires + interface_ports })) } // Returns the module, full interface, and the output range for the function call syntax - fn desugar_func_call(&mut self, func_and_args : &[SpanExpression], closing_bracket_pos : usize) -> Option<(&Module, Box<[FlatID]>, Range)> { + fn desugar_func_call(&mut self, func_and_args : &[SpanExpression], closing_bracket_pos : usize) -> Option<(&Module, InterfacePorts)> { let (name_expr, name_expr_span) = &func_and_args[0]; // Function name is always there let func_instantiation_id = match name_expr { Expression::Named(LocalOrGlobal::Local(l)) => { @@ -291,7 +298,7 @@ impl<'inst, 'l, 'm, 'resolved> FlatteningContext<'inst, 'l, 'm, 'resolved> { } Expression::Named(LocalOrGlobal::Global(ref_span)) => { let module_id = self.linker.resolve_module(*ref_span, &self.errors)?; - let md = self.linker.get_module(module_id); + let md = &self.linker.get_module(module_id); self.alloc_module_interface(md.link_info.name.clone(), md, module_id, *name_expr_span) } _other => { @@ -300,11 +307,11 @@ impl<'inst, 'l, 'm, 'resolved> FlatteningContext<'inst, 'l, 'm, 'resolved> { } }; let func_instantiation = &self.instantiations[func_instantiation_id].extract_submodule(); - let md = self.linker.get_module(func_instantiation.module_uuid); + let md = &self.linker.get_module(func_instantiation.module_uuid); - let submodule_local_wires = func_instantiation.local_wires.clone(); + let submodule_local_wires = func_instantiation.interface_ports.clone(); - let (inputs, output_range) = md.flattened.func_call_syntax_interface(); + let inputs = submodule_local_wires.func_call_syntax_inputs(); let mut args = &func_and_args[1..]; @@ -329,12 +336,12 @@ impl<'inst, 'l, 'm, 'resolved> FlatteningContext<'inst, 'l, 'm, 'resolved> { /*if self.typecheck(arg_read_side, &md.interface.interface_wires[field].typ, "submodule output") == None { continue; }*/ - let func_input_port = &submodule_local_wires[field]; + let func_input_port = &submodule_local_wires.ports[field]; self.instantiations.alloc(Instantiation::Connection(Connection{num_regs: 0, from: arg_read_side, to: ConnectionWrite{root : *func_input_port, path : Vec::new(), span : *name_expr_span, is_remote_declaration : self.is_remote_declaration}})); } } - Some((md, submodule_local_wires, output_range)) + Some((md, submodule_local_wires)) } fn flatten_expr(&mut self, (expr, expr_span) : &SpanExpression) -> Option { let (is_compiletime, source) = match expr { @@ -374,15 +381,17 @@ impl<'inst, 'l, 'm, 'resolved> FlatteningContext<'inst, 'l, 'm, 'resolved> { (arr_wire.is_compiletime && arr_idx_wire.is_compiletime, WireSource::ArrayAccess{arr, arr_idx}) } Expression::FuncCall(func_and_args) => { - let (md, interface_wires, outputs_range) = self.desugar_func_call(func_and_args, expr_span.1)?; + let (md, interface_wires) = self.desugar_func_call(func_and_args, expr_span.1)?; - if outputs_range.len() != 1 { + let output_range = interface_wires.func_call_syntax_outputs(); + + if output_range.len() != 1 { let info = error_info(md.link_info.span, md.link_info.file, "Module Defined here"); 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; } - return Some(interface_wires[outputs_range.start]) + return Some(interface_wires.ports[output_range.start]) } }; @@ -423,8 +432,9 @@ impl<'inst, 'l, 'm, 'resolved> FlatteningContext<'inst, 'l, 'm, 'resolved> { let _wire_id = self.flatten_declaration::(*decl_id, false); } Statement::Assign{to, expr : (Expression::FuncCall(func_and_args), func_span), eq_sign_position} => { - let Some((md, interface, outputs_range)) = self.desugar_func_call(&func_and_args, func_span.1) else {continue}; - let outputs = &interface[outputs_range]; + let Some((md, interface)) = self.desugar_func_call(&func_and_args, func_span.1) else {continue}; + let output_range = interface.func_call_syntax_outputs(); + let outputs = &interface.ports[output_range]; let func_name_span = func_and_args[0].1; let num_func_outputs = outputs.len(); @@ -509,99 +519,53 @@ impl<'inst, 'l, 'm, 'resolved> FlatteningContext<'inst, 'l, 'm, 'resolved> { } } } -} -#[derive(Debug)] -pub struct FlattenedInterfacePort { - pub wire_id : FlatID, - pub port_name : Box, - pub span : Span -} - -#[derive(Debug)] -pub struct FlattenedModule { - pub instantiations : FlatAlloc, - pub errors : ErrorCollector, - pub outputs_start : usize, - pub interface_ports : Box<[FlatID]>, - pub resolved_globals : ResolvedGlobals -} -impl FlattenedModule { - pub fn empty(file : FileUUID) -> FlattenedModule { - FlattenedModule { - instantiations : FlatAlloc::new(), - errors : ErrorCollector::new(file), - interface_ports : Box::new([]), - outputs_start : 0, - resolved_globals : ResolvedGlobals::new() - } - } - - // Todo, just treat all inputs and outputs as function call interface - pub fn func_call_syntax_interface(&self) -> (Range, Range) { - (0..self.outputs_start, self.outputs_start..self.interface_ports.len()) - } - pub fn inputs(&self) -> &[FlatID] { - &self.interface_ports[..self.outputs_start] - } - pub fn outputs(&self) -> &[FlatID] { - &self.interface_ports[self.outputs_start..] - } - /* - 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 + ==== Type Checking ==== */ - pub fn initialize(linker : &Linker, module : &Module) -> FlattenedModule { - let mut instantiations = FlatAlloc::new(); - let resolved_globals : RefCell = RefCell::new(ResolvedGlobals::new()); - let mut context = FlatteningContext{ - decl_to_flat_map: module.declarations.iter().map(|_| None).collect(), - instantiations: &mut instantiations, - errors: ErrorCollector::new(module.link_info.file), - is_remote_declaration : false, - linker : GlobalResolver::new(linker, module.link_info.file, &resolved_globals), - module, - }; - - let interface_ports = context.initialize_interface::(); - - context.flatten_code(&module.code); - - FlattenedModule { - errors : context.errors, - instantiations : instantiations, - interface_ports, - outputs_start : module.outputs_start, - resolved_globals : resolved_globals.into_inner() - } + fn typecheck_wire_is_of_type(&self, wire : &WireInstance, expected : &Type, context : &str) { + typecheck(&wire.typ, wire.span, expected, context, self.type_list_for_naming, &self.errors); } - /* 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); + // Typechecks things like that arrays have compiletime integer sizes + fn typecheck_type_generic_parameters(&self, typ : &Type) { + match typ { + Type::Error => {} + Type::Unknown => unreachable!(), // Should only run this on types that have been properly resolved! + Type::Named{id:_, span:_} => {} + Type::Array(arr_box) => { + let (arr_typ, size_val) = arr_box.deref(); + self.typecheck_type_generic_parameters(arr_typ); + let size_val_wire = &self.instantiations[*size_val].extract_wire(); + self.typecheck_wire_is_of_type(size_val_wire, &INT_TYPE, "Array size"); + } + } } - pub fn typecheck(&mut self, linker : &Linker) { + fn typecheck(&mut self) { 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::WireDeclaration(decl) => { + if let Some(latency_spec) = decl.latency_specifier { + let latency_spec_wire = &self.instantiations[latency_spec].extract_wire(); + self.typecheck_wire_is_of_type(latency_spec_wire, &INT_TYPE, "latency specifier"); + } + self.typecheck_type_generic_parameters(&decl.typ); + } Instantiation::IfStatement(stm) => { let wire = &self.instantiations[stm.condition].extract_wire(); - self.typecheck_wire_is_of_type(wire, &BOOL_TYPE, "if statement condition", linker) + self.typecheck_wire_is_of_type(wire, &BOOL_TYPE, "if statement condition") } Instantiation::ForStatement(stm) => { let loop_var = &self.instantiations[stm.loop_var_decl].extract_wire_declaration(); let start = &self.instantiations[stm.start].extract_wire(); let end = &self.instantiations[stm.end].extract_wire(); - self.typecheck_wire_is_of_type(start, &loop_var.typ, "for loop", linker); - self.typecheck_wire_is_of_type(end, &loop_var.typ, "for loop", linker); + self.typecheck_wire_is_of_type(start, &loop_var.typ, "for loop"); + self.typecheck_wire_is_of_type(end, &loop_var.typ, "for loop"); } Instantiation::Wire(w) => { let result_typ = match &w.source { @@ -610,22 +574,22 @@ impl FlattenedModule { } &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) + typecheck_unary_operator(op, &right_wire.typ, right_wire.span, self.type_list_for_naming, &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); + self.typecheck_wire_is_of_type(left_wire, &input_left_type, &format!("{op} left")); + self.typecheck_wire_is_of_type(right_wire, &input_right_type, &format!("{op} right")); 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, &INT_TYPE, "array index", linker); - if let Some(typ) = typecheck_is_array_indexer(&arr_wire.typ, arr_wire.span, linker, &self.errors) { + self.typecheck_wire_is_of_type(arr_idx_wire, &INT_TYPE, "array index"); + if let Some(typ) = typecheck_is_array_indexer(&arr_wire.typ, arr_wire.span, self.type_list_for_naming, &self.errors) { typ.clone() } else { Type::Error @@ -635,7 +599,7 @@ impl FlattenedModule { value.get_type_of_constant() } &WireSource::NamedConstant(id) => { - let NamedConstant::Builtin{name:_, typ, val:_} = &linker.constants[id]; + let NamedConstant::Builtin{name:_, typ, val:_} = &self.linker.get_constant(id); typ.clone() } }; @@ -643,7 +607,6 @@ impl FlattenedModule { 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); @@ -651,9 +614,9 @@ impl FlattenedModule { match p { &ConnectionWritePathElement::ArrayIdx{idx, idx_span} => { let idx_wire = self.instantiations[idx].extract_wire(); - self.typecheck_wire_is_of_type(idx_wire, &INT_TYPE, "array index", linker); + self.typecheck_wire_is_of_type(idx_wire, &INT_TYPE, "array index"); if let Some(wr) = write_to_type { - write_to_type = typecheck_is_array_indexer(wr, idx_span, linker, &self.errors); + write_to_type = typecheck_is_array_indexer(wr, idx_span, self.type_list_for_naming, &self.errors); } } } @@ -661,41 +624,40 @@ impl FlattenedModule { // 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]); + if conn_root.identifier_type == IdentifierType::Generative { + must_be_compiletime_with_info(from_wire, "Assignments to generative variables", &self.errors, || vec![error_info(conn_root.get_full_decl_span(), self.errors.file, "Declared here")]); } // Typecheck the value with target type if let Some(target_type) = write_to_type { - self.typecheck_wire_is_of_type(from_wire, &target_type, "connection", linker); + self.typecheck_wire_is_of_type(from_wire, &target_type, "connection"); } } } } // Post type application. Flag any remaining Type::Unknown - for (_id, inst) in &self.instantiations { + for (_id, inst) in self.instantiations.iter() { inst.for_each_embedded_type(&mut |typ, span| { if typ.contains_error_or_unknown::() { - self.errors.error_basic(span, format!("Unresolved Type: {}", typ.to_string(linker))) + self.errors.error_basic(span, format!("Unresolved Type: {}", typ.to_string(self.type_list_for_naming))) } }); } } /* Additional Warnings */ - pub fn find_unused_variables(&self) { + fn find_unused_variables(&self, interface : &InterfacePorts) { // Setup Wire Fanouts List for faster processing let mut gathered_connection_fanin : FlatAlloc, FlatIDMarker> = self.instantiations.iter().map(|_| Vec::new()).collect(); - for (inst_id, inst) in &self.instantiations { + for (inst_id, inst) in self.instantiations.iter() { match inst { Instantiation::Connection(conn) => { gathered_connection_fanin[conn.to.root].push(conn.from); } Instantiation::SubModule(sm) => { - for w in sm.outputs() { + for w in sm.interface_ports.outputs() { gathered_connection_fanin[*w].push(inst_id); } } @@ -719,7 +681,7 @@ impl FlattenedModule { let mut wire_to_explore_queue : Vec = Vec::new(); - for port in self.outputs() { + for port in interface.outputs() { is_instance_used_map[*port] = true; wire_to_explore_queue.push(*port); } @@ -739,7 +701,7 @@ impl FlattenedModule { wire.source.for_each_input_wire(&mut mark_not_unused); } Instantiation::SubModule(submodule) => { - for port in submodule.inputs() { + for port in submodule.interface_ports.inputs() { mark_not_unused(*port); } } @@ -751,7 +713,7 @@ impl FlattenedModule { } // Now produce warnings from the unused list - for (id, inst) in &self.instantiations { + for (id, inst) in self.instantiations.iter() { if !is_instance_used_map[id] { if let Instantiation::WireDeclaration(decl) = inst { if !decl.is_remote_declaration { @@ -762,3 +724,62 @@ impl FlattenedModule { } } } + +#[derive(Debug)] +pub struct FlattenedInterfacePort { + pub wire_id : FlatID, + pub port_name : Box, + pub span : Span +} + +#[derive(Debug)] +pub struct FlattenedModule { + pub instantiations : FlatAlloc, + pub errors : ErrorCollector, + pub interface_ports : InterfacePorts, + pub resolved_globals : ResolvedGlobals +} + +impl FlattenedModule { + pub fn empty(file : FileUUID) -> FlattenedModule { + FlattenedModule { + instantiations : FlatAlloc::new(), + errors : ErrorCollector::new(file), + interface_ports : InterfacePorts::empty(), + resolved_globals : ResolvedGlobals::new() + } + } + + /* + 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 mut instantiations = FlatAlloc::new(); + let resolved_globals : RefCell = RefCell::new(ResolvedGlobals::new()); + let mut context = FlatteningContext{ + decl_to_flat_map: module.declarations.iter().map(|_| None).collect(), + instantiations: &mut instantiations, + errors: ErrorCollector::new(module.link_info.file), + is_remote_declaration : false, + linker : GlobalResolver::new(linker, module.link_info.file, &resolved_globals), + type_list_for_naming : &linker.types, + module, + }; + + let interface_ports = context.initialize_interface::(); + + context.flatten_code(&module.code); + context.typecheck(); + context.find_unused_variables(&interface_ports); + + FlattenedModule { + errors : context.errors, + instantiations : instantiations, + interface_ports, + resolved_globals : resolved_globals.into_inner() + } + } +} diff --git a/src/instantiation/mod.rs b/src/instantiation/mod.rs index e626d7e..d0b22d3 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, UUIDRange}, ast::{Operator, IdentifierType, Span}, typing::{ConcreteType, Type, BOOL_CONCRETE_TYPE, INT_CONCRETE_TYPE}, flattening::{FlatID, Instantiation, FlatIDMarker, ConnectionWritePathElement, WireSource, WireInstance, Connection, ConnectionWritePathElementComputed, FlattenedModule, FlatIDRange}, errors::ErrorCollector, linker::{Linker, NamedConstant}, value::{Value, compute_unary_op, compute_binary_op}, tokenizer::kw}; +use crate::{arena_alloc::{UUID, UUIDMarker, FlatAlloc, UUIDRange}, ast::{Operator, IdentifierType, Span, InterfacePorts}, typing::{ConcreteType, Type, BOOL_CONCRETE_TYPE, INT_CONCRETE_TYPE}, flattening::{FlatID, Instantiation, FlatIDMarker, ConnectionWritePathElement, WireSource, WireInstance, Connection, ConnectionWritePathElementComputed, FlattenedModule, FlatIDRange}, errors::ErrorCollector, linker::{Linker, NamedConstant}, value::{Value, compute_unary_op, compute_binary_op}, tokenizer::kw}; pub mod latency; @@ -39,7 +39,7 @@ pub struct MultiplexerSource { #[derive(Debug)] pub enum StateInitialValue { Combinatorial, - State{initial_value : Value} + State{initial_value : Value} // Value::Unset for non initialized State } #[derive(Debug)] @@ -89,21 +89,22 @@ pub struct RealWire { pub source : RealWireDataSource, pub original_wire : FlatID, pub typ : ConcreteType, - pub name : Box + pub name : Box, + pub latency_specifier : Option } #[derive(Debug)] pub struct SubModule { pub original_flat : FlatID, pub instance : Rc, - pub wires : Vec, + pub wires : InterfacePorts, pub name : Box } #[derive(Debug)] pub struct InstantiatedModule { pub name : Box, // Unique name involving all template arguments - pub interface : Option>, // Interface is only valid if all wires of the interface were valid + pub interface : Option>, // Interface is only valid if all wires of the interface were valid pub wires : FlatAlloc, pub submodules : FlatAlloc, pub errors : ErrorCollector, @@ -182,7 +183,7 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { } fn concretize_type(&self, typ : &Type, span : Span) -> Option { match typ { - Type::Error | Type::Unknown => unreachable!("Bad types should be caught in flattening: {}", typ.to_string(self.linker)), + Type::Error | Type::Unknown => unreachable!("Bad types should be caught in flattening: {}", typ.to_string(&self.linker.types)), Type::Named{id, span : _} => { Some(ConcreteType::Named(*id)) } @@ -322,7 +323,7 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { let Instantiation::Wire(wire) = &self.flattened.instantiations[flat_id] else {unreachable!()}; let typ = self.concretize_type(&wire.typ, wire.span)?; let name = self.get_unique_name(); - Some(self.wires.alloc(RealWire{source : RealWireDataSource::Constant{value}, original_wire : flat_id, typ, name})) + Some(self.wires.alloc(RealWire{source : RealWireDataSource::Constant{value}, original_wire : flat_id, typ, name, latency_specifier : None})) } } } @@ -361,11 +362,20 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { } }; let name = self.get_unique_name(); - Some(self.wires.alloc(RealWire{ name, typ, original_wire, source})) + Some(self.wires.alloc(RealWire{name, typ, original_wire, source, latency_specifier : None})) } fn extend_condition(&mut self, condition : Option, additional_condition : WireID, original_wire : FlatID) -> WireID { if let Some(condition) = condition { - self.wires.alloc(RealWire{typ : BOOL_CONCRETE_TYPE, name : self.get_unique_name(), original_wire, source : RealWireDataSource::BinaryOp{op: Operator{op_typ : kw("&")}, left : condition, right : additional_condition}}) + self.wires.alloc(RealWire{ + typ : BOOL_CONCRETE_TYPE, + name : self.get_unique_name(), + original_wire, + source : RealWireDataSource::BinaryOp{ + op: Operator{op_typ : kw("&")}, + left : condition, + right : additional_condition + }, + latency_specifier : None}) } else { additional_condition } @@ -376,9 +386,9 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { let instance_to_add : SubModuleOrWire = match &self.flattened.instantiations[original_wire] { Instantiation::SubModule(submodule) => { let Some(instance) = self.linker.instantiate(submodule.module_uuid) else {return None}; // Avoid error from submodule - let interface_real_wires = submodule.local_wires.iter().map(|port| { - self.generation_state[*port].extract_wire() - }).collect(); + let interface_real_wires = submodule.interface_ports.map(&mut |port, _is_input| { + self.generation_state[port].extract_wire() + }); SubModuleOrWire::SubModule(self.submodules.alloc(SubModule { original_flat: original_wire, instance, wires : interface_real_wires, name : submodule.name.clone()})) } Instantiation::WireDeclaration(wire_decl) => { @@ -399,7 +409,13 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { let is_state = if wire_decl.identifier_type == IdentifierType::State{StateInitialValue::State{initial_value: Value::Unset}} else {StateInitialValue::Combinatorial}; RealWireDataSource::Multiplexer{is_state, sources : Vec::new()} }; - SubModuleOrWire::Wire(self.wires.alloc(RealWire{ name: wire_decl.name.clone(), typ, original_wire, source})) + let latency_specifier = if let Some(lat_spec_flat) = wire_decl.latency_specifier { + let val = self.get_generation_value(lat_spec_flat)?; + Some(self.extract_integer_from_value(val, self.flattened.instantiations[lat_spec_flat].extract_wire().span)?) + } else { + None + }; + SubModuleOrWire::Wire(self.wires.alloc(RealWire{name: wire_decl.name.clone(), typ, original_wire, source, latency_specifier})) } } Instantiation::Wire(w) => { @@ -434,7 +450,16 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { self.instantiate_flattened_module(then_range, Some(then_cond)); if !else_range.is_empty() { - let else_condition_bool = self.wires.alloc(RealWire{typ : BOOL_CONCRETE_TYPE, name : self.get_unique_name(), original_wire, source : RealWireDataSource::UnaryOp{op : Operator{op_typ : kw("!")}, right : condition_wire}}); + let else_condition_bool = self.wires.alloc(RealWire{ + typ : BOOL_CONCRETE_TYPE, + name : self.get_unique_name(), + original_wire, + source : RealWireDataSource::UnaryOp{ + op : Operator{op_typ : kw("!")}, + right : condition_wire + }, + latency_specifier : None + }); let else_cond = self.extend_condition(condition, else_condition_bool, original_wire); self.instantiate_flattened_module(else_range, Some(else_cond)); } @@ -472,10 +497,10 @@ 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> { + fn make_interface(&self) -> Option> { let mut result = Vec::new(); - result.reserve(self.flattened.interface_ports.len()); - for port in self.flattened.interface_ports.iter() { + result.reserve(self.flattened.interface_ports.ports.len()); + for port in self.flattened.interface_ports.ports.iter() { match &self.generation_state[*port] { SubModuleOrWire::Wire(w) => { result.push(*w) @@ -486,7 +511,7 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> { _other => unreachable!() // interface wires cannot point to anything else } } - Some(result) + Some(InterfacePorts{ports : result.into_boxed_slice(), outputs_start : self.flattened.interface_ports.outputs_start}) } } diff --git a/src/linker.rs b/src/linker.rs index aac3b0a..a114f40 100644 --- a/src/linker.rs +++ b/src/linker.rs @@ -348,10 +348,8 @@ impl Linker { let md = &self.modules[id];// Have to get them like this, so we don't have a mutable borrow on self.modules across the loop println!("Flattening {}", md.link_info.name); - let mut flattened = FlattenedModule::initialize(&self, md); + let flattened = FlattenedModule::initialize(&self, md); println!("Typechecking {}", &md.link_info.name); - flattened.typecheck(self); - flattened.find_unused_variables(); let md = &mut self.modules[id]; // Convert to mutable ptr md.flattened = flattened; @@ -389,17 +387,24 @@ impl ResolvedGlobals { pub struct GlobalResolver<'linker, 'resolved_list> { linker : &'linker Linker, file : &'linker FileData, - resolved_globals : &'resolved_list RefCell } impl<'linker, 'resolved_list> GlobalResolver<'linker, 'resolved_list> { pub fn new(linker : &'linker Linker, file_id : FileUUID, resolved_globals : &'resolved_list RefCell) -> GlobalResolver<'linker, 'resolved_list> { - GlobalResolver{linker, file : &linker.files[file_id], resolved_globals} + GlobalResolver{ + linker, + file : &linker.files[file_id], + resolved_globals + } } pub fn new_sublinker(&self, file_id : FileUUID) -> GlobalResolver<'linker, 'resolved_list> { - GlobalResolver{linker : self.linker, file : &self.linker.files[file_id], resolved_globals : self.resolved_globals} + GlobalResolver{ + linker : self.linker, + file : &self.linker.files[file_id], + resolved_globals : self.resolved_globals + } } pub fn resolve_global(&self, name_span : Span, errors : &ErrorCollector) -> Option { @@ -438,10 +443,6 @@ impl<'linker, 'resolved_list> GlobalResolver<'linker, 'resolved_list> { } } - pub fn get_module(&self, uuid : ModuleUUID) -> &'linker Module { - &self.linker.modules[uuid] - } - pub fn make_bad_error_location_error(&self, elem : NameElem, expected : &str, identifier_span : Span, errors : &ErrorCollector) { let info = self.linker.get_linking_error_location(elem); let infos = if let Some((file, span)) = info.location { @@ -488,4 +489,14 @@ impl<'linker, 'resolved_list> GlobalResolver<'linker, 'resolved_list> { } } } + + pub fn get_module(&self, index: ModuleUUID) -> &'linker Module { + &self.linker.modules[index] + } + pub fn get_constant(&self, index: ConstantUUID) -> &'linker NamedConstant { + &self.linker.constants[index] + } + pub fn get_type(&self, index: TypeUUID) -> &'linker NamedType { + &self.linker.types[index] + } } diff --git a/src/parser.rs b/src/parser.rs index 8e62ccc..1056b8b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -485,7 +485,7 @@ impl<'file> ASTParserContext<'file> { } } - fn parse_interface(&mut self, token_stream : &mut TokenStream, scope : &mut LocalVariableContext<'_, 'file>) -> (Box<[DeclID]>, usize) { + fn parse_interface(&mut self, token_stream : &mut TokenStream, scope : &mut LocalVariableContext<'_, 'file>) -> InterfacePorts { // Current implementation happens to order inputs then outputs, but refactorings should ensure this remains the case let mut interface_decls = Vec::new(); @@ -496,7 +496,7 @@ impl<'file> ASTParserContext<'file> { self.parse_bundle(token_stream, &mut interface_decls, IdentifierType::Output, scope); } - (interface_decls.into_boxed_slice(), outputs_start) + InterfacePorts{ports : interface_decls.into_boxed_slice(), outputs_start} } fn parse_statement(&mut self, token_stream : &mut TokenStream, scope : &mut LocalVariableContext<'_, 'file>, code_block : &mut CodeBlock) -> Option<()> { @@ -710,7 +710,7 @@ impl<'file> ASTParserContext<'file> { self.eat_plain(token_stream, kw(":"), "module")?; let mut scope = LocalVariableContext::new_initial(); - let (ports, outputs_start) = self.parse_interface(token_stream, &mut scope); + let ports = self.parse_interface(token_stream, &mut scope); let (block_tokens, block_span) = self.eat_block(token_stream, kw("{"), "module")?; @@ -725,7 +725,7 @@ impl<'file> ASTParserContext<'file> { span }; let declarations = std::mem::replace(&mut self.declarations, FlatAlloc::new()); - Some(Module{declarations, ports, outputs_start, code, link_info, flattened : FlattenedModule::empty(self.errors.file), instantiations : InstantiationList::new()}) + Some(Module{declarations, ports, code, link_info, flattened : 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 2265b00..d50cda4 100644 --- a/src/typing.rs +++ b/src/typing.rs @@ -1,6 +1,6 @@ use std::ops::Deref; -use crate::{ast::{Operator, Span}, linker::{get_builtin_type, TypeUUID, Linker, Linkable}, tokenizer::kw, flattening::FlatID, errors::ErrorCollector, value::Value}; +use crate::{ast::{Operator, Span}, linker::{get_builtin_type, TypeUUID, Linker, Linkable, NamedType, TypeUUIDMarker}, tokenizer::kw, flattening::FlatID, errors::ErrorCollector, value::Value, arena_alloc::ArenaAllocator}; // Types contain everything that cannot be expressed at runtime #[derive(Debug, Clone)] @@ -16,7 +16,7 @@ pub enum Type { } impl Type { - pub fn to_string(&self, linker : &Linker) -> String { + pub fn to_string(&self, linker_types : &ArenaAllocator) -> String { match self { Type::Error => { "{error}".to_owned() @@ -25,9 +25,9 @@ impl Type { "{unknown}".to_owned() } Type::Named{id, span:_} => { - linker.types[*id].get_full_name() + linker_types[*id].get_full_name() } - Type::Array(sub) => sub.deref().0.to_string(linker) + "[]", + Type::Array(sub) => sub.deref().0.to_string(linker_types) + "[]", } } pub fn for_each_generative_input(&self, f : &mut F) { @@ -70,12 +70,12 @@ pub const INT_TYPE : Type = Type::Named{id : get_builtin_type("int"), span : Non pub const BOOL_CONCRETE_TYPE : ConcreteType = ConcreteType::Named(get_builtin_type("bool")); pub const INT_CONCRETE_TYPE : ConcreteType = ConcreteType::Named(get_builtin_type("int")); -pub fn typecheck_unary_operator(op : Operator, input_typ : &Type, span : Span, linker : &Linker, errors : &ErrorCollector) -> Type { +pub fn typecheck_unary_operator(op : Operator, input_typ : &Type, span : Span, linker_types : &ArenaAllocator, errors : &ErrorCollector) -> Type { if op.op_typ == kw("!") { - typecheck(input_typ, span, &BOOL_TYPE, "! input", linker, errors); + typecheck(input_typ, span, &BOOL_TYPE, "! input", linker_types, errors); BOOL_TYPE } else if op.op_typ == kw("-") { - typecheck(input_typ, span, &INT_TYPE, "- input", linker, errors); + typecheck(input_typ, span, &INT_TYPE, "- input", linker_types, errors); INT_TYPE } else { let gather_type = match op.op_typ { @@ -86,8 +86,8 @@ pub fn typecheck_unary_operator(op : Operator, input_typ : &Type, span : Span, l x if x == kw("*") => INT_TYPE, _ => unreachable!() }; - if let Some(arr_content_typ) = typecheck_is_array_indexer(input_typ, span, linker, errors) { - typecheck(arr_content_typ, span, &gather_type, &format!("{op} input"), linker, errors); + if let Some(arr_content_typ) = typecheck_is_array_indexer(input_typ, span, linker_types, errors) { + typecheck(arr_content_typ, span, &gather_type, &format!("{op} input"), linker_types, errors); } gather_type } @@ -123,17 +123,17 @@ fn type_compare(expected : &Type, found : &Type) -> bool { _ => false, } } -pub fn typecheck(found : &Type, span : Span, expected : &Type, context : &str, linker : &Linker, errors : &ErrorCollector) { +pub fn typecheck(found : &Type, span : Span, expected : &Type, context : &str, linker_types : &ArenaAllocator, errors : &ErrorCollector) { if !type_compare(expected, found) { - let expected_name = expected.to_string(linker); - let found_name = found.to_string(linker); + let expected_name = expected.to_string(linker_types); + let found_name = found.to_string(linker_types); errors.error_basic(span, format!("Typing Error: {context} expects a {expected_name} but was given a {found_name}")); assert!(expected_name != found_name, "{expected_name} != {found_name}"); } } -pub fn typecheck_is_array_indexer<'a>(arr_type : &'a Type, span : Span, linker : &Linker, errors : &ErrorCollector) -> Option<&'a Type> { +pub fn typecheck_is_array_indexer<'a>(arr_type : &'a Type, span : Span, linker_types : &ArenaAllocator, errors : &ErrorCollector) -> Option<&'a Type> { let Type::Array(arr_element_type) = arr_type else { - let arr_type_name = arr_type.to_string(linker); + let arr_type_name = arr_type.to_string(linker_types); errors.error_basic(span, format!("Typing Error: Attempting to index into this, but it is not of array type, instead found a {arr_type_name}")); return None; };