[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: problems with forward invocation (gcc bug?)
From: |
Richard Frith-Macdonald |
Subject: |
Re: problems with forward invocation (gcc bug?) |
Date: |
Tue, 31 Jul 2001 10:18:49 +0100 |
On Tuesday, July 31, 2001, at 08:56 AM, Wim Oudshoorn wrote:
Problems with forwarding.
Looking somewhat deeper in the problem I encountered
with forwarding it seems there are few problems
with the GNUstep forwarding mechanism.
(A) Finding type information of the selector.
(B) Thread safety.
Besides these issues I wonder why the method
- forward::
is never called anymore? Is this done deliberatily?
If so, why does NSObject still contain the method
- forward:: (which turns it into - forwardInvocation:)?
If ffcall is not in use, the runtime still calls forward::
so the code in NSObject is there to handle that.
Also I think that (A) will also creates problems with
other parts of GNUstep that rely on type information
of the selector, e.g. `methodSignatureForSelector:' etc.
Ok, now the issues I am struggling with.
(There also exists the possibility
that I screwed up the GNUstep + GCC configuration.)
A. Type info
=========================================================
The problem is best illustrated with two little programs:
--- testforward1.m ----
#include <Foundation/Foundation.h>
@implementation Test: NSObject
@end
int main (void)
{
Test* x = [Test new];
[x cString];
}
--- testforward2.m ----
#include <Foundation/Foundation.h>
@implementation Test: NSObject
@end
int main (void)
{
id x = [Test new];
[x cString];
}
------------------------
The only difference between the two programs is that
the variable `x' is typed `Test *' in the first program
and `id' in the second. If you execute these programs they
both exit with an exception:
testforward1:
Uncaught exception
NSInvalidArgumentException,
reason: Invalid selector cString (no type information)
testforward2:
Uncaught exception
NSInvalidArgumentException,
reason: Test does not recognize cString
As far as I can see testforward2 generates the correct exception.
The reason for the difference is that in the first program
gcc KNOWS that `x' is of type `Test *' and this class does
not implement `cString'.
Therefore the selector generated by the compiler has
NO type information associated with it.
In the second program the compiler does NOT KNOW to which
class `x' belongs and (probably during linking) merges
this selector with the selector of `NSString'.
I think this is really a compiler bug ... the compiler should know
how the method is being called, so it should generate the correct
type information. IMO the types supplied by the compiler should
always reflect the actual arguments passed and return type expected
by the compiled code.
For this problem there is a rather trivial fix, namely
in the function `GSInvocationCallback' refetch
the selector if it has no type information.
(the GNU objc runtime provides methods to
get a selector with type information)
While this would (mostly) work, it could fail if there are two classes
with methods of the same name but different signatures, since the type
signature selected may not match that of the method eventually called,
or it may not match the arguments/return type expected in the compiled
code, or both!.
A completely reliable solution does require the compiler to be fixed.
Then the invocation code could compare the type signatures supplied by
the caller and the receiver, and raise an exception if they don't match.
An additional problem arises with Distributed Objects, the class to which
the message is being sent may not exist in the process, so the runtime
may not have any typed version of the selector at all! In this situation
a methodSignatureForSelector: message should be sent to the object in the
remote process in order to find out what signature it expects - but the
existing code can't do that because it needs to send the message to the
receiver, but it can't unpack the receiver from the stackframe without
knowing what the return type is. To workaround this without the compiler
being fixed to provide the calling signature, we would need to modify the
runtime to call gs_objc_msg_forward() with two parameters (receiver and
selector), not just with the selector. Then gs_objc_msg_forward could
ask the receiver for its method signature, and use the resulting type
information.
[Patch available on request]
B. Thread safety
=========================================================
While debugging problem (A) I found that the basic
forwarding mechanism does the following:
objc runtime GSFFCallInvocation
<gs_objc_msg_forward>
|-------1. get forward implementation ---> |
. |
. Store selector in
. global variable.
. |
|<-----------------------------------------
|
|
| <GSInvocationCallback>
| -----2. call forward implementation ---->|
|
Retrieve selector
from the global
variable.
|
Forward the call
to -forwardInvocation
And because the selector is in the `va_list'
argument of the second call anyway it seems that we can dispose
of the global variable. But in order to retrieve the selector
from the `va_list' we need to initialize the `va_list' with
the return type of the method and to figure out the return
type we need the selector. So we have chicken and egg problem.
Note: As far as I can see the va_list only looks if the
return type is a struct or something else.
(When a struct is returned gcc adds an
extra hidden parameter at the beginning of
the argument list. The hidden parameter
is the adress of the return struct).
For this I can see basically three solutions.
Sol 1. Add lock around message forwarding in the
objc runtime.
Disadvantages:
1. changes the runtime
2. locks until the GSinvocationCallback is
finished.
Sol 2. Add lock in GSFFCallInvocation.
That is, lock in gs_objc_msg_forward and
unlock in GSInvocationCallback
Disadvantages:
1. It is very bad to assume that
code that calls gs_objc_msg_forward will
really execute the returned function.
Sol 3. Make a pool of callback wrappers of
GSInvocation callback's and let
the function gs_objc_msg_forward return the correct
version, or create one if no suitable version exist.
Disadvantages:
1. Is more complicated.
As you can probably tell, Sol 3 has my vote.
[I have not implemented this yet.]
A simple pool of wrappers would be a bit inefficient ... it would have
to be
a map table protected by a lock. A more efficient solution would be to
have
a small number of wrappers (one for each of the common return types) and
use
a lock protected pool only for methods with unknown or complex return
types.