The difference is that Parrot is passing in the destination argument
while these languages are returning the operator result.
E.g.
def myadd(self, r):
return self - r
class I(int):
__add__ = myadd
i = I(44)
print i, i + 2
Parrot is expecting and calling a subroutine like:
.sub __add
.param left
.param right
.param dest
...
.end
and it's run in void context. This calling convention matches the "add"
opcode, where the destination argument has to exist. But it's not suited
for HLLs as the HLL compiler can't track the subroutine usage back to
overloading and adjust the emitted code.
A short-term solution would be to evaluate the return result (if the
function returns something) and assign that value to the "dest" argument.
Parrot would call
.sub __add
.param left
.param right
.param dest # HLL can use it or create a temp
...
.return(temp) # or .return (dest) if HLL knows about dest
.end
while this is still a bit sub-optimal because of the additional temp,
the HLL might create, it would at least match the semantics.
A more radical change would be to just adapt these opcodes to create an
appropriate PMC with the result.
Comments?
leo
[ fullquote ]
> E.g.
> Parrot would call
Thinking more about that it seems that we don't have much chance to keep
the current scheme that the destination is passed in.
a Python snippet:
def myadd(self, r):
return I(self.v - r)
class I(object):
__add__ = myadd
def __init__(self, v):
self.v = v
def __repr__(self):
return "I(%d)" % self.v
i = I(44)
j = i + 2
k = j + 2
print i, j, k
The "add" opcode is overloaded and returns a new object of class "I".
The current scheme is:
$P0 = new Undef
$P0 = add i, 2
*If* the HLL compiler can somehow make a connection between the "myadd"
function as an overloaded "add" opcode, it could try to morph the passed
in destination argument. The whole morph code is bulky and slow,
changing one PMC into an arbitrary different one needs deallocating the
old one (what happens with finalizers?) and constructing a new one in
place.
But I doubt that a compiler can track such a usage (myadd could be in a
different file and imported). That means we are pre-constructing a LHS
PMC for nothing with all implications on GC and performance.
Second: looking at pmc.c:pmc_reuse() the code isn't able to and can't
never return a singleton as the result of an operation, because that
would mean to change the address of the argument, where only it's
pointer is passed.
> Comments?
> leo
leo
As enjoyable as this discussion has been, I'd like to ask that it be put
on hold for a few days. I've nearly got all the previously defined
languages/python/t/basic tests running, and once they are running, I'd
like to do a bit of refactoring and documentation of a complete proposal
on how this can work.
- Sam Ruby
P.S. Peeking at the current implementation may not be wise, as a number
of shortcuts were taken (example: int objects behave as their own class,
etc), which confuses the picture.
(This is probably out of order -- I've a lot of mail I'm backed up on
unfortunately, but since it was CC'd directly to me I'll take it)
The note here is that Parrot's MMD function signature for binary ops
doesn't match what Python needs. Parrot is:
void binary_mmd_op(pmc left, pmc right, pmc dest)
where Python is:
pmc dest = left.add(pmc right)
And, as you can see, the difference is more than just python creating
a destination where we require one to be passed in -- it's a method
call as well.
I fully expected this to be an issue. Perl 5 and perl 6 are going to
have different conventions, (and I think there may well be at least
two separate ones for perl 6, but I may be misrememebering) Ruby
doesn't match, and neither do any of the other languages. I think
there was some discussion back when this was first batted around, but
there might not have been.
The short answer here is to cope: that is, when installing an MMD
function, including one of the default MMD functions for a class, a
language needs to generate the appropriate wrapper function if
necessary to translate between what parrot provides and what the
language itself wants.
Nothing much for it -- no matter what we choose it's going to be
wrong for someone, so the sensible thing to do is choose the scheme
that works best for the underlying model (which we have) and leave it
to compilers and class libraries to translate to their own preferred
form. I think we're likely to find that the scheme we have catches on
reasonably well once we've hit release and start seeing widespread
use.
--
Dan
--------------------------------------it's like this-------------------
Dan Sugalski even samurai
d...@sidhe.org have teddy bears and even
teddy bears get drunk
> I fully expected this to be an issue. Perl 5 and perl 6 are going to
> have different conventions, (and I think there may well be at least
> two separate ones for perl 6, but I may be misrememebering) Ruby
> doesn't match, and neither do any of the other languages.
[ ... ]
> Nothing much for it -- no matter what we choose it's going to be
> wrong for someone, so the sensible thing to do is choose the scheme
> that works best for the underlying model (which we have) and leave it
> to compilers and class libraries to translate to their own preferred
> form. I think we're likely to find that the scheme we have catches on
> reasonably well once we've hit release and start seeing widespread
> use.
Shouldn't the underlying model be at least near the expectations of HLLs
we want to support? It doesn't buy us anything, if we force all
languages to create wrappers. And "works best for the underlying model"
just means the code exists.
Finally the current implementation can't handle singletons (like PyNone) as
a return result from such opcodes.
leo
> The note here is that Parrot's MMD function signature for binary ops
> doesn't match what Python needs. Parrot is:
> void binary_mmd_op(pmc left, pmc right, pmc dest)
> where Python is:
> pmc dest = left.add(pmc right)
Perl6 allows (according to S06) for calling a multi method:
mulit sub f($a, $b: $c) { ... }
function call syntax:
f($x, $y, $z);
as well as method call syntax on the first invocant:
$x.f($y, $z);
Perl5 as well as Python have the invocant as the first function
argument.
We could now of course translate at runtime between these call schemes
(prepend P2 and shift all up or move P5 into current_object and shift
arguments down). But this takes some time.
Why do we have the special notion of current_object in the first place?
Why not just pass all in as P5, P6, ...?
leo
I agree that this is the way to go. Especially if we have some marker
somewhere that tells us that we were called as a method.
Luke
Does the Perl6 compiler know function signatures at compile time?
S06 states: "Passing two many or too few invocants is a fatal error."
For a runtime check we'd need additionally the MMD object count.
> Luke
leo
Not quite. That'll be
class_self."__super"(args)
so again the invocant is the first argument after interpreter.
leo
Believe it or not, I think we are agreeing.
To invoke a method on an object using Parrot Calling Conventions, P2
needs to be the object used for dispatch purposes, and P5 needs to be
the actual object. In many cases, they will be the same, but in some
cases they will differ.
This isn't obvious from PDD03, but a simple clarification would take
care of that.
- Sam Ruby
*g*
> To invoke a method on an object using Parrot Calling Conventions, P2
> needs to be the object used for dispatch purposes, and P5 needs to be
> the actual object. In many cases, they will be the same, but in some
> cases they will differ.
Yep. Another good reason to pass the object as P5 (too).
> This isn't obvious from PDD03, but a simple clarification would take
> care of that.
I'm still waiting for Dan's decision.
> - Sam Ruby
leo