ANNOUNCE: Harpy -- run-time code generation library
Hi everybody, we're pleased to announce the first release of Harpy. Harpy is a library for run-time code generation of x86 machine code. It provides not only a low level interface to code generation operations, but also a convenient domain specific language for machine code fragments, a collection of code generation combinators and a disassembler. Harpy homepage: http://uebb.cs.tu-berlin.de/harpy/ Kind regards, Martin Grabmüller and Dirk Kleeblatt
Very nice! Regarding the use of labels, did you consider using "circular programming with recursive do" to define and reference labels like in: Russell O'Connor, Assembly: Circular Programming with Recursive do http://haskell.org/sitewiki/images/1/14/TMR-Issue6.pdf The advantage of that technique is that you don't have to create a bunch of labels at the start of your assembly-program. You just define them at the place where you need them. Using recursive do notation you can even reference (jmp) to labels _before_ you define them! Thanks, Bas van Dijk On 5/11/07, Dirk Kleeblatt <klee@cs.tu-berlin.de> wrote:
Hi everybody,
we're pleased to announce the first release of Harpy.
Harpy is a library for run-time code generation of x86 machine code. It provides not only a low level interface to code generation operations, but also a convenient domain specific language for machine code fragments, a collection of code generation combinators and a disassembler.
Harpy homepage: http://uebb.cs.tu-berlin.de/harpy/
Kind regards, Martin Grabmüller and Dirk Kleeblatt
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
Bas van Dijk wrote:
Regarding the use of labels, did you consider using "circular programming with recursive do" to define and reference labels like in:
Russell O'Connor, Assembly: Circular Programming with Recursive do http://haskell.org/sitewiki/images/1/14/TMR-Issue6.pdf
Yes, we considered this technique, but we came to the conclusion that it is not applicable in our setting: we are writing directly to malloc'ed memory, so our operations are strict in the label parameter. Thus, the fixpoint of the recursive mdo would be bottom. However, these were (up to now) only theoretical thoughts, maybe our hypothesis of strict operations can be weakened. Kind regards, Dirk
Dirk Kleeblatt wrote:
Bas van Dijk wrote:
Regarding the use of labels, did you consider using "circular programming with recursive do" to define and reference labels like in:
Russell O'Connor, Assembly: Circular Programming with Recursive do http://haskell.org/sitewiki/images/1/14/TMR-Issue6.pdf
Yes, we considered this technique, but we came to the conclusion that it is not applicable in our setting: we are writing directly to malloc'ed memory, so our operations are strict in the label parameter. Thus, the fixpoint of the recursive mdo would be bottom.
However, these were (up to now) only theoretical thoughts, maybe our hypothesis of strict operations can be weakened.
Note that even currently, your operations cannot be strict in the address a label refers to because this may be determined later than the first use of the label. In other words, your example code fac = do loopTest <- newLabel loopStart <- newLabel ensureBufferSize 160 push ecx mov ecx (Disp 8, esp) mov eax (1 :: Word32) (1) jmp loopTest loopStart @@ mul ecx sub ecx (1 :: Word32) (2) loopTest @@ cmp ecx (0 :: Word32) jne loopStart pop ecx ret already shows that the function jmp that generates a jmp-instruction may not be strict in the position it jumps to as the address behind loopTest is only known later at line (2). Also, the explicit declaration of labels has an inherent safety problem. Namely, nobody prevents you from using a label twice, like for example in loopStart @@ mul exc ... loopStart @@ cmp ecx (0 :: Word32) Declaring a label (via f.i.) loopStart <- mul exc at it's instruction doesn't have this problem. Furthermore, having to call 'ensureBufferSize 160' is very strange for this is data that can be calculated automatically. In short, it looks like the "CodeGen e s a"-monad eagerly writes code without doing much book-keeping. I think that changing this enables recursive do. I also think that having liftIO in the CodeGen-monad is plain wrong. I mean, CodeGen is a monad that generates code without any execution taking place. The execution part is already handled by runCodeGen. Having liftIO means that arbitrary Haskell programs can be intertwined with assembly generation and I doubt that you want that. Regards, apfelmus
apfelmus schrieb:
Also, the explicit declaration of labels has an inherent safety problem. Namely, nobody prevents you from using a label twice, like for example in
loopStart @@ mul exc ... loopStart @@ cmp ecx (0 :: Word32)
Your are right. In the next version, Harpy *will* prevent you from defining a label twice. It was just an oversight on my part not to check for duplicate definitions. Thanks for spotting this problem. Martin
apfelmus wrote:
Note that even currently, your operations cannot be strict in the address a label refers to because this may be determined later than the first use of the label. In other words, your example code
fac = do [...] (1) jmp loopTest [...] (2) loopTest @@ cmp ecx (0 :: Word32) [...] already shows that the function jmp that generates a jmp-instruction may not be strict in the position it jumps to as the address behind loopTest is only known later at line (2).
When generating code for (1), the label loopTest is used as an index into a map, to see whether there's a code position associated with it. If it is, the code position is used to compute the jump offset for the jmp instruction, if not (as in this example), a dummy value is placed in the code buffer, and Harpy remembers this position to be patched later on. At (2), the label is defined, and this leads to patching all instructions that have been emitted before this definition. So, yes, the code position is only used after the definition of the label. But the "look up in a map"-part makes the jmp operation strict in the label parameter. We could omit the map, and just remember where to patch the code, but then we'd need to call explicitly some function after code generation that does the patching. We had implemented this, but found the current solution easier to use, since backpatching is completely automatic and hidden from the user. However, this is just a description of the current implementation, not an argument that there's no better implementation. Probably there is, maybe using the binary package.
Also, the explicit declaration of labels has an inherent safety problem. [...] Declaring a label (via f.i.)
loopStart <- mul exc
at it's instruction doesn't have this problem.
This looks quite elegant, I'll think about it...
Furthermore, having to call 'ensureBufferSize 160' is very strange for this is data that can be calculated automatically.
As I wrote at haskell-cafe, we require this only for performance reasons, to keep buffer overflow checks as seldom as possible. But there might be better ways to do this.
I also think that having liftIO in the CodeGen-monad is plain wrong. I mean, CodeGen is a monad that generates code without any execution taking place. The execution part is already handled by runCodeGen. Having liftIO means that arbitrary Haskell programs can be intertwined with assembly generation and I doubt that you want that.
Feel free to doubt, but this is exactly what we want. :-) Also, note that runCodeGen runs the code _generation_, executing the generated code is done _within_ the CodeGen monad via the functions generated by callDecl (or the predefined functions in the Harpy.Call module). This is even more intertwined, but intentional. Of course, again a different design is possible, making runCodeGen return a binary code object, that can be called from the IO monad. But then, the user has to care about releasing code buffers, and not to have unevaluated closures having code pointers to already released run-time generated code. Kind regards, Dirk
Dirk Kleeblatt wrote:
apfelmus wrote:
Note that even currently, your operations cannot be strict in the address a label refers to because this may be determined later than the first use of the label. In other words, your example code
fac = do [...] (1) jmp loopTest [...] (2) loopTest @@ cmp ecx (0 :: Word32) [...] already shows that the function jmp that generates a jmp-instruction may not be strict in the position it jumps to as the address behind loopTest is only known later at line (2).
When generating code for (1), the label loopTest is used as an index into a map, to see whether there's a code position associated with it. If it is, the code position is used to compute the jump offset for the jmp instruction, if not (as in this example), a dummy value is placed in the code buffer, and Harpy remembers this position to be patched later on. At (2), the label is defined, and this leads to patching all instructions that have been emitted before this definition.
So, yes, the code position is only used after the definition of the label. But the "look up in a map"-part makes the jmp operation strict in the label parameter.
We could omit the map, and just remember where to patch the code, but then we'd need to call explicitly some function after code generation that does the patching. We had implemented this, but found the current solution easier to use, since backpatching is completely automatic and hidden from the user.
However, this is just a description of the current implementation, not an argument that there's no better implementation. Probably there is, maybe using the binary package.
Also, the explicit declaration of labels has an inherent safety problem. [...] Declaring a label (via f.i.)
loopStart <- mul exc
at it's instruction doesn't have this problem.
This looks quite elegant, I'll think about it...
If this is what I think it is (tying the knot), then essentially the thunk becomes the field reference, backpatching is thunk update, and the Haskell environment is the Map. It should be possible to limit the laziness just to the "fields", but I'm not sure if it's possible to "limit the strictness".
Furthermore, having to call 'ensureBufferSize 160' is very strange for this is data that can be calculated automatically.
As I wrote at haskell-cafe, we require this only for performance reasons, to keep buffer overflow checks as seldom as possible. But there might be better ways to do this.
I also think that having liftIO in the CodeGen-monad is plain wrong. I mean, CodeGen is a monad that generates code without any execution taking place. The execution part is already handled by runCodeGen. Having liftIO means that arbitrary Haskell programs can be intertwined with assembly generation and I doubt that you want that.
Feel free to doubt, but this is exactly what we want. :-)
Also, note that runCodeGen runs the code _generation_, executing the generated code is done _within_ the CodeGen monad via the functions generated by callDecl (or the predefined functions in the Harpy.Call module). This is even more intertwined, but intentional.
Of course, again a different design is possible, making runCodeGen return a binary code object, that can be called from the IO monad. But then, the user has to care about releasing code buffers, and not to have unevaluated closures having code pointers to already released run-time generated code.
Having this as an option would be very nice I suspect. I'd like it.
Any plans for ARM/Thumb machine code generation? On 5/11/07, Dirk Kleeblatt <klee@cs.tu-berlin.de> wrote:
Hi everybody,
we're pleased to announce the first release of Harpy.
Harpy is a library for run-time code generation of x86 machine code. It provides not only a low level interface to code generation operations, but also a convenient domain specific language for machine code fragments, a collection of code generation combinators and a disassembler.
Harpy homepage: http://uebb.cs.tu-berlin.de/harpy/
Kind regards, Martin Grabmüller and Dirk Kleeblatt
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
Anatoly Yakovenko schrieb:
Any plans for ARM/Thumb machine code generation?
Currently: no. It would be possible to support other architectures by adding appropriate backend modules, but currently, the library is not really prepared for that. One reason is that we only develop for x86-machines (and don't own any ARM machines). The other is that currently more than 90% of the code are machine-dependant. For the future, we'd like to be able to support more architectures, but it's not very high on our priority list. Maybe interest of others could change that... Best regards, Martin
* Martin Grabmueller:
For the future, we'd like to be able to support more architectures, but it's not very high on our priority list. Maybe interest of others could change that...
LLVM as a target could be interesting as well, and would avoid the need to write tons of optimizers.
Florian Weimer schrieb:
* Martin Grabmueller:
For the future, we'd like to be able to support more architectures, but it's not very high on our priority list. Maybe interest of others could change that...
LLVM as a target could be interesting as well, and would avoid the need to write tons of optimizers.
[Sorry for the delay.] LLVM is indeed interesting, but has several drawbacks: - written in C++ (we don't have experience in interfacing Haskell and C++) - has been used with imperative languages yet, no experience available on using it for FP. - rather large system - and finally, I have to admit: a bit of Not Invented Here One of our goals was indeed to have a Haskell-only code generator, which has the advantage that it is easier to install, use and distribute. Regards, Martin
On Jun 12, 2007, at 1:52 AM, Martin Grabmueller wrote:
LLVM is indeed interesting, but has several drawbacks:
- written in C++ (we don't have experience in interfacing Haskell and C++)
You'd have to write a C wrapper, or generate the LLVM intermediate language directly from Haskell.
- has been used with imperative languages yet, no experience available on using it for FP.
Very true. On the other hand, the LLVM team would probably value the feedback. Having LLVM work with FP languages would be a benefit to the FP community, too.
- rather large system
Well, so is Haskell. :-) Why is this a drawback?
- and finally, I have to admit: a bit of Not Invented Here
One of our goals was indeed to have a Haskell-only code generator, which has the advantage that it is easier to install, use and distribute.
Targeting LLVM would make porting Haskell to a new platform much easier. Maybe it would make sense to "port" ghc to emit code that way, in addition to C--? Deborah
Given your reservation regarding LLVM, you may be interested in vmgen, developed and used as a part of gforth. It is also claimed that a JVM built with vmgen had performance comparable to state of the art JITs. If I remember the author of both gforth (including vmgen) and the experimantal JVM, is Anton Ertl. Personally I have never used it, and do not know how good it is, so I am not trying to push it. However, what little I do know about it seems to address your concerns with LLVM:
LLVM is indeed interesting, but has several drawbacks:
- written in C++ (we don't have experience in interfacing Haskell and C++)
vmgen is C AFAIK
- has been used with imperative languages yet, no experience available on using it for FP.
Forth is not a functional language by a long shot, but forth code does tend to have a functional flavour. (if you squint hard enough :) )
- rather large system
The whole of gforth is not that big, and vmgen is just a part of that.
- and finally, I have to admit: a bit of Not Invented Here
One of our goals was indeed to have a Haskell-only code generator, which has the advantage that it is easier to install, use and distribute.
Yes, there is that. Anyway as I said, I do not know how much mileage you could get out of it, but it seemed to be worth mentioning, given what you said about LLVM. cheers Daniel
Regards, Martin
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
participants (9)
-
Anatoly Yakovenko -
apfelmus -
Bas van Dijk -
Daniel Mahler -
Deborah Goldsmith -
Derek Elkins -
Dirk Kleeblatt -
Florian Weimer -
Martin Grabmueller