spec: Typecheck padding#670
Conversation
Codex Code ReviewNo issues found in the PR diff. I reviewed the changed TOML padding values and the new recursive padding/template checker in
It completed with |
Review: spec/typecheck-paddingSummaryThis PR introduces type-checking for padding values in chip specs, fixing two concrete bugs:
It also reorders The overall approach is sound. Two issues worth addressing: IssuesMedium – Medium – Unhandled Minor notes
|
| continue | ||
| chips.append(Chip.from_file(config, file)) | ||
| chips.append(chip := Chip.from_file(config, file)) | ||
| template_checkers[chip.name] = template_checker(template_checkers, chip) |
There was a problem hiding this comment.
this recursion is trippy: to set template_checkers[some chip]: Callable, it passes in all of template_checkers, which gets bound (?) into a function which is then returned?
This, I believe, requires proper documentation, either directly here, or by wrapping this in a well-named function and documenting that function. I cannot make heads or tails of what is happening here, nor what template_checkers even means / is supposed to do.
There was a problem hiding this comment.
template_checkers is a collection of functions, indexed by template name/tag, that checks whether a given assignment satisfies the template.
Passing in template_checkers could be delayed to the invocations of these checkers as well, we're just tying the knot a bit earlier here.
We need to pass template_checkers to an individual checker somewhere to be able to deal with templates inside of a template definition (such as IS_BIT inside of ADD).
The downside of delaying the access to all templates to later is that the typing of template_checkers itself needs to become recursive, since now the Callable needs an argument of the type dict[str, Callable[dict[...], Optional[Type], ...]. Which could in itself work out
There was a problem hiding this comment.
Hmmm.
Having reviewed again, I'm not convinced the recursion is the best way to go. Why not introduce a check_assignment for templates and have a (global?) tag_to_chip map?
Then the last part on check_assignment on chips would look something like this:
| template_checkers[chip.name] = template_checker(template_checkers, chip) | |
| for c in self.constraints: | |
| with reporter.context(repr(c)): | |
| all_signatures = c.typecheck(env) | |
| templates = filter(lambda s: isinstance(s, TemplateSignature)) | |
| for sig in templates: | |
| tag_to_chip[sig.tag].check_assignment(sig) |
which I find a lot easier to read.
There was a problem hiding this comment.
I don't see a big difference, besides now getting a global variable that I don't love having to setup if we ever do end up using this as a "library" for potential other tooling.
There was a problem hiding this comment.
The one other difference is that now check_assignment takes a Signature, which I find to not as cleanly represents what an assignment should be. Perhaps there's a way to decompose it into something that maps sig into an assignment given only the chip and then chaining a check_assignment — which is currently encoded in the template checker.
There was a problem hiding this comment.
Aha, and after a quick attempt at implementation, another downside appears: we can't easily have this mapping for SUB, which needs to rearrange some things before passing it on to the logic for ADD.
So the replacement would end up being a map from tag to (chip, extractor) where extractor needs to give you the assignment for a signature, which is comparable to the current approach, but now implemented as a suggestion.
There was a problem hiding this comment.
Now simplified after stacking the PR on top of #695
| def check_assignment( | ||
| self, | ||
| template_checkers: dict[str, "TemplateChecker"], | ||
| values: Optional[dict[str, Type]] = None, | ||
| ): |
There was a problem hiding this comment.
Rather than having check_assignment default to checking the padding when no values are provided, I'd instead propose introducing check_padding which calls check_assignment on the padding.
There was a problem hiding this comment.
Also: should we additionally verify that self.variables INSECTION values.keys() = self.variables? That way we can strain out assignments that propose setting non-existent variables.
| for c in self.constraints: | ||
| for sig in c.typecheck(env): | ||
| # Recurse on templates | ||
| if isinstance(sig, TemplateSignature) and sig.tag in template_checkers: |
There was a problem hiding this comment.
what if sig.tag not in template_checkers? Wouldn't that indicate a bug?
There was a problem hiding this comment.
Not really, we could be checking a chip in isolation and not invoking chip.py with all templates present
There was a problem hiding this comment.
Should we at least have the reporter mention this? Not as an issue, but at least as an heads up.
There was a problem hiding this comment.
Perhaps, yeah. Added
| type TemplateChecker = Callable[[dict[str, TemplateChecker], Optional[Type], list[Type], Optional[Type]], None] | ||
|
|
||
| def build_template_checker(chip: Chip) -> TemplateChecker: | ||
| def check(template_checkers: dict[str, TemplateChecker], cond: Optional[Type], input: list[Type], output: Optional[Type]) -> None: |
There was a problem hiding this comment.
Consider passing in sig: TemplateSignature rather than decomposing it into cond, input and output.
| continue | ||
| chips.append(Chip.from_file(config, file)) | ||
| chips.append(chip := Chip.from_file(config, file)) | ||
| template_checkers[chip.name] = template_checker(template_checkers, chip) |
There was a problem hiding this comment.
Hmmm.
Having reviewed again, I'm not convinced the recursion is the best way to go. Why not introduce a check_assignment for templates and have a (global?) tag_to_chip map?
Then the last part on check_assignment on chips would look something like this:
| template_checkers[chip.name] = template_checker(template_checkers, chip) | |
| for c in self.constraints: | |
| with reporter.context(repr(c)): | |
| all_signatures = c.typecheck(env) | |
| templates = filter(lambda s: isinstance(s, TemplateSignature)) | |
| for sig in templates: | |
| tag_to_chip[sig.tag].check_assignment(sig) |
which I find a lot easier to read.
This manages to catch both the observed issue with the CPUs padding for
half_instruction_lengthand an previously unknown issue in the padding for DVRM.