Live code updates
Live code updates is something that is best explained by an example. Imagine you are developing a user interface for something. What one usually does is to look at the result, test it to some extent, close the program, alter the source code and then start the program to run the test again. For small non-ui programs this works pretty well as the start and test steps can be fully automated and usually does not take any time worth mentioning. However, when you have some UI, this is not as easy since the UI contains a lot of state. Of course you can automate it to some degree, but wouldn't it be easier to just be able to hit re-compile in your editor, and skip all that?
That is about what the live code updates is all about. They will allow you to modify your code while the program is running, and then simply load and compile the new code while the program is running. This means that you will keep all your state, and in many cases this will speed up the time it takes to test your latest changes, increasing your productivity. Having an interactive compiler also has the benefit of being able to ask the compiler for type definitions and other things. In most IDE:s there is a feature like "Go to definition", which is implemented by letting the IDE "compile" your code once more, and then look up the information from that. When you have an interactive compiler already running, it is much easier to just ask the compiler, since the compiler already has all the information you need.
Challenges
As long as nothing but the code is updated, incremental compilation is quite easy. What you have to do is to keep track of all the pointers to the specific function in the generated code, and replace them with a pointer to the newly compiled code. You also have to be carefull not to remove code that someone is executing, or will return to soon. If each thread has a main loop that the code returns to eventually, you can simply delay deletion of code until all threads have reached the threads main loop at least once since the update was made.
What is more tricky is when you alter data structures of the program. The tricky part here is that you need to find and update all created instances of the types that were altered. As long as you have a custom allocator, or keep all objects in some list-like structure, this will be easy to do. The tricky part is, however, when someone has added a member variable to a type, so that the objects have to grow. One solution is to pre-allocate some extra memory that can be used in these cases. This will not work well for long lived objects that are extended frequently or by large amounts at once. Instead we have to accept that we may have to re-allocate the objects sometimes. Just re-allocating objects and copy the contents is not problematic in itself. What is tricky is that you have to find all references to the old object and replace them with references to the new object.
Since we have already got a mechanism for reading all objects on the heap, we can update these quite easily. The hard part is the stack. When some code is executed, it is certainly keeping some references to objects on the stack. We need to update those as well. Aside from the stack, these references may be kept in registers in the CPU, or there may be temporary pointers into objects. How can we know what to update in all cases?
My idea of solving this is to introduce "breakpoints" into the generated code when running in non-release builds. When we need to update the stack of a thread, we can then stop the thread, enable the breakpoints, start the thread and wait until it hit our breakpoint. If we make sure that we do not allow any pointers to be stored in registers, nor pointers into objects at these break points, we can look up what is supposed to be on the stack at that time and find the possible pointers that way.
Another thing you need to do, is to make sure you update any member offsets in the previously generated code. There are two alternatives here, either you make a note of where you stored member offsets in the generated code somewhere and update them, or you simply re-compile all functions that uses the updated type.
It is worth to notice that I have not yet implemented this, so I might have missed something vital here. However, it does seem doable with enough metadata.
Comments
New comment
You can use GitHub flavored markdown here. Parsed by Parsedown, which does not support all of GitHub's features. For example, specifying the language of code listings is not supported.