├── .editorconfig ├── .github ├── mailbot.json └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── artifact ├── run-vm-setup.sh ├── vm-init.sh └── vm-setup.sh ├── bench ├── run-daisy-nfsd.sh ├── set-gojournal-patch.sh ├── start-daisy-nfsd.sh └── stop-daisy-nfsd.sh ├── cmd ├── daisy-eval │ └── main.go ├── daisy-nfsd │ └── main.go └── fs-test │ └── main.go ├── dafny_go ├── bytes │ ├── bytes.go │ └── bytes_test.go ├── encoding │ ├── encoding.go │ └── encoding_test.go ├── jrnl │ ├── addr.go │ ├── alloc.go │ ├── alloc_test.go │ ├── jrnl.go │ └── jrnl_test.go └── time │ ├── time.go │ └── time_test.go ├── dfyconfig.toml ├── etc ├── check ├── ci-install-nfs.sh ├── ci-macos-setup-nfs.sh ├── ci-setup-demo.sh ├── dafnydep ├── dafnygen-imports.py ├── dafnyq ├── run-demo.sh ├── summarize-timing ├── test_timing.py └── timing.py ├── eval ├── .gitignore ├── README.md ├── aws │ ├── README.md │ ├── aws-setup.sh │ ├── eval.sh │ └── setup-image.sh ├── bench.plot ├── bench.py ├── bench.sh ├── bench_configs.sh ├── benchmarks.go ├── blktrace.sh ├── data │ └── .gitignore ├── eval.go ├── eval.py ├── eval.sh ├── eval_test.go ├── fdbench.sh ├── fig │ └── .gitignore ├── filebench.sh ├── fs.go ├── nfsdist.bt ├── plot.sh ├── pyproject.toml ├── run-filebench.sh ├── scale.plot ├── scale.sh ├── suite.go └── tests.sh ├── go.mod ├── go.sum ├── nfsd ├── fh.go ├── mkfs.go ├── mount.go ├── ops.go └── stats.go ├── src ├── .dir-locals.el ├── compile.dfy ├── examples │ └── bank.dfy ├── fs │ ├── README.md │ ├── block_fs.dfy │ ├── byte_fs.dfy │ ├── cursor.dfy │ ├── dir │ │ ├── dirent.dfy │ │ └── mem_dirent.dfy │ ├── dir_fs.dfy │ ├── indirect │ │ ├── ind_block.dfy │ │ └── pos.dfy │ ├── indirect_fs.dfy │ ├── inode │ │ ├── inode.dfy │ │ └── mem_inode.dfy │ ├── inode_fs.dfy │ ├── nfs.s.dfy │ ├── paths.dfy │ ├── super.dfy │ └── typed_fs.dfy ├── jrnl │ ├── jrnl.s.dfy │ ├── kinds.s.dfy │ └── kinds_facts.dfy ├── machine │ ├── bytes.s.dfy │ ├── bytes_test.dfy │ ├── int_encoding.s.dfy │ ├── machine.dfy │ └── time.s.dfy ├── nonlin │ ├── arith.dfy │ └── roundup.dfy └── util │ ├── bytes.dfy │ ├── collections.dfy │ ├── lock_order.dfy │ ├── marshal.i.dfy │ ├── min_max.dfy │ ├── pow.dfy │ └── std.dfy └── tests ├── .gitignore ├── bank_test.go ├── demo-expected.out ├── demo.sh ├── dir_test.go └── nfs_test.go /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.sh] 2 | indent_style = space 3 | indent_size = 4 4 | -------------------------------------------------------------------------------- /.github/mailbot.json: -------------------------------------------------------------------------------- 1 | { 2 | "commitEmailFormat": "html", 3 | "commitList": "tchajed@gmail.com,kaashoek@mit.edu,nickolai@csail.mit.edu,joseph.tassarotti@bc.edu" 4 | } 5 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - "**" 8 | pull_request: 9 | workflow_dispatch: 10 | 11 | # settings shared across all jobs 12 | env: 13 | dafny: "4.8.1" 14 | go: "^1.23.0" 15 | 16 | jobs: 17 | verify: 18 | name: Verify 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Install Dafny 23 | uses: dafny-lang/setup-dafny-action@v1.8.0 24 | with: 25 | dafny-version: ${{ env.dafny }} 26 | - name: Verify 27 | run: make -j2 verify 28 | test-support: 29 | name: Test dafny_go 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v4 33 | - uses: actions/setup-go@v5 34 | with: 35 | go-version: ${{ env.go }} 36 | - name: Test support library 37 | run: | 38 | go test -v -timeout=1m ./dafny_go/... ./eval 39 | test-compiled: 40 | name: Test NFS server 41 | runs-on: ubuntu-latest 42 | timeout-minutes: 10 43 | steps: 44 | - uses: actions/checkout@v4 45 | - name: Install Dafny 46 | uses: dafny-lang/setup-dafny-action@v1.8.0 47 | with: 48 | dafny-version: ${{ env.dafny }} 49 | - uses: actions/setup-go@v5 50 | with: 51 | go-version: ${{ env.go }} 52 | - name: Install goimports 53 | run: | 54 | go install golang.org/x/tools/cmd/goimports@latest 55 | - name: Compile and test 56 | run: | 57 | echo "::group::Compile" 58 | make compile 59 | echo "::endgroup::" 60 | echo "::group::Test compiled code" 61 | go test -bench=. -timeout=1m -v ./tests 62 | go build ./cmd/daisy-nfsd 63 | echo "::endgroup::" 64 | - name: Setup 65 | run: | 66 | echo "::group::Install NFS" 67 | ./etc/ci-install-nfs.sh 68 | echo "::endgroup::" 69 | - name: Run syscall tests 70 | run: | 71 | ./bench/run-daisy-nfsd.sh go run ./cmd/fs-test /mnt/nfs 72 | test-macos: 73 | name: Test NFS server (macOS) 74 | runs-on: macos-latest 75 | timeout-minutes: 10 76 | steps: 77 | - uses: actions/checkout@v4 78 | - name: Install Dafny 79 | uses: dafny-lang/setup-dafny-action@v1.8.0 80 | with: 81 | dafny-version: ${{ env.dafny }} 82 | - uses: actions/setup-go@v5 83 | with: 84 | go-version: ${{ env.go }} 85 | - name: Install goimports 86 | run: | 87 | go install golang.org/x/tools/cmd/goimports@latest 88 | - name: Compile 89 | run: | 90 | make compile 91 | go build ./cmd/daisy-nfsd 92 | - name: Setup 93 | run: | 94 | echo "::group::Start NFS services" 95 | ./etc/ci-macos-setup-nfs.sh 96 | echo "::endgroup::" 97 | - name: Run syscall tests 98 | run: | 99 | rpcinfo -p 100 | ./bench/run-daisy-nfsd.sh \ 101 | -disk "/tmp/disk.img" \ 102 | -nfs-mount-opts "nordirplus,locallocks,retrycnt=10" \ 103 | -mount-path /tmp/nfs \ 104 | go run ./cmd/fs-test /tmp/nfs 105 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dfy.ok 2 | /.dafnydeps.d 3 | # from profile-collect 4 | /dafny-qi.profile 5 | /dafnygen 6 | *.prof 7 | # from go build 8 | /daisy-nfsd 9 | /daisy-eval 10 | # from eval scripts 11 | /nfs.out 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DFY_FILES := $(shell find src -name "*.dfy") 2 | OK_FILES := $(DFY_FILES:.dfy=.dfy.ok) 3 | 4 | DAFNY_CORES := "50%" 5 | DAFNY_BASIC_ARGS := --verification-time-limit 20 --cores $(DAFNY_CORES) 6 | DAFNY_ARGS := --disable-nonlinear-arithmetic 7 | DAFNY = ./etc/dafnyq verify $(DAFNY_BASIC_ARGS) $(DAFNY_ARGS) 8 | 9 | Q:=@ 10 | 11 | default: all 12 | 13 | compile: dafnygen/dafnygen.go 14 | 15 | verify: $(OK_FILES) 16 | 17 | all: verify compile 18 | 19 | .dafnydeps.d: $(DFY_FILES) etc/dafnydep 20 | @echo "DAFNYDEP" 21 | $(Q)./etc/dafnydep $(DFY_FILES) > $@ 22 | 23 | # do not try to build dependencies if cleaning 24 | ifeq ($(filter clean,$(MAKECMDGOALS)),) 25 | -include .dafnydeps.d 26 | endif 27 | 28 | # allow non-linear reasoning for nonlin directory specifically 29 | src/nonlin/%.dfy.ok: DAFNY_ARGS = 30 | 31 | %.dfy.ok: %.dfy 32 | @echo "DAFNY $<" 33 | $(Q)$(DAFNY) "$<" 34 | $(Q)touch "$@" 35 | 36 | # Compilation produces output in dafnygen-go, which we preprocess with 37 | # dafnygen-imports.py to change import paths (for go module compatibility) and 38 | # to place the output under dafnygen without a src directory. 39 | # 40 | # We then run gofmt to simplify the code for readability and goimports to clean 41 | # up unused imports emitted by Dafny. 42 | dafnygen/dafnygen.go: src/compile.dfy $(DFY_FILES) 43 | @echo "DAFNY COMPILE $<" 44 | $(Q)./etc/dafnyq translate go --no-verify --include-runtime --output dafnygen $< 45 | $(Q)rm -rf dafnygen 46 | $(Q)cd dafnygen-go/src && ../../etc/dafnygen-imports.py ../../dafnygen 47 | $(Q)rm -r dafnygen-go 48 | $(Q)gofmt -w -r '(a) -> a' ./dafnygen 49 | $(Q)goimports -w ./dafnygen 50 | 51 | clean: 52 | @echo "CLEAN" 53 | $(Q)find . -name "*.dfy.ok" -delete 54 | $(Q)rm -f .dafnydeps.d 55 | $(Q)rm -rf dafnygen 56 | $(Q)rm -f daisy-nfsd cpu.prof mem.prof nfs.out 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DaisyNFS 2 | 3 | [![CI](https://github.com/mit-pdos/daisy-nfsd/actions/workflows/main.yml/badge.svg)](https://github.com/mit-pdos/daisy-nfsd/actions/workflows/main.yml) 4 | 5 | A verified crash-safe, concurrent NFS server. The idea is to make operations 6 | atomic with a verified transaction system from 7 | [GoTxn](https://github.com/mit-pdos/go-journal), and then verify the atomic 8 | behavior of each operation in Dafny. The atomicity justifies using sequential 9 | proofs in Dafny to reason about the body of each transaction, which we prove 10 | implements an NFS server. This proof strategy combines interactive theorem 11 | proving in Perennial, to reason about the tricky concurrency and crash safety in 12 | the transaction system, with automated proofs in Dafny for the file-system code. 13 | 14 | ## Architecture 15 | 16 | There are three main components: 17 | 18 | - The Dafny file-system implementation in [`src/fs`](src/fs/) 19 | - The Go interfaces assumed by the Dafny code implemented in 20 | [`dafny_go`](dafny_go/) (the jrnl API is a thin wrapper around the 21 | github.com/mit-pdos/go-journal/txn package). 22 | - The NFS server binary that calls the verified Dafny code is implemented 23 | between [`nfsd`](nfsd/) and [`cmd/daisy-nfsd`](cmd/daisy-nfsd/). 24 | 25 | The Dafny proof is split into three parts: 26 | 27 | - external interfaces only assumed in Dafny via `{:extern}`: 28 | [`src/jrnl`](src/jrnl) (the transaction system) and 29 | [`src/machine`](src/machine) (Go primitives) 30 | - verified helper libraries in [`src/util`](src/util) that would basically be in 31 | a decent Dafny standard library 32 | - the actual file-system proof, documented in its own [README](src/fs/README.md) 33 | 34 | At the top level of the repo we also have various scripts. [`eval`](eval/) and 35 | [`bench`](bench/) have scripts to run performance experiments (see the [eval 36 | README](eval/README.md) for more details). [`artifact`](artifact/) has an older 37 | setup for running the evaluation on a VM; these days we use an AWS setup. 38 | [`etc`](etc/) has miscellaneous scripts used for debugging and to implement 39 | continuous integration. 40 | 41 | ## Compiling 42 | 43 | Run `make` to compile and verify everything, or `make compile` to just compile 44 | from Dafny to Go. Then you can build the server with `go build ./cmd/daisy-nfsd`. 45 | 46 | You'll need Dafny 4: 47 | 48 | - On Arch Linux you can get `dafny-bin` from the AUR 49 | - On macOS use `brew install dafny` 50 | - For other systems the easiest solution is to download a binary release from 51 | , extract it, and add it to your 52 | $PATH (this is what we have to do in CI, which runs on Ubuntu 22.04). 53 | 54 | Compilation additionally depends on `goimports` to remove unused imports: 55 | 56 | ```sh 57 | go install golang.org/x/tools/cmd/goimports@latest 58 | ``` 59 | 60 | Then you can run some sanity tests over the bank and file-system examples. 61 | These are ordinary Go tests that import the code generated from Dafny and 62 | run it, linking with [go-nfsd](https://github.com/mit-pdos/go-nfsd), 63 | specifically with its `twophase` package. To run these tests, 64 | after compiling with `make compile`, run: 65 | 66 | ```sh 67 | go test -v ./tests 68 | ``` 69 | 70 | ## Running the NFS server 71 | 72 | ### Linux 73 | 74 | You'll need some basic utilities: the rpcbind service to tell the server what 75 | port to run on, and the NFS client utilities to mount the file system. On Arch 76 | Linux these are available using `pacman -S rpcbind nfs-utils` and on Ubuntu you 77 | can use `apt-get install rpcbind nfs-common`. 78 | 79 | You might need to start the rpcbind service with `systemctl start rpcbind`. It 80 | seems to help if you also run `systemctl start rpc-statd` (it should be 81 | auto-launched when needed, though). The `rpcinfo -p` command is useful for 82 | verifying that an `portmapper` service is running on port 111. 83 | 84 | Now run `go run ./cmd/daisy-nfsd` to start the server (with an in-memory disk) 85 | and `sudo mount localhost:/ /mnt/nfs` to mount it using the Linux NFS client. 86 | 87 | If you encounter an error with the message `Too many levels of symbolic links.`, 88 | don't panic! This is actually due to a bug in the Linux NFS client, which was 89 | fixed in December 2020 in [commit 90 | 3b2a09f127e02](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=3b2a09f127e025674945e82c1ec0c88d6740280e). 91 | If a READDIR result was larger than a page, Linux would simply discard the extra 92 | data, resulting in a corrupted response. You'll need at least version 5.11 of 93 | the kernel to get the fix (you can check what you have with `uname -r`). 94 | 95 | ### macOS 96 | 97 | On macOS you already have `rpcbind` and the NFS client utilities, but you'll 98 | need to start a couple services with: 99 | 100 | ```sh 101 | sudo launchctl start com.apple.rpcbind 102 | sudo launchctl start com.apple.lockd 103 | ``` 104 | 105 | You can mount with `sudo mount localhost:/ /mnt/nfs` as for Linux, or without 106 | becoming root in Finder with Go > Connect to server... and connecting to 107 | `nfs://localhost/`. 108 | 109 | ## Developing 110 | 111 | We provide a library at `dafny_go` that exports some external APIs that are 112 | axiomatized using `{:extern}` modules, classes, and methods in Dafny. Some of 113 | these are core primitives, like `[]byte` and little-endian encoding, while the 114 | big one is the `jrnl` package which interfaces between Dafny and 115 | `github.com/mit-pdos/go-nfsd/txn`. 116 | 117 | The support library is trusted and hence its agreement with the Dafny spec is 118 | important. 119 | 120 | You can run tests for this support library with `go test`: 121 | 122 | ```sh 123 | go test ./dafny_go/... 124 | ``` 125 | 126 | ## Checking verification performance 127 | 128 | To time verification and analyze the results easily, we have a script to process timing from Dafny's `/trace` option. Use it with the following fish function: 129 | 130 | ```fish 131 | function dafny_time 132 | set -l file $argv[1] 133 | dafny /timeLimit:10 /compile:0 /arith:5 /noNLarith /trace $file $argv[2..-1] > .timing.prof 134 | cat .timing.prof | ./etc/summarize-timing 135 | end 136 | ``` 137 | 138 | The timing infrastructure itself is implemented as a library in `etc/`. It even 139 | has tests, which you can run with `pytest`. 140 | -------------------------------------------------------------------------------- /artifact/run-vm-setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | VM="daisy-nfs-vm" 6 | if [ $# -ge 1 ]; then 7 | VM="$1" 8 | fi 9 | 10 | multipass start "$VM" 11 | sleep 3 12 | multipass exec "$VM" -- git -C daisy-nfsd pull 13 | multipass exec "$VM" -- git -C daisy-nfsd gc 14 | multipass exec "$VM" -- ./daisy-nfsd/artifact/vm-setup.sh 15 | multipass stop "$VM" 16 | sleep 3 17 | 18 | set +e 19 | 20 | # if setup already exists, replace it 21 | if sudo VBoxManage snapshot "$VM" list | grep -q 'Setup'; then 22 | sudo VBoxManage snapshot "$VM" edit "Setup" --name "old Setup" 23 | sudo VBoxManage snapshot "$VM" take "Setup" 24 | sudo VBoxManage snapshot "$VM" delete "old Setup" 25 | else 26 | sudo VBoxManage snapshot "$VM" take "Setup" 27 | fi 28 | sleep 1 29 | -------------------------------------------------------------------------------- /artifact/vm-init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | VM=daisy-nfs-vm 6 | if [ $# -ge 1 ]; then 7 | VM="$1" 8 | fi 9 | 10 | # cache password 11 | sudo true 12 | multipass launch --name "$VM" --disk 20G --mem 6G --cpus 4 groovy 13 | sudo VBoxManage controlvm "$VM" natpf1 "host-ssh,tcp,,10422,,22" 14 | multipass exec "$VM" -- sudo apt-get -y update 15 | multipass exec "$VM" -- sudo apt-get -y upgrade 16 | multipass exec "$VM" -- sudo apt-get -y install zsh 17 | multipass exec "$VM" -- wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh 18 | multipass exec "$VM" -- sh install.sh --unattended 19 | multipass exec "$VM" -- rm install.sh 20 | multipass exec "$VM" -- sudo chsh -s /usr/bin/zsh ubuntu 21 | multipass exec "$VM" -- git clone https://github.com/mit-pdos/daisy-nfsd 22 | multipass exec "$VM" -- git config --global pull.ff only 23 | multipass stop "$VM" 24 | -------------------------------------------------------------------------------- /artifact/vm-setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Set up an Ubuntu VM with dependencies to run the evaluation. 4 | # 5 | # Run this in the VM. Requires no setup after installing Ubuntu. 6 | # 7 | # Make sure to _reboot_ after running, since this script installs a new kernel. 8 | 9 | set -eu 10 | 11 | cd 12 | 13 | # Install really basic dependencies 14 | 15 | sudo apt-get update 16 | sudo apt-get install -y git python3-pip wget unzip 17 | 18 | # SSH with empty password 19 | sudo passwd -d ubuntu 20 | sudo sed -e 's/#PermitEmptyPasswords no/PermitEmptyPasswords yes/' -i /etc/ssh/sshd_config 21 | sudo sed -e 's/PasswordAuthentication no/PasswordAuthentication yes/' -i /etc/ssh/sshd_config 22 | sudo systemctl restart sshd 23 | 24 | # Get source code 25 | 26 | ## assumes https://github.com/mit-pdos/daisy-nfsd has already been cloned to 27 | ## ~/daisy-nfsd (since this is the easiest way to run this script) 28 | ln -s ~/daisy-nfsd/eval ~/artifact 29 | 30 | git clone \ 31 | --recurse-submodules \ 32 | https://github.com/mit-pdos/perennial 33 | 34 | mkdir ~/code 35 | cd ~/code 36 | git clone https://github.com/mit-pdos/go-nfsd 37 | git clone https://github.com/mit-pdos/xv6-public 38 | git clone --depth=1 https://github.com/linux-test-project/ltp 39 | git clone --depth=1 https://github.com/pimlie/ubuntu-mainline-kernel.sh 40 | cd 41 | 42 | cat >> ~/.profile <> ~/.zshrc 50 | 51 | # Install Dafny 52 | 53 | DAFNY_VERSION=3.1.0 54 | wget -O /tmp/dafny.zip "https://github.com/dafny-lang/dafny/releases/download/v$DAFNY_VERSION/dafny-$DAFNY_VERSION-x64-ubuntu-16.04.zip" 55 | cd 56 | unzip /tmp/dafny.zip 57 | mv dafny .dafny-bin 58 | rm /tmp/dafny.zip 59 | echo "export PATH=\$HOME/.dafny-bin:\$PATH" >> ~/.profile 60 | 61 | # Set up NFS client and server 62 | 63 | sudo apt-get install -y rpcbind nfs-common nfs-server 64 | sudo mkdir -p /srv/nfs/bench 65 | sudo chown "$USER:$USER" /srv/nfs/bench 66 | sudo mkdir -p /mnt/nfs 67 | sudo chown "$USER:$USER" /mnt/nfs 68 | echo "/srv/nfs/bench localhost(rw,sync,no_subtree_check,fsid=0)" | sudo tee -a /etc/exports 69 | 70 | ## for simplicity we enable these services so they are automatically started, 71 | ## but they can instead be started manually on each boot 72 | sudo systemctl enable rpcbind 73 | sudo systemctl enable rpc-statd 74 | sudo systemctl disable nfs-server 75 | # can't run go-nfsd and Linux NFS server at the same time 76 | sudo systemctl stop nfs-server 77 | 78 | # Set up Linux file-system tests 79 | 80 | sudo apt-get install -y autoconf m4 automake pkg-config 81 | cd ~/code/ltp 82 | make -j4 autotools 83 | ./configure 84 | make -j4 -C testcases/kernel/fs/fsstress 85 | make -j4 -C testcases/kernel/fs/fsx-linux 86 | cd 87 | 88 | # Install Python dependencies 89 | 90 | pip3 install argparse pandas 91 | 92 | # gnuplot (for creating graphs) 93 | 94 | sudo apt-get install -y gnuplot-nox 95 | 96 | # Install Go and Go dependencies 97 | 98 | GO_FILE=go1.16.3.linux-amd64.tar.gz 99 | wget https://golang.org/dl/$GO_FILE 100 | sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf $GO_FILE 101 | rm $GO_FILE 102 | # shellcheck disable=SC2016 103 | echo 'export PATH=$HOME/go/bin:/usr/local/go/bin:$PATH' >> ~/.profile 104 | export PATH=/usr/local/go/bin:$PATH 105 | go install golang.org/x/tools/cmd/goimports@latest 106 | 107 | cd ~/code/go-nfsd 108 | # fetch dependencies 109 | go build ./cmd/go-nfsd && rm go-nfsd 110 | cd 111 | 112 | # Install Coq 113 | 114 | # opam dependencies 115 | sudo apt-get install -y m4 bubblewrap 116 | # coq dependencies 117 | sudo apt-get install -y libgmp-dev 118 | 119 | # use binary installer for opam since it has fewer dependencies than Ubuntu 120 | # package 121 | wget https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh 122 | # echo is to answer question about where to install opam 123 | echo "" | sh install.sh --no-backup 124 | rm install.sh 125 | 126 | opam init --auto-setup --bare 127 | # TODO: temporarily disabled since this takes forever 128 | #opam switch create 4.11.0+flambda 129 | #eval $(opam env) 130 | #opam install -y -j4 coq.8.13.1 131 | 132 | # Upgrade to Linux 5.11 to get fix for NFS client bug: 133 | # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/fs/nfs?id=3b2a09f127e025674945e82c1ec0c88d6740280e 134 | sudo ~/code/ubuntu-mainline-kernel.sh/ubuntu-mainline-kernel.sh --yes -i v5.11.16 135 | # remove old kernel to save space 136 | # 137 | # TODO: this requires approval because it removes the current kernel, until the 138 | # system is rebooted 139 | # sudo apt-get -y remove linux-image-5.8.0-50-generic linux-modules-5.8.0-50-generic linux-headers-5.8.0-50 140 | 141 | sudo apt clean 142 | opam clean 143 | -------------------------------------------------------------------------------- /bench/run-daisy-nfsd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | # 6 | # Usage: ./run-daisy-nfsd.sh go run ./cmd/fs-smallfile 7 | # 8 | 9 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 10 | # root of repo 11 | cd "$DIR"/.. 12 | 13 | disk_file=/dev/shm/nfs.img 14 | size_mb=400 15 | cpu_list="" 16 | nfs_mount_path="/mnt/nfs" 17 | # go-journal patch 18 | patch_file="" 19 | extra_args=() 20 | while [[ $# -gt 0 ]]; do 21 | case "$1" in 22 | -disk) 23 | shift 24 | disk_file="$1" 25 | shift 26 | ;; 27 | -size) 28 | shift 29 | size_mb="$1" 30 | shift 31 | ;; 32 | -mount-path) 33 | shift 34 | nfs_mount_path="$1" 35 | extra_args+=("-mount-path" "$nfs_mount_path") 36 | shift 37 | ;; 38 | -patch) 39 | shift 40 | patch_file="$1" 41 | shift 42 | ;; 43 | --cpu-list) 44 | shift 45 | cpu_list="$1" 46 | shift 47 | ;; 48 | # some argument in -foo=value syntax 49 | -*=*) 50 | extra_args+=("$1") 51 | shift 52 | ;; 53 | -*) 54 | extra_args+=("$1" "$2") 55 | shift 56 | shift 57 | ;; 58 | *) 59 | break 60 | ;; 61 | esac 62 | done 63 | 64 | if [[ -n "$patch_file" && ! -f "$patch_file" ]]; then 65 | echo "Invalid patch file $patch_file" 1>&2 66 | exit 1 67 | fi 68 | 69 | if [ -n "$patch_file" ]; then 70 | ./bench/set-gojournal-patch.sh "$patch_file" 71 | fi 72 | 73 | if [ -z "$cpu_list" ]; then 74 | ./bench/start-daisy-nfsd.sh -disk "$disk_file" -size "$size_mb" "${extra_args[@]}" || exit 1 75 | else 76 | taskset --cpu-list "$cpu_list" ./bench/start-daisy-nfsd.sh -size "$size_mb" -disk "$disk_file" "${extra_args[@]}" || exit 1 77 | fi 78 | 79 | function cleanup { 80 | ./bench/stop-daisy-nfsd.sh "$nfs_mount_path" 81 | if [ -n "$patch_file" ]; then 82 | ./bench/set-gojournal-patch.sh --undo 83 | fi 84 | 85 | # only delete regular files 86 | if [ -f "$disk_file" ]; then 87 | rm -f "$disk_file" 88 | fi 89 | } 90 | trap cleanup EXIT 91 | 92 | echo "# daisy-nfsd -disk $disk_file ${extra_args[*]}" 1>&2 93 | echo "run $*" 1>&2 94 | "$@" 95 | -------------------------------------------------------------------------------- /bench/set-gojournal-patch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | # Set up DaisyNFS to use a patch on go-journal 6 | # 7 | # Assumes the environment variables for the eval have been setup (eg, 8 | # GO_JOURNAL_PATH). 9 | 10 | usage() { 11 | echo "Usage: $0 (--undo | PATCH_FILE)" 2>&1 12 | } 13 | 14 | undo=false 15 | while [[ $# -gt 0 ]]; do 16 | case "$1" in 17 | -undo | --undo) 18 | shift 19 | undo=true 20 | ;; 21 | -help | --help) 22 | usage 23 | exit 0 24 | ;; 25 | -*) 26 | usage 27 | exit 1 28 | ;; 29 | *) 30 | break 31 | ;; 32 | esac 33 | done 34 | 35 | if [ "$undo" = "true" ]; then 36 | cd "$GO_JOURNAL_PATH" 37 | git restore . 38 | cd "$DAISY_NFSD_PATH" 39 | go mod edit -dropreplace github.com/mit-pdos/go-journal 40 | exit 0 41 | fi 42 | 43 | if [[ $# -eq 0 ]]; then 44 | echo "Missing patch file" 1>&2 45 | usage 46 | exit 1 47 | fi 48 | 49 | patch_file=$(realpath "$1") 50 | 51 | cd "$GO_JOURNAL_PATH" 52 | if ! git diff --quiet --exit-code; then 53 | echo "go-journal repo is dirty, not applying patch" 1>&2 54 | exit 1 55 | fi 56 | git apply "$patch_file" 57 | 58 | cd "$DAISY_NFSD_PATH" 59 | go mod edit -replace github.com/mit-pdos/go-journal="$GO_JOURNAL_PATH" 60 | -------------------------------------------------------------------------------- /bench/start-daisy-nfsd.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | # 6 | # Usage: ./start-daisy-nfsd.sh 7 | # 8 | # default disk is /dev/shm/nfs.img but can be overriden by passing -disk again 9 | # 10 | 11 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 12 | # root of repo 13 | cd "$DIR"/.. 14 | 15 | disk_path=/dev/shm/nfs.img 16 | nfs_mount_opts="" 17 | nfs_mount_path="/mnt/nfs" 18 | extra_args=() 19 | size_mb=400 20 | while [[ "$#" -gt 0 ]]; do 21 | case "$1" in 22 | -disk) 23 | shift 24 | disk_path="$1" 25 | shift 26 | ;; 27 | -size) 28 | shift 29 | size_mb="$1" 30 | shift 31 | ;; 32 | -nfs-mount-opts) 33 | shift 34 | nfs_mount_opts="$1" 35 | shift 36 | ;; 37 | -mount-path) 38 | shift 39 | nfs_mount_path="$1" 40 | shift 41 | ;; 42 | -*=*) 43 | extra_args+=("$1") 44 | shift 45 | ;; 46 | -*) 47 | extra_args+=("$1" "$2") 48 | shift 49 | shift 50 | ;; 51 | esac 52 | done 53 | 54 | make --quiet compile 55 | go build ./cmd/daisy-nfsd 56 | if [ -e "$disk_path" ]; then 57 | dd if=/dev/zero of="$disk_path" bs=4k count=$((size_mb * 1024 / 4)) 1>/dev/null 2>&1 58 | fi 59 | # ${a[@]+"${a[@]}"} checks if a is set and if so, expands to it as an array 60 | # "${a[@]}" should be sufficient but this idiom is compatible with bash 3 which macOS ships with 61 | # see https://stackoverflow.com/questions/7577052/bash-empty-array-expansion-with-set-u 62 | ./daisy-nfsd -debug=0 -disk "$disk_path" -size "$size_mb" ${extra_args[@]+"${extra_args[@]}"} 1>nfs.out 2>&1 & 63 | sleep 2 64 | killall -0 daisy-nfsd # make sure server is running 65 | killall -SIGUSR1 daisy-nfsd # reset stats after recovery 66 | 67 | # mount options for Linux NFS client: 68 | # 69 | # vers=3 is the default but nice to be explicit 70 | # 71 | # nordirplus tells the client not to try READDIRPLUS (it will fall back to 72 | # READDIR but always first try READDIRPLUS) 73 | # 74 | # nolock tells the client not to try to use the Network Lock Manager for 75 | # advisory locks since our server doesn't run one; instead, clients use local 76 | # locks which work fine if there's only one client 77 | _nfs_mount="vers=3,nordirplus,nolock" 78 | if [ -n "$nfs_mount_opts" ]; then 79 | _nfs_mount="vers=3,nordirplus,$nfs_mount_opts" 80 | fi 81 | 82 | sudo mount -t nfs -o "${_nfs_mount}" localhost:/ "$nfs_mount_path" 83 | -------------------------------------------------------------------------------- /bench/stop-daisy-nfsd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Usage: ./stop-daisy-nfsd.sh 5 | # 6 | 7 | path="$1" 8 | if [ -z "$path" ]; then 9 | path=/mnt/nfs 10 | fi 11 | 12 | killall -s SIGINT daisy-nfsd 13 | sudo umount -f "$path" 14 | -------------------------------------------------------------------------------- /cmd/fs-test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "reflect" 8 | 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | func printFormatArgs(args []interface{}) { 13 | if len(args) > 0 { 14 | fmtString := args[0].(string) 15 | fmt.Fprintf(os.Stderr, fmtString+"\n", args[1:]...) 16 | } 17 | } 18 | 19 | func assertEqual(actual interface{}, expected interface{}, msgAndArgs ...interface{}) { 20 | if !reflect.DeepEqual(actual, expected) { 21 | fmt.Fprintf(os.Stderr, "%v != %v\n", actual, expected) 22 | printFormatArgs(msgAndArgs) 23 | os.Exit(1) 24 | } 25 | } 26 | 27 | func assertNoError(err error, msgAndArgs ...interface{}) { 28 | if err != nil { 29 | fmt.Fprintf(os.Stderr, "error: %v\n", err) 30 | printFormatArgs(msgAndArgs) 31 | os.Exit(1) 32 | } 33 | } 34 | 35 | func assertIsError(err error, msgAndArgs ...interface{}) { 36 | if err == nil { 37 | fmt.Fprintf(os.Stderr, "unexpected success\n") 38 | printFormatArgs(msgAndArgs) 39 | os.Exit(1) 40 | } 41 | } 42 | 43 | func main() { 44 | flag.Parse() 45 | if flag.NArg() < 1 { 46 | fmt.Fprintf(os.Stderr, "Usage: fs-test \n") 47 | os.Exit(1) 48 | } 49 | 50 | mnt := flag.Arg(0) 51 | ents, err := os.ReadDir(mnt) 52 | assertNoError(err) 53 | assertEqual(len(ents), 0) 54 | 55 | err = os.Chdir(mnt) 56 | assertNoError(err) 57 | 58 | err = os.Mkdir("foo", 0755) 59 | assertNoError(err) 60 | 61 | fd, err := unix.Open("message.txt", unix.O_CREAT|unix.O_RDWR, 0644) 62 | assertNoError(err) 63 | n, err := unix.Pwrite(fd, []byte("hello, "), 0) 64 | assertNoError(err) 65 | assertEqual(n, len("hello, "), "short write") 66 | _, err = unix.Pwrite(fd, []byte("world\n"), int64(n)) 67 | assertNoError(err) 68 | unix.Fsync(fd) 69 | unix.Close(fd) 70 | 71 | ents, err = os.ReadDir(".") 72 | assertNoError(err) 73 | assertEqual(len(ents), 2) 74 | 75 | // attempt to unlink a directory 76 | err = unix.Unlink("foo") 77 | assertIsError(err) 78 | 79 | _, err = os.ReadDir("message.txt") 80 | assertIsError(err, "readdir non-dir") 81 | 82 | ents, err = os.ReadDir("foo") 83 | assertNoError(err, "readdir foo") 84 | assertEqual(len(ents), 0, "foo is non-empty?") 85 | 86 | contents, err := os.ReadFile("message.txt") 87 | assertNoError(err, "read message.txt") 88 | assertEqual(contents, []byte("hello, world\n"), "file has wrong contents") 89 | 90 | err = unix.Rename("message.txt", "msg.txt") 91 | assertNoError(err, "move file within same directory") 92 | err = unix.Rename("msg.txt", "foo/msg.txt") 93 | assertNoError(err, "move file between directories") 94 | 95 | // attempt to delete foo with msg.txt still inside 96 | err = unix.Rmdir("foo") 97 | assertIsError(err, "RMDIR on non-empty directory") 98 | 99 | // delete foo/msg.txt and then foo 100 | err = unix.Unlink("foo/msg.txt") 101 | assertNoError(err, "REMOVE") 102 | err = unix.Rmdir("foo") 103 | assertNoError(err, "RMDIR") 104 | } 105 | -------------------------------------------------------------------------------- /dafny_go/bytes/bytes.go: -------------------------------------------------------------------------------- 1 | package bytes 2 | 3 | import "fmt" 4 | 5 | // Bytes wraps a byte slice []byte 6 | type Bytes struct { 7 | Data []byte 8 | } 9 | 10 | func NewBytes(sz uint64) *Bytes { 11 | return &Bytes{Data: make([]byte, sz)} 12 | } 13 | 14 | func (bs *Bytes) Len() uint64 { 15 | return uint64(len(bs.Data)) 16 | } 17 | 18 | func (bs *Bytes) Get(i uint64) byte { 19 | return bs.Data[i] 20 | } 21 | 22 | func (bs *Bytes) Set(i uint64, b byte) { 23 | bs.Data[i] = b 24 | } 25 | 26 | func (bs *Bytes) Append(b byte) { 27 | bs.Data = append(bs.Data, b) 28 | } 29 | 30 | func (bs *Bytes) AppendBytes(other *Bytes) { 31 | if other == bs { 32 | panic("attempt to append to self") 33 | } 34 | bs.Data = append(bs.Data, other.Data...) 35 | } 36 | 37 | func (bs *Bytes) Subslice(start uint64, end uint64) { 38 | bs.Data = bs.Data[start:end] 39 | } 40 | 41 | func (bs *Bytes) CopySegment(dst uint64, other *Bytes, src uint64, count uint64) { 42 | if other == bs { 43 | panic("attempt to CopySegment self") 44 | } 45 | copy(bs.Data[dst:], other.Data[src:src+count]) 46 | } 47 | 48 | func (bs *Bytes) Split(off uint64) *Bytes { 49 | // this "full slice expression" is necessary to make the two Bytes 50 | // independent (otherwise bs will have the returned Bytes as its capacity, 51 | // which is overwritten by Append) 52 | // 53 | // see https://golang.org/ref/spec#Slice_expressions 54 | first := bs.Data[:off:off] 55 | second := bs.Data[off:] 56 | bs.Data = first 57 | return &Bytes{Data: second} 58 | } 59 | 60 | func (bs *Bytes) Print() { 61 | fmt.Printf("%v", bs.Data) 62 | } 63 | -------------------------------------------------------------------------------- /dafny_go/bytes/bytes_test.go: -------------------------------------------------------------------------------- 1 | package bytes 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestBytes(t *testing.T) { 10 | bs := NewBytes(1) 11 | bs.Append(1) 12 | assert.Equal(t, byte(0), bs.Get(0)) 13 | assert.Equal(t, byte(1), bs.Get(1)) 14 | } 15 | 16 | func TestAppend(t *testing.T) { 17 | bs := NewBytes(1) 18 | bs2 := NewBytes(3) 19 | bs.AppendBytes(bs2) 20 | assert.Equal(t, uint64(4), bs.Len()) 21 | } 22 | 23 | func TestSubslice(t *testing.T) { 24 | // this test is replicated as a Dafny method TestSubslice in machine.dfy 25 | bs := NewBytes(5) 26 | bs.Set(1, 3) 27 | bs.Set(2, 4) 28 | bs.Subslice(1, 3) 29 | assert.Equal(t, uint64(2), bs.Len()) 30 | assert.Equal(t, byte(3), bs.Get(0)) 31 | assert.Equal(t, byte(4), bs.Get(1)) 32 | } 33 | 34 | func TestCopySegment(t *testing.T) { 35 | bs := NewBytes(5) 36 | bs.Set(2, 3) 37 | bs2 := NewBytes(3) 38 | bs2.Set(0, 1) 39 | bs2.Set(2, 2) 40 | 41 | bs2.CopySegment(1, bs, 2, 1) 42 | 43 | assert.Equal(t, []byte{0, 0, 3, 0, 0}, bs.Data) 44 | assert.Equal(t, []byte{1, 3, 2}, bs2.Data) 45 | } 46 | 47 | func TestCopySegmentAll(t *testing.T) { 48 | bs := NewBytes(3) 49 | bs.Data = []byte{1, 2, 3} 50 | 51 | bs2 := NewBytes(5) 52 | 53 | count := bs.Len() 54 | bs2.CopySegment(0, bs, 0, count) 55 | assert.Equal(t, []byte{1, 2, 3, 0, 0}, bs2.Data) 56 | } 57 | 58 | func TestCopySegmentClone(t *testing.T) { 59 | bs := &Bytes{Data: []byte{1, 2, 3}} 60 | 61 | count := bs.Len() 62 | bs2 := NewBytes(count) 63 | bs2.CopySegment(0, bs, 0, count) 64 | assert.Equal(t, bs.Data, bs2.Data) 65 | } 66 | 67 | func TestSplit(t *testing.T) { 68 | bs := NewBytes(3) 69 | bs.Data[1] = 1 70 | bs2 := bs.Split(1) 71 | 72 | assert.Equal(t, uint64(1), bs.Len()) 73 | assert.Equal(t, uint64(2), bs2.Len()) 74 | 75 | assert.Equal(t, []byte{0}, bs.Data) 76 | assert.Equal(t, []byte{1, 0}, bs2.Data) 77 | 78 | bs.Append(2) 79 | assert.Equal(t, byte(2), bs.Get(1), "we just wrote that") 80 | assert.Equal(t, byte(1), bs2.Get(0), "Split did not create independent slice") 81 | } 82 | -------------------------------------------------------------------------------- /dafny_go/encoding/encoding.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/mit-pdos/daisy-nfsd/dafny_go/bytes" 7 | ) 8 | 9 | func UInt64Put(x uint64, off uint64, bs *bytes.Bytes) { 10 | binary.LittleEndian.PutUint64(bs.Data[off:], x) 11 | } 12 | 13 | func UInt64Get(bs *bytes.Bytes, off uint64) uint64 { 14 | return binary.LittleEndian.Uint64(bs.Data[off:]) 15 | } 16 | 17 | func UInt32Put(x uint32, off uint64, bs *bytes.Bytes) { 18 | binary.LittleEndian.PutUint32(bs.Data[off:], x) 19 | } 20 | 21 | func UInt32Get(bs *bytes.Bytes, off uint64) uint32 { 22 | return binary.LittleEndian.Uint32(bs.Data[off:]) 23 | } 24 | -------------------------------------------------------------------------------- /dafny_go/encoding/encoding_test.go: -------------------------------------------------------------------------------- 1 | package encoding 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/mit-pdos/daisy-nfsd/dafny_go/bytes" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestEncodeOf0(t *testing.T) { 11 | bs := bytes.NewBytes(8) 12 | for i := 0; i < 8; i++ { 13 | bs.Data[i] = 1 14 | } 15 | 16 | UInt64Put(0, 0, bs) 17 | 18 | assert.Equal(t, []byte{0, 0, 0, 0, 0, 0, 0, 0}, bs.Data[:8], 19 | "encoding of zero is not 8 zeros") 20 | } 21 | 22 | func TestEncodeOf0_32(t *testing.T) { 23 | bs := bytes.NewBytes(4) 24 | for i := 0; i < 4; i++ { 25 | bs.Data[i] = 1 26 | } 27 | 28 | UInt32Put(0, 0, bs) 29 | 30 | assert.Equal(t, []byte{0, 0, 0, 0}, bs.Data[:4], 31 | "encoding of zero (32-bit) is not 4 zeros") 32 | } 33 | 34 | func TestEncodeDecodeAt0(t *testing.T) { 35 | assert := assert.New(t) 36 | for _, x := range []uint64{ 37 | 0, 1, 38 | 0xFFFF_FFFF_FFFF_FFFF, 39 | 0x0102_0304_0506_0708, 40 | } { 41 | bs := bytes.NewBytes(8) 42 | UInt64Put(x, 0, bs) 43 | assert.Equal(x, UInt64Get(bs, 0)) 44 | } 45 | } 46 | 47 | func TestEncodeDecodeOffset(t *testing.T) { 48 | assert := assert.New(t) 49 | var x uint64 = 0x0102_0304_0506_0708 50 | bs := bytes.NewBytes(16) 51 | UInt64Put(x, 4, bs) 52 | assert.Equal(x, UInt64Get(bs, 4)) 53 | 54 | bs = bytes.NewBytes(4 + 8) 55 | UInt64Put(x, 4, bs) 56 | assert.Equal(x, UInt64Get(bs, 4)) 57 | } 58 | 59 | func TestEncodeDecodeAt0_32(t *testing.T) { 60 | assert := assert.New(t) 61 | for _, x := range []uint32{ 62 | 0, 1, 63 | 0xFFFF_FFFF, 64 | 0x0102_0304, 65 | } { 66 | bs := bytes.NewBytes(8) 67 | UInt32Put(x, 0, bs) 68 | assert.Equal(x, UInt32Get(bs, 0)) 69 | } 70 | } 71 | 72 | func TestEncodeDecodeOffset_32(t *testing.T) { 73 | assert := assert.New(t) 74 | var x uint32 = 0x0102_0304 75 | bs := bytes.NewBytes(16) 76 | UInt32Put(x, 4, bs) 77 | assert.Equal(x, UInt32Get(bs, 4)) 78 | 79 | bs = bytes.NewBytes(4 + 8) 80 | UInt32Put(x, 4, bs) 81 | assert.Equal(x, UInt32Get(bs, 4)) 82 | } 83 | -------------------------------------------------------------------------------- /dafny_go/jrnl/addr.go: -------------------------------------------------------------------------------- 1 | package jrnl 2 | 3 | // manual definition of Addr datatype 4 | 5 | type Addr_Addr struct { 6 | Blkno Blkno 7 | Off uint64 8 | } 9 | 10 | type Addr struct { 11 | // note this is not exactly what Dafny would emit: it would put an interface 12 | // here which in practice is always the Addr_Addr struct 13 | Addr_Addr 14 | } 15 | 16 | type CompanionStruct_Addr_ struct{} 17 | 18 | var Companion_Addr_ = CompanionStruct_Addr_{} 19 | 20 | func (CompanionStruct_Addr_) Create_Addr_(blkno Blkno, off uint64) Addr { 21 | return Addr{Addr_Addr{blkno, off}} 22 | } 23 | 24 | func (_this Addr) Dtor_blkno() uint64 { 25 | return _this.Addr_Addr.Blkno 26 | } 27 | 28 | func (_this Addr) Dtor_off() uint64 { 29 | return _this.Addr_Addr.Off 30 | } 31 | -------------------------------------------------------------------------------- /dafny_go/jrnl/alloc.go: -------------------------------------------------------------------------------- 1 | package jrnl 2 | 3 | import "github.com/mit-pdos/go-journal/alloc" 4 | 5 | type Allocator struct { 6 | alloc alloc.Alloc 7 | } 8 | 9 | func NewAllocator(max uint64) *Allocator { 10 | a := alloc.MkMaxAlloc(max) 11 | return &Allocator{alloc: *a} 12 | } 13 | 14 | func (a *Allocator) MarkUsed(x uint64) { 15 | a.alloc.MarkUsed(x) 16 | } 17 | 18 | func (a *Allocator) Alloc() uint64 { 19 | return a.alloc.AllocNum() 20 | } 21 | 22 | func (a *Allocator) Free(x uint64) { 23 | a.alloc.FreeNum(x) 24 | } 25 | 26 | func (a *Allocator) NumFree() uint64 { 27 | return a.alloc.NumFree() 28 | } 29 | -------------------------------------------------------------------------------- /dafny_go/jrnl/alloc_test.go: -------------------------------------------------------------------------------- 1 | package jrnl 2 | 3 | import "testing" 4 | 5 | func getArbitrary(m map[uint64]bool) uint64 { 6 | for x := range m { 7 | return x 8 | } 9 | return 0 10 | } 11 | 12 | func TestAllocator(t *testing.T) { 13 | // the allocator has no preconditions so we just attempt to exercise it a bit 14 | used := make(map[uint64]bool) 15 | a := NewAllocator(128) 16 | 17 | for i := 0; i < 20; i++ { 18 | x := a.Alloc() 19 | used[x] = true 20 | } 21 | for i := 0; i < 10; i++ { 22 | x := getArbitrary(used) 23 | a.Free(x) 24 | } 25 | 26 | for i := 0; i < 70; i++ { 27 | a.Alloc() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /dafny_go/jrnl/jrnl.go: -------------------------------------------------------------------------------- 1 | package jrnl 2 | 3 | import ( 4 | "github.com/mit-pdos/daisy-nfsd/dafny_go/bytes" 5 | "github.com/mit-pdos/go-journal/addr" 6 | "github.com/mit-pdos/go-journal/txn" 7 | "github.com/tchajed/goose/machine/disk" 8 | ) 9 | 10 | type Disk = disk.Disk 11 | 12 | func DiskSize(d *Disk) uint64 { 13 | return (*d).Size() 14 | } 15 | 16 | type Blkno = uint64 17 | type Txn struct { 18 | txn *txn.Txn 19 | } 20 | 21 | func dafnyAddrToAddr(a Addr) addr.Addr { 22 | return addr.Addr{Blkno: a.Blkno, Off: a.Off} 23 | } 24 | 25 | type Jrnl struct { 26 | log *txn.Log 27 | } 28 | 29 | func NewJrnl(d *Disk) *Jrnl { 30 | return &Jrnl{log: txn.Init(*d)} 31 | } 32 | 33 | func (jrnl *Jrnl) Begin() *Txn { 34 | return &Txn{txn: txn.Begin(jrnl.log)} 35 | } 36 | 37 | func (jrnl *Jrnl) Flush() { 38 | jrnl.log.Flush() 39 | } 40 | 41 | func (txn *Txn) Read(a Addr, sz uint64) *bytes.Bytes { 42 | a_ := dafnyAddrToAddr(a) 43 | buf := txn.txn.ReadBuf(a_, sz) 44 | return &bytes.Bytes{Data: buf} 45 | } 46 | 47 | func (txn *Txn) ReadBit(a Addr) bool { 48 | a_ := dafnyAddrToAddr(a) 49 | return txn.txn.ReadBufBit(a_) 50 | } 51 | 52 | func (txn *Txn) Write(a Addr, bs *bytes.Bytes) { 53 | a_ := dafnyAddrToAddr(a) 54 | txn.txn.OverWrite(a_, bs.Len()*8, bs.Data) 55 | bs.Data = nil 56 | } 57 | 58 | func (txn *Txn) WriteBit(a Addr, b bool) { 59 | a_ := dafnyAddrToAddr(a) 60 | txn.txn.OverWriteBit(a_, b) 61 | } 62 | 63 | func (txn *Txn) Commit(wait bool) bool { 64 | ok := txn.txn.Commit(wait) 65 | return ok 66 | } 67 | 68 | func (txn *Txn) Abort() { 69 | txn.txn.ReleaseAll() 70 | } 71 | 72 | func (txn *Txn) NDirty() uint64 { 73 | return txn.txn.NDirty() 74 | } 75 | -------------------------------------------------------------------------------- /dafny_go/jrnl/jrnl_test.go: -------------------------------------------------------------------------------- 1 | package jrnl 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/mit-pdos/daisy-nfsd/dafny_go/bytes" 8 | "github.com/stretchr/testify/assert" 9 | "github.com/tchajed/goose/machine/disk" 10 | ) 11 | 12 | // mkAddr builds the Dafny representation of an address 13 | // 14 | // Mainly for testing purposes; Dafny-generated code constructs a datatype using 15 | // a struct literal. 16 | func mkAddr(blkno Blkno, off uint64) Addr { 17 | if blkno < 513 { 18 | panic(fmt.Sprintf("invalid blkno %d < 513", blkno)) 19 | } 20 | if off > 8*4096 { 21 | panic(fmt.Sprintf("out-of-bounds offset %d", off)) 22 | } 23 | return Addr{Addr_Addr: Addr_Addr{Blkno: blkno, Off: off}} 24 | } 25 | 26 | func TestReadWriteTxn(t *testing.T) { 27 | assert := assert.New(t) 28 | 29 | var d disk.Disk = disk.NewMemDisk(1000) 30 | jrnl := NewJrnl(&d) 31 | data := []byte{1, 2, 3, 4} 32 | a0 := mkAddr(513, 0) 33 | a1 := mkAddr(513, 4*8) 34 | 35 | // write initial non-zero data 36 | { 37 | txn := jrnl.Begin() 38 | bs := &bytes.Bytes{Data: data} 39 | txn.Write(a0, bs) 40 | txn.Commit(true) 41 | } 42 | 43 | // copy it 44 | { 45 | txn := jrnl.Begin() 46 | bs := txn.Read(a0, uint64(len(data))*8) 47 | txn.Write(a1, bs) 48 | txn.Commit(true) 49 | } 50 | 51 | // read from new location 52 | { 53 | txn := jrnl.Begin() 54 | bs := txn.Read(a1, uint64(len(data))*8) 55 | assert.Equal(data, bs.Data) 56 | txn.Commit(true) 57 | } 58 | } 59 | 60 | func TestReadWriteBits(t *testing.T) { 61 | assert := assert.New(t) 62 | 63 | var d disk.Disk = disk.NewMemDisk(1000) 64 | jrnl := NewJrnl(&d) 65 | 66 | correctBit := func(a Addr) bool { 67 | b := false 68 | if a.Off%3 == 0 { 69 | b = true 70 | } 71 | if a.Blkno == 513 { 72 | return b 73 | } 74 | return !b 75 | } 76 | 77 | { 78 | txn := jrnl.Begin() 79 | for off := uint64(0); off < 8*4096; off++ { 80 | a := mkAddr(513, off) 81 | txn.WriteBit(a, correctBit(a)) 82 | 83 | a = mkAddr(514, off) 84 | txn.WriteBit(a, correctBit(a)) 85 | } 86 | txn.Commit(true) 87 | } 88 | 89 | { 90 | txn := jrnl.Begin() 91 | for off := uint64(0); off < 8*4096; off++ { 92 | a := mkAddr(513, off) 93 | assert.Equal(correctBit(a), txn.ReadBit(a), "addr %v is incorrect", a) 94 | 95 | a = mkAddr(514, off) 96 | assert.Equal(correctBit(a), txn.ReadBit(a), "addr %v is incorrect", a) 97 | } 98 | txn.Commit(true) 99 | } 100 | } 101 | 102 | func TestReadModify(t *testing.T) { 103 | assert := assert.New(t) 104 | 105 | var d disk.Disk = disk.NewMemDisk(1000) 106 | jrnl := NewJrnl(&d) 107 | a0 := mkAddr(513, 0) 108 | 109 | { 110 | txn := jrnl.Begin() 111 | buf := txn.Read(a0, 4096*8) 112 | buf.Data[0] = 1 113 | txn.Commit(true) 114 | } 115 | 116 | { 117 | txn := jrnl.Begin() 118 | buf := txn.Read(a0, 4096*8) 119 | assert.Equal(byte(0), buf.Data[0]) 120 | } 121 | } 122 | 123 | func TestAbortTxn(t *testing.T) { 124 | var d disk.Disk = disk.NewMemDisk(1000) 125 | jrnl := NewJrnl(&d) 126 | data := []byte{1, 2, 3, 4} 127 | a0 := mkAddr(513, 0) 128 | 129 | { 130 | txn := jrnl.Begin() 131 | bs := &bytes.Bytes{Data: data} 132 | txn.Write(a0, bs) 133 | txn.Abort() 134 | } 135 | 136 | { 137 | txn := jrnl.Begin() 138 | // after abort this shouldn't deadlock 139 | _ = txn.Read(a0, 4096*8) 140 | ok := txn.Commit(true) 141 | assert.True(t, ok, "read-only transaction should succeed") 142 | } 143 | } 144 | 145 | func TestWriteBufferTxn(t *testing.T) { 146 | var d disk.Disk = disk.NewMemDisk(1000) 147 | jrnl := NewJrnl(&d) 148 | data := []byte{1, 2, 3, 4} 149 | a0 := mkAddr(513, 0) 150 | 151 | { 152 | txn := jrnl.Begin() 153 | bs := &bytes.Bytes{Data: data} 154 | txn.Write(a0, bs) 155 | assert.Empty(t, bs.Data, 156 | "Write should empty input buffer so txn can own it") 157 | txn.Commit(true) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /dafny_go/time/time.go: -------------------------------------------------------------------------------- 1 | package time 2 | 3 | import "time" 4 | 5 | func TimeUnixNano() uint64 { 6 | return uint64(time.Now().UnixNano()) 7 | } 8 | -------------------------------------------------------------------------------- /dafny_go/time/time_test.go: -------------------------------------------------------------------------------- 1 | package time 2 | 3 | import "testing" 4 | 5 | func TestUnixTimeNano(t *testing.T) { 6 | time := TimeUnixNano() 7 | if time < 0 { 8 | t.Error("expected positive time") 9 | } 10 | if time < 1610000000*1_000_000_000 { 11 | t.Error("time should be in nanoseconds since epoch") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /dfyconfig.toml: -------------------------------------------------------------------------------- 1 | includes = ["src/**/*.dfy"] 2 | -------------------------------------------------------------------------------- /etc/check: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ## Some simple sanity checks for code quality in infrastructure 6 | 7 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 8 | DIR="$SCRIPT_DIR/.." 9 | cd "$DIR" 10 | 11 | # Markdown 12 | markdown-link-check "README.md" 13 | markdown-link-check "eval/README.md" 14 | markdown-link-check "src/fs/README.md" 15 | 16 | # Python 17 | black --diff "etc/dafnydep" 18 | mypy "etc/dafnydep" 19 | mypy etc/*.py 20 | black --diff etc/*.py eval/*.py 21 | pytest etc 22 | 23 | # Bash 24 | shellcheck "etc/dafnyq" 25 | shellcheck "etc/check" 26 | shellcheck eval/*.sh 27 | shellcheck bench/*.sh 28 | 29 | # Go 30 | echo "gofmt" 31 | gofmt -d nfsd dafny_go 32 | echo "go vet" 33 | go vet ./nfsd/... ./dafny_go/... 34 | -------------------------------------------------------------------------------- /etc/ci-install-nfs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | 4 | sudo apt-get -y install rpcbind nfs-common 5 | sudo systemctl start rpcbind 6 | sudo mkdir /mnt/nfs 7 | sudo chown "$USER" /mnt/nfs 8 | -------------------------------------------------------------------------------- /etc/ci-macos-setup-nfs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | sudo launchctl start com.apple.rpcbind 6 | # this is not needed as long as you mount with -o locallocks 7 | sudo launchctl start com.apple.lockd 8 | 9 | sleep 2 10 | sudo launchctl list | grep rpcbind 11 | 12 | mkdir /tmp/nfs 13 | -------------------------------------------------------------------------------- /etc/ci-setup-demo.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | 4 | # make xv6 available 5 | git clone https://github.com/mit-pdos/xv6-public ~/xv6 6 | echo "XV6_PATH=$HOME/xv6" >> "$GITHUB_ENV" 7 | 8 | # install exa manually 9 | wget -O /tmp/exa.zip https://github.com/ogham/exa/releases/download/v0.10.1/exa-linux-x86_64-v0.10.1.zip 10 | unzip -d ~/exa-release /tmp/exa.zip 11 | rm /tmp/exa.zip 12 | echo "$HOME/exa-release/bin" >> "$GITHUB_PATH" 13 | 14 | # install starship manually (with some tweaks to install command to make 15 | # unattended) 16 | curl -fsSL https://starship.rs/install.sh | sudo sh -s -- --yes 17 | -------------------------------------------------------------------------------- /etc/dafnydep: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import re 4 | import sys 5 | from os import path 6 | from typing import List 7 | 8 | INCLUDE_RE = re.compile( 9 | r"""^\s* # leading whitespace 10 | include \s+ 11 | "(?P.*)" 12 | $""", 13 | re.VERBOSE, 14 | ) 15 | 16 | 17 | def get_includes(fname: str) -> List[str]: 18 | """Parse Dafny file `fname` for include directives.""" 19 | includes = [] 20 | with open(fname, "r") as f: 21 | for line in f: 22 | m = INCLUDE_RE.search(line) 23 | if m: 24 | includes.append(m.group("path")) 25 | return includes 26 | 27 | 28 | def relativize(fname: str, include: str) -> str: 29 | """Resolve `include` to a normalized path, based on the directory of `fname`.""" 30 | working_dir = path.dirname(fname) 31 | p = path.join(working_dir, include) 32 | return path.normpath(p) 33 | 34 | 35 | def dependency_list(fname: str, includes: List[str]) -> str: 36 | """Return list of dependencies for `fname` from raw include paths 37 | `includes`.""" 38 | includes = [relativize(fname, include) + ".ok" for include in includes] 39 | return " ".join(includes) 40 | 41 | 42 | print("# auto-generated by ./etc/dafnydep") 43 | for fname in sys.argv[1:]: 44 | includes = get_includes(fname) 45 | deps = dependency_list(fname, includes) 46 | print("{}.ok: {}".format(fname, deps)) 47 | -------------------------------------------------------------------------------- /etc/dafnygen-imports.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import os 5 | import typing 6 | from os import path 7 | 8 | 9 | def process_file(src: typing.TextIO, dst: typing.TextIO): 10 | """Fix imports in file src and write output to dst.""" 11 | imports = 0 12 | for l in src.readlines(): 13 | if l.strip() == "import (": 14 | imports += 1 15 | elif imports == 1: 16 | if l.strip() == ")": 17 | imports += 1 18 | else: 19 | parts = l.strip().split() 20 | if len(parts) == 2: 21 | impname = parts[0] 22 | imppath = parts[1].replace('"', "") 23 | if "/" not in imppath and imppath != "reflect" and imppath != "os": 24 | imppath = "github.com/mit-pdos/daisy-nfsd/dafnygen/" + imppath 25 | l = '%s "%s"\n' % (impname, imppath) 26 | dst.write(l) 27 | 28 | 29 | def main(): 30 | if len(sys.argv) != 2: 31 | print("Usage: dafnygen-imports.py ", file=sys.stderr) 32 | sys.exit(1) 33 | dstdir = sys.argv[1] 34 | for (dirpath, _, files) in os.walk("."): 35 | os.mkdir(path.abspath(path.join(dstdir, dirpath))) 36 | for fn in files: 37 | with open(path.join(dirpath, fn)) as src: 38 | with open(path.join(dstdir, dirpath, fn), "w") as dst: 39 | process_file(src, dst) 40 | 41 | 42 | if __name__ == "__main__": 43 | main() 44 | -------------------------------------------------------------------------------- /etc/dafnyq: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Quiet version of Dafny - outputs only on error 4 | 5 | # capture output 6 | out=$(mktemp '/tmp/dafny-output.XXXXXXXX') || exit 127 7 | 8 | cleanup() { 9 | rm "$out" 10 | } 11 | 12 | dafny "$@" 1> "$out" 13 | status="$?" 14 | 15 | trap cleanup EXIT 16 | 17 | # on error, print output (and output to stderr while we're at it) 18 | if [ "$status" -ne 0 ]; then 19 | cat "$out" >&2 20 | fi 21 | 22 | exit "$status" 23 | -------------------------------------------------------------------------------- /etc/run-demo.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 5 | # go to project root 6 | cd "$DIR"/.. 7 | 8 | # this script needs XV6_PATH set 9 | if [ ! -d "$XV6_PATH" ]; then 10 | echo "set XV6_PATH to a checkout of https://github.com/mit-pdos/xv6-public" >&2 11 | exit 1 12 | fi 13 | 14 | set -u 15 | 16 | # force color output that matches gold output 17 | export TERM=xterm-256color 18 | 19 | ./bench/run-daisy-nfsd.sh ./tests/demo.sh /mnt/nfs | 20 | sed -E 's/20[0-9]{2}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}/2021-05-07 17:53/' | 21 | tee tests/demo-actual.out 22 | diff tests/demo-expected.out tests/demo-actual.out 23 | # if files differ diff will return a non-zero code and the script will fail 24 | 25 | rm tests/demo-actual.out 26 | -------------------------------------------------------------------------------- /etc/summarize-timing: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | from timing import parse_df 5 | import pandas as pd 6 | import argparse 7 | 8 | 9 | def main(): 10 | parser = argparse.ArgumentParser() 11 | 12 | parser.add_argument("--max", 13 | type=int, 14 | default=10, 15 | help="number of methods to show (0 for all)") 16 | args = parser.parse_args() 17 | 18 | df = parse_df(sys.stdin) 19 | df.sort_values(by="time_s", ascending=False, inplace=True) 20 | 21 | top_df = df.head(args.max) if args.max > 0 else df 22 | pd.set_option('display.max_rows', None) 23 | pd.set_option('display.max_columns', None) 24 | pd.set_option('display.width', None) 25 | pd.set_option('display.max_colwidth', None) 26 | print(top_df) 27 | 28 | total_s = df.time_s.sum() 29 | total_methods = len(df) 30 | print(f"total: {total_s:0.2f} methods: {total_methods}") 31 | 32 | 33 | if __name__ == "__main__": 34 | main() 35 | -------------------------------------------------------------------------------- /etc/test_timing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from timing import get_name, get_time, parse_df 4 | 5 | 6 | def test_name_parse(): 7 | line = r"""Verifying TypedFs.TypedFilesys.FreeFiles (correctness) ...""" 8 | d = get_name(line) 9 | assert d is not None 10 | assert d["type"] == "correctness" 11 | assert d["name"] == "TypedFilesys.FreeFiles" 12 | 13 | 14 | def test_timing_parse1(): 15 | line = r"""[0.006 s, solver resource count: 15761, 3 proof obligations] verified""" 16 | d = get_time(line) 17 | assert d is not None 18 | assert d["time_s"] == 0.006 19 | assert d["obligations"] == 3 20 | assert d["result"] == "ok" 21 | 22 | 23 | def test_timing_parse2(): 24 | line = r""" [0.005 s, solver resource count: 9753, 1 proof obligation] verified""" 25 | d = get_time(line) 26 | assert d is not None 27 | assert d["time_s"] == 0.005 28 | assert d["obligations"] == 1 29 | 30 | 31 | def test_timing_parse3(): 32 | line = r""" [60.987 s, solver resource count: 132341, 161 proof obligations] timed out""" 33 | d = get_time(line) 34 | assert d is not None 35 | assert d["time_s"] == 60.987 36 | assert d["result"] == "timeout" 37 | 38 | def test_timing_parse_new(): 39 | """Dafny 3.4 adds a new solver resource count to the trace""" 40 | line = r""" [20.471 s, solver resource count: 32600962, 233 proof obligations] timed out""" 41 | d = get_time(line) 42 | assert d is not None 43 | assert d["time_s"] == 20.471 44 | assert d["result"] == "timeout" 45 | 46 | 47 | def test_df_parse(): 48 | lines = r""" 49 | Verifying TypedFs.TypedFilesys.zeroFreeSpace (well-formedness) ... 50 | [TRACE] Using prover: /opt/homebrew/bin/z3 51 | [0.098 s, solver resource count: 329601, 2 proof obligations] verified 52 | 53 | Verifying TypedFs.TypedFilesys.zeroFreeSpace (correctness) ... 54 | [0.719 s, solver resource count: 4046295, 87 proof obligations] errors 55 | src/fs/typed_fs.dfy(597,4): Error: a postcondition could not be proved on this return path 56 | src/fs/typed_fs.dfy(594,31): Related location: this is the postcondition that could not be proved 57 | src/fs/typed_fs.dfy(94,9): Related location 58 | src/fs/typed_fs.dfy(599,28): Error: a precondition for this call could not be proved 59 | src/fs/byte_fs.dfy(1063,15): Related location: this is the precondition that could not be proved 60 | src/fs/byte_fs.dfy(134,12): Related location 61 | 62 | Verifying TypedFs.TypedFilesys.TotalFiles (correctness) ... 63 | [0.005 s, solver resource count: 9753, 1 proof obligation] verified 64 | 65 | Verifying TypedFs.TypedFilesys.FreeFiles (correctness) ... 66 | [0.006 s, solver resource count: 15761, 3 proof obligations] verified 67 | 68 | Dafny program verifier finished with 51 verified, 28 errors 69 | """.split( 70 | "\n" 71 | ) 72 | df = parse_df(lines) 73 | assert df.shape == (4, 5) 74 | assert df.at[3, "name"] == "TypedFilesys.FreeFiles" 75 | assert df.at[1, "time_s"] == 0.719 76 | assert df.at[2, "obligations"] == 1 77 | 78 | 79 | #def test_df_parse_errors(): 80 | # lines = r""" 81 | #Verifying CheckWellformed$$IndFs.IndFilesys.write ... 82 | # [0.253 s, 5 proof obligations] verified 83 | # 84 | #Verifying Impl$$IndFs.IndFilesys.write ... 85 | # [9.172 s, 114 proof obligations] error 86 | #/Users/tchajed/code/daisy-nfsd/src/Dafny/examples/fs/indirect_fs.dfy(379,10): Error: A postcondition might not hold on this return path. 87 | #/Users/tchajed/code/daisy-nfsd/src/Dafny/examples/fs/indirect_fs.dfy(360,14): Related location: This is the postcondition that might not hold. 88 | #/Users/tchajed/code/daisy-nfsd/src/Dafny/examples/fs/indirect_fs.dfy(339,9): Related location 89 | #/Users/tchajed/code/daisy-nfsd/src/Dafny/examples/fs/indirect_fs.dfy(330,9): Related location 90 | #/Users/tchajed/code/daisy-nfsd/src/Dafny/examples/fs/indirect_fs.dfy(289,10): Related location 91 | #/Users/tchajed/code/daisy-nfsd/src/Dafny/examples/fs/indirect_fs.dfy(290,68): Related location 92 | #Execution trace: 93 | # (0,0): anon0 94 | # (0,0): anon10_Then 95 | # (0,0): anon11_Then 96 | # (0,0): anon12_Else 97 | # 98 | #Verifying CheckWellformed$$IndFs.__default.config ... 99 | # [0.315 s, 15 proof obligations] verified 100 | # 101 | #Verifying CheckWellformed$$IndFs.__default.config__properties ... 102 | # [0.259 s, 1 proof obligation] verified 103 | # 104 | #Verifying Impl$$IndFs.__default.config__properties ... 105 | # [1.582 s, 3 proof obligations] verified 106 | # 107 | #Verifying Impl$$IndFs.__default.config__totals ... 108 | # [2.290 s, 15 proof obligations] verified 109 | # 110 | #Verifying CheckWellformed$$IndFs.__default.MkRole ... 111 | # [0.320 s, 2 proof obligations] verified 112 | # """.split( 113 | # "\n" 114 | # ) 115 | # df = parse_df(lines) 116 | # assert df.shape == (7, 5) 117 | # assert df.at[1, "result"] == "error" 118 | -------------------------------------------------------------------------------- /etc/timing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # process Dafny's /trace output and organizing timing info 4 | 5 | import sys 6 | import re 7 | from typing import List, Dict, Any 8 | import pandas as pd 9 | 10 | NAME_RE = re.compile( 11 | r"""Verifying\s 12 | (?P[a-zA-Z0-9_]*)\. 13 | (?P[a-zA-Z0-9_.]*) 14 | \s\((?P[a-zA-Z-]*)\) 15 | \s\.\.\. 16 | """, 17 | re.VERBOSE, 18 | ) 19 | 20 | 21 | def get_name(line): 22 | m = NAME_RE.match(line) 23 | if m is None: 24 | return None 25 | return {"type": m.group("type"), "name": m.group("name")} 26 | 27 | 28 | TIME_RE = re.compile( 29 | r""" 30 | \s*\[ 31 | (?P