Re: Register VM WIP

2012-05-16 Thread Andy Wingo
On Wed 16 May 2012 02:39, Noah Lavine noah.b.lav...@gmail.com writes:

 Do you mean that the register pool will grow and shrink for each
 function call? Is that why the stack frames can be fixed-size?

The register pool is the set of locals on the stack.  Registers for one
function are stored in the stack frame.

Andy
-- 
http://wingolog.org/



Re: Register VM WIP

2012-05-16 Thread Andy Wingo
On Wed 16 May 2012 06:23, Mark H Weaver m...@netris.org writes:

 It's surprising to me for another reason: in order to make the
 instructions reasonably compact, only a limited number of bits are
 available in each instruction to specify which registers to use.

It turns out that being reasonably compact isn't terribly important --
more important is the number of opcodes it takes to get something done,
which translates to the number of dispatches.  Have you seen the direct
threading VM implementation strategy?  In that case the opcode is not
an index into a jump table, it's a word that encodes the pointer
directly.  So it's a word wide, just for the opcode.  That's what
JavaScriptCore does, for example.  The opcode is a word wide, and each
operand is a word as well.

The design of the wip-rtl VM is to allow 16M registers (24-bit
addressing).  However many instructions can just address 2**8 registers
(8-bit addressing) or 2**12 registers (12-bit addressing).  We will
reserve registers 253 to 255 as temporaries.  If you have so many
registers as to need more than that, then you have to shuffle operands
down into the temporaries.  That's the plan, anyway.

Cheers,

Andy
-- 
http://wingolog.org/



Re: Register VM WIP

2012-05-16 Thread David Kastrup
Mark H Weaver m...@netris.org writes:

 I certainly agree that we should have a generous number of registers,
 but I suspect that the sweet spot for a VM is 256, because it enables
 more compact dispatching code in the VM, and yet is more than enough to
 allow a decent register allocator to generate good code.

 That's my educated guess anyway.  Feel free to prove me wrong :)

The counterproof will usually be done by benchmarking, and will even
differ between different processors sharing the same instruction set.

I see two ways out:
a) pick the register size individually for each function, as small as
possible without spillage.  Which makes the whole indistinguishable from
a stack-based VM.
b) don't generate the final bytecode until the code is actually being run.

That means that _if_ code is precompiled, it will be precompiled into
either stack-based VM or some other representation better suited to
compile into code for a certain amount of registers.  Of course, the
threshold to picking actual registers of the available processor and
compiling native code is then not all too large.

-- 
David Kastrup




Re: Register VM WIP

2012-05-16 Thread Noah Lavine
Hi Mark,

You are thinking along very similar lines to how I used to think. But
I have a different way to think about it that might make it seem
better.

In our current VM, we have two stacks: the local-variable stack, which
has frames for different function calls and is generally what you'd
think of as a stack, and the temporary-variable stack, which is
literally a stack in the sense that you only operate on the top of it.
The temporary-variable stack makes us do a lot of unnecessary work,
because we have to load things from the local-variable stack to the
temporary-variable stack.

I think what Andy is proposing to do is to get rid of the
temporary-variable stack and operate directly on the local-variable
stack. We shouldn't think of these registers as being like machine
registers, and in fact maybe registers is not a good name for these
objects. They are really just variables in the topmost stack frame.
This should only reduce memory usage, because the local-variable stack
stays the same and the temporary-variable stack goes away (some
temporaries might move to the local-variable stack, but it can't be
more than were on the temporary-variable stack, so that's still a
win).

The machine I was initially thinking of, and I imagine you were too,
is different. I had imagined a machine where the number of registers
was limited, ideally to the length of a processor cache line, and was
separate from the local-variables stack. In such a machine, the
registers are used as a cache for the local variables, and you get to
deal with all the register allocation problems that a standard
compiler would. That would accomplish the goal of keeping more things
in cache.

The registers as cache idea may result in faster code than the
directly addressing local variables idea, but it's also more
complicated to implement. So it makes sense to me that we would try
directly addressing local variables first, and maybe later move to
using a fixed-size cache of registers. It also occurs to me that the
RTL intermediate language, which is really just a language for
directly addressing an arbitrary number of local variables, is a
standard compiler intermediate language. So it might be useful to have
that around anyway, because we could more easily feed its output into,
for instance, GCC.

Andy, is this an accurate description of the register VM? And Mark and
everyone else, does it seem better when you look at it this way?

Noah

On Wed, May 16, 2012 at 9:44 AM, Mark H Weaver m...@netris.org wrote:
 Hi Andy!

 Andy Wingo wi...@pobox.com writes:
 On Wed 16 May 2012 06:23, Mark H Weaver m...@netris.org writes:

 It's surprising to me for another reason: in order to make the
 instructions reasonably compact, only a limited number of bits are
 available in each instruction to specify which registers to use.

 It turns out that being reasonably compact isn't terribly important --
 more important is the number of opcodes it takes to get something done,
 which translates to the number of dispatches.  Have you seen the direct
 threading VM implementation strategy?  In that case the opcode is not
 an index into a jump table, it's a word that encodes the pointer
 directly.  So it's a word wide, just for the opcode.  That's what
 JavaScriptCore does, for example.  The opcode is a word wide, and each
 operand is a word as well.

 The design of the wip-rtl VM is to allow 16M registers (24-bit
 addressing).  However many instructions can just address 2**8 registers
 (8-bit addressing) or 2**12 registers (12-bit addressing).  We will
 reserve registers 253 to 255 as temporaries.  If you have so many
 registers as to need more than that, then you have to shuffle operands
 down into the temporaries.  That's the plan, anyway.

 I'm very concerned about this design, for the same reason that I was
 concerned about NaN-boxing on 32-bit platforms.  Efficient use of memory
 is extremely important on modern architectures, because of the vast (and
 increasing) disparity between cache speed and RAM speed.  If you can fit
 the active set into the cache, that often makes a profound difference in
 the speed of a program.

 I agree that with VMs, minimizing the number of dispatches is crucial,
 but beyond a certain point, having more registers is not going to save
 you any dispatches, because they will almost never be used anyway.
 2^12 registers is _far_ beyond that point.

 As I wrote before concerning NaN-boxing, I suspect that the reason these
 memory-bloated designs are so successful in the JavaScript world is that
 they are specifically optimized for use within a modern web browser,
 which is already a memory hog anyway.  Therefore, if the language
 implementation wastes yet more memory it will hardly be noticed.

 If I were designing this VM, I'd work hard to allow as many loops as
 possible to run completely in the cache.  That means that three things
 have to fit into the cache together: the VM itself, the user loop code,
 and the user data.  IMO, the sum 

Re: Register VM WIP

2012-05-16 Thread Andy Wingo
Howdy,

On Wed 16 May 2012 15:44, Mark H Weaver m...@netris.org writes:

 The design of the wip-rtl VM is to allow 16M registers (24-bit
 addressing).  However many instructions can just address 2**8 registers
 (8-bit addressing) or 2**12 registers (12-bit addressing).  We will
 reserve registers 253 to 255 as temporaries.  If you have so many
 registers as to need more than that, then you have to shuffle operands
 down into the temporaries.  That's the plan, anyway.

 I'm very concerned about this design, for the same reason that I was
 concerned about NaN-boxing on 32-bit platforms.  Efficient use of memory
 is extremely important on modern architectures, because of the vast (and
 increasing) disparity between cache speed and RAM speed.  If you can fit
 the active set into the cache, that often makes a profound difference in
 the speed of a program.

 I agree that with VMs, minimizing the number of dispatches is crucial,
 but beyond a certain point, having more registers is not going to save
 you any dispatches, because they will almost never be used anyway.
 2^12 registers is _far_ beyond that point.

I'm probably not explaining myself clearly.  Here goes.

I willingly grant that 256 registers is usually enough.  But there are
valid reasons to use 2**12 registers: for example in the mov
instruction, if you have an 8-bit opcode, you have 24 bits left.  Using
12 for each operand makes sense.  There are other cases in which you
want to reference 24-bit values, for relative jumps; and even 32-bit
values, to reference constants using relative addressing.  (64 MB is too
small a limit for one compilation unit.  16 GB is fine.)

Likewise I can imagine cases in which you might end up with more than
2**12 active locals, especially in the presence of macros.  In that case
you spill.  But where do you spill?  For Guile, this means spilling to
additional registers, and having to shuffle with long-mov.  Otherwise
you would spill to a vector or something.  The WIP-RTL strategy
adequately captures the edge case while making the normal case fast.

 If I were designing this VM, I'd work hard to allow as many loops as
 possible to run completely in the cache.  That means that three things
 have to fit into the cache together: the VM itself, the user loop code,
 and the user data.  IMO, the sum of these three things should be made as
 small as possible.

Certainly.

 I certainly agree that we should have a generous number of registers,
 but I suspect that the sweet spot for a VM is 256, because it enables
 more compact dispatching code in the VM, and yet is more than enough to
 allow a decent register allocator to generate good code.

 That's my educated guess anyway.  Feel free to prove me wrong :)

I will do better: I will prove you right and prove me right at the same
time :)  The instructions in wip-rtl try to stay in one 32-bit unit.  In
that case they have limits, usually 8 bits.  But when they need to
spill, they will do so on the stack, not on the heap.

Regards,

Andy
-- 
http://wingolog.org/



Re: Register VM WIP

2012-05-16 Thread Andy Wingo
Hi Stefan,

On Fri 11 May 2012 22:29, Stefan Israelsson Tampe stefan.ita...@gmail.com 
writes:

 1. What about growing stacks any coments if they will be easier to manage
 for this setup. Can one copy the C stack logic?

Having a fixed-size frame means that it's easier to have disjoint
stacks, since a register VM addresses operands relative to the frame
pointer and not the stack pointer.  I hope to be able to decrease our
default stack size, and allow it instead to grow dynamically.

 2. Is there an instruction that does what call does but can be used for tail 
 call's
 when it needs it e.g. the code
  for (n = 0; n  nargs; n++)
     LOCAL_SET (n, old_fp[ip[4 + n]]);
 that is missing for the tail code

This is another advantage of wip-rtl.  In it, the compiler is
responsible for shuffling tail arguments.  It can do a parallel move
possibly without shuffling args to the top of the stack.  Then tail-call
just sets a new procedure and jumps to its entry.

 3. I would appriciate if the frame is always below say 256 SCM:s of the fp 
 stack limit
 that way when preparing tail calling one doesn't usally need to check if the 
 argument fit's
 when issuing a tail call.

See above :)

 4. I think the logic code hook I recently investigated could easily fit into 
 this VM engine with
 using similar techniques as I described in previous mails.

I'm still working back through the mails; remind me again if it seems I
overlooked this mail.

Cheers,

Andy
-- 
http://wingolog.org/



Re: Register VM WIP

2012-05-16 Thread Andy Wingo
On Wed 16 May 2012 16:54, Noah Lavine noah.b.lav...@gmail.com writes:

 In our current VM, we have two stacks: the local-variable stack, which
 has frames for different function calls and is generally what you'd
 think of as a stack, and the temporary-variable stack, which is
 literally a stack in the sense that you only operate on the top of it.

Nice explanation!  Surely register vm is a poor name, if one can have
a variable number of registers -- registers are normally something of
which one has a fixed number.

Cheers,

Andy
-- 
http://wingolog.org/



Re: Register VM WIP

2012-05-16 Thread Mark H Weaver
Hi Andy,

Andy Wingo wi...@pobox.com writes:
 Likewise I can imagine cases in which you might end up with more than
 2**12 active locals, especially in the presence of macros.  In that case
 you spill.  But where do you spill?

You spill to them to stack of course, which brings me to my next point:
as discussed in chapter 4 of David Kranz's thesis on the Orbit compiler
(highly recommended reading) it is sometimes better to store a local on
the stack, even if you have registers to spare.  The reason is that
registers must be saved and restored for every procedure call, and as we
all know, Scheme has no shortage of those.

What's your plan for saving and restoring such a large register file?

Best,
 Mark



Re: Register VM WIP

2012-05-16 Thread Noah Lavine
 Perhaps it needs a different name than register virtual machine.

How about RTL VM, since it's a virtual machine that interprets RTL?

Or maybe frame-addressed VM, because the operations address objects
in the current stack frame?

Noah



Re: Register VM WIP

2012-05-16 Thread Ludovic Courtès
Hi,

Noah Lavine noah.b.lav...@gmail.com skribis:

 I think what Andy is proposing to do is to get rid of the
 temporary-variable stack and operate directly on the local-variable
 stack. We shouldn't think of these registers as being like machine
 registers, and in fact maybe registers is not a good name for these
 objects. They are really just variables in the topmost stack frame.

Yeah, I too was confused the first time I heard about “register VMs.”

The key idea is that opcodes encode the offset of the operand they work
on, rather than working only on the last words of the stack (for example,
‘add x y’ instead of ‘local-ref x’ + ‘local-ref y’ + ‘add’ + ‘local-set x’.)

Thanks,
Ludo’.



Re: Register VM WIP

2012-05-15 Thread Andy Wingo
On Mon 14 May 2012 23:28, Andrew Gwozdziewycz apg...@gmail.com writes:

 On Mon, May 14, 2012 at 5:09 PM, Ludovic Courtès l...@gnu.org wrote:

 Presumably the tricky part will be the register allocator, right?

 The register based VMs I've seen ignore this issue by allowing for an
 infinite set of registers. :)

Indeed, that's the plan :)  The first shot at an allocator will look a
lot like the one in (language tree-il analyze).

Andy
-- 
http://wingolog.org/



Re: Register VM WIP

2012-05-15 Thread Andy Wingo
Heya Ludovic,

On Mon 14 May 2012 23:09, l...@gnu.org (Ludovic Courtès) writes:

6(local-ref 1)
8(make-int8:0)
9(ee?)   
   10(local-set 2)   ;;
   12(local-ref 2)   ;; → use ‘local-set* 2’, which doesn’t pop
   14(br-if-not :L187)   ;; - 21

The register VM will do a br-if-= N M ! :LABEL, where ! is a flag
indicating that the test is inverted.  It addresses the operands
directly in registers, no need to push test values.  But yes, there are
places we could win, like local-sub1 and local-mov, as you say.

Regards,

Andy
-- 
http://wingolog.org/



Re: Register VM WIP

2012-05-15 Thread Noah Lavine
Hello,

 The register based VMs I've seen ignore this issue by allowing for an
 infinite set of registers. :)

 Indeed, that's the plan :)  The first shot at an allocator will look a
 lot like the one in (language tree-il analyze).

That was a bit surprising to me. Do you mean that the register pool
will grow and shrink for each function call? Is that why the stack
frames can be fixed-size?

Thanks,
Noah



Re: Register VM WIP

2012-05-15 Thread Mark H Weaver
Noah Lavine noah.b.lav...@gmail.com writes:
 The register based VMs I've seen ignore this issue by allowing for an
 infinite set of registers. :)

 Indeed, that's the plan :)  The first shot at an allocator will look a
 lot like the one in (language tree-il analyze).

 That was a bit surprising to me. Do you mean that the register pool
 will grow and shrink for each function call? Is that why the stack
 frames can be fixed-size?

It's surprising to me for another reason: in order to make the
instructions reasonably compact, only a limited number of bits are
available in each instruction to specify which registers to use.

  Mark



Re: Register VM WIP

2012-05-14 Thread Ludovic Courtès
Hi Andy!

This all looks pretty exciting!  Being able to get rid of all repeated
‘local-{ref,set}’ instructions sounds compelling.  And it does seem to
bring us one step closer to native code.

Presumably the tricky part will be the register allocator, right?

Looking at the ‘countdown’ example, I wonder how much could be achieved
in the stack VM by using well-chosen super-instructions:

   0(assert-nargs-ee/locals 17)
   2(br :L186)  ;; - 30
   6(local-ref 1)
   8(make-int8:0)
   9(ee?)   
  10(local-set 2)   ;;
  12(local-ref 2)   ;; → use ‘local-set* 2’, which doesn’t pop
  14(br-if-not :L187)   ;; - 21
  18(local-ref 2)
  20(return)
  21(local-ref 1)   ;;
  23(sub1)  ;; → use ‘local-sub1 1’  
  24(local-set 1)   ;;
  26(br :L188)  ;; - 6
  30(local-ref 0)   ;;
  32(local-set 1)   ;; → use ‘local-mov 0 1’   
  34(br :L188)  ;; - 6

This would amount to making some of the instructions like those of a
register VM, but it could be done incrementally.

Anyway, this is inspiring, and promising!

Thanks,
Ludo’.




Re: Register VM WIP

2012-05-14 Thread Andrew Gwozdziewycz
On Mon, May 14, 2012 at 5:09 PM, Ludovic Courtès l...@gnu.org wrote:
 Hi Andy!

 This all looks pretty exciting!  Being able to get rid of all repeated
 ‘local-{ref,set}’ instructions sounds compelling.  And it does seem to
 bring us one step closer to native code.

 Presumably the tricky part will be the register allocator, right?

The register based VMs I've seen ignore this issue by allowing for an
infinite set of registers. :)

-- 
http://www.apgwoz.com



Re: Register VM WIP

2012-05-11 Thread Stefan Israelsson Tampe
Hi,

This looks very good. i like the hole approach and this approach has the
potential to address most of the issues I have seen when disassembling
guile-2.0 output. A few notes.

1. What about growing stacks any coments if they will be easier to manage
for this setup. Can one copy the C stack logic?

2. Is there an instruction that does what call does but can be used for
tail call's
when it needs it e.g. the code
 for (n = 0; n  nargs; n++)
LOCAL_SET (n, old_fp[ip[4 + n]]);
that is missing for the tail code

3. I would appriciate if the frame is always below say 256 SCM:s of the fp
stack limit
that way when preparing tail calling one doesn't usally need to check if
the argument fit's
when issuing a tail call. If you compile a function that tail call more
then 254 (?) arguments
then you can as well check because then be free relative the argument
handling.

4. I think the logic code hook I recently investigated could easily fit
into this VM engine with
using similar techniques as I described in previous mails.

Thanks for your work on this
Stefan


On Fri, May 11, 2012 at 6:19 PM, Andy Wingo wi...@pobox.com wrote:

 Hi all,

 This mail announces some very early work on a register VM.  The code is
 in wip-rtl (work in progress, register transfer language.  The latter
 bit is something of a misnomer.).  There is not much there yet:
 basically just the VM, an assembler, and a disassembler.  Still, it's
 interesting, and I thought people might want to hear more about it.

 So, the deal: why is it interesting to switch from a stack VM, which is
 what we have, to a register VM?  There are three overriding
 disadvantages to the current VM.

  1) With our stack VM, instructions are smaller.  They do less, so you
 need more of them.  This increases dispatch cost, which is the
 largest cost of a VM.

  2) On a stack VM, there is a penalty to naming values.  Since the only
 values that are accessible to an instruction are the ones on the
 top of the stack, whenever you want to use more names, you end up
 doing a lot of local-ref / local-set operations.  In contrast an
 instruction for a register VM can address many more operands, so
 there is much less penalty to storing something on the stack.  (The
 penalty is not so much in the storage, but in the pushing and
 popping needed to access it.)

  3) Our stack VM has variable-sized stack frames, so we need to check
 for overflow every time we push a value on the stack.  This is
 quite costly.

 The WIP register VM fixes all of these issues.

 The basic design of the VM is: 32-bit instruction words, 8-bit opcodes,
 variable-length instructions, maximum of 24-bit register addressing, and
 static, relocatable allocation of constants.

 Also, with the wip-rtl VM there is no stack pointer: locals are
 addressed directly via the frame pointer, and the call frame for a
 function is of a fixed size.  Indeed the IP and FP are the only state
 variables of the VM, which makes it much easier to think about native
 compilation, given the scarcity of CPU registers on some architectures.

 See vm-engine.c from around line 1000 for a commented set of
 instructions.  It's messy in many ways now, but hey.

 As far as performance goes, we won't know yet.  But at least for a
 simple loop, counting down from a billion, the register VM is a few
 times faster than the stack VM.  Still, I would be happy if the general
 speedup were on the order of 40%.  We'll see.

 Here's that loop in rtl VM:

   (use-modules (system vm rtl))

   (assemble-rtl-program
 0
 '((assert-nargs-ee/locals 1 2)
   (br fix-body)
   loop-head
   (make-short-immediate 2 0)
   (br-if-= 1 2 out)
   (sub1 1 1)
   (br loop-head)
   fix-body
   (mov 1 0)
   (br loop-head)
   out
   (make-short-immediate 0 #t)
   (return 0)))

 There are various ways to improve this, but its structure is like what
 the stack VM produces.

 Compare to the current opcode:

scheme@(guile-user) (define (countdown n) (let lp ((n n)) (or (zero?
 n) (lp (1- n)
scheme@(guile-user) ,x countdown
Disassembly of #procedure countdown (n):

   0(assert-nargs-ee/locals 17) ;; 1 arg, 2 locals
   2(br :L186)  ;; - 30
   6(local-ref 1)   ;; `n'
   8(make-int8:0)   ;; 0
   9(ee?)
  10(local-set 2)   ;; `t'
  12(local-ref 2)   ;; `t'
  14(br-if-not :L187)   ;; - 21
  18(local-ref 2)   ;; `t'
  20(return)
  21(local-ref 1)   ;; `n'
  23(sub1)
  24(local-set 1)   ;; `n'
  26(br :L188)  ;; - 6
  30(local-ref 0)   ;; `n'
  32(local-set 1)
  34(br :L188)  ;; - 6

 OK, time to set down the keyboard; been