On Dec 1, 2004, at 7:23 AM, Dan Sugalski wrote:
At 12:06 AM -0800 12/1/04, Jeff Clites wrote:On Nov 30, 2004, at 11:45 AM, Dan Sugalski wrote:
In this example:
% cat continuation6.ruby def strange callcc {|continuation| $saved = continuation} end
def outer a = 0 strange() a = a + 1 print "a = ", a, "\n" end
Through the joys of reference types, a will continue to increase forevermore, assuming the compiler hasn't incorrectly put a in an int register. (Which'd be wrong)
Separate question, but then what would happen for languages which _do_ use primitive types? (Presumably, Perl6 would do that in the "my int" case.) If proper behavior requires basically never using the primitive I/N types, that seems like a waste.
Two potential options. One, they have a backing PMC for the lexicals (which they'll probably need anyway) and flush/restore at spots where things could get lost. Two, they don't actually get a low-level type but the compiler cheats and acts as if it was in spots where it's safe. (I admit, I'd always planned on having "my int $foo" use a PMC. The win would be for "my int @foo" which would also get a PMC, but one that had optimized backing store for the values)
But so it sounds like I and N registers are a bit of a waste then, no? They won't really be used. Even in your "my int @foo" case, you could have an optimized backing store for the array, but you'd have to on-the-fly create a PMC to hold any values you pulled out of the array, e.g. for a subsequent "x = @foo[1]" (in Perl6 syntax)--x would have to hold a full PMC there, it seems, so nothing there would end up in an I register (except as in intermediate in immediately creating the PMC).
Seems like instead we'd be able to simply use an I register, and be done with it. The returny-ness of a return continuation seems bogus, if it's supposed to survive promotion to a real continuation. All we need is some sort of per-frame storage (like lexical pads provide, but for all register types), and yes explicit moving of things in-and-out of that storage if we want to re-use registers (and only necessary across function call if we do plan on re-using a register). That's what happens in hardware CPUs.
But even with all of what you say below, I still think there's an issue with overlapping variable lifetimes. Consider my original pseudocode example again, with one added print statement:
x = 1 foo() print x print y y = 2 return y
The "first time" foo() returns, the "print y" should find y holding undef (or something like that), and subsequent times it should find it holding 2. (That's certainly what the analogous goto would do.) That means that even with an intermediate Reference struct, you still can't use the same register to hold the Reference for x and the Reference for y, because their lifetimes overlap, but only due to the presence of continuations--without them, you could re-use the register. Re-entering the frame needs to leave all of the local variables intact, and that's stretching their lifetimes out to extend across most of the frame. Or so it would seem.
The contents can change over and over without the register itself ever changing.
But in this Ruby case, "a = a + 1" actually creates a new Fixnum instance, so "a" ends up holding a different instance each time--you can verify that by printing out a.id in the print statement.
This is where the magic of objects comes in, for particularly loose values of "magic". Ruby uses a double-reference system for objects the same way that perl 5 does -- that is, the underlying structure for a doesn't hold the object, rather it holds a pointer to another structure that holds the object. So it looks like:
(name a) -> (a struct, addr 0x04) -> (object struct addr 0x08)
and after the store it looks like:
(name a) -> (a struct, addr 0x04) -> (object struct addr 0x0C)
The PMC register would hold the 0x04 struct pointer, basically a Reference PMC, and assignments to it just switch the thing it refers to.
But what's the point of that--seems like a waste of an intermediate struct. And I assume the intermediate struct would be a PMC, no?
Essentially things like I and N registers are value types, PMC and strings are (for us) reference types, and many objects (including ruby and perl's objects) are double-reference types.
In Ruby "everything is an object", so we'd have double references even for numbers (number-ish PMCs). That loops back to the point of my question in the thread "Basic compilation example (a + b)?". If "a + b" compiles as "add P16, P18, P17", and registers actually hold some intermediate Reference type, then the MMD of "add" would be type-dispatching on the reference type, not the type of the objects inside (as the MMD is currently written). Since Ruby (and Python) have type-less variables, that implies to me a single Reference type at least for them, so our MMD would always call the same function for "add", and not do any useful dispatching. Or should our MMD instead "look inside" the Reference held in the register?
JEff