Table of Contents
ISY Summer of Code 2022
Posted 2022-08-16 by Gustav SΓΆrnΓ€s
This summer I've had the opportunity to work in a Summer of Code organized by the department of Electrical Engineering (ISY) at LinkΓΆping University. This post will summarize a few of the changes I worked on and had particularly fun with.
Backgroundπ
I was selected to work half-time for two
months on Spade, but I had a lot of freedom in what exactly I did.
A complete listing of all merged merge requests (at least in the spade-lang
Gitlab organization, so not counting e.g. prr
) can be found on
Gitlab
(from spade!75 and onwards). Apart from that I also helped review some merge
requests (also on
Gitlab).
You can read more about Spade on the main Spade website. Spade, as well as most of the tooling, is implemented in Rust, so some of the content here will also touch on Rust.
About meπ
Before I started university, I had not done any hardware programming (or hardware anything really, apart from building my desktop computer) at all. University introduced me first to digital electronics using physical wires and later to VHDL and hardware programming.
I had already been contributing to Spade for a couple of months, and I had used it twice in game jam projects. (Not really the intended use case but it worked out alright.) Still, I wouldn't call myself a expert in hardware by any means. What's nice about a hardware compiler is that I usually don't have to worry that much about the actual hardware going on since the compiler hides most of the weirdness behind abstractions. I could for example work on the parser without worrying at all about Verilog.
Tracing in the compilerπ
As a first task, I wanted to get a better feel for the Spade compiler structure.
We had specialized tracing (logging) for the parser and typechecker, but I
wanted something more like the tracing found in the Rust
compiler. It turns out Rust
uses public libraries (tracing
,
"scoped, structured logging and diagnostics", and
tracing-tree
for the output) so
we could add them right away.
Adding tracing
and tracing-tree
was first done in
spade!75. We opted to
not add the tracing events themselves all at once since the compiler is too
large for that. Instead, we added them as needed when we worked on fixing other
bugs.
We still have two old systems that can annotate functions and show a tree-like view of the program flow (for parsing and type checking) which are left for now. At some point they could probably be incorporated into tracing.
Multipart suggestionsπ
One important goal of the Spade compiler is to be friendly to humans. Here's an example error message from a small fix I did in spade!94:
This can be compared with the output for the equivalent error (two entities with the same name) when compiling some VHDL in one of my university courses, using a to-be-unnamed proprietary compiler.
We use a library called codespan
which renders
our source code annotated with labels that can point to different spans, as seen
above. Before the Summer of Code we had already implemented support for
suggestions in which we attach suggestions to error messages. These suggestions
consist of a span to remove and a text to replace it with. What's nice is that
all combinations of add and remove (only addition, only removal, and
replacement) could be chosen by leaving either the span or the text empty.
In codespan!2, I then added support for suggestions that consist of multiple parts.
There is still work left to do. For starters, suggestions currently need to be on the same line to be rendered.
At some point we also probably want to upstream this into the original codespan repository.
Board presets in Swim configurationsπ
In addition to Spade, we also have a build tool called Swim. It handles some of the work in getting your Spade code programmed onto an FPGA. It does this through a series of steps.
- Build
- Synthesis
- Place and route
- Packing
- Uploading
Since FPGA models have unique hardware characteristics, we need to specify different values and programs for different boards in a Swim configuration file. Here's how it looks for the Go Board I have at home.
Compare this to the configuration required for the ECPIX-5. Notably, [pnr]
,
[upload]
and [packing]
contains completely different values.
Now, most of this configuration will be the same for each Go Board, or ECPIX-5,
or insert your FPGA of choice here. So in
swim!26 I added
support for a [board]
-directive in the configuration file. You could then instead of the
above configurations write:
The fields pin_file
and pcf
are used to map pins on the board to input and output
signals in the code, which means it is not the same from project to project and
therefore still specified.
Lock file for "pinning" dependencies in Swimπ
Before the Summer of Code, we had already added support for dependencies in Swim.
When you run swim build
for the first time, Swim will fetch a copy of the
repository, checkout the specified branch and build it as a separate module.
After the first build, Swim won't re-fetch the repository unless you run swim update
(so you don't have to have an internet connection ever time you build).
But if you later build this project on another computer, what happens if the
branch has been updated in the repository? To ensure the project uses the same
dependencies, we create a file swim.lock
containing the hash of the commit
which takes priority over the specified reference in swim.toml
. (It's a bit
hard to exemplify but you might be aware of lock files from other build tools
like Cargo, npm and Nix (flakes).)
The hardest part about this change wasn't the actual change, but testing it.
What good is a complex feature like this if you can't trust that it even works
from the start? Most of Swim relied on relative paths and changing the active
directory (like cd
) which in combination with tests running in parallel made
the test suite more racy than an F1 GP. The solution was to not have
relative paths and tests that all change the active directory, by instead
making all paths absolute paths instead.
What's more, the logging output was
not captured by the test suite, so it all went to the same terminal, making it
borderline impossible to debug where stuff went wrong. (Usually, Cargo collects
output from tests and only prints it per test for failed tests. The solution was
to wrap the log call in a println
with fern::Output::call(|record| println!("{}", record.args()));
.) My misery can
be found in swim!37.
This also came during the worst of the summer heat, and I got to experience first-hand the loss of productivity when it's 30+Β°C degrees outside and somewhere around 25-28 Β°C inside for an entire week.
Gitlab CIπ
Speaking of tests, I found myself debugging CI pipelines at multiple points for different projects. I've created new CI configurations for our mdbook, this blog and our codespan fork. We also have a tool called Trawler that tries to build a list of projects to ensure we don't accidentally break "real" code (and as an incentive to keep a couple of external projects up-to-date), so for that I wrote a JUnit-generator since Gitlab understands and renders JUnit-reports.
This blogπ
Towards the end of the summer we felt like it would be nice to write a bit about what I had done and how everything had went. Thus, this blog. I started with a scrapped personal website and modified the look to be more like the main spade-lang.org. You're browsing the result right now :).
I've been getting used to Zola more and more, but some things are still a bit difficult to do (and more importantly, not really documented). I have collected an example in the appendix. Still, right when I want to give up and make an ugly hack in the Zola source, a solution usually presents itself.
Gitlab support for prr
for offline code reviewsπ
During development I found a tool called prr
.
From the repository:
prr
is a tool that brings mailing list style code reviews to Github PRs. This means offline reviews and inline comments, more or less.
I had found myself on quite a few train rides without internet access, so this
sounded like something I could use. I had gotten permission to work a bit on
unrelated tooling if it was to help me work on Spade, and since we use
Gitlab instead of Github I got to work on adding Gitlab support. This didn't
take too long (a day or two) since I had already used the
gitlab
-crate for doing API calls to Gitlab
before.
The resulting fork can be found on Github. It doesn't support spanned comments due to some funky API documentation.
Appendix A: Zola workaround exampleπ
In Zola, the special variables page
and section
aren't inherited when using
include "something.html"
(I think). So here's a weird solution to work around
that:
Appendix B: Spanned comments using the Gitlab APIπ
While working on spanned comments (comments that target a line range instead of
a single line) I had a """fun""" problem with the API documentation. There is a
header in the documentation called Create a new merge request
thread
which shows us how to create a thread pointing to a single line. Great! Let's
use it. We use the already existing code to take a review file and parse it into
a list of InlineComment
. Then, we use an iterator and map
(transform, sort
of) each inline comment into its own thread.
(This code is simplified to make a point. Check the original if you're interested.)
If you're not used to builders and/or Rust this might look a bit odd but the
point is that each InlineComment
is turned into a
CreateMergeRequestDiscussion
with a body and position. Both of those are
mapped from the InlineComment
without much trouble.
Let's try to apply this to spanned comments. The API (Parameters for multiline comments) tells us that we need a line code and type for the start and end of each comment. The line code is documented right below this header and combines the hash of the file we're commenting on, the line number before the change and the line number after the change. This is information we have, so it should be fine. The API also wants a "type" and I quote:
- Attribute:
position[line_range][start][type]
- Type: string
- Required: yes
- Description: "Use
new
for lines added by this commit, otherwiseold
"
Since our chosen API wrapper is well made it mirrors this. Well, I tried implementing it, but it didn't work. It's a bit difficult to debug since the comments still show up as normal single line comments. I did however compare the request that is sent (top) with the one sent when using Gitlabs own web UI (bottom):
Interestingly, we see that Gitlab:
- Sets
type
tonull
even though it should be a string that is either"old"
or"new"
according to the documentation. - Sets
old_line
andnew_line
online_range[start]
andline_range[end]
, fields that don't exist in the documentation. They're also duplicated from information that is already present in the line code.
Maybe there is a way to "bypass" the type system and modify the API calls with custom fields? In any case, I've given up on spanned comments for now since single line comments look almost exactly the same. If you have any ideas, feel free to drop by the forked repo at sornas/prr (github).