Recently, I implemented last year's ICFP problem. Part of it is
writing a VM that consists of memory slots, some registers and input
and input ports. It takes a list of instructions, works them off one
after the other, reading from input or memory, calculating something
and the changing registers or writing to memory or output.

My implementation worked, but I wanted to make it faster, and thought
compiling the instructions into a function using a macro could help.
Indeed, my vm got 5 times faster, using arrays and an imperative style
(generated by the compiler). But the macro I wrote is butt-ugly, so I
thought I might get some advice on this list...

To keep it simple I'll use an even simpler VM that only has memory and
a simple instruction set.

Example of an instruction:
[+ 2 3]
That means: read memory at address 2, read memory at address 3 and
write the sum of both values back to address 2.

This is how I use the compiler macro, feeding in 3 instructions:

user=> (def compiled (compile-instructions '([+ 2 3] [- 0 1] [+ 1
0])))
#'user/compiled
user=> (fn? compiled)
true

Now let's allocate an int array for the memory, using a size of 4
slots:

user=> (def memory (into-array Integer/TYPE (range 4)))
#'user/memory
user=> (seq memory)
(0 1 2 3)

And now let's run the instructions on that memory:
user=> (compiled memory)
0
user=> (seq memory)
(-1 0 5 3)

And finally, here comes the code for the macro - but be warned, I'm
lacking formal macro education, which is why I'd appreciate any good
comment:

(defmacro compile-instructions
  [instructions]
  (let [memory (gensym "memory-")]
    (apply list 'fn [memory]
           (map (fn [[op m1 m2]]
                  (list aset memory m1 (list op (list aget memory m1) (list aget
memory m2))))
                (eval instructions)))))

I'm not using the backtick as it seemed simpler for what I was trying
to do. I don't know if eval is evil and there's a better alternative?

I'm actually not so sure whether it is good to roll out all
instructions for performance. It might makes things actually slower,
as the JIT can compile less, apart from the fact that a Java method is
limited to 64k in size, so the number of instructions I can compile is
limited:

user=> (def compiled (compile-instructions (take 1150 (cycle '([+ 2 3]
[- 0 1] [+ 1 0])))))
java.lang.ClassFormatError: Invalid method Code length 65581 in class
file user$compiled__97 (NO_SOURCE_FILE:25)

It blows up at just above 1k instructions.

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to