imageworks/OpenShadingLanguage — pull request #312 by lgritz (2013-12-11 21:14:55+00:00)
Title: Refactor: Create a LLVM_Util class

Create a LLVM_Util class (in llvm_util.{h,cpp}) that handles ALL direct calls to LLVM APIs, and has NO special knowledge of OSL or its internals.

BackendLLVM (which is intimately tied to OSL) now is built on top of LLVM_Util. Neither BackendLLVM nor the rest of liboslexec needs to directly touch LLVM API calls and don't even need to include a single llvm header.

LLVM_Util has no dependencies on anything else in OSL (though does want TypeDesc and ustring), and could be used as a standalone library from other apps, to vastly simplify using LLVM to dynamically JIT code and then execute it. An example of this can be found in llvmutil_test.cpp.

I'll reproduce the guts of llvmutil_test.cpp here so you can see how easy it is:

#include
#include
#include "llvm_util.h"

// This demonstrates the use of LLVM_Util to generate the following
// function on the fly, JIT it, and call it:
// int myadd (int arg1, int arg2)
// {
// return arg1 + arg2;
// }

int main (int argc, char *argv[])
{
// Setup
OSL::pvt::LLVM_Util ll;

// Make a function with prototype: int myadd (int arg1, int arg2)
// and make it the current function.
llvm::Function *func = ll.make_function ("myadd", // name
false, // fastcall
ll.type_int(), // return
ll.type_int(), // arg1
ll.type_int()); // arg2
ll.current_function (func);

// Generate the ops for this function: return arg1 + arg2
llvm::Value *arg1 = ll.current_function_arg (0);
llvm::Value *arg2 = ll.current_function_arg (1);
llvm::Value *sum = ll.op_add (arg1, arg2);
ll.op_return (sum);

// Optimize it
ll.setup_optimization_passes (0);
ll.do_optimize ();

// Print the optimized bitcode
std::cout << "Generated the following bitcode:\n"
<< ll.bitcode_string(func) << "\n";

// Ask for a callable function (will JIT on demand)
typedef int (*IntFuncOfTwoInts)(int,int);
IntFuncOfTwoInts myadd = (IntFuncOfTwoInts) ll.getPointerToFunction (func);

// Call it:
int result = myadd (13, 29);
std::cout << "The result is " << result << "\n";

return 0;
}

Running this program produces the following output:

Generated the following bitcode:

define i32 @myadd(i32, i32) {
%3 = add i32 %1, %0
ret i32 %3
}

The result is 42

So, I did this mainly to clean up OSL's internals and cleanly separate the parts that directly interface LLVM from the parts that are intimately tied to other OSL internals.

But what we have in the end is a little library that would be a great start for any app that wants to do some LLVM-based JIT of dynamic code, without having to get its hands very dirty at all.

I apologize for the ugliness of the diffs. It's a major refactor of some hairy code, and a TON of stuff moved between files, between classes, had things renamed, or other subtle changes. But the overall scheme of what we're doing is the same, and probably the sequence of actual low-level LLVM API calls is the same as it ever was, but the logic on our side of the fence is vastly cleaner.