Hot-code reloading on macOS/arm64 with Zig
jakubkonka.com
jakubkonka.com
The debugger entitlement is even more powerful, but once again since you’re modifying your own memory you don’t need it.
What about evolutionary/genetic programming? That can definitely take advantage of this as well.
There’s just so much software out there and so many little bits of information one can gather.
You also wouldn't need the entitlement anymore.
You can see some examples here when he just tests logic changes:
(Though, he restarts often since he’s editing preloaded assets also)
Now almost 30 years later is when we see them being adopted.
"Lucid Energize Demo VHS 1993"
https://www.youtube.com/watch?v=pQQTScuApWk
Visual Age for C++ v4 had a similar capability, it was image based and allowed for Smalltalk like workflows.
Since last year they have been working on it to make it more dynamic, improving the use cases.
It is these little things that make it so much better than the UNIX alternatives.
However there is also ROOT and now CINT from CERN, or Live++.
Why not? Perhaps I'm missing the point, but can't this be mutex'd away? Or perhaps you're refering to the fact that the hot code reloading mechanism should provide the mutex implicitly?
But if you write
result = 0
while true:
result += 2 * do_something()
and change that to this and reload: result = 1
while true:
result -= 2 * do_something()
then even "ideal, correct behavior" at a reload level can give you end result values that are not possible to get while running either version of the code end-to-end. It's not possible to "fix" that because it's doing exactly what you told it to do - either it preserves values and can produce impossible values, or it does not and it's not really hot-swapping any more. Or it does some mix of that and it has even more surprising / wrong edge cases.There are of course uses for systems with hot-swap boundaries/transactions to prevent swapping during critical regions, or to do something like stop and replace actors rather than touching their internal state, and they exist (e.g. erlang). But that requires your code to already be adopting those semantics, and it inherently defines boundaries on what is swapped and what is not. In that case the root "inconsistent transaction state" concern is not really a hot swapping issue, it's a failure to define your boundaries and semantics correctly - a normal bug / relying on undefined behavior.
If you can restrict the code that runs between frames, you can just replace the logic everywhere between the frames, and not have to worry about what code the threads are executing at this time - since game logic is not running.
They can support you with good advice in the further developing of Zig, I'm sure! Good luck!
The presented approach might also be more resource efficient as it is writing directly to program's memory rather than unloading and reloading a dynamic library, but this is very much a guess and I would need to do some benchmarking to get a better feel for it.
In general though, this approach is possible in Zig since first of all, we have our own linker for each target file format (ELF, MachO, COFF-coming-soon-tm), secondly, the compiler generates the executable file directly, and thirdly, incremental updates are super granular in order to minimise writes to the file as much as possible.
Crazy right?
This is because incremental compilation & linking is actually the same problem as hot code swapping! This was just a natural fallout of the design of the compiler.
Same deal with Jakub's hot code swapping branch. It's 257 additions and 9 deletions and has the same ~150 lines of adding the socket server [2].
So in answer to your question, the PoC adds about 150 lines to an 186,865 line codebase.
Mach for Zig looks interesting too, but it's very new.
This isn't crazy - this is how just-in-time compilers work.
In the case of JIT, it may swap the bytecode but the flow of execution remains the same.
Hot code reloading usually requires a different approach.
Are you able to elaborate on this further?
1a. Does this mean the process gets restarted during reload?
1b. If no restart, how are active threads handled? What if a thread should no longer exist post-reload?
If I've inlined a method, and someone redefines that method in a language that allows that, then I'm going to need to reload that code.
Which does seem a little crazy, and the post doesn't go into details about how the compiler knows when it's reasonable to rewrite instructions -- it seems like you would need some runtime mechanism in the child to coordinate this, or you'd run the risk of... well, literally anything could happen, with the wrong patch at the wrong time. I feel like I might be missing something. The more I think about this the crazier it seems. :)
(And if you had this inter-process channel for coordinating changes, then the child process could also just patch itself based on messages from the compiler, removing the need for special cross-process memory-writing privileges. LiveReload for native code...)
I had been thinking about what mechanism you could put in place to coordinate, but you made me realize that if your language provides some kind of event loop, then that would be the right place to have the "please stop while I push this old function under the rug" functionality. And languages that have async/await have to have such a runtime, which can make the whole thing completely transparent to the programmer. But you still need the inter-process comms to let the runtime know it should wait for a bit, so your last sentence still applies: at that point have the process rewrite itself.
JIT languages tend to be also GC languages, which tend to have a very good idea of when it's safe to stop a thread, and what the values in the registers and stack mean at a particular point in time.