Re: bound methods

2005-02-01 Thread Sam Ruby
Leopold Toetsch wrote:
Sam Ruby [EMAIL PROTECTED] wrote:
The common cases I want to optimize for are common functions being
called as common functions.  And common methods being called as methods.
Yep, that's very reasonable.
The easiest way to optimize for the common methods being called as
methods is if the current object is not passed as the first argument.
Why so? Python methods don't have a notion of an object - it's just the
first argument passed into a function. So I don't quite understand your
conclusion.
In the case of
  x.y(z)
The caller is passing one argument.  The recipient is expecting two. 
The recipient defines what is expected as the first argument (in class 
methods it is the class, in instance methods it is the instance).

So, in the general case, the callee is responsible for inserting a 
parameter at the beginning of the PMC parameters.  Insertion by setting 
a value into P2 is cheaper than insertion by shifting and then setting a 
value into P5.

Yes, copying will be required in cases where functions are called as
methods.
How do you detect this case and when are arguments shifted then - note:
I prefer the term shifting as copying is done anyway in all sub calls
(see src/sub.c:copy_regs).
I agree that shifting is a more appropriate term.
At definition time, the PyFunc PMC has a number of properties (or flags) 
which are set defining names of the formal arguments, defaults, and the 
like.  A similar approach can be used to define whether the first 
parameter is expected in P2 or P5.  Note: this is not yet implemented 
for Python on Parrot.

Note: different languages may chose different strategies as to when to 
shift.  What is important is that we come to an agreement on what is 
expected of the caller.

- Sam Ruby


Re: bound methods

2005-02-01 Thread Leopold Toetsch
Sam Ruby [EMAIL PROTECTED] wrote:
 Leopold Toetsch wrote:

 Why so? Python methods don't have a notion of an object - it's just the
 first argument passed into a function. So I don't quite understand your
 conclusion.

 In the case of

x.y(z)

 The caller is passing one argument.  The recipient is expecting two.
 The recipient defines what is expected as the first argument (in class
 methods it is the class, in instance methods it is the instance).

Yes. Let's start at the called sub/method.

Specifically a user function y would looks this:

1)

  .sub y
.param pmc x
.param pmc z

as this is the general translation of the user code y. If you know
that it's a method, you currently could translate it like:

2)

  .sub y method
.param pmc z
x = interpinfo .INTERPINFO_CURRENT_OBJECT

But at the caller site you don't know anything about y - so with your
proposed argument passing the code in y would need a prologue to
handle both cases in both callee variants, right?

 So, in the general case, the callee is responsible for inserting a
 parameter at the beginning of the PMC parameters.

That's not all. Case 2) above - the method - can in all our target
languages be called with a sub call syntax - AFAIK:

  y(x, z)

This is the normal call syntax for Perl6 multi-subs.

With two different schemes for the callee, we got two variants that
would work w/o further argument adaption and two variants that need
either shifting arguments up or down. But this prologue would be needed
in every function or method.

 ... Insertion by setting
 a value into P2 is cheaper than insertion by shifting and then setting a
 value into P5.

Already optimizing? - SCNR. Anyway: src/sub.c:copy_regs() already copies
registers from the caller's register frame to the callee. Implementing
argument shifting there is cheap - if it's still needed.

By defining that all subs or methods get the arguments (including the
object) passed in P5, P6 ... we'd only have half of the mis-matching
argument orderings.

 At definition time, the PyFunc PMC has a number of properties (or flags)
 which are set defining names of the formal arguments, defaults, and the
 like.

Yes, more complications.

 ... A similar approach can be used to define whether the first
 parameter is expected in P2 or P5.

Sure. But why should we go the more complicated way in the first place?

 Note: different languages may chose different strategies as to when to
 shift.  What is important is that we come to an agreement on what is
 expected of the caller.

From the caller's POV its quite clear if it's a method or a function
call. But if the other end has two incarnations, things get messy.

 - Sam Ruby

leo


Re: bound methods

2005-02-01 Thread Sam Ruby
Leopold Toetsch wrote:
Sam Ruby [EMAIL PROTECTED] wrote:
Leopold Toetsch wrote:

... But that doesn't
work fur user methods, especially if there is no indication that a user
function is used as a method in the first place.
 def find(s, sub):
   ...

In Python, this is statically determinable.  If that sequence is
directly nested inside a class, it is a method, otherwise it is a
function.

No, we had that several times. You did show examples, where this isn't
true. Here is another one:
def add(l, r):
print in add
return 42
class C(object):
__add__ = add
c = C()
print c + 1
add is just a plain function. There is no indication whatsoever that
it might be used as a method, when add is compiled. But as infix+ is
a method call l.__add__(r) this doesn't work, if the object is in P2.

I've proposed several times that arguments including the object should be
passed from P5 up. The invocant - if any - can still be reachable with
the Cinterpinfo opcode, but it'll be probably used just in calls to SUPER
or next_method or such.

Less shifting will be required if the object is passed in P2.

That's only true for NCI methods. And IIRC, your arguments were the same
some time ago. How do you compile the add above, when the object is in
P2?

Notes: at the moment, every Python method call creates a bound object,
and shifts PMC arguments.
This shouldn't be necessary for normal method calls like:
 s.f(r)

But this translates into two VTABLE calls.  find_method and invoke.
find_method needs to return a bound method.

Why don't you just use the callmethodcc opcode? That's exactly what is
happening here.

A find_method_and_invoke VTABLE entry (or more simply call_method) would
not need to return the intermediary bound method.

There is no need for an intermediate object, if it's a plain method
call.

If there were a call_method VTABLE entry and if P2 were passed into
methods, all of this would be unecessary in the majority of cases.
A separate vtable slot doesn't really help. Code that looks like a
function call can actually be a method call (and vv). Separating the
call into two vtables will just duplicate the call sequence. But let's
first convince Dan that all arguments are passed from P5 up, then we'll
see what we have.

Less shifting will be required if the object is passed in P2.

See above.
And why are you duplicating object arguments into P5, if the object
should go into P2?
,--[ dynclasses/pyint.pmc ]---
| METHOD PMC* __hex__(PMC *self) {
`-
You are inventing an additional self argument here just to work around
the problem that the object is passed in P2. The actual object pmc is
ignored:
,--[ dynclasses/pyint.c ]---
| PMC*
| Parrot_PyInt___hex__(Interp* interpreter, PMC* pmc, PMC *self)
`---
As said in another message, there is no problem with NCI methods. It's
just a matter of translating the O signature char.
The problem are user functions. There might be an indication that's a
method (like a definition inside a class block) - but not always. And if
the latter is the case *only once*, we just have to call all methods
with plain function call argument passing. Or prepend *all* functions
with an argument check wrapper that shift arguments around, if the call
was actually a method call.
The current CVS is in a state of transition.
I originally implemented to the current set of PDDs.  Unhappy with the 
amount of copying of PMC registers, I optimistically converted over to 
the pass self as P5 approach that you suggested.  I found that that 
simply moved the problem to other places, and copying was unavoidable. 
In fact, it generally made things worse.

Given that copying is unavoidable, what I would like to do is to 
optimize for what I consider the most common cases, and make all the 
remaining cases work properly.

The common cases I want to optimize for are common functions being 
called as common functions.  And common methods being called as methods.

The easiest way to optimize for the common methods being called as 
methods is if the current object is not passed as the first argument.

Yes, copying will be required in cases where functions are called as 
methods.

- Sam Ruby


Re: bound methods

2005-02-01 Thread Leopold Toetsch
Sam Ruby [EMAIL PROTECTED] wrote:

 The common cases I want to optimize for are common functions being
 called as common functions.  And common methods being called as methods.

Yep, that's very reasonable.

 The easiest way to optimize for the common methods being called as
 methods is if the current object is not passed as the first argument.

Why so? Python methods don't have a notion of an object - it's just the
first argument passed into a function. So I don't quite understand your
conclusion.

This isn't a python-only problem. Perl5 doesn't have an object either.
Perl6 multi-methods provide both features:

  foo($a, $b);# probably a MMD call on both
  $a.foo($b); # a method call on 1st invocant

The function or multi-sub foo can be basically anything in the first
case including a lexical function foo and not a method at all.

As long as we have different calling conventions for these 2 variants we
have just a better chance for a mismatch IMHO.

 Yes, copying will be required in cases where functions are called as
 methods.

How do you detect this case and when are arguments shifted then - note:
I prefer the term shifting as copying is done anyway in all sub calls
(see src/sub.c:copy_regs).

 - Sam Ruby

leo


Re: bound methods

2005-01-31 Thread Leopold Toetsch
Sam Ruby [EMAIL PROTECTED] wrote:
 Leopold Toetsch wrote:

 WRT functionality: for a call it has to shift up PMC arguments and
 insert the object as P5, right?

 At the moment, it contains this logic.  My plans are to remove the
 shifting and set the object into P2 / INTERP-ctx.current_object.

Dan didn't answer issues WRT argument passing yet. In the current scheme
your plan would be ok - for e.g. a NCI find method. But that doesn't
work fur user methods, especially if there is no indication that a user
function is used as a method in the first place.

  def find(s, sub):
...

I've proposed several times that arguments including the object should be
passed from P5 up. The invocant - if any - can still be reachable with
the Cinterpinfo opcode, but it'll be probably used just in calls to SUPER
or next_method or such.

 Notes: at the moment, every Python method call creates a bound object,
 and shifts PMC arguments.

This shouldn't be necessary for normal method calls like:

  s.f(r)

 ... Removing the shifting of PMC arguments will
 require the insertion of an interpinfo opcode into each method.

Or fixing argument passsing like above.

 If there were a call_method VTABLE entry and if P2 were passed into
 methods, all of this would be unecessary in the majority of cases.

A separate vtable slot doesn't really help. Code that looks like a
function call can actually be a method call (and vv). Separating the
call into two vtables will just duplicate the call sequence. But let's
first convince Dan that all arguments are passed from P5 up, then we'll
see what we have.

 - Sam Ruby

leo


Re: bound methods

2005-01-31 Thread Leopold Toetsch
Sam Ruby [EMAIL PROTECTED] wrote:
 Leopold Toetsch wrote:

[ provide Bound_Meth core PMC classes ]

 Cool.  I'd likely still subclass it to provide a get_string.

Yep.

 WRT implementation: I'd like to swap struct_val/pmc_val for all Sub
 classes. It's just cleaner but should be almost fully transparent to
 users of these classes.

 No objection.

Done that now. PMC_sub(SELF) aka PMC_struct_val(SELF) holds now
basically all the subroutine information: segment, address, name of Subs
or the C function pointer for NCI.

This makes PMC_pmc_val() consistently available as the bound object.

leo


Re: bound methods

2005-01-31 Thread Sam Ruby
Leopold Toetsch wrote:
Sam Ruby [EMAIL PROTECTED] wrote:
Leopold Toetsch wrote:

WRT functionality: for a call it has to shift up PMC arguments and
insert the object as P5, right?

At the moment, it contains this logic.  My plans are to remove the
shifting and set the object into P2 / INTERP-ctx.current_object.
Dan didn't answer issues WRT argument passing yet. In the current scheme
your plan would be ok - for e.g. a NCI find method. But that doesn't
work fur user methods, especially if there is no indication that a user
function is used as a method in the first place.
  def find(s, sub):
...
In Python, this is statically determinable.  If that sequence is 
directly nested inside a class, it is a method, otherwise it is a 
function.

I've proposed several times that arguments including the object should be
passed from P5 up. The invocant - if any - can still be reachable with
the Cinterpinfo opcode, but it'll be probably used just in calls to SUPER
or next_method or such.
Less shifting will be required if the object is passed in P2.
Notes: at the moment, every Python method call creates a bound object,
and shifts PMC arguments.
This shouldn't be necessary for normal method calls like:
  s.f(r)
But this translates into two VTABLE calls.  find_method and invoke. 
find_method needs to return a bound method.

A find_method_and_invoke VTABLE entry (or more simply call_method) would 
not need to return the intermediary bound method.

... Removing the shifting of PMC arguments will
require the insertion of an interpinfo opcode into each method.
Or fixing argument passsing like above.
Which amounts to not removing the shifting.
If there were a call_method VTABLE entry and if P2 were passed into
methods, all of this would be unecessary in the majority of cases.
A separate vtable slot doesn't really help. Code that looks like a
function call can actually be a method call (and vv). Separating the
call into two vtables will just duplicate the call sequence. But let's
first convince Dan that all arguments are passed from P5 up, then we'll
see what we have.
Less shifting will be required if the object is passed in P2.
- Sam Ruby


Re: bound methods

2005-01-31 Thread Leopold Toetsch
Sam Ruby [EMAIL PROTECTED] wrote:
 Leopold Toetsch wrote:

 ... But that doesn't
 work fur user methods, especially if there is no indication that a user
 function is used as a method in the first place.

   def find(s, sub):
 ...

 In Python, this is statically determinable.  If that sequence is
 directly nested inside a class, it is a method, otherwise it is a
 function.

No, we had that several times. You did show examples, where this isn't
true. Here is another one:

def add(l, r):
print in add
return 42

class C(object):
__add__ = add

c = C()
print c + 1

add is just a plain function. There is no indication whatsoever that
it might be used as a method, when add is compiled. But as infix+ is
a method call l.__add__(r) this doesn't work, if the object is in P2.

 I've proposed several times that arguments including the object should be
 passed from P5 up. The invocant - if any - can still be reachable with
 the Cinterpinfo opcode, but it'll be probably used just in calls to SUPER
 or next_method or such.

 Less shifting will be required if the object is passed in P2.

That's only true for NCI methods. And IIRC, your arguments were the same
some time ago. How do you compile the add above, when the object is in
P2?

Notes: at the moment, every Python method call creates a bound object,
and shifts PMC arguments.

 This shouldn't be necessary for normal method calls like:

   s.f(r)

 But this translates into two VTABLE calls.  find_method and invoke.
 find_method needs to return a bound method.

Why don't you just use the callmethodcc opcode? That's exactly what is
happening here.

 A find_method_and_invoke VTABLE entry (or more simply call_method) would
 not need to return the intermediary bound method.

There is no need for an intermediate object, if it's a plain method
call.

If there were a call_method VTABLE entry and if P2 were passed into
methods, all of this would be unecessary in the majority of cases.

 A separate vtable slot doesn't really help. Code that looks like a
 function call can actually be a method call (and vv). Separating the
 call into two vtables will just duplicate the call sequence. But let's
 first convince Dan that all arguments are passed from P5 up, then we'll
 see what we have.

 Less shifting will be required if the object is passed in P2.

See above.

And why are you duplicating object arguments into P5, if the object
should go into P2?

,--[ dynclasses/pyint.pmc ]---
| METHOD PMC* __hex__(PMC *self) {
`-

You are inventing an additional self argument here just to work around
the problem that the object is passed in P2. The actual object pmc is
ignored:

,--[ dynclasses/pyint.c ]---
| PMC*
| Parrot_PyInt___hex__(Interp* interpreter, PMC* pmc, PMC *self)
`---

As said in another message, there is no problem with NCI methods. It's
just a matter of translating the O signature char.

The problem are user functions. There might be an indication that's a
method (like a definition inside a class block) - but not always. And if
the latter is the case *only once*, we just have to call all methods
with plain function call argument passing. Or prepend *all* functions
with an argument check wrapper that shift arguments around, if the call
was actually a method call.

 - Sam Ruby

leo


Re: bound methods

2005-01-31 Thread Leopold Toetsch
Sam Ruby [EMAIL PROTECTED] wrote:
 Leopold Toetsch wrote:

 Not quite. It's just:

   f = getattribute Parrot_string, find

 nothing more. The Cget_attr_str vtable has to do the right thing, i.e.
 if the attribute is a callable, it has to return a bound method object.

 Exactly.

[ I've checked in the Bound_NCI PMC now ]

The default implementation of get_attr_str already did a method lookup
(it should probably use VTABLE_find_method, though). For the easy case -
the method is NCI - now a Bound_NCI object is returned.

Invoking that places the bound object as P2, which is fine for NCI
calls, as the calling conventions are fully under our control. It's
just a matter, how the signature O is translated: either take the next
argument from P2 of from the next in P5, P6 ...
So actually for NCI methods the current scheme is fine as shifting
arguments isn't needed. Still remaining and unsolved is the problem with
user methods argument passing, which does just not match.

 Cset_pointer sets the pointer to the actual subroutine.

That's not quite right. Cset_pointer set's the function address of a
callable. Anyway, a Bound_NCI isa NCI - it's not a container object
like your implementation is. This simplifies inheritance vastly.

 ... Cset_pmc
 sets the pointer to the bound object.

And we've additionally CPMC* get_pmc(), which returns the bound
object.

 - Sam Ruby

leo


bound methods (was: Calling conventions, invocations, and suchlike things)

2005-01-30 Thread Leopold Toetsch
Dan Sugalski [EMAIL PROTECTED] wrote:
 At 5:04 PM -0500 1/18/05, Sam Ruby wrote:

 f = Parrot.find
 print f(r)

Note that I referenced the method as an attribute, and then called
it as a function.

 Mmm, syntax! :) Luckily it makes no difference to us at the parrot
 level. What that should translate to is something like:

  $P0 = find_method Parrot_string, find
   # Elided check for failed lookup and fallback to attribute fetch
  $P1 = make_bound_method(Parrot_string, $P0)

Not quite. It's just:

  f = getattribute Parrot_string, find

nothing more. The Cget_attr_str vtable has to do the right thing, i.e.
if the attribute is a callable, it has to return a bound method object.

Furthermore, the function remembers what object it is bound to.
This is accomplished by VTABLE_find_method creating a new
PyBoundMeth PMC which contains two references, one to the object,
and one to the method.

 While a good idea, I think it's not the right way to handle this.
 Binding objects to methods to create invokable subs is going to be
 something we're going to need for a lot of the languages, so I think
 we'd be better served providing a general facility to do it rather
 than leaving it to each individual language designer to do it. Should
 save some work all around too.

Yeah. When this came up last, I've proposed two ways to handle it:

1) inside the Sub/NCI PMC
2) by a distinct Bound_Meth PMC class derived from 1)

The latter is probably cleaner. Binding the object to the callable could
be done e.g. by the Cset_pmc vtable.

leo


Re: bound methods

2005-01-30 Thread Sam Ruby
Leopold Toetsch wrote:
Dan Sugalski [EMAIL PROTECTED] wrote:
At 5:04 PM -0500 1/18/05, Sam Ruby wrote:

   f = Parrot.find
   print f(r)
Note that I referenced the method as an attribute, and then called
it as a function.

Mmm, syntax! :) Luckily it makes no difference to us at the parrot
level. What that should translate to is something like:

$P0 = find_method Parrot_string, find
 # Elided check for failed lookup and fallback to attribute fetch
$P1 = make_bound_method(Parrot_string, $P0)
Not quite. It's just:
  f = getattribute Parrot_string, find
nothing more. The Cget_attr_str vtable has to do the right thing, i.e.
if the attribute is a callable, it has to return a bound method object.
Exactly.
Furthermore, the function remembers what object it is bound to.
This is accomplished by VTABLE_find_method creating a new
PyBoundMeth PMC which contains two references, one to the object,
and one to the method.

While a good idea, I think it's not the right way to handle this.
Binding objects to methods to create invokable subs is going to be
something we're going to need for a lot of the languages, so I think
we'd be better served providing a general facility to do it rather
than leaving it to each individual language designer to do it. Should
save some work all around too.
Yeah. When this came up last, I've proposed two ways to handle it:
1) inside the Sub/NCI PMC
2) by a distinct Bound_Meth PMC class derived from 1)
The latter is probably cleaner. Binding the object to the callable could
be done e.g. by the Cset_pmc vtable.
That's exactly how PyBoundMeth works today.
Cset_pointer sets the pointer to the actual subroutine.  Cset_pmc 
sets the pointer to the bound object.

- Sam Ruby


Re: bound methods

2005-01-30 Thread Leopold Toetsch
Sam Ruby [EMAIL PROTECTED] wrote:
 Leopold Toetsch wrote:

 2) by a distinct Bound_Meth PMC class derived from 1)

 The latter is probably cleaner. Binding the object to the callable could
 be done e.g. by the Cset_pmc vtable.

 That's exactly how PyBoundMeth works today.

 Cset_pointer sets the pointer to the actual subroutine.  Cset_pmc
 sets the pointer to the bound object.

Great. I saw the checkin but didn't have a close look at it. So if even
the interface is the same, we really should put it into the core
classes.

WRT implementation: I'd like to swap struct_val/pmc_val for all Sub
classes. It's just cleaner but should be almost fully transparent to
users of these classes.

WRT functionality: for a call it has to shift up PMC arguments and
insert the object as P5, right?

 - Sam Ruby

leo


Re: bound methods

2005-01-30 Thread Sam Ruby
Leopold Toetsch wrote:
Sam Ruby [EMAIL PROTECTED] wrote:
Leopold Toetsch wrote:

2) by a distinct Bound_Meth PMC class derived from 1)
The latter is probably cleaner. Binding the object to the callable could
be done e.g. by the Cset_pmc vtable.

That's exactly how PyBoundMeth works today.

Cset_pointer sets the pointer to the actual subroutine.  Cset_pmc
sets the pointer to the bound object.
Great. I saw the checkin but didn't have a close look at it. So if even
the interface is the same, we really should put it into the core
classes.
Cool.  I'd likely still subclass it to provide a get_string.
WRT implementation: I'd like to swap struct_val/pmc_val for all Sub
classes. It's just cleaner but should be almost fully transparent to
users of these classes.
No objection.
WRT functionality: for a call it has to shift up PMC arguments and
insert the object as P5, right?
At the moment, it contains this logic.  My plans are to remove the 
shifting and set the object into P2 / INTERP-ctx.current_object.

Notes: at the moment, every Python method call creates a bound object, 
and shifts PMC arguments.  Removing the shifting of PMC arguments will 
require the insertion of an interpinfo opcode into each method.

If there were a call_method VTABLE entry and if P2 were passed into 
methods, all of this would be unecessary in the majority of cases.

- Sam Ruby