On Wednesday, 20 March 2019 at 14:27:54 UTC, Ethan wrote:
On Tuesday, 19 March 2019 at 19:50:15 UTC, Craig wrote:
For example, with windows I could simply compile to a dll then extract the code, or just use LoadLibrary and it effectively does all the work(steps 1 & 2 & 3).

LoadLibrary performs steps 1 and part of step 2. The DllMain function of a library is called separately by Windows for each thread in your system, which is where the bulk of step 2 is handled. Step 3 isn't handled at all by LoadLibrary, and is instead entirely up to you to deal with via the GetProcAddress function.

If you want DLLs to operate in that step 1-2-3 manner, then the compiler can generate a static library that handles that all for you. But, as you might expect, that removes the hot reloading capability as that is all handled before WinMain is entered.

If you expect hot reloading to work without effort, you're going to have to compile your DLLs with -betterC. The D Runtime is not yet in a standalone DLL (unless I've missed an announcement over the last couple of months). Each DLL you build will require the D runtime to be embedded. Things get really tricky from there.

If you ever use your DLL for more than calling functions with basic types, or value aggregates (structs) containing basic types, then be aware that without -betterC that there is potential for the moduleinfo and typeinfo systems to go a bit screwy. Especially once druntime lives in its own DLL, as druntime expects those systems to be initialised once at startup. POSIX libraries will have the same issues, but I have nod had a need to investigate further over the last few months.

LoadLibrary is not portable though and seems excessive to do something that should be rather simple unless there is something I'm missing that has to be done that is complex.

LoadLibrary is the equivalent to dlopen on POSIX systems, with one very important difference: Windows does not provide lazy symbol evaluation. As such, extra work is required to handle that. The average usecase is covered by the mentioned static lib, but any hot reloading is handled in a custom manner by every Windows codebase you'll encounter.

The static lib I mention, in fact, redirects every cross-DLL call to a jump table. And cross-DLL data sharing gets hairy in Windows. Function parameters aren't a problem, but Windows explicitly disallows thread local variables to cross the DLL boundary for example (even if you expose it for export and perform a symbol lookup, it won't work and you'll get garbage back).

As I said, it's not a simple thing by any means. My work has attempted to hide all that complexity by boiling it all down to "include these headers/modules, link this lib, and get on with life". If you find the resulting code too complex (and I freely admit that I haven't got the integration in to as foolproof a manner as I am aiming for) then very definitely read through the presentations to get a clear idea of why something that should be so simple really isn't.

You are making this more complicated because you don't understand that I don't need a general purpose solution. I simply need to call simple functions that do not do much, not full blown applications.

For example, Suppose I want to write a fractal display application and instead of hard coding the function to display, it's external.

e.g.,
import std.complex;
alias C = Complex!double;

C Mandelbrot(C z, C c) { return z*z + c; }


This function does not require much of anything to run. The machine code can be copied and pasted at will to any executable memory location and ran... it can, in fact, be inlined with a little caution(reserve enough space and ignore the stack code). No initialization needs to be nor garbage collecting.

For a little more robustness one would want to allow for GC, which should not be hard, and context passing so more useful functionality can be had.


If D had a Dstring2Machine that compiled the above code to machine code then it could be used directly without issue.

Because these functions are deterministic and the host determines their usage, there is little issues to worry about. Hot swapping is simply changing a pointer when the function is not being called.

As long as certain guidelines are followed one can do this quite simply without too much issue and it allows for a scripting like solution for D apps. It allows one to inject functionality in to an application that runs at full speed and is relatively fast to compile.

The idea is not to be able to connect two full blow external apps but to connect little pieces of functionality to an app that allow for vast flexibility. Since each unit of functionality is essentially pure, there are no major issues.

It's this simple:

1. Take source and compile it in to machine code.
2. App extracts machine code and inserts it in to it's memory space to be able to execute.

Do you agree that, at least in some cases, this is entirely feasible and easy?


I'll give you a hint:

1.

module simple;

void foo() { };

compile directly in to machine code(the most raw form possible containing foo).

2.

App load the machine code, execute, reload and execute if desired...


Since foo does nothing it's just a nop. We are just copying around a ret. It's impossible for anything to ever go awry here excluding buggy code/compiler/etc.


From here one can expand the model to include something that is more useful.

The idea is to expand it enough to make it generally quite useful but not so much one has to include the kitchen sink.

I don't need a heavy version of binderoo, I need a light one. Something that lets me do basic "scripting" so I can modify the application behavior at runtime without recompiling the application.

If you think the example is stupid and useless then

int age() { return 43; }

and nothing changes.

of course you will then think that example is stupid because we could just put it in a text file and read that rather than go through all this trouble.

so then

C calc(C z, C c) { return z*z*z*z*z*z + c*z + c; }

and to get around this without leveraging the D compiler one has to make a full blown parser... even though it's still just relatively basic code like age. It just includes some calculations.

It's no different from foo though. Just basic code.

But we can go further by allowing more information to be passed to and from the function. The function can also be impure such as

void foo() { writeln("I'm IMPURE!"); }

which will work without much work(might require fixing up a few symbols/pointers).


All it is, is delayed compilation. I'm simply wanting to delay the compilation of a piece of code in an app from the main compilation. All the other stuff about the OS and TLS are irrelevant in this context.

One could sort of do this with the object files and just recompile the functions and relink everything. But with a little work this step can be avoided since it's not needed and slower(but more robust).
















Reply via email to