To me, hot loading is a game changer. By that I mean the ability to compile 
code changes at runtime without the need to restart the application. Especially 
in game development where rapid prototyping is key, it is an incredibly useful 
feature to have. I'd go as far as to say that any modern language that's 
supposed to be viable should be designed with hot loading in mind.

The implementation I know of is to have the majority of your project to be 
compiled into a dynlib, while the main binary doesn't contain much more than 
the main loop and some code to check if the lib file has been updated, and if 
it was, it reloads the lib and gets the proc addresses.

In nim, this works fine at first, but once you want to do heap allocations 
(e.g. new, newSeq, ...) within your dynamically loaded code, it gets tricky. 
Since we probably want those allocations to persist when the lib is being 
reloaded, we store the refs into a state struct that's created in the main 
binary and then continuously passed to the linked code. This doesn't work at 
all unless you link both the main binary and the lib against 
[nimrtl](https://nim-lang.org/docs/nimc.html#dll-generation). Now unfortunately 
this removes support for threads and causes stack traces to be incomplete (not 
available in almost all cases).

While these are pretty hefty losses (especially the missing stack traces hit 
hard), I still went on and worked with this setup for couple of month now. I 
can toggle hot loading on/off at compile time, so when I'm not sure what's 
going and need a stack trace, I can just make a normal build that imports the 
code the normal way instead of loading it from a lib. Then I get nice stack 
traces again.

Now sadly, there's one more problem that makes it increasingly difficult to 
work with hot loading. It looks like the allocations are somehow "unstable", 
even when linking against nimrtl. I am not sure what causes this as I know 
nothing about the inner workings of the GC, but I observe that when I insert or 
remove a chunk from the code that goes into the lib, the applications reloads 
the lib and then crashes with SIGSEGV: Illegal storage access. (Attempt to read 
from nil?) upon operations like seq.del or seq.setlen.

I also observed that whether it crashes or not seems to depend on how much code 
I modified. When just making some single-line edits, it reloads and keeps going 
most of the time. When changing a few more things, it is much more likely to 
crash.

One theory is that when I do unloadLib, any allocations associated with that 
lib are being marked for the GC even though they're still referenced elsewhere.

So I wonder, is there anyone else who has toyed around with hot loading and hit 
similar barriers? Anyone that may have an insight to share as to way this 
doesn't work like I want it to?

Btw if there's interest, I can share a little test case that contains a minimal 
version of my hot loading implementation which can be used to reproduce what I 
described above.

Reply via email to