Rob, Thanks for your comments. The debugger was really an experiment to see what was possible to add a source-level debugger to Poly/ML and whether it would be useful. I didn't want to make changes to the low-level parts of the compiler so it works by adding code at the point where the machine-independent intermediate code is generated from the parse tree. That has a number of advantages, in particular code compiled with the debug switch on can be freely mixed with non-debuggable code.
- The example in the documentation doesn't quite work as stated. The problem
seems to be a general one: the only break-points seem to be at the head of a function (i.e., with the parameter bindings done, but not any bindings in let-expressions). When you go up the stack, values bound between the head of the function and the point of execution are not in scope. I.e., if you have something like:
fun f x = let y = h x; in g y end;
and break in g, when you go up to f, you can't see y (you can rerun the binding, but that may not be the right thing to do if h has side-effects).
It's clear that I've missed out an important point from the description: line breaks are significant. The debugger works by inserting extra code into the code-stream. Originally this just provided the ability to set a break point on the line but I later expanded it to record the environment. This code adds a very considerable overhead at run-time so it is currently only inserted at the start of a function or if the source line has changed. So, the simple way to get what you want is to split the function onto several lines: fun f x = let val y = h x in g y end;
Only inserting the code on a new line makes sense if all the code is doing is checking for a breakpoint. There's no point in having two breakpoints on the same line if the user can't specify which one to break at. From your comments it seems that this isn't the right thing to do for the environment and I guess this should be changed. My experience with source-level debuggers has been with debuggers for C and C++ where everything is line-based. For ML where functions are often small that probably isn't right.
- If you mistype a function name in the parameter to breakIn, it doesn't
cause an error (maybe this is a side-effect of a feature? it does seem to be very tolerant of the function names, e.g., you seem to be able leave off structure names and can stop in functions that aren't visible outside their structure).
Setting a break-point merely records the request in a table. When the code is actually executed the debugging code in the prelude of the function looks to see if there is an entry in the table and breaks if there is.
- It would be very nice indeed if you could break at points where an
exception is raised. I can see that this might be a bit tricky because an awful lot of my ML code is failure-driven, so lots of exceptions get raised "unexceptionally" and you'd want a way of describing "exceptional" exceptions, In my case the ones I'm interested in are the ones that aren't going to be caught.
You can use PolyML.exception_trace to produce a trace of exceptions that are not caught. Unlike the debugger this adds almost no overhead at run-time. You can then set explicit breakpoints where the exceptions are raised. I'll think about this but I think there may be problems with the interaction between the debugger and the low-level exception mechanism.
- Is there are any run-time overhead in having code compiled with
PolyML.Compiler.debug true?
Yes, a lot. Whenever a debugged function is entered there is a call to the debugger and a similar call at the exit. In addition there is a call whenever a line number has changed, provided there is ML code on it. These calls build up an environment structure which adds garbage-collection load. In addition many optimisations are in effect disabled by the need to keep the environment around, e.g. tail-recursion removal.
- ProofPower puts a wrapper around the compiler's read-eval-print loop (by
calling PolyML.compiler with appropriate arguments to do the reading and printing). It would be nice if we could do the same with the debugger's read-eval-print loop.
I'll think about that but I don't know whether it would be possible. The debugger needs to be able to create its read-eval-print loop dynamically so there would have to be a way to register your own function.
Regards, David.