Wednesday, January 31, 2007

ARM port day 2

I worked on the ARM assembler today; it now supports all ARM condition codes (almost every instruction is conditional), as well as "addressing mode 1" instructions (arithmetic), multiplication, and "addressing mode 2" instructions (single value load/store). Multiple load/stores and a few other rarely-used instructions are still missing:

The last time I was porting Factor to a new platform, the compiler worked differently; it would write generated machine code directly to memory, which made the assembler impossible to unit test. Now, the assembler just appends machine code to the array being built by an enclosing call to make, making it very easy to unit test. I did test-driven development today; I used the GNU assembler to assemble bits of code, add a unit test asserting that Factor generated the same machine code for the given input, and code away until the tests passed:

ARM assembly is quite interesting, with lots of operand and addressing modes. The instruction encoding also involves lots of bit fields, so I had to come up with a new abstraction to put together integers with shifts and ors.

The PowerPC instruction format is not as complicated as ARM, so the PowerPC assembler used to just have words which would shift and or values by hand:

In the above word, we are building a bit field where some values come from the stack, some are literal, and the last one is obtained by applying the register word to a stack value. Writing this out by hand would be a pain in any language. Fortunately Factor makes it very easy to build mini-DSLs like this.

Here are some ARM assembly instructions, with GNU and Factor syntax side by side:

As you can see, it looks a bit funny, but remember that the whole point of this exercise is to write an assembler library which is called dynamically by the compiler to emit code; this is not for users who want to write applications in assembly.

Another thing I did was sort out how to flush the instruction cache on ARM (thanks to Mackenzie Straight aka eiz in #concatenative), so I provided a proper implementation of the flush_icache function, which is called by the Factor VM to flush the instruction cache when a new compiled code block is added. On PowerPC, this is implemented in assembly, but on ARM only the kernel is permitted to flush the instruction cache so it has to go through a system call. Also, this system call is not actually exported by glibc. However it is easy enough to call it directly using macros from asm/unistd.h:

Tomorrow, I'll do some more work on the assembler and see what other instructions are needed by the backend. I'll also start the backend proper and have Factor compiling simple (and perhaps complex) words. And of course I'll document what I did in a blog entry, just like I did today and yesterday.