Objective-C Issues

forward-performv

People ( From: lines )
 "Geoffrey S. Knauth" 
 Andrew Athan 
 burchard@geom.umn.edu
 Dennis Glatting 
 Kenneth Lerman 
 Kresten Krab Thorup 
 michael@stb.info.com (Michael Gersten)
 Paul Burchard 
 rms@gnu.ai.mit.edu (Richard Stallman)
 Steve Naroff 
Subjects ( 28 mails )
 The forward/performv Puzzle
 Re: The forward/performv Puzzle
 The forward/performv Puzzle
 Re: The forward/performv Puzzle
 Re: The forward/performv Puzzle
 Re: The forward/performv Puzzle
 The forward/performv Puzzle
 Re: The forward/performv Puzzle
 Re: The forward/performv Puzzle [corrected]
 Re: The forward/performv Puzzle
 Re: The forward/performv Puzzle
 The forward/performv Puzzle
 Re: The forward/performv Puzzle
 forward/performv: summary
 [rms@gnu.ai.mit.edu: The forward/performv Puzzle]
 The forward/performv Puzzle
 [burchard@geom.umn.edu: Re: The forward/performv Puzzle]
 Re: The forward/performv Puzzle
 [rms@gnu.ai.mit.edu: The forward/performv Puzzle]
 The forward/performv Puzzle
 [burchard@geom.umn.edu: Light at the end of the forwarding tunnel?]
 Light at the end of the forwarding tunnel?
 [casey!gaboon!seltd!lerman@uunet.uu.net: Re: The forward/performv Puzzle]
 Re: The forward/performv Puzzle
 [Steve_Naroff@next.com: Re: The forward/performv Puzzle]
 Re: The forward/performv Puzzle
 Re: Optimizing the GNU objc runtime
 Re: Gnu objc runtime [comments and bug reports]
Document
Date: Mon, 18 Jan 93 15:34:32 -0600
From: burchard@geom.umn.edu
To: gnu-objc@prep.ai.mit.edu
Subject: The forward/performv Puzzle

I have written a very, very, very preliminary socket-based distributed object 
system that is intended for porting to GNU Objective-C.  So far, this has been 
primarily an exercise to see what is actually required from the run-time in 
order to implement such a system.

There is one major feature missing from GNU's Objective-C run-time at the moment, 
which prevents this project from progressing to the more desirable very, very 
preliminary :-) stage.  That feature is the functionality of NeXT's forward and 
performv, which are required in order that arbitrary messages can be captured by 
proxy objects and then later executed by the remote objects they stand for.

The puzzle I would like to pose to this mailing list is how such a run-time 
facility can best (most portably) be implemented.  It doesn't have to be 
compatible with NeXT's forward and performv; it just needs to provide their 
capabilities (in fact, NeXT's API is proabably a good one to avoid).

This is a tricky puzzle, though, the underlying difficulty being that the 
C language does not support the run-time construction of argument lists and 
return values.  (It doesn't really support the run-time deconstruction of 
argument lists either, except that we now have standard hack for it called 
"stdarg.h".)

This puzzle is ironically made more difficult by the portable message-dispatching 
implementation chosen by GNU Obj-C.  The problem here is that objc_msgSend() only
gets to choose a method implementation, and is helpless to change the message's 
"self" and "_cmd" (which are hard-compiled into the message call in the GNU 
run-time system).  If this were not the case, a limited form of forwarding could 
be implemented portably.

This portability puzzle vexes NeXT's distributed object system as well, resulting 
in problems like not being able to return doubles from remote messages.

The benefits of distributed objects are too great, though, to abandon this quest 
if it turns out that no 100% pure solution exists.  More portable is better, but 
I'd like to hear any practical ideas that will work well with the GNU compiler.

Solutions?  Ideas, anyone?

Thanks,
--------------------------------------------------------------------
Paul Burchard	
``I'm still learning how to count backwards from infinity...''
--------------------------------------------------------------------



Date: Tue, 19 Jan 93 08:05:01 -0800
From: Dennis Glatting 
To: 
Subject: Re: The forward/performv Puzzle
Cc: gnu-objc@prep.ai.mit.edu
Reply-To: dennis_glatting@trirex.com


I looked at doing forwardv:: for the first release of the run time.  To implement 
forwardv:: is a perplexing problem:  

* Implementation of forwardv:: would require stack frame copying.  
  (or at least modification of the existing stack frame.)
* The microprocessor itself poses problems such as word width, alignment, and 
  -- possibly on some RISC machines -- parameter passing using register windows.
* Each function used to implement forwardv:: may need to know the amount of data 
  pushed onto the stack for local variables.  (So to index backward.)
* The dispatch function must know about passed arguments.  While this is encoded
  and available, what do you do for variable arguments and structures?  Oh yea, 
  how about void*?
* The stack must be indexed to extract return addresses -- that is, if this can 
  be done with your microprocessor of the day.  There are also microprocessor/
  compiler specific data pushed onto the stack that the functions may need to 
  know about such as the variable frame on 680x0 microprocessors (note the link 
  instruction on each function entry).

At the time, these issues and others I haven't mentioned caused me think that DO 
reeks of assembly code that is not only tailored to the microprocessor, by the 
vendor's operating system as well.  This is a significant maintenance issue and 
is in no way portable.

The other item I wanted to add to the run time is "active objects" but it suffers 
from many of the same issues.

I think you touched on something I suspect about the NeXT run time:  it contains 
microprocessor/vendor specific assembly code.

I invite ideas of how to do this in a somewhat portable fashion.  Portability was 
one of the chief goals of the run time.


-dpg



From: Steve Naroff 
Date: Tue, 19 Jan 93 17:50:24 -0800
To: dennis_glatting@trirex.com
Subject: The forward/performv Puzzle
Cc: gnu-objc@prep.ai.mit.edu


The "style" of forwarding required for DO is hard to do in portable way (given 
the low level nature of C). C compilers have a hard enough time agreeing on 
structure layout! Nevertheless, I believe you are over estimating the assembly 
required implement this. The two hooks in the NeXT runtime that support forwarding 
are:

extern id _objc_msgForward (id self, SEL sel, ...);
extern id objc_msgSendv (id self, SEL op,
		unsigned arg_size, marg_list arg_frame);

The assembly code amounts to less than 1 page for both of these routines (let me 
know if you want the code).

You could imagine a style of forwarding that works fine locally (and is totally 
portable). The interface is:

- forward:(SEL)op;	// adequate for supporting delegation.

"forward:" would be implemented by any class interested in providing a delegate. 
It would not be directly responsible for sending the message (which is why it is 
more portable). This is also parallel with the way you separate "lookup from 
dispatch" in the GNU runtime.

regards, snaroff.

Begin forwarded message:

Date: Tue, 19 Jan 93 08:05:01 -0800
From: Dennis Glatting 
To: 
Subject: Re: The forward/performv Puzzle
Cc: gnu-objc@prep.ai.mit.edu
Reply-To: dennis_glatting@trirex.com


I looked at doing forwardv:: for the first release of the run time.  To implement 
forwardv:: is a perplexing problem: 

* Implementation of forwardv:: would require stack frame copying.  
  (or at least modification of the existing stack frame.)
* The microprocessor itself poses problems such as word width, alignment, and 
  -- possibly on some RISC machines -- parameter passing using register windows.
* Each function used to implement forwardv:: may need to know the amount of data 
  pushed onto the stack for local variables.  (So to index backward.)
* The dispatch function must know about passed arguments.  While this is encoded 
  and available, what do you do for variable arguments and structures?  Oh yea, 
  how about void*?
* The stack must be indexed to extract return addresses -- that is, if this can 
  be done with your microprocessor of the day.  There are also 
  microprocessor/compiler specific data pushed onto the stack that the functions 
  may need to know about such as the variable frame on 680x0 microprocessors 
  (note the link instruction on each function entry).

At the time, these issues and others I haven't mentioned caused me think that DO 
reeks of assembly code that is not only tailored to the microprocessor, by the 
vendor's operating system as well.  This is a significant maintenance issue and 
is in no way portable.

The other item I wanted to add to the run time is "active objects" but it suffers 
from many of the same issues.

I think you touched on something I suspect about the NeXT run time:  it contains 
microprocessor/vendor specific assembly code.

I invite ideas of how to do this in a somewhat portable fashion.  Portability was 
one of the chief goals of the run time.


-dpg



Date: Wed, 20 Jan 93 23:40 PST
From: michael@stb.info.com (Michael Gersten)
To: gnu-objc@prep.ai.mit.edu
Subject: Re: The forward/performv Puzzle

Here's my comments on how to forward:

The issue is how to portable forward a call to someone else, given that you don't 
know how far up the stack the stack frame runs, nor the format of said stack frame.

Method one:
You decide to pass a message to an object. You call the dispatcher. The dispatcher 
must do one of two things: It must either call (or return the address of) the 
specified routine, or failing that, the "forward" routine.

Assumptions used: The number of arguments declared to be taken by the routine (in 
this case, an arbitrary number of args from the original routine, and the probably 
0 args declared by the forward routine) do not affect the format of the stack frame.

Disadvantages: Cannot retreive the arguments in the forward routine, nor can the 
forward routine pass those arguments to another routine.

Method two:
All method calls get another arg pushed on the stack, an invisible arg specifying 
how many bytes have been pushed on the stack. This is used to determine the stack 
size, which can then be "independently" copied
Advantages: Fairly portable (see below)
Disadvantages: Does not properly handle the case of one arg being a pointer to 
another arg -- you'll have instead a pointer to something back up the stack in 
another frame.
	Still relies on the forward method knowing the stack info.

Method three:
Attempting to emulate the next mechanism:
The forward method will be called with a special argument type, that can only be 
used by passing it to the performv method. The performv method will use this 
special type, with the goal of being implementation independent, to call the routine.

In order for performv to be so independent, it must take the new object, the new 
method name, and the old info; it would then unwind the stack to the point 
indicated in the special argument, modifies the stack frame in place to substitute 
the new object (which may be the same as the old), and the new method, and then 
reruns the send routine.

This requires that the send routine, when it fails, must instead send to the 
forward routine, and with a slightly different stack frame -- it needs to put 
onto the stack a new stack frame specifying the original object, the original 
method, and this special argument for performv.

Advantages: It works. It's fairly portable. It doesn't break under any arg types
Disadvantages: Requires that the machine have some way to unwind a stack frame 
(does this conflict with register windows machines?). This code (the unwinding 
of the stack, and the setting up info needed to unwind) would probably be machine 
specific, and only used in one or two places (not really much different than 
requiring each machien description to provide a prologue and epilogue routine). 
Not completely compatible with a message dispatcher that works by returning a 
pointer for someone else to call -- would require that the code so involved 
change from

	push args
	push obj
	push method
	call lookup
	jump addr

to
...
	call lookup
	jump NZ,addr
	jump massage_stack_and_forward

other arg passing conventions may require other changes -- you need a new #define 
for "the name of the insn pattern to use (usually jne) after calling the method 
lookup routine; must not jump if a lookup failure occurs". Also not quite standard 
in the implementation of forward and performv -- note that performv under this 
system does not return to the caller (forward), but instead to the caller of 
forward. Since sending a message "method1" to "foo" is translated into a "forward" 
message, this has the result of returning directly after the call to method1, with 
no way for that routine to know that a failure occured (and no way for the forward 
routine to do something AFTER the performv, which may vary well be desireable). No 
easy way to check for a "double forward", where the forward routine cannot operate 
because of a failed send (infinite loop in the error handler). Still, it looks 
like the best of the implementation independent ways. Would someone like to shoot 
it down please?
--
	Michael Gersten		michael@stb.info.com
NeXT Registered Developer (NeRD) # 3860 -- Hire me! Quick!
Will program computers for food (and net connection, health benefits, cash,...)

Date: Thu, 21 Jan 93 11:53:49 -0600
From: burchard@geom.umn.edu
To: michael@stb.info.com (Michael Gersten)
Subject: Re: The forward/performv Puzzle
Cc: gnu-objc@prep.ai.mit.edu

> (does this conflict with register windows machines?)

To avoid lots of hypothetical discussions, I suggest everyone thinking about this 
take a look at section 14.7 of the GNU CC manual, on C calling conventions (about 
a dozen pages).  It was an eye-opener for me---I hadn't realized that some 
platforms will go so far as to split a single argument into pieces, and pass part 
on the stack and part in a register.  And don't count on there being stack space 
"reserved" for the missing args/pieces that are passed in registers.

The varargs facilities in GNU CC provide a bit of guidance but not a lot of actual 
help.  The one thing that could be helpful is the builtin "function" 
__builtin_saveregs().  Don't try taking a pointer to this thing---it's really a 
slippery compiler trick to cause the insertion of some assembly code at the 
beginning of the routine (no matter where it's "called").  The assembly code for 
__builtin_saveregs() is maintained for each platform that needs it in libgcc2.c.  
Thankfully it isn't, in the end, all that terrible, and gives some hints about how 
the opposite operation (of loading arguments properly from a more uniform format) 
would proceed.

Still, after looking at all this, I have to regard the stack-oriented argument 
info maintained and published by the Obj-C runtime as being somewhat euphemistic.

--------------------------------------------------------------------
Paul Burchard	
``I'm still learning how to count backwards from infinity...''
--------------------------------------------------------------------

Date: Sat, 23 Jan 93 15:04:24 -0500
From: rms@gnu.ai.mit.edu (Richard Stallman)
To: Steve_Naroff@next.com
Cc: dennis_glatting@trirex.com, gnu-objc@prep.ai.mit.edu
In-Reply-To: <9301200150.AA24759@oz.NeXT.COM> 
  (message from Steve Naroff on Tue, 19 Jan 93 17:50:24 -0800)
Subject: The forward/performv Puzzle

I woul like to see a feature in C to allow construction of an argument
list with types and number of args chosen at run time,
plus a feature to call a function with such an argument list.
Forwarding could use this feature and would then not need
any assembler language support.


We would need to have descriptors for types.  They would not
necessarily have to give complete information about the type, but they
would have to give enough to decide how to pass an argument.  Perhaps
a __builtin_classify_type value plus the size would be enough info.

Then the arglist would be an array of this structure:

struct datum { int typeclass; int size; void *address; };

You would call a function using apply:

void apply (struct datum value, struct datum *arguments);


With some work, it would be possible to arrange to write apply
in libgc2.c and make it portable by using the same FUNCTION_ARG...
macros used in expand_call.  We would have to make those macros
use a struct datum rather than a tree node, to represent type info,
and change the machine descriptions, but that is ok.


Please tell me if you volunteer to write this.

Date: Sat, 23 Jan 93 16:29:14 -0600
From: burchard@geom.umn.edu
To: rms@gnu.ai.mit.edu (Richard Stallman)
Subject: Re: The forward/performv Puzzle
Cc: gnu-objc@prep.ai.mit.edu

Oops....a couple of typos made their way into our letters...just to avoid any 
confusion here they are....

Richard Stallman wrote:
>  void apply (struct datum value, struct datum *arguments);

where I imagine he meant something like:

  void apply(struct datum val, void (*func)(), struct datum *args);

Then I wrote:
>  static double double_identity_function(x) { return(x); }

meaning, of course,

  static double double_identity_function(double x) { return(x); }

-PB

Date: Sat, 23 Jan 93 17:01:04 -0600
From: burchard@geom.umn.edu
To: gnu-objc@prep.ai.mit.edu
Subject: Re: The forward/performv Puzzle [corrected]

[ Corrected version of my earlier letter. ]

Richard Stallman proposes:
>  void apply (struct datum value, struct datum *arguments);

I'm assuming that he meant:

  void apply(struct datum val, void (*func)(), struct datum *args);

The above is what is needed for performv.  For forwarding (and to get
the full benefit of the above facility) the opposite capability is
also required---namely, the ability for a single function to receive
calls issued with varying prototypes.  This will require two
"functions"

  void get_args(struct datum *arguments);

  void put_return(struct datum value);

In both cases, you'd supply type information; in put_return() you'd also 
supply the value.

In general, these two cannot be honest function calls, since for
example get_args() contains an implicit __builtin_saveregs().
Sometimes kludges are available to avoid assembler insertion, though.
For example, the following hack tricks many platforms into returning a
double from a nominal 32-bit function:

  #define RETURN_DOUBLE(x)				\
	({						\
	double rtn = x;					\
	unsigned int fake = *(unsigned int *)&rtn;	\
	double_identity_function(rtn);			\
	return(fake);					\
	})

  /* don't inline this */
  static double double_identity_function(double x) { return(x); }


Richard further proposes:
>  struct datum { int typeclass; int size; void *address; };

I don't think this machine-specific encoding is currently accessible
at run-time, because __builtin_classify_type() is of necessity a
compiler directive rather than an honest function call (due to the way
it is "called").

I think what is needed here is a simple, portable type
encoding---which conveniently enough already exists.  The Objective-C
runtime uses a simple string encoding which covers all possible C
types, including compound types.  So I'd suggest

  struct datum { const char *typedesc; void *address; };

You'd need to write a library function to convert this to the
machine-specific form, but the code for it would be simple, and
more-or-less xeroxed from BUILT_IN_CLASSIFY_TYPE.  Actually, for
better efficiency, we could even leave space in the datum structure
for caching the machine-specific info:

  struct datum
  {
  	/* User-level information */
  	const char *typedesc;
	void *address;

	/* System-cached information */
	int typeclass;
	int size;
  };

--------------------------------------------------------------------
Paul Burchard	
``I'm still learning how to count backwards from infinity...''
--------------------------------------------------------------------


Date: Sat, 23 Jan 93 15:20:20 -0600
From: burchard@geom.umn.edu
To: rms@gnu.ai.mit.edu (Richard Stallman)
Subject: Re: The forward/performv Puzzle
Cc: gnu-objc@prep.ai.mit.edu

Richard Stallman proposes:
>  void apply (struct datum value, struct datum *arguments);

The above is what is needed for performv.  For forwarding (and to get
the full benefit of the above facility) the opposite capability is
also required---namely, the ability for a single function to receive
calls issued with varying prototypes.  This will require two
"functions"

  void get_args(struct datum *arguments);

  void put_return(struct datum value);

In both cases, you'd supply type information; in put_return() you'd
also supply the value.

In general, these two cannot be honest function calls, since for
example get_args() contains an implicit __builtin_saveregs().
Sometimes kludges are available to avoid assembler insertion, though.
For example, the following hack tricks many platforms into returning a
double from a nominal 32-bit function:

  #define RETURN_DOUBLE(x)				\
	({						\
	double rtn = x;					\
	unsigned int fake = *(unsigned int *)&rtn;	\
	double_identity_function(rtn);			\
	return(fake);					\
	})

  /* don't inline this */
  static double double_identity_function(x) { return(x); }


Richard further proposes:
>  struct datum { int typeclass; int size; void *address; };

I don't think this machine-specific encoding is currently accessible
at run-time, because __builtin_classify_type() is of necessity a
compiler directive rather than an honest function call (due to the way
it is "called").

I think what is needed here is a simple, portable type
encoding---which conveniently enough already exists.  The Objective-C
runtime uses a simple string encoding which covers all possible C
types, including compound types.  So I'd suggest

  struct datum { const char *typedesc; void *address; };

You'd need to write a library function to convert this to the
machine-specific form, but the code for it would be simple, and
more-or-less xeroxed from BUILT_IN_CLASSIFY_TYPE.  Actually, for
better efficiency, we could even leave space in the datum structure
for caching the machine-specific info:

  struct datum
  {
  	/* User-level information */
  	const char *typedesc;
	void *address;

	/* System-cached information */
	int typeclass;
	int size;
  };

--------------------------------------------------------------------
Paul Burchard	
``I'm still learning how to count backwards from infinity...''
--------------------------------------------------------------------

Date: Sat, 23 Jan 93 15:20:20 -0600
From: burchard@geom.umn.edu
To: rms@gnu.ai.mit.edu (Richard Stallman)
Subject: Re: The forward/performv Puzzle
Cc: gnu-objc@prep.ai.mit.edu

Richard Stallman proposes:
>  void apply (struct datum value, struct datum *arguments);

The above is what is needed for performv.  For forwarding (and to get
the full benefit of the above facility) the opposite capability is
also required---namely, the ability for a single function to receive
calls issued with varying prototypes.  This will require two
"functions"

  void get_args(struct datum *arguments);

  void put_return(struct datum value);

In both cases, you'd supply type information; in put_return() you'd
also supply the value.

In general, these two cannot be honest function calls, since for
example get_args() contains an implicit __builtin_saveregs().
Sometimes kludges are available to avoid assembler insertion, though.
For example, the following hack tricks many platforms into returning a
double from a nominal 32-bit function:

  #define RETURN_DOUBLE(x)				\
	({						\
	double rtn = x;					\
	unsigned int fake = *(unsigned int *)&rtn;	\
	double_identity_function(rtn);			\
	return(fake);					\
	})

  /* don't inline this */
  static double double_identity_function(x) { return(x); }


Richard further proposes:
>  struct datum { int typeclass; int size; void *address; };

I don't think this machine-specific encoding is currently accessible
at run-time, because __builtin_classify_type() is of necessity a
compiler directive rather than an honest function call (due to the way
it is "called").

I think what is needed here is a simple, portable type
encoding---which conveniently enough already exists.  The Objective-C
runtime uses a simple string encoding which covers all possible C
types, including compound types.  So I'd suggest

  struct datum { const char *typedesc; void *address; };

You'd need to write a library function to convert this to the
machine-specific form, but the code for it would be simple, and
more-or-less xeroxed from BUILT_IN_CLASSIFY_TYPE.  Actually, for
better efficiency, we could even leave space in the datum structure
for caching the machine-specific info:

  struct datum
  {
  	/* User-level information */
  	const char *typedesc;
	void *address;

	/* System-cached information */
	int typeclass;
	int size;
  };

--------------------------------------------------------------------
Paul Burchard	
``I'm still learning how to count backwards from infinity...''
--------------------------------------------------------------------

Date: Sat, 23 Jan 93 19:55:49 -0500
From: rms@gnu.ai.mit.edu (Richard Stallman)
To: burchard@geom.umn.edu
Cc: gnu-objc@prep.ai.mit.edu
In-Reply-To: <9301232120.AA15572@mobius.geom.umn.edu> (burchard@geom.umn.edu)
Subject: The forward/performv Puzzle

    The above is what is needed for performv.  For forwarding (and to get  
    the full benefit of the above facility) the opposite capability is  
    also required---namely, the ability for a single function to receive  
    calls issued with varying prototypes.

Doesn't varargs accomplish that?

But we do need in addition a way to find out what sorts of arguments a
given method expects.

    I don't think this machine-specific encoding is currently accessible  
    at run-time, because __builtin_classify_type() is of necessity a  
    compiler directive rather than an honest function call (due to the  
    way it is "called").

It is true that ___builtin_classify_type is only computed at compile time,
but that isnot a problem.  There is no occasion in the scheme I proposed
to wish it were otherwise.


Date: Sat, 23 Jan 93 20:05:37 -0600
From: burchard@geom.umn.edu
To: rms@gnu.ai.mit.edu (Richard Stallman)
Subject: Re: The forward/performv Puzzle
Cc: gnu-objc@prep.ai.mit.edu

[More about RMS's excellent "apply" idea...]
>
> > [Paul wants] the ability for a single function to receive 
> > calls issued with varying prototypes.
>
> Doesn't varargs accomplish that?

Unfortunately not.  The "apply" extension is supposed to be a run-time
version of function-pointer casting, and thus compatible with function
prototypes.  This unfortunately makes it *incompatible* with varargs,
because "..." arguments effectively use a different calling convention
than normal prototyped arguments (due to type promotion).

And of course varargs only attempts to handle argument types, not return types.

> But we do need in addition a way to find out what sorts of
> arguments a given method expects.

Correct---in this respect the "apply" extension is no different than
varargs; we must use "required" args (or something similar) to get
that info.

In an Obj-C forwarding implementation, self and _cmd would be the
required args.  The Obj-C runtime would be able to determine from that
data what the remaining expected arg types were.

> It is true that ___builtin_classify_type is only
> computed at compile time, but that isnot a problem.  There
> is no occasion in the scheme I proposed to wish it were
> otherwise.

This may be true if "apply" is simply a way of avoiding large case
statements (i.e., if you always "apply" functions consistently with
their prototypes, which are in principle known at compile time).

However, Obj-C forwarding requires that we be able to "apply" a fixed
function as if it had different prototypes.  Moreover, in a
distributed object setting, those prototypes may not---even in
principle!---be knowable at compile time.  Therefore, the type system
must be fully accessible at run time.

Actually, this isn't worth haggling over, because the run-time
equivalent of ___builtin_classify_type is so trivial to write.

--------------------------------------------------------------------
Paul Burchard	
``I'm still learning how to count backwards from infinity...''
--------------------------------------------------------------------

From: Andrew Athan 
Date: Tue, 26 Jan 93 09:59:22 -0600
To: gnu-objc-runtime@prep.ai.mit.edu
Subject: forward/performv: summary


I'm not sure how many of you (probably all of you?) are on the
gnu-objc list, so I am forwarding parts of a discussion on
implementing forward:/performv: behavior in gnu-objc (this is
currently missing).

No-one has yet volunteered to write the code.

aca
-----------------------------------------------------
> uunet!geom.umn.edu!burchard -- started this all off

> There is one major feature missing from GNU's Objective-C run-time
> at the moment, which prevents this project from progressing to the
> more desirable very, very preliminary :-) stage.  That feature is the
> functionality of NeXT's forward and performv, which are required in
> order that arbitrary messages can be captured by proxy objects and
> then later executed by the remote objects they stand for.


>Michael Gersten
> To avoid lots of hypothetical discussions, I suggest everyone
> thinking about this take a look at section 14.7 of the GNU CC manual,
> on C calling conventions (about a dozen pages).

>Dennis Glatting on some of the issues
> * Implementation of forwardv:: would require stack frame copying.
> (or at least modification of the existing stack frame.)
> * The microprocessor itself poses problems such as word width,
> alignment, and -- possibly on some RISC machines -- parameter passing
> using register windows.
> * Each function used to implement forwardv:: may need to know the
> amount of data pushed onto the stack for local variables.  (So to
> index backward.)
> * The dispatch function must know about passed arguments.  While this
> is encoded and available, what do you do for variable arguments and
> structures?  Oh yea, how about void*?
> * The stack must be indexed to extract return addresses -- that is, if
> this can be done with your microprocessor of the day.  There are also
> microprocessor/compiler specific data pushed onto the stack that the
> functions may need to know about such as the variable frame on 680x0
> microprocessors (note the link instruction on each function entry).

> uunet!geom.umn.edu!burchard -- a summary of what needs to be done
> 1. id objc_msgSend(id self, SEL op);
	return objc_forwardingHandler if method missing

2. id objc_forwardingHandler(id self, SEL op);
	ask runtime for expected arg types, based on self and op
	use __builtin_get_args() to get those expected args
	send -forward::return: to object if it implements it, passing
		back the return value with __builtin_put_return()
	else call usual runtime error handler
	
3. -forward:(SEL)op :(typed_data_t *)args return:(typed_data_t)rtn;
	[optional instance method]

4. -perform:(SEL)op :(typed_data_t *)args return:(typed_data_t)rtn;
	[builtin Object method]
	use __builtin_apply() to call objc_msgSend() with the
		correct cast for the method being performed
	[this scheme will allow the call to be forwarded again]

5. void __builtin_get_args(typed_data_t *args);
	[a convenience built on GCC's varargs support]
	insert __builtin_saveregs()
	loop through the given type info:
		get low-level info with __builtin_classify_typedesc()
		use varargs code, ___builtin_args_info(), and
			__builtin_next_arg()) to do the real work
	[we can't use va macros because this is a runtime operation]

6. void __builtin_classify_typedesc(typed_data_t *data);
	[run-time version of GCC's __builtin_classify_type(),
		__alignof__(), and sizeof() combined]
	fill in machine-dependent fields of structure, based on
		given type descriptor string

7. void __builtin_put_return(typed_data_t value);
	machine-dependent; based on GNU CC return convention macros
	[REQUIRES SOME WORK]
	
8. void __builtin_apply(typed_data_t rtn; void (*f)(), typed_data_t *args);
	machine-dependent; based on calling conventions...
	[REQUIRES REAL WORK]

[In the above, we are using

	typedef struct _typed_data_t {
	    const char *typedesc;		/* as in Obj-C */
	    void *value;
	    /* ... fields for machine-dependent data... */
	} typed_data_t;

> Rms' call to arms
> rms> Please tell me if you volunteer to write this.

aca

From: "Geoffrey S. Knauth" 
Date: Sun, 31 Jan 93 13:35:19 -0500
To: gsk@marble.com
Subject: [rms@gnu.ai.mit.edu: The forward/performv Puzzle]

Return-Path: 
Date: Sun, 24 Jan 93 14:24:41 -0500
From: rms@gnu.ai.mit.edu (Richard Stallman)
To: burchard@geom.umn.edu
Cc: gnu-objc@prep.ai.mit.edu
In-Reply-To: <9301240205.AA15818@mobius.geom.umn.edu> (burchard@geom.umn.edu)
Subject: The forward/performv Puzzle

    > It is true that ___builtin_classify_type is only
    > computed at compile time, but that isnot a problem.  There
    > is no occasion in the scheme I proposed to wish it were
    > otherwise. 


    This may be true if "apply" is simply a way of avoiding large case  
    statements (i.e., if you always "apply" functions consistently with  
    their prototypes, which are in principle known at compile time).  


    However, Obj-C forwarding requires that we be able to "apply" a fixed  
    function as if it had different prototypes.

That is not how I envision apply to be used.  apply is to be used to
call an individual method chosen at run time.  Each method has a fixed
set of argument types.  In a correct program, it will always be applied
with arguments of those types.

The forwarder will be called with arguments of different types,
but it will not be called with apply.  It *uses* apply.



From: "Geoffrey S. Knauth" 
Date: Sun, 31 Jan 93 13:35:27 -0500
To: gsk@marble.com
Subject: [burchard@geom.umn.edu: Re: The forward/performv Puzzle]

Return-Path: 
Date: Sun, 24 Jan 93 14:42:07 -0600
From: burchard@geom.umn.edu
To: gnu-objc@prep.ai.mit.edu
Subject: Re: The forward/performv Puzzle

> > However, Obj-C forwarding requires that we be able to "apply" a 

> > fixed function as if it had different prototypes.

> The forwarder will be called with arguments of different
> types, but it will not be called with apply.

Exactly: the forwarding handler in the Obj-C run-time has no choice  
about the way it is called, because once a pointer to it is returned  
from objc_msgSend(), BAM!, it gets called as if it had the prototype  
of the desired (missing) method.  So certainly, we have no chance to  
call it with "apply".

OK, so now we are inside that forwarding handler, having been sent  
the arguments the method would have expected, and we want to either  
"apply" some replacement method to those args, or ship off the arg  
frame to something like a forward:: method, or make use of whatever  
your favorite forwarding protocol is (more on this below).

But...but...how do we get a hold of those args that we were sent, in  
order to "apply" a new method to them, or forward:: them somewhere  
else?  And once we determine a return value, how do we get that out  
of the the forwarding handler and back into the code the missing  
method was called from?  This is why forwarding requires "get_args"  
and "put_return", not just "apply".

> It *uses* apply. 


Actually, that would be tragic.  If it's going to be of any use for  
distributed objects, the forwarding handler cannot presume upon  
itself the sole power to call a replacement method.  After all, the  
replacement method may need to be called in a completely different  
execution context!

For this reason, the "forward" and "perform" parts of the  
method-replacement process must be cleanly separated; only "perform"  
is allowed to use "apply".  We get a 3-step process:

1.  objc_msgSend() offers up a forwarding handler to be called in  
place of the missing method.  This handler bundles up the args into a  
standard format, and passes along the expected return value when it's  
all over.  [This uses only "get_args" and "put_return".]

2.  Using some standard Obj-C "forward" message, the forwarding  
handler passes all this information, in standard format, to the  
object whose method was missing.  [This uses nothing special.]

3.  The object may do whatever it likes with this information.  One  
possibility is to compute a new object, selector, and arg frame from  
the given information, and pass that to some standard Obj-C "perform"  
message.  Another possibility is to send instructions to a remote  
process to do the same.  [This uses only "apply".]

In other words, NeXT (or would that be Steve?) basically discovered  
the right strategy for forwarding; the API just needs a bit of  
touching up.

--------------------------------------------------------------------
Paul Burchard	
``I'm still learning how to count backwards from infinity...''
--------------------------------------------------------------------




From: "Geoffrey S. Knauth" 
Date: Sun, 31 Jan 93 13:35:37 -0500
To: gsk@marble.com
Subject: [rms@gnu.ai.mit.edu: The forward/performv Puzzle]

Return-Path: 
Date: Sun, 24 Jan 93 17:05:49 -0500
From: rms@gnu.ai.mit.edu (Richard Stallman)
To: gnu-objc@prep.ai.mit.edu
In-Reply-To: <9301242041.AA16505@mobius.geom.umn.edu> (burchard@geom.umn.edu)
Subject: The forward/performv Puzzle

    But...but...how do we get a hold of those args that we were sent, in  
    order to "apply" a new method to them, or forward:: them somewhere  
    else?

It should work to do this with varargs, even when args have promotable
types such as float.  A properly declared "real" varargs function will
never receive a nameless arg of type float, as it would have been
promoted to double.  But it will work in the forwarder to use va_arg
for an arg of type float if that is what was passed.

As for finding out how many args were passed, and of what type,
you need to get that from the tables defining the methods of the class.

Storing the return value does need some new construct.
A new built-in function should be able to do the job.

    Actually, that would be tragic.  If it's going to be of any use for  
    distributed objects, the forwarding handler cannot presume upon  
    itself the sole power to call a replacement method.  After all, the  
    replacement method may need to be called in a completely different  
    execution context!

I have not been using your distinction between "forward" and
"perform".  When I talk of "the forwarder" I have been alking about
the whole process of forwarding a message to another object.



From: "Geoffrey S. Knauth" 
Date: Sun, 31 Jan 93 13:35:45 -0500
To: gsk@marble.com
Subject: [burchard@geom.umn.edu: Light at the end of the forwarding tunnel?]

Return-Path: 
Date: Sun, 24 Jan 93 17:00:20 -0600
From: burchard@geom.umn.edu
To: rms@gnu.ai.mit.edu (Richard Stallman)
Subject: Light at the end of the forwarding tunnel?
Cc: gnu-objc@prep.ai.mit.edu

> > But...but...how do we get a hold of those args that we were
> > sent, in   order to "apply" a new method to them, or
> > forward:: them somewhere   else? 

> 

> With varargs.  This can work, even when args have
> promotable types such as float.  A "real" varargs
> function will never receive an arg of type float, but it
> will work in the forwarder to ask for an arg of type float if
> that is what it was given.  


Aha, I didn't realize GCC's varargs support was already general  
enough to handle non-promoted arg types.  That's wonderful!

I stand, happily, corrected.  This saves over 1/3 of the work!

> Storing the return value does need some new construct. A
> new built-in function should be able to do the job. 


Right, and this won't be nearly as complicated as grabbing args.

> I have not been using your distinction between "forward"
> and "perform".  When I talk of "the forwarder" I have been
> alking about the whole process of forwarding a message to
> another object. 


OK, I'm glad we're on the same wavelength. So, here is an 8-step plan  
of what needs to be done.  The "apply" function appears once again as  
the biggest job:

1. id objc_msgSend(id self, SEL op);
	return objc_forwardingHandler if method missing

2. id objc_forwardingHandler(id self, SEL op);
	ask runtime for expected arg types, based on self and op
	use __builtin_get_args() to get those expected args
	send -forward::return: to object if it implements it, passing
		back the return value with __builtin_put_return()
	else call usual runtime error handler
	
3. -forward:(SEL)op :(typed_data_t *)args return:(typed_data_t)rtn;
	[optional instance method]

4. -perform:(SEL)op :(typed_data_t *)args return:(typed_data_t)rtn;
	[builtin Object method]
	use __builtin_apply() to call objc_msgSend() with the
		correct cast for the method being performed
	[this scheme will allow the call to be forwarded again]

5. void __builtin_get_args(typed_data_t *args);
	[a convenience built on GCC's varargs support]
	insert __builtin_saveregs()
	loop through the given type info:
		get low-level info with __builtin_classify_typedesc()
		use varargs code, ___builtin_args_info(), and
			__builtin_next_arg()) to do the real work
	[we can't use va macros because this is a runtime operation]

6. void __builtin_classify_typedesc(typed_data_t *data);
	[run-time version of GCC's __builtin_classify_type(), 

		__alignof__(), and sizeof() combined]
	fill in machine-dependent fields of structure, based on
		given type descriptor string

7. void __builtin_put_return(typed_data_t value);
	machine-dependent; based on GNU CC return convention macros
	[REQUIRES SOME WORK]
	
8. void __builtin_apply(typed_data_t rtn; void (*f)(), typed_data_t  
*args);
	machine-dependent; based on calling conventions...
	[REQUIRES REAL WORK]

[In the above, we are using

	typedef struct _typed_data_t {
	    const char *typedesc;		/* as in Obj-C */
	    void *value;
	    /* ... fields for machine-dependent data... */
	} typed_data_t;

So is it time to start dividing this task up?  Are people excited?   
Or should we plan the implementation of the "apply" function in more  
detail first?

--------------------------------------------------------------------
Paul Burchard	
``I'm still learning how to count backwards from infinity...''
--------------------------------------------------------------------




From: "Geoffrey S. Knauth" 
Date: Sun, 31 Jan 93 13:35:55 -0500
To: gsk@marble.com
Subject: [casey!gaboon!seltd!lerman@uunet.uu.net: Re: The forward/performv Puzzle]

Return-Path: 
From: Kenneth Lerman 
X-Mailer: SCO System V Mail (version 3.2)
To: burchard@geom.umn.edu, rms@gnu.ai.mit.edu
Subject: Re: The forward/performv Puzzle
Cc: gnu-objc@prep.ai.mit.edu
Date: Sun, 24 Jan 93 15:06:11 EST


It was asked:
|    The above is what is needed for performv.  For forwarding (and to get
|    the full benefit of the above facility) the opposite capability is
|    also required---namely, the ability for a single function to receive
|    calls issued with varying prototypes.

|Doesn't varargs accomplish that?

No, it does not.  Varargs, in general, only works if the function was
declared to have a variable number of arguments.  Of course, some
compilers on some platforms may have a varargs which will work anyway.

Ken



From: "Geoffrey S. Knauth" 
Date: Sun, 31 Jan 93 13:39:29 -0500
To: gsk@marble.com
Subject: [Steve_Naroff@next.com: Re: The forward/performv Puzzle]

Return-Path: 
From: Steve Naroff 
Date: Mon, 25 Jan 93 10:43:32 -0800
To: rms@gnu.ai.mit.edu (Richard Stallman)
Subject: Re: The forward/performv Puzzle
Cc: Steve_Naroff@next.com, dennis_glatting@trirex.com,
        gnu-objc@prep.ai.mit.edu, wood@next.com, mwagner@next.com


rms> Please tell me if you volunteer to write this.

I am real busy, but Tom Wood (who now works with me) might be  
interested in writing this. I will discuss it with him.

thanks, snaroff.

Return-Path: 
Date: Sat, 20 Feb 93 14:04:33 -0600
From: Paul Burchard 
To: Kresten Krab Thorup 
Subject: Re: Optimizing the GNU objc runtime
Cc: gnu-objc@gnu.ai.mit.edu
Reply-To: burchard@geom.umn.edu

Kresten Krab Thorup  writes:
> We will here use the trick of installing a special
> method __objc_missingMethod at all indices for which
> there do not exist a corresponding message.  That
> function handles the case where a message is not
> recognized, and if possible, forward the message to
> `doesNotRecognize:' in the receiver.

This is a good trick---and it thankfully leaves open a way to  
implement full-fledged forwarding as previously discussed on this  
mailing list.  Not only that, but your proposed system should even  
make forwarding noticeably faster, by cutting the message dispatch  
overhead, which is normally paid several extra times during the  
forwarding process.

Your proposal seems like a good idea; I'd love to have efficient  
messaging for some numerical algorithms I'm working with.  My only  
question would be how much time a "typical" program would spend at  
startup, filling all the dispatch caches. Would the wait be  
noticeable on a human timescale?  (If you have ~100 classes with ~100  
methods each, and you want to keep extra startup overhead to no more  
than 1/20 second, that only allows you 5 ms per lookup.)

--------------------------------------------------------------------
Paul Burchard	
``I'm still learning how to count backwards from infinity...''
--------------------------------------------------------------------

Return-Path: 
Date: Sun, 21 Feb 1993 23:22:37 +0100
From: Kresten Krab Thorup 
To: rms@gnu.ai.mit.edu (Richard Stallman)
In-Reply-To: <9302212157.AA01459@mole.gnu.ai.mit.edu>
Subject: Re: Gnu objc runtime [comments and bug reports]
Cc: krab@iesd.auc.dk, gsk@marble.com

Richard Stallman writes:
>Could you please send me individual separate patches for those three
>problems?

Do you really want this?  Most of these bugfixes are outdated the
moment I implement the new messenger and initialization mechanism
described in my papers `Optimizing ...' and `How to do lookup...'.
In my oppinion it would be waste of time patching up the current
runtime, at least `core.c'. 

Realize the facts:  This is a completely new runtime, since an objc
runtime is practically nothing but initialization an message lookup.  

I have been working hard for the last days, trying to convince you
that I actually do know what I am talking about.  This is what you
questioned in the first place.

I am willing to supply the patches you requested, but I would rather,
if I was `allowed' to recode the initialization and lookup sections
completely.  

All I need in order to do this is that special `__builtin_forward'
which I am not able to program myself, since I am not into assember,
neither the internals of gcc.  However, if you would do this for me,
you should do it for sparc architecture first, since I am sitting on a
Sun.

Regards, Thanks, Please, etc.
Kresten

NOTE // The letter is Cc'ed to gsk since it contains essential
	questions for the gnu-objc project.			//

Statistics
 filename:           forward-performv
 number of mails:    28
 number of writers:  10
 line count:         1175
 word count:         6816
 character count:    46474

created by Helge Hess ( helge@mdlink.de )
MDlink online service center ( www.mdlink.de )