Is it possible to use DMD as a library to compile strings at runtime?

H. S. Teoh hsteoh at quickfur.ath.cx
Sun Feb 2 06:03:01 UTC 2020


On Sun, Feb 02, 2020 at 03:16:46AM +0000, Saurabh Das via Digitalmars-d-learn wrote:
> On Saturday, 1 February 2020 at 20:37:03 UTC, H. S. Teoh wrote:
[...]
> > I've actually done this before in an equation grapher program: the
> > user inputs an equation, the program generates D code to compute the
> > equation, then runs dmd to compile it into a shared library, and
> > opens the shared library and looks up the symbol to execute the
> > compiled code.  Dmd is fast enough that this actually works fairly
> > well. When the input to dmd is small, it's so fast you don't even
> > notice it.
[...]
> This approach seems more tractable at present. Would you have any
> example code lying around for this?
[...]

It's very simple. Let's say you have your code in some string called
'code'. Since dmd nowadays can take stdin as input (specify "-" as input
filename), all you have to do is to assemble your dmd command and use
std.process's awesome API to run it:

	/*
	 * Step 1: Compile the code
	 */
	string code = ...;
	auto cmd = [
		"/usr/bin/dmd", // or wherever your dmd is
		"-O",	// or whatever other flags you need
		"-fPIC", "-shared", // this is important
		"-of" ~ soFilename, // specify output filename
		"-"	// read from stdin
	]

	// This part is a bit involved because we have to spawn the
	// compiler as a child process then write our code string into
	// its stdin.
	// Alternatively, just write your code into a temporary file and
	// pass the filename to dmd, then you can just use
	// std.process.execute() which has a much simpler API.
        import std.process : pipeProcess, Redirect, wait;
        auto pipes = pipeProcess(cmd, Redirect.stdin | Redirect.stdout |
                                      Redirect.stderrToStdout);

	// Send code to compiler
        pipes.stdin.write(code);
        pipes.stdin.flush();
        pipes.stdin.close();

	// Read compiler output (optional)
        auto app = appender!string();
        enum chunkSize = 4096;
        pipes.stdout.byChunk(chunkSize)
                    .copy(app);

        // Wait for compiler to finish
        auto status = wait(pipes.pid);
        auto output = app.data;
        if (status != 0)
            throw new Exception("Failed to compile code:\n" ~ output);

	/*
	 * Step 2: Load the compiled library.
	 */
	// This is for Posix; replace with Windows equivalent if you're
	// on Windows.
	auto libhandle = dlopen(soFilename.toStringz, RTLD_LAZY | RTLD_LOCAL);
	if (libhandle is null) ... /* handle error here */

	// Look up entry point by symbol.
	string entryPoint = ...; /* symbol of library entry point */
	alias FunType = int function(string); // your function signature here
	auto funptr = cast(FunType) dlsym(libhandle, entryPoint.toStringz);

	/*
	 * Step 3: Use the compiled code.
	 */

	// Call the compiled function with whatever arguments.
	int result = funptr("my input");

	...

	// Cleanup once you're done with the library.
	dlclose(libhandle);
	std.file.remove(soFilename);


T

-- 
First Rule of History: History doesn't repeat itself -- historians merely repeat each other.


More information about the Digitalmars-d-learn mailing list