gearleaf

LLVM External Functions

Shamus

It's been a while. Maybe you noticed! I've been working on various odds and ends, and my focus has shifted around a bit. I'll probably talk about this more in the future, but the short version is: Yes, I am still working on Geas but it may be taking a bit of a backseat for a while, and no, I'm not dead, nor have I just stopped doing anything worthwhile in my spare time.

I've been working on a project using LLVM lately. I got it compiling on Windows, I got it linked into my project, and I even got a little LLVM JIT function built and running. It takes in an int32 and returns that number + 1. Hooray.

Problems cropped up with the next step: Making it possible for the VM to call out to my external C functions. In all of the tutorials I could find that discussed doing this, it seems like they declared a function prototype for an external function, and then it just worked. I am here to tell you that, in my environment, developing using Visual C++ on Windows, it does not just work.

After a fair bit of beating my head against various walls and tearing out most of the hair that I have left, I finally figured out how to make it work. I want to record it here so that if anyone else runs into this problem, maybe Google will direct them here and I can help them out.

Here's the important C code...

C++ code
GenericValue gv;
std::vector args;

FunctionType *ft = FunctionType::get(Type::getVoidTy(context), false); 
Function *g = Function::Create(ft, Function::ExternalLinkage, Twine("testCall"), module);
gv = exec->runFunction(g, args);

... and what LLVM tells me was the content of the module ...

LLVM Module output
; ModuleID = 'egg'

declare void @testCall()

... which looks fine. But that exec->runFunction call would crash and burn with the following error message:

LLVM ERROR: Tried to execute an unknown external function: void ()* testCall

It's probably worthwhile to note here that this same thing happened with the version of Kaleidoscope, the toy language from the LLVM tutorials, but only if I tried to import one of the functions defined in the Kaleidoscope code, like putchard() (or so it seemed). I could import say, sin() just fine.

Now, it looks like the reason this works for others is because they have libffi installed, a fancy bit of code that lets you introspect on your compiled code to find other function names at runtime. That sounds great, but let's just say that trying to get it to compile on Windows to work with Visual Studio was not great. So I figured that there must be some way to do this without using ffi, since it seemed to have conditional compile paths for if it wasn't there.

And there is! It's a good thing LLVM is open source — I didn't find the answer anywhere in documentation. I found it by delving through the source code, tracking down the error I was getting, and tracing execution around. Good times.

Anyway, the upshot is that I discovered two important things:

  1. LLVM actually inserts some common library functions (sin, printf, etc.) into your externals for you, no matter what. This is why Kaleidoscope was able to extern in things like sin() but not putchard().
  2. To manually insert a symbol into LLVM's interpreter, you need to use sys::DynamicLibrary::AddSymbol(), and you need to write a wrapper function.

I saw lots of mention of wrapper functions around but I couldn't see any sort of explanation. Basically, you have a function with a signature like

GenericValue lle_X_FUNCTIONNAME(FunctionType *ft, const std::vector &args)

with FUNCTIONNAME replaced appropriately (say, lle_X_testCall). In that function, as you can see, you get a FunctionType descriptor so you can tell which version of the function's been called, and a vector of arguments. You can do whatever you want with these; you're back in C at this point so the world is your oyster!

With your wrapper in place, you add it to the DynamicLibrary symbol table

sys::DynamicLibrary::AddSymbol("lle_X_FUNCTIONNAME", (void *)lle_X_FUNCTIONNAME);

and you're good to go. Make sure you've included "llvm/System/DynamicLibrary.h". When you call a function, it will search this symbol table, and find your function, and then call it. And you, if you're like me, will lean back, let out a long-suffering sigh, and whisper "finally... finally" to the ceiling.

Code is from my project, hasn't been tested in this form, so YMMV.

#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/DerivedTypes.h"
#include "llvm/Constants.h"
#include "llvm/Instructions.h"
#include "llvm/ModuleProvider.h"
#include "llvm/Analysis/Verifier.h"
#include "llvm/ExecutionEngine/ExecutionEngine.h"
#include "llvm/ExecutionEngine/Interpreter.h"
#include "llvm/ExecutionEngine/JIT.h"
#include "llvm/ExecutionEngine/GenericValue.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/System/DynamicLibrary.h"

using namespace llvm;

GenericValue lle_X_testCall(FunctionType *ft, const std::vector &args) {
	Log::debug("testCall wrapper has executed.");

	GenericValue gv;
	gv.IntVal = 0;
	return gv;
}

int main(int argc, char **argv) {
	LLVMContext context;
	Module *module = new Module("egg", context);
	ExecutionEngine *exec = EngineBuilder(module).create();

	FunctionType *ft = FunctionType::get(Type::getVoidTy(context), false); 
	Function *g = Function::Create(ft, Function::ExternalLinkage, Twine("testCall"), module);
	sys::DynamicLibrary::AddSymbol("lle_X_testCall", (void *)lle_X_testCall);

	GenericValue gv;
	std::vector args;

	args.clear();
	gv = exec->runFunction(g, args);
}

Anyway, I've written this just after figuring this all out so I could make sure that something was out there. There are probably a lot of improvements to be made on the code above. Really I'm just hoping that if you're fighting with the same problem, this will point you in the right direction. Good luck!

Battle System Targeting

Shamus

Happy 2010, guys. The end of the year saw me looking for a new job; I won't get into the whole reason because it is a long story fraught with stress and unfortunate circumstances. In any case, I have a new job which is quite cool. Unfortunately, along the way, and with the holidays crashing into my life as they do every year, things were really busy and I didn't get a whole lot of time for Geas.

Every now and then I would pick it up, shake it around, and poke at bits though. In my last post I had just gotten the battle system started. Unfortunately, the internals involving targeting were kind of sketchy and the more complex I tried to make things the worse it got.

But, somewhat like being turned into a newt, eventually it got better. About fifteen minutes ago I finally wrapped up the last of the code monkeying and decided to turn one of my big headaches (the tidying required after killing a monster) into an avenue for new features: I'm going to leave "dead" monsters, just like dead player characters, on the battlefield as fodder for skills and the like. I think this makes a lot of sense seeing as one of my main characters is a necromancer.

Download most recent alpha build of Geas!

No pictures this week, since it doesn't LOOK terribly different from the last time I posted.

Anyway, I mean to pick this up and start posting weekly again but time is a harsh mistress. We'll see. Either way, McGrue, just because I suck doesn't give you a free pass! September 21st, really? You were calling me out two weeks ago, going on about how you had so much work done. Where are the posts?

Edit: Okay, sorry, looks like Grue decided that his last post wasn't good enough for his front page. (Because his front page is sanctified in some way or something, I don't know. I guess maybe he's ashamed?) He's actually only a day behind schedule now.

Spam

Shamus

Just noticed that my comments got hit by some spambots two days ago. I'll have to implement a captcha on the commenting thing when I've got some time, I guess. I was kind of hoping to avoid it, but I guess not! Ah well. Really, deep down in my heart of hearts I knew this would happen.

Fighting It

Shamus

New version of Geas with all the changes I've made in the last few months since I've posted properly, including a battle system that sort of "works" but is incredibly simplistic and not especially fun.

Download this week's alpha release of Geas!


Discussion

Quite a bit has changed but it's not immediately obvious. Hit F1 to start a fight with some slimes. Esc exits the battle immediately, or you can just defeat the slimes. Only Attack is implemented, and anything else will just waste your turn.

I've also added a console that is currently not terribly useful but will be eventually, in theory. Since it was written to be included in KISSKISS it should be relatively simply to drop into any project, which means that we now have a (rather "feature light") console for VergeLua now. You can open the console by hitting Tab.

The other somewhat-interesting thing is that, yet again, I am using the newest version of the Verge executable, which is more up to date than the last official V3 release. This exe allows a couple of interesting things that Overkill has added and I haven't tried out yet, like allowing you to pass functions directly to the Hook* functions, instead of just string function names. It also allows you to use (some) control character escape sequences like \n and \f in VergeC, so font subsets are available to the VergeC-using masses again.

If you want to get just the newly compiled verge.exe, you can get it right here. If you do try it out with your project, please let me know how it goes, especially if you encounter any bugs.

New Site

Shamus

The site is switched over. There are possibly some lingering errors but things look all right. Contrary to what I said earlier, the RSS feeds should redirect such that you don't have to worry too much about changing your feed subscriptions.

This change happened because I finally decided that Drupal, which I was using before, was just too large and required me to worry about it too much. It also took up way more space than it needed to for what I was using it for. So, being a programming nerd, I decided to write my own... sort of.

I kept hearing about Django and, while I'm not the biggest fan of Python ever, the little bits I'd seen seemed quite nice. I decided that both as a professional development move and also to clean up my cluttered, bloated site, I would rewrite it to use Django instead. This is "sort of" writing it myself because Django is more of a framework to make it really easy to write your own stuff, rather than a full blown CMS that will take care of your kitchen sink for you, like Drupal.

Anyway I've been working on this off and on for the last little while and now it's done. While I was at it I started adding other bits and pieces into the site to make sure it wasn't hard. Ironically, now that I don't have a CMS to hold my hand all the way through adding a new page, it's kind of faster for me to do so.

Anyway, now that this change is out of the way, I need to stop procrastinating on getting back on board with Gruedorf, and posting other miscellaneous things that I have to talk about.

Older »