[Mono-dev] Incremental C# compiler

Jonathan Gilbert 2a5gjx302 at sneakemail.com
Fri Jul 14 11:49:31 EDT 2006

At 09:28 AM 13/07/2006 -0500, Jerry Haltom wrote:
>The only use I've ever had for edit and continue, which to me, is a very
>important use, is in the debugger. I can step through code, find an
>error, and quickly replace it immediately, and test the results locally
>in that method by stepping back and then forwards again.
>Without it, I have to shutdown the entire application, which might have
>a large running state to arrive at the bug, and start it again.

I wasn't aware that there was any common use-case other than in a debugger.

Of course, irrespective of the implementation, the ultimate goal of any
edit-and-continue support must be to allow methods that are currently
executing to be changed. Initially, though, the simpler goal of simply
allowing entire methods to be added, changed or removed is probably a
better target. Perhaps even the simplest goal of all, merely to allow the
*bodies* of methods to be changed, would be a useful starting point. All I
wanted to emphasize with my messages is that any work done for
edit-and-continue should be done with the ultimate goals in mind, so that
assumptions aren't made which limit what can be added in the future and
might possibly require the earlier code to be rewritten.

Consider that your use could be satisfied by this if, upon seeing an error
in a method, you used the "set next statement" functionality, which I
assume is already present, to cancel the method's execution, and then did
the edit-and-continue when the method was not running. It would not be as
convenient as the full-blown support offered by some commercial IDEs, but
it would, in many cases, still be a heck of a lot more convenient than
restarting the debug session.

I can see 3 logical steps to the implementation of edit-and-continue in mono:

Stage 1: Only permit method bodies to be replaced -- no addition or removal
of methods, and no changes allowed to methods that are currently executing
in any thread.
Main change to code: Alter MCS/GMCS to be able to run multiple times from
the same AppDomain cleanly, without leaks or interference from one run to
the next, and with all output kept in-memory.

Stage 2: Permit methods to be added & removed. Methods which are running
may not be changed or removed.
Main change to code: Add or rework metadata tables in order to permit
on-the-fly changes to be adequately inexpensive.

Stage 3: Permit the full capabilities of edit-and-continue -- will require
a non-conservative garbage collector implementation, in order to track data
flow through the machine's registers.
Main change to code: Adding code to permit execution contexts to be mapped
from the old version of a method to the new version. May or may not involve
resurrecting the interpreter to simplify the last stage of this process, in
which the context (variable values & execution pointer) is applied to the
new compiler output, but will quite probably involve platform-specific
native code that will need to be adapted by hand to each platform mono

I think at all times, a time guideline should be adhered to, such that an
edit-and-continue pass not take longer than, say, 10 seconds. I would find
even that to be a rather long time to wait, but I could live with that in
exchange for being able to edit a running application. Anything longer than
that, and I'd probably give up and just restart the debug session with the
new code :-)

I don't know how memory-expensive a GMCS parse tree is, but as a possible
helper for reducing (re)compile times, it might be useful to keep the old
code's parse tree around, so that it can be diffed against the new
version's parse tree as an initial pruning of what needs to be recompiled.
This would only be done in a debug session ("mono --debug ..."), of course,
and it would require some way to preserve the rest of GMCS's context as
well, as I assume loaded types would need to be the same objects in order
for the comparisons to succeed. Once actually recompiling the methods, some
way to hide the fact that the method (or perhaps entier type) being
compiled already existed in GMCS's metadata would need to be added, so that
no collision would be detected.

Of course, if none of this is feasible, the fallback would need to be to
invoke GMCS externally and then *after* the compilation process do the diff
operation on the IL code itself. This would perform significantly worse, in
my estimation :-)

I also just realized that a couple of entire classes of changes hasn't even
been mentioned yet: adding/removing members other than methods, and adding
entirely new data types (I can't see any obvious reason why new data types
couldn't be added, though I can see that removing data types would be a
pain, even more of a pain than supporting the removal of methods in classes).

Jonathan Gilbert

More information about the Mono-devel-list mailing list