9 comments

[ 3.8 ms ] story [ 36.3 ms ] thread
Thanks for linking to this!

I've been building a toy language by mutating the excellent Lua 5.2 runtime, it's one of my silly side projects aimed at taking me out of my coding comfort zone.

Looking into the LuaJIT code to figure out how they did it has been on my list for a few days now, and this introduction to DynASM provides an nice starting point (LuaJIT itself is based on Lua 5.1 and unlikely to be updated).

Author here -- I'm happy to see the continued interest in this article. I had a lot of fun writing it. DynASM is some great work and doesn't have a lot of documentation, so I was hoping to bridge this gap with an introductory tutoral. And Brainfuck provided just the right problem for writing a short and sweet program that does something interesting.
I made a very fast BF JIT based on this post: https://github.com/rmmh/beefit

I implemented a fair number of optimizations, but there aren't many large benchmarks to really crunch down on. An experimental register allocation pass was awkward (DynASM doesn't really support run-time register selection) and didn't improve performance much.

Very well-written bite-sized intro. I've always wondered how well JITs would do if they had access to a scratchpad RAM (kind of like many GPUs). It's a very different problem than with a register allocator but I feel it'd be more useful for a JIT since it probably knows more about the program structure than whatever LRU variant the cache algorithm is using.
I would have appreciated if you would also had described the downsides of using DynASM compared to other jitters.

E.g. In lightning you don't care too much about the target architecture, as you jit to an abstract RISC machine. So the jit library is portable (not in DynASM), does handle the calling conventions for you (not in DynASM), and does register optimizations automatically (max 6 with lightning), again not in DynASM).

The real trick you seem to like is the syntax abstraction, using | as prefix. But normal C can do the same with __asm(string) and it's easier to use C macros for portable ASM, and LISP jit libraries have long done the same, with the advantage in LISP that the JIT macros in lisp are part of the normal language. No need to deal with restricted C macros.

> I would have appreciated if you would also had described the downsides of using DynASM compared to other jitters.

The article wasn't meant to be a comparative evaluation of DynASM vs other dynamic code generation engines. If I was comparing it against engines like LLVM and Lightning that have their own IR and optimizers I would have also mentioned DynASM's advantages compared to them, such as:

- DynASM is much smaller: DynASM's runtime is something like 2k of object code. Lightning is more like 150k and LLVM is many times bigger than that.

- DynASM is faster: I haven't benchmarked against lightning but LLVM is known not to be very fast at JITting, and just the fact that Lightning is performing its own optimizations means that DynASM is probably faster (note that I'm talking specifically about the speed of doing the code generation itself).

- DynASM gives greater control: you have full control over what instructions are emitted. If you want to play a little fast and loose with calling conventions for speed, you can (I do this in my JIT). If you want to use SSE/AVX/etc. or other very specialized instructions like x86's parsing instructions, you can.

Basically, if you already have your own IR and optimizer and you just want a code generator, and want control over what specific processor instructions you are emitting, then DynASM is a great choice. If you don't have your own optimized IR, or you don't want to write a separate code generator per platform, and you don't mind the size/speed overhead of other engines, then other projects might be a better choice.

> The real trick you seem to like is the syntax abstraction, using | as prefix. But normal C can do the same with __asm(string)

If you think __asm(string) is the same as DynASM, then you missed the point of the article.

__asm(string) is great if you just want to write some static ASM that is fully known at compile time. But how are you going to use __asm(string) to do this?

    void emit_mov(int immediate) {
      // This won't work, you can't substitute a variable
      // into an __asm() expression.
      __asm("mov eax, immediate");
    }
Sure you could pass the immediate to the ASM via a register, but that's not what we're trying to do, we're trying to build code at runtime. __asm() won't help us with that.

> and it's easier to use C macros for portable ASM

Not sure what you mean by this. If you're talking about a BPF-like approach like I discussed in my article, that's not portable and not very readable IMO.