Re: compiling the instructions of a simple vm into a function

2010-06-19 Thread Nicolas Oury
There is two way to make a domain-specific language with a clojure back-end
:
- if you want the language to be an extension of Clojure, still to be used
in a REPL or by clojure source
   - you write macros. That's what I call embedded.  There is a lot of
litterature on Embedded Domain Specific Language in functional languages. (A
*lot* in Haskell, for example)
In this situation, eval is most of the time evil.

- you want to read instructions that are very far from Clojure in a file and
execute them. (Your case, not by choice but because of the ICFP  specs).
Then, eval is necessary. But you have to call it only once per compiled
function, ideally, and never while executing the code (for performance).
In your situation, I would write
(defn compile-instruction [instructions] ...)

Then, when reading the instructions
(let [compiled-function (eval (compile-instruction (read-file...)))]
 ...

Then, eval is called once, just after reading and compiling. And
compiled-function is a real Clojure function.

Have fun,

Nicolas.







On Sat, Jun 19, 2010 at 3:39 AM, Eugen Dück eu...@dueck.org wrote:

 Thanks Nicolas,

 your first variant resembles the generated code much closer than my
 initial approach, which is great. I need the eval though, to be able
 to pass in non literals. In my real program I'm reading the
 instructions from a binary file. So if I want to be able to do
 something like this:

 (def three-instructions '([+ 2 3] [- 0 1] [+ 1 0]))
 (def compiled (compile-instructions three-instructions))

 The macro would have to look like this:

 (defmacro compile-instructions
  [instructions]
  (let [memory (gensym memory-)]
   `(fn [~memory]
  ~@(map (fn [[op m1 m2]]
 `(aset ~memory ~m1 (~op (aget ~memory ~m1) (aget
 ~memory ~m2
(eval instructions)

 But I like your suggestion to turn it into a function even better:

 (defn compile-instructions
  [instructions]
  (let [memory (gensym memory-)]
(eval
 `(fn [~memory]
   ~@(map (fn [[op m1 m2]]
`(aset ~memory ~m1 (~op (aget ~memory ~m1) (aget ~memory
 ~m2
   instructions)

 And

 (def compiled (compile-instructions three-instructions)))

 just works as before. So I guess macros don't add any value here.

  eval is evil, but eval is not evil is a compiler (you have to evaluate
 the
  code you read).
  However eval is evil again in an embedded compiler, when you use macro
 to
  extend Clojure.

 What do you mean by embedded compiler?

 Eugen

 --
 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.comclojure%2bunsubscr...@googlegroups.com
 For more options, visit this group at
 http://groups.google.com/group/clojure?hl=en


-- 
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

compiling the instructions of a simple vm into a function

2010-06-18 Thread Eugen Dück
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


Re: compiling the instructions of a simple vm into a function

2010-06-18 Thread Nicolas Oury
First a simple version, equivalent to yours but more readable.


(defmacro compile-instructions
 [instructions]
 (let [memory (gensym memory-)]
   `(fn [~memory]
  ~@(map (fn [[op m1 m2]]
 `(aset ~memory ~m1 (~op (aget ~memory ~m1) (aget ~memory
~m2
   instructions


(def memory (into-array Integer/TYPE (range 4)))
(def compiled (compile-instructions ([+ 2 3] [- 0 1] [+ 1 0])))


And another one to workaround the JVM 64K feature. I split the generated
code into blocks of 500 instructions.

(def length-threshold 500)

(defmacro protected-fn [binding-form  body]
   (if (= (count body) length-threshold)
  `(fn ~binding-form ~...@body)
   (let [small-funs  (partition length-threshold  length-threshold ()
body)
 let-block (map (fn [bod] (let [name (gensym let-)] [name
 `(fn [...@binding-form] ~@ bod)] )) small-funs)]
  `(let [~@(apply concat let-block)]
 (protected-fn [...@binding-form] ~@(map (fn [[name code]] `(~name
~...@binding-form)) let-block))

(defmacro compile-instructions-2
 [instructions]
 (let [memory (gensym memory-)]
   `(protected-fn [~memory]
  ~@(map (fn [[op m1 m2]]
 `(aset ~memory ~m1 (~op (aget ~memory ~m1) (aget ~memory
~m2
   instructions

(def compiled-2 (eval `(compile-instructions-2 ~(take 1150 (cycle '([+ 2 3]
[- 0 1] [+ 1 0]))

This last line is quite ugly. In fact, if you want to use
compile-instruction programatically, maybe it should not be a macro but a
function.
Then using a code-generating function in a macro is easy.

Plus, it is easy to eval the generated code.
But, be careful, you have to eval it once, just after the compiling phase.
Then, you get a real function.
eval is evil, but eval is not evil is a compiler (you have to evaluate the
code you read).
However eval is evil again in an embedded compiler, when you use macro to
extend Clojure.

-- 
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

Re: compiling the instructions of a simple vm into a function

2010-06-18 Thread Eugen Dück
Thanks Nicolas,

your first variant resembles the generated code much closer than my
initial approach, which is great. I need the eval though, to be able
to pass in non literals. In my real program I'm reading the
instructions from a binary file. So if I want to be able to do
something like this:

(def three-instructions '([+ 2 3] [- 0 1] [+ 1 0]))
(def compiled (compile-instructions three-instructions))

The macro would have to look like this:

(defmacro compile-instructions
 [instructions]
 (let [memory (gensym memory-)]
   `(fn [~memory]
  ~@(map (fn [[op m1 m2]]
 `(aset ~memory ~m1 (~op (aget ~memory ~m1) (aget
~memory ~m2
   (eval instructions)

But I like your suggestion to turn it into a function even better:

(defn compile-instructions
 [instructions]
 (let [memory (gensym memory-)]
   (eval
`(fn [~memory]
   ~@(map (fn [[op m1 m2]]
`(aset ~memory ~m1 (~op (aget ~memory ~m1) (aget ~memory ~m2
  instructions)

And

(def compiled (compile-instructions three-instructions)))

just works as before. So I guess macros don't add any value here.

 eval is evil, but eval is not evil is a compiler (you have to evaluate the
 code you read).
 However eval is evil again in an embedded compiler, when you use macro to
 extend Clojure.

What do you mean by embedded compiler?

Eugen

-- 
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