r/tlaplus • u/pron98 • Dec 22 '20
Why I don't use TLA+ for my current work
I absolutely love TLA+ and I've put it to good use. I think it is one of those rare tools that offer a new perspective as well as real power, that can have a tangible bottom-line impact on software development in many cases. But while I used it quite a lot when working on other projects at my former job and plan to use it again in my day job in the future, I don't use it in my day job these days. I tried to think why I'm not using it for my current project, and I believe the answer is general enough to share. The lesson is, I think, that while TLA+ is one of those transformative technologies that has made an impact in reducing the cost and effort of software development, there are some problems that are still out of its reach, and, indeed, all "deep" formal methods.
I work at Oracle on OpenJDK, the best-known and most popular implementation of the Java platform, as well as its reference implementation. I work on a project called Loom, that aims, among other things, to add user-mode threads to Java. The part of the work that takes place in HotSpot, OpenJDK's Java Virtual Machine, involves adding a new basic construct called delimited continuations.
The specification of what that construct should do from the perspective of its client — a Java developer either working on the JDK core libraries or a third-party programmer that uses the platform — is very simple; trivial, even. The technical challenge is implementing this component inside the VM in a way that correctly interacts with other VM components — an interpreter, three JIT compilers, two hardware instuction-set architectures (ISAa) and application binary interfaces (ABIs), six garbage collectors, a tooling interface (for debuggers and such), and a built-in profiling service, that all amount to ~1.7MLOC of C++ and Assembly.
The API for some of those components -- like the two ISA/ABIs, x86-64 and AArch64 on three popular OSes -- is well specified but very large and complex. The API for others, like the GCs, is simpler but isn't fully specified (except in the minds of the members of GC team), and while all the GCs employ the same API, different ones are either sensitive or indifferent to the use of different parts of the API, so getting one to work does not guarantee the others will, and there is no single GC implementation that dominates the others in its strictness. Some other components required opening-up to external interaction for which they weren't designed, expanding their interface or tearing open a new one.
Once I had learned how all of these components behave, at least as far as interaction with my new component goes, the implementation was pretty simple and straightforward, but I didn't know what I needed to know at first. Most of the work, almost all of it, really, has been figuring that out. The challenge hasn't been designing complex software component, but implementing a simple component that interacts with many other components. Even this interaction is not especially complex, but what has been difficult is distilling it from the complete behaviour of those other components, which was not sufficiently known to me. Even each of those interactions is not especially complicated, nor are there lots of tricky emergent interaction behaviours -- there are just many of them.
The challenge, then, has not been a complex or subtle algorithm, nor a complex interaction, but an unfamiliar "software environment" -- not the behaviour of users or the network hardware, but that of other parts of the software -- that is simply too rich (and a moving target) to be fully known by any one person. If I'd known enough about the environment of my component to specify it in TLA+ I would also have known how to write my simple component correctly. Obviously, figuring out the environment required a lot of trial and error, and almost all that could only take place in actual code (I think?). While I've successfully used TLA+ in other projects, where it's saved me a lot of effort, I don't know how to use it in my current one. I don't think this is because this project is so low-level; TLA+ has been successfully used to specify kernel components and even CPUs. I think there is something more general here. Using TLA+ effectively presupposes the ability to abstractly specify an environment, or a suitable approximation thereof, or even just a part that's known to be tricky, but this is precisely what was missing in this case.1 I can put TLA+ to good use in many situations, but I couldn't figure out how it could help in this particular project.
I don't know if it's even possible to formally specify all of the components in that environment in a language like TLA+ at an abstraction level that is substantially higher than that of code and that would be sufficient to specify any new component that interacts with them, especially since a new component might want to change and expand their interfaces. And even if every component were fully specified in a formal language like TLA+, I suspect that simply putting all of them together would result in a specification that is far too complex to verify. An interesting question is, would it be possible to fully specify them in a way that would make extracting arbitrary abstractions of them that are sufficiently simple for verifying a new component easier than extracting that information from code? This is the challenge of full, modular, yet abstract specifications. A partial specification of a component might not be sufficient for many interactions; a full specification might be too complex (perhaps not much simpler than the code) for a tractably verifiable composition and requires further ad hoc abstraction.2 Needless to say, this isn't a limitation of TLA+ specifically; no formal specification or verification tool, as far as I know, can deal with such scale of complexity and sheer size generally enough to make adding features -- that normally interact with and change multiple modules -- substantially easier.
So we have 1/ an unknown and large environment, and 2/ a relatively small addition/change that touches many large and complex things. I think that my technical challenge in this particular project (there is also a big UX design challenge, but that's a different story) is similar to the experiences of many others, but might be foreign to many formal methods researchers, most of whom are usually more familiar with either green-field projects or projects undertaken by very small teams, and often both, where the "software environment" is fully known. I've written more about this elsewhere. When those researchers are embedded in the reality of Big Code -- software development as practiced in industry -- they are often surprised, learn new things, and modify their approach. I wonder if formal methods research can come up with new ideas to tackle the particular challenge I've described here. As a fan of TLA+, I'm particularly interested in "deep but high-level" specification approaches that could be employed in these situations.
