Skip to content

Commit

Permalink
Integrate submodule instantiation more closely with typechecking
Browse files Browse the repository at this point in the history
- We should now be ready for inference
  • Loading branch information
VonTum committed Nov 17, 2024
1 parent 41c7c97 commit e924ca5
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 158 deletions.
8 changes: 8 additions & 0 deletions src/flattening/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,14 @@ impl SubModuleInstance {
&corresponding_module.link_info.name
}
}
/// If it is named, then return the [Span] of the name, otherwise return the span of the module ref
pub fn get_most_relevant_span(&self) -> Span {
if let Some((_name, span)) = &self.name {
*span
} else {
self.module_ref.get_total_span()
}
}
}

#[derive(Debug)]
Expand Down
116 changes: 72 additions & 44 deletions src/instantiation/concrete_typecheck.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::typing::{concrete_type::{ConcreteType, BOOL_CONCRETE_TYPE, INT_CONCRETE_TYPE}, type_inference::FailedUnification};
use crate::typing::{concrete_type::{ConcreteType, BOOL_CONCRETE_TYPE, INT_CONCRETE_TYPE}, delayed_constraint::{DelayedConstraint, DelayedConstraintStatus, DelayedConstraintsList}, template::check_all_template_args_valid, type_inference::FailedUnification};

use super::*;

Expand Down Expand Up @@ -95,22 +95,70 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> {
}
}

pub fn instantiate_submodule(&self, sm: &SubModule) -> bool {
let submod_instr = self.md.link_info.instructions[sm.original_instruction].unwrap_submodule();
let sub_module = &self.linker.modules[sm.module_uuid];
fn finalize(&mut self) {
for (_id, w) in &mut self.wires {
if let Err(()) = w.typ.fully_substitute(&self.type_substitutor) {
let typ_as_str = w.typ.to_string(&self.linker.types);

let span = self.md.get_instruction_span(w.original_instruction);
self.errors.error(span, format!("Could not finalize this type, some parameters were still unknown: {typ_as_str}"));
}
}

// Print all errors
for FailedUnification{mut found, mut expected, span, context} in self.type_substitutor.extract_errors() {
// Not being able to fully substitute is not an issue. We just display partial types
let _ = found.fully_substitute(&self.type_substitutor);
let _ = expected.fully_substitute(&self.type_substitutor);

let expected_name = expected.to_string(&self.linker.types);
let found_name = found.to_string(&self.linker.types);
self.errors.error(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(&mut self) {
let mut delayed_constraints : DelayedConstraintsList<Self> = DelayedConstraintsList::new();
for (sm_id, _sm) in &self.submodules {
delayed_constraints.push(SubmoduleTypecheckConstraint {sm_id});
}

self.typecheck_all_wires();

delayed_constraints.resolve_delayed_constraints(self);

self.finalize();
}
}

struct SubmoduleTypecheckConstraint {
sm_id: SubModuleID
}

impl DelayedConstraint<InstantiationContext<'_, '_>> for SubmoduleTypecheckConstraint {
fn try_apply(&mut self, context : &mut InstantiationContext) -> DelayedConstraintStatus {
let sm = &context.submodules[self.sm_id];

let submod_instr = context.md.link_info.instructions[sm.original_instruction].unwrap_submodule();
let sub_module = &context.linker.modules[sm.module_uuid];

if !check_all_template_args_valid(
&self.errors,
&context.errors,
submod_instr.module_ref.get_total_span(),
&sub_module.link_info,
&sm.template_args,
) {
return false;
return DelayedConstraintStatus::NoProgress;
};

if let Some(instance) = sub_module.instantiations.instantiate(
sub_module,
self.linker,
context.linker,
sm.template_args.clone(),
) {
for (port_id, concrete_port) in &instance.interface_ports {
Expand All @@ -125,15 +173,15 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> {
// We have a second routine that reports invalid interfaces.
let source_code_port = &sub_module.ports[port_id];
for span in &connecting_wire.name_refs {
self.errors.error(*span, format!("Port '{}' is used, but the instantiated module has this port disabled", source_code_port.name))
context.errors.error(*span, format!("Port '{}' is used, but the instantiated module has this port disabled", source_code_port.name))
.info_obj_different_file(source_code_port, sub_module.link_info.file)
.info_obj_same_file(submod_instr);
}
}
(Some(_concrete_port), None) => {
// Port is enabled, but not used
let source_code_port = &sub_module.ports[port_id];
self.errors
context.errors
.warn(
submod_instr.module_ref.get_total_span(),
format!("Unused port '{}'", source_code_port.name),
Expand All @@ -145,8 +193,8 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> {
.info_obj_same_file(submod_instr);
}
(Some(concrete_port), Some(connecting_wire)) => {
let wire = &self.wires[connecting_wire.maps_to_wire];
self.type_substitutor.unify_must_succeed(&wire.typ, &concrete_port.typ)
let wire = &context.wires[connecting_wire.maps_to_wire];
context.type_substitutor.unify_must_succeed(&wire.typ, &concrete_port.typ)
}
}
}
Expand All @@ -161,14 +209,14 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> {
{
if instance.interface_ports[representative_port].is_none() {
for span in interface_references {
self.errors.error(*span, format!("The interface '{interface_name}' is disabled in this submodule instance"))
context.errors.error(*span, format!("The interface '{interface_name}' is disabled in this submodule instance"))
.info_obj_same_file(submod_instr)
.info((sm_interface.name_span, sub_module.link_info.file), format!("Interface '{interface_name}' declared here"));
}
}
} else {
for span in interface_references {
self.errors.todo(*span, format!("Using empty interface '{interface_name}' (This is a TODO with Actions etc)"))
context.errors.todo(*span, format!("Using empty interface '{interface_name}' (This is a TODO with Actions etc)"))
.info_obj_same_file(submod_instr)
.info((sm_interface.name_span, sub_module.link_info.file), format!("Interface '{interface_name}' declared here"));
}
Expand All @@ -185,46 +233,26 @@ impl<'fl, 'l> InstantiationContext<'fl, 'l> {
}

sm.instance.set(instance).expect("Can only set the instance of a submodule once");
true
DelayedConstraintStatus::Resolved
} else {
self.errors.error(
context.errors.error(
submod_instr.module_ref.get_total_span(),
"Error instantiating submodule",
);
false
DelayedConstraintStatus::NoProgress
}
}

fn finalize(&mut self) {
for (_id, w) in &mut self.wires {
if let Err(()) = w.typ.fully_substitute(&self.type_substitutor) {
let typ_as_str = w.typ.to_string(&self.linker.types);

let span = self.md.get_instruction_span(w.original_instruction);
self.errors.error(span, format!("Could not finalize this type, some parameters were still unknown: {typ_as_str}"));
}
}
fn report_could_not_resolve_error(&self, context : &InstantiationContext) {
let sm = &context.submodules[self.sm_id];

// Print all errors
for FailedUnification{mut found, mut expected, span, context} in self.type_substitutor.extract_errors() {
// Not being able to fully substitute is not an issue. We just display partial types
let _ = found.fully_substitute(&self.type_substitutor);
let _ = expected.fully_substitute(&self.type_substitutor);

let expected_name = expected.to_string(&self.linker.types);
let found_name = found.to_string(&self.linker.types);
self.errors.error(span, format!("Typing Error: {context} expects a {expected_name} but was given a {found_name}"));

assert!(
expected_name != found_name,
"{expected_name} != {found_name}"
);
}
}
let submod_instr = context.md.link_info.instructions[sm.original_instruction].unwrap_submodule();
let sub_module = &context.linker.modules[sm.module_uuid];

pub fn typecheck(&mut self) {
self.typecheck_all_wires();
let name = submod_instr.get_name(sub_module);

self.finalize();
let message = format!("Could not fully instantiate {name}!");

context.errors.error(submod_instr.get_most_relevant_span(), message);
}
}
7 changes: 1 addition & 6 deletions src/instantiation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::{

use crate::typing::{
concrete_type::ConcreteType,
template::{check_all_template_args_valid, ConcreteTemplateArgs},
template::ConcreteTemplateArgs,
};

use self::latency_algorithm::SpecifiedLatency;
Expand Down Expand Up @@ -357,11 +357,6 @@ fn perform_instantiation(
}
}

println!("Instantiating submodules for {}", md.link_info.name);
if !context.submodules.iter().all(|(_id, sm)| context.instantiate_submodule(sm)) {
return context.extract();
}

println!("Concrete Typechecking {}", md.link_info.name);
context.typecheck();

Expand Down
63 changes: 63 additions & 0 deletions src/typing/delayed_constraint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@


#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DelayedConstraintStatus {
Resolved,
Progress,
NoProgress
}

pub trait DelayedConstraint<T> {
fn try_apply(&mut self, shared_object : &mut T) -> DelayedConstraintStatus;
fn report_could_not_resolve_error(&self, shared_object : &T);
}

/// This is for unification of constraints that may not be resolveable right away
///
/// Such as struct field access. vec.x cannot resolve the type of x before the type of vec has been resolved
///
/// The given function should only make changes when it can be successfully resolved
///
/// When the constraint has been resolved, it should return 'true'
///
/// For convenience, a &mut T is provided such that a shared mutable object can be used
pub struct DelayedConstraintsList<T>(Vec<Box<dyn DelayedConstraint<T>>>);

impl<T> DelayedConstraintsList<T> {
pub fn new() -> Self {
Self(Vec::new())
}

/// Add a constraint
pub fn push<C : DelayedConstraint<T> + 'static>(&mut self, constraint: C) {
self.0.push(Box::new(constraint));
}

/// Will keep looping over the list of constraints, and try to apply them.
///
/// Calls [DelayedConstraint::report_could_not_resolve_error] on all constraints that weren't resolved
pub fn resolve_delayed_constraints(mut self, shared_object: &mut T) {
while self.0.len() > 0 {
let mut progress_made = false;
self.0.retain_mut(|constraint| {
match constraint.try_apply(shared_object) {
DelayedConstraintStatus::Resolved => {progress_made = true; false}
DelayedConstraintStatus::Progress => {progress_made = true; true}
DelayedConstraintStatus::NoProgress => true
}
});
if !progress_made {
for constraint in std::mem::replace(&mut self.0, Vec::new()) {
constraint.report_could_not_resolve_error(shared_object);
}
return; // Exit
}
}
}
}

impl<T> Drop for DelayedConstraintsList<T> {
fn drop(&mut self) {
assert_eq!(self.0.len(), 0, "DelayedConstraintsList was not resolved. ");
}
}
2 changes: 1 addition & 1 deletion src/typing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ pub mod abstract_type;
pub mod concrete_type;
pub mod template;
pub mod type_inference;

pub mod delayed_constraint;
2 changes: 1 addition & 1 deletion src/typing/type_inference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ pub trait HindleyMilner<VariableIDMarker: UUIDMarker> : Sized {
///
/// If any pair couldn't be unified, return false
///
/// This is never called by the user, only by [unify]
/// This is never called by the user, only by [TypeSubstitutor::unify]
fn unify_all_args<F : FnMut(&Self, &Self) -> bool>(left : &Self, right : &Self, unify : &mut F) -> bool;

/// Has to be implemented per
Expand Down
2 changes: 1 addition & 1 deletion test.sus
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ module use_bad_interface {
/*
interface
action
request
query
trigger
*/

Expand Down
16 changes: 16 additions & 0 deletions test.sus_errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,22 @@ Error: Could not instantiate ::FIFO because the template arguments 'T', 'DEPTH',
│ ─────┬─────
│ ╰─────── 'READY_SLACK' defined here
─────╯
Error: Could not fully instantiate fiii!
╭─[test.sus:649:7]
649 │ FIFO fiii
│ ──┬─
│ ╰─── Could not fully instantiate fiii!
─────╯
Error: Pre-emptive error because latency-unspecified 'o' is never written to.
(This is because work-in-progress code would get a lot of latency counting errors while unfinished)
╭─[test.sus:648:33]
648 │ interface use_fifo : -> int o
│ ┬
│ ╰── Pre-emptive error because latency-unspecified 'o' is never written to.
(This is because work-in-progress code would get a lot of latency counting errors while unfinished)
─────╯
Error: MIN is not a valid template argument of ::int
╭─[test.sus:672:8]
Expand Down
Loading

0 comments on commit e924ca5

Please sign in to comment.