Patrick Stevens | Announcing WoofWare.PawPrint, a deterministic .NET runtime<br>Lighting
Toggle navigation menu About Me<br>About This Site<br>Film List<br>Games<br>Home<br>Lifehacks<br>Posts<br>Reading List<br>Top Posts
Announcing WoofWare.PawPrint, a deterministic .NET runtime
I just released an early version of WoofWare.PawPrint to NuGet.<br>PawPrint is a deterministic .NET runtime - think CHESS.<br>It runs the BCL of .NET 10.<br>It interprets IL, shimming out only the BCL’s JIT intrinsics and native code: there are no shortcuts.<br>Enough is implemented that it can do:<br>Console.Writeline<br>async void Main(string[] args) {...}<br>Task.Run<br>A whole load of reflection<br>Many of the low-level synchronisation primitives like Monitor<br>It uses a variant of Probabilistic Concurrency Testing when scheduling threads, in an attempt to maximise the exploration of “interesting” thread orderings.<br>How I decided it was ready<br>I’ve taken six standard race conditions and tested that we can deterministically identify them.<br>More concretely, inspired by Deadlock Empire, these tests are of the form “demonstrate that some interleaving of threads results in some known bad condition happening” (like a deadlock or an exception being thrown).<br>Every test I tried, the test harness found the bug immediately, often taking only a couple of trial seeds.<br>What it’s not ready for<br>Well, I expect that if you use it, it will blow up almost immediately.<br>The BCL contains very large amounts of native code, and it must be explicitly modelled in PawPrint for it to execute.<br>An upcoming piece of work will be to allow the user to plug in their own implementations so that they aren’t blocked on the built-in incompleteness.<br>Overall design<br>PawPrint is ultimately intended to allow time-travel debugging and control over history.<br>To that end, it maintains an extremely rich internal model of the IL machine.<br>Everything is provenance-tracked; every pointer knows what object/field/method/whatever it’s pointing to, and every byte array knows whether it’s e.g. “a projection of object Foo into raw bytes” vs “just a bunch of bytes the user gave me”.<br>All arithmetic results know whether they are “a sum of raw integers” or “a difference of pointers within the same array” or whatever.<br>The use of LLMs<br>Original design is my own, and I started writing it by hand.<br>Sonnet 4.6 came out at some point during this process, and I started using it for reference information about .NET.<br>I also used Gemini 2 Pro to perform fuzzy search through the ECMA-335 spec.<br>Then in 2026 I got the same LLM psychosis everyone else has, and used Claude Opus 4.6/7 and GPT-5.5 to “complete” it.<br>This was a massive accelerator, and I believe it shaved literally years off the project, at the cost of making the code Claude-shaped in the small.<br>This project is particularly LLM-suited because there is a reference implementation (.NET 10 itself) and a spec (ECMA-335).<br>Errors the bots made<br>Sadly it was still necessary for me to maintain architectural direction during this project.<br>There was only one place where I completely abdicated a complex architectural decision to GPT-5.5 because I was too lazy to decide myself, and that was a disaster which I ended up completely rewriting by hand.<br>That decision was about the handling of the fact that native code and some unsafe casts require genuine byte arrays to compute a result, and there’s a bunch of Unsafe.As calls in the BCL which make make it hard to avoid laundering provenance-tracked pointers through flat bytes.<br>I religiously track provenance in PawPrint.<br>GPT-5.5 chose to represent arrays as being located in specific locations in memory, by assigning them fake addresses in a certain range.<br>This got more and more unwieldy over time, and arithmetic operations on them became annoying because we lost their provenance as soon as we decided there was a genuine integer representing their location.<br>Eventually I tore that out and replaced those integers with synthetic “I am the address of heap object Foo” markers; arithmetic on such objects will generally crash PawPrint, but that’s fine because the resulting integer values are generally undefined by .NET anyway.<br>(There is specific support for performing arithmetic on pointers known to be within the same array.)
Published: 2026-06-04<br>Last modified: 2026-06-04 07:41<br>654 words
Submit anonymous feedback
Patrick Stevens
© Patrick Stevens 2026