Debuggable concurrency extensions for standard ML

We are developing an interactive debugger with reverse execution for the language Standard ML extended to include concurrent threads in the style of Modula-2+. Our debugging approach is based on automatic instrumentation in the source language of the user’s source code; this makes the debugger completely independent of the compiler back-end, run-time system, and target hardware. The debugger operates entirely inside the concurrency model and has no special concurrency privileges. In this paper, we consider some of the challenges of debugging a non-deterministic concurrent symbolic language “in itself. ” Issues considered include logging nondeterministic activity, obtaining more secure semantics for our concurrency primitives, controlling distributed computations, and defining suit able time models. We conclude by suggesting an alternative simulation-based approach to dealing with non-determinism. 1 Debugging Standard ML Standard ML [22] is a general purpose programming language featuring first-class functions, strong typing with polymorphism, and a powerful exception mechanism. Normal programming style in ML is mostly functional, but the language also supports the creation and manipulation of mutable objects (ref cells). Our highly optimizing compiler for the language, Standard ML of New Jersey (SML-NJ) [1], also supports mutable arrays and first-class continuations [7]. ML’s compile-time type-checking guarantees that *Supported in part by NSF Grant CC R9002786. Permission to copy without fee all or part of this material is granted provided that the copies are not made or distributed for direct commercial advantage, the ACM copyright notice and the title of the publication and its date appear, and notice ia given that copying is by permission of tha Association for Computing Machinery. To copy otherwise, or to republish, requires a fee and/or specific permission. @ 1991 ACM (). 89791.457. 0/91/001 1/0720. $1.50” programs have no run-time insecurities (“core is never dumped” ). This means that one can always study the run-time behavior of a program—even a buggy one— from inside the language, without reference to the underlying machine model (assuming the compiler functions correctly). We have used this fact to build an effective interactive debugger for sequential ML based on automatic source code instrumentation [25]. When code is compiled under debugger control, special hooks are insert ed at frequent intervals (roughly once per basic block). Later, when the program runs, the debugger can use these hooks to gain control, answer user queries, and support breakpointing. All the added code is itself written in ML. Since the instrumented code is just ordinary ML code from the perspective of the compiler and the run-time system, the resulting debugging system is completely independent of the back-end and target machine, and cannot be damaged by the compiler’s aggressive optimization strategies (indeed, it can benefit from them). , An important feature of our debugger is a reverse ezecution facility. Points in the program’s execution history are specified by times; these are values of a soft ware instruction counter (SIC) that is incremented each time a debugger hook point is encountered (we call each such encounter an event). The programmer can use debugger commands to jump back and forth in time, freely interleaving forward execution and replay. Breakpoints may be set either at source locations or at particular time values. Reverse execution is also used internally by the debugger. In SML-NJ, all data (including stack frames) are allocated on the heap; a datum has no statically known location and is subject to being moved by the garbage collector, so it must be accessed via a pointer. The straightforward way for the debugger to keep track of calling history and the locations of variables would require logging very large numbers of pointers, most of which would never be needed. To avoid this, the debugger answers user queries (e.g., for the value of an identifier) by jumping back in time to obtain pointers only after they are known to be relevant.