Homework 4: Dafny
Due date: April 12, 11pm
Grading: 5% of your course grade: 2% for Part 1 (basic scheduler), 2% for Part 2 (smarter scheduler), 1% for Part 3 (scheduler correctness)
Dafny is a verification-ready programming language. It's an imperative language, vaguely influenced by C#, but with built-in automatic verification to check the correctness of your code as you write it. Once your code is confirmed to be correct by the Dafny verifier, it can be automatically compiled to languages like C#, Go, or JavaScript.
In this homework we'll use Dafny to explore some variations on a verified operating system scheduler, similar to one in the seL4 verified OS kernel that we read about in lecture recently. We'll implement two schedulers, prove them correct against some higher-level specifications, and then think a little about what types of bugs our verification does and does not preclude.
Table of contents
Prerequisites
We'll be working with Dafny in this homework, so the first step is to it set up on your system.
I strongly suggest using Visual Studio Code to complete this homework, as it has a full-featured Dafny integration with many nice features. Regardless of operating system, install Visual Studio Code before going any further.
Now install the Dafny VSCode extension
by clicking the Install button on its homepage
or by searching for dafny-lang.ide-vscode
in VSCode's extension pane.
Make sure you get the right extension, as there are some deprecated ones still available—the one you're looking for
is authored by "dafny-lang" rather than "Correctness Lab".
Next, follow the appropriate instructions below for your system to get the extension fully set up. If any of the instructions are unclear, see the official Dafny installation instructions for more detailed help.
macOS
First, install the .NET 6.0 SDK using Homebrew:
brew install dotnet-sdk
If you have an Intel Mac, you're done. The first time you open a .dfy
file in VSCode,
the Dafny extension will automatically download and install the latest Dafny release.
If you have an Apple Silicon Mac, unfortunately, there's a little more work to do
as the Dafny VSCode extension doesn't yet recognize these Macs correctly.
I've prepared a Dafny release for Apple Silicon Macs
that you need to download and extract somewhere on your system
(here I'm going to assume you extracted it to the folder /Users/bornholt/dafny
,
so for example you have a file /Users/bornholt/dafny/Dafny.dll
).
Now open the Settings UI in VSCode
(press Cmd + Shift + P, search for "settings ui", press enter).
Search for "dafny" in the settings UI.
Set the "Dafny: Compiler Runtime Path" to /Users/bornholt/dafny/Dafny.dll
,
and set the "Dafny: Language Server Runtime Path" to /Users/bornholt/dafny/DafnyLanguageServer.dll
.
Now once you open a .dfy
file,
the Dafny extension should be able to find your new Dafny installation and use it.
If you instead see an error like "The Dafny Language Server server crashed 5 times",
something went wrong—make sure you got all the paths right.
Linux
Install the .NET 6.0 SDK by following the instructions for your distribution.
That should be all you need to do. The first time you open a .dfy
file in VSCode,
the Dafny extension will automatically download and install the latest Dafny release.
Windows
Nothing more to do! The first time you open a .dfy
file in VSCode,
the Dafny extension will automatically download and install the latest Dafny release.
Set up the code
We'll be using GitHub Classroom to check out and submit this homework.
Follow the GitHub Classroom URL on Canvas to create your private copy of the homework repository, and then clone that repository to your machine. For example, the repository it created for me is called hw4-dafny-jamesbornholt
, so I would do:
git clone git@github.com:cs395t-svs/hw4-dafny-jamesbornholt.git
cd hw4-dafny-jamesbornholt
To make sure everything's working, open the Scheduler.dfy
file from this directory in VSCode.
You should see "Verification Failed" in the bottom left of the window (in the status bar),
and some red squiggly underlines scattered around the file.
You'll know you've finished the homework when you see "Verification Succeeded"!
Part 1: A basic scheduler
We're going to write specifications and implementations for an operating system scheduler, vaguely inspired by Figures 3 and 4 in the seL4 paper we read recently.
Open Scheduler.dfy
. It comes with a bunch of code already written for you. You shouldn't modify any code other than that annotated with TODO
comments.
First, there's a class called Task
that we'll use as the unit of actions being scheduled by the scheduler.
Tasks have an ID and can be in one of two states—runnable or not runnable.
Second, there's a trait called Scheduler
. Think of traits like you would think of interface
in Java—they're abstract definitions of what a class should do. Here, the Scheduler
trait defines the interface to a scheduler implementation.
A scheduler maintains a list (a seq
, in Dafny terms) of tasks it is managing,
as well as a field next
pointing to the currently scheduled task
(or null
if no task is currently scheduled).
A scheduler has two methods:
AddTask(task: Task)
adds a new task to the scheduler.Next()
chooses the next task to schedule, and returns a booleanidle
reflecting whether there was a task scheduled. Ifidle
is true, then no task was scheduled (and so the system should idle). Finally, a scheduler has an invariantValid
that defines its correctness.
Third, we have a class called BasicScheduler
that implements the Scheduler
interface.
For part 1, your task is to finish the implementation of BasicScheduler
,
and in particular, to fill in its Next
implementation.
The BasicScheduler
is a not a very good scheduler:
it should always schedule the first task in tasks
,
even if that task is not runnable.
Fill in the missing postcondition for Next
as well as its body,
such that Next
returns true only if there are no tasks to run.
Do not modify any of the other annotations or the Valid
predicate.
You'll know you're done when Dafny can prove all the postconditions of Next
,
and can also verify the three test methods whose names start with TestBasicScheduler
.
A couple of hints to get you started:
- If
foo
is a variable of typeseq<T>
, you can get the length offoo
by writing|foo|
. - Your postcondition might want to talk about equivalence; you can use the
<==>
operator to mean bi-implication/equivalence. - If you need some help with Dafny syntax or semantics, the Dafny website has links to many tutorials and other documentation.
Part 2: A slightly smarter scheduler
Now let's take a look at the RunnableScheduler
.
This is a scheduler that goes just one step further than BasicScheduler
:
it should always schedule the first runnable task (a task such that task.runnable == true
);
if there are no runnable tasks under the control of the scheduler, it will idle.
In the RunnableScheduler
, fill in the missing postcondition for Next
as well as its body,
such that Next
returns true if and only if there are no runnable tasks.
Do not modify any of the other annotations or the Valid
predicate.
You'll know you're done when Dafny can prove all the postconditions of Next
,
and can also verify the three test methods whose names start with TestRunnableScheduler
.
A couple of hints on this one:
- Despite the very small change in functionality, the implementation of
Next
will be quite a bit more complex this time. It will require a loop over the tasks, and as we learned in lecture, loops basically always require loop invariants!- In this case, you'll likely need two loop invariants: one "standard" invariant about the loop induction variable
i
, and one more complex invariant.
- In this case, you'll likely need two loop invariants: one "standard" invariant about the loop induction variable
- Coming up with loop invariants is a bit of an art (and inferring them automatically is a very active research area).
- One way to make progress on invariants is to work backwards from the postcondition you need. For
Next
, you'll likely end up in a situation where you can't prove the postcondition in the case where it returnstrue
. Try to insert anassert
statement into the code, just before you returntrue
, such that Dafny no longer complains about the postcondition being violated in thetrue
case. Once you come up with such an assertion, you can work backwards to turn it into an invariant of the loop. - The loop invariant helps Dafny remember what it knows about all the iterations the loop has executed so far. For loops over sequences, there's a pretty common pattern for such invariants—the expression
states that some predicateforall j :: 0 <= j < n ==> f(a[j])
f
is true for the firstn
elements of the sequencea
.
- One way to make progress on invariants is to work backwards from the postcondition you need. For
Part 3: Scheduler correctness
Although we've now proven the correctness of two schedulers, there's a very significant issue with both of them, such that no real (or even toy) operating system would consider them "correct".
- What is the issue, and why does our proof say they are correct despite suffering this issue?
- Does seL4's scheduler proof (the example in Figures 3 and 4 of the paper) have the same problem?
- What could we do about it?
Answer these three questions by writing a comment in the Part3
method in Scheduler.dfy
.
No need to write too much—a sentence per question should be enough.
What to submit
Submit your solutions by committing your changes in Git and pushing them to the private repository GitHub Classroom created for you in the Set up the code step. If you haven't used Git before, Chapters 1 and 2 of the Git book are a good tutorial.
The only file you should need to modify is Scheduler.dfy
. GitHub will automatically select your most recent pushed commit before the deadline as your submission; there's no need to manually submit anything else via Canvas or GitHub.
GitHub Classroom will autograde your submission every time you push, by just running the Dafny verifier on Scheduler.dfy
. If you can't complete the entire homework and so the autograder fails, don't worry—I will still be grading manually for partial credit.