Interesting, I'm read through the tutorial, and took the source, while tweaking it to conform to RPython, then feeding it into PyPy's translator.
What strikes me is that:
- with PyPy everything is implemented natively, i.e without resorting to foreign abstractions: instead of def CodeGen, I'd write def eval (or whatever my native AST evaluation wants), and instead of g_llvm_builder.fadd(left, right), I'd just do the native left+right. This makes it conceptually less of a mind split, and writing stuff like the BF interpreter is just straightforward [0].
- with LLVM, the concepts are nicely abstracted, and the API reasonably pythonic. I know I'm using LLVM but I don't feel like I'm hitting some thin C wrapper. Those concepts are ported and reinvested into other languages with ease: now that I got them by reading the python tutorial, I'm confident I can find my way in another project on another, less known to me language. Also, due to this abstracted common ground, access to functions coming from outside, (possibly other languages) is made easy. With PyPy I'm stuck to what's made available to RPython land.
Your observations are pretty accurate. I'm fortunate enough to work with the maintainer of llvmpy and he's done some great work in making llvmpy pretty Pythonic. Should also check llvm-cbuilder the EDSL for building up higher level LLVM constructs: