[Date Prev][Date Next][Thread Prev][Thread Next][Author Index][Date Index][Thread Index]

Re: rewriting proxies' sending half


 - Response to Chris Hibbert's varargs proposal:
    - Shrinking the call further.
    - An alternative that avoids varargs AND may be smaller yet.
 - A garbage-collector unsafety that I just noticed in one of our common
   idioms, and how to fix it.
 - DSPTR proposal.

> From: hibbert@evilotto (Chris Hibbert)
> The handleSend method is pretty simple.  It uses VARARGS, and knows
> that the first few arguments describe the return value type, receiver
> type, receiver object number, and the message to be sent.  The rest of
> the arguments describe, in pairs, each of the arguments and its type.
> If we need to support output args, then there can be a marker
> somewhere along the way that marks the transition.  handleSend()
> doesn't need to know anything about particular messages, it just
> transmits whatever the proxy claims its message looks like.

How about a single argument carrying the entire signature of those that
follow, rather than doubling the number of arguments?  Stubble already
generates such a signature for the receive side (though it appears as
code, rather than a table.)

(The effect of overloading on return type can be obtained by passing a
 reference to storage for the return value rather than accepting the
 return value through the normal function-return mechanism.)

> Is there anything wrong with this scheme?  Is there any reason I
> shouldn't add the handleSend interface to CommXcvr's public interface
> (and make the old protocol private and non-virtual)?  

What are you buying, besides the appearance of simplicity in the
automatically-generated code?

It isn't execution time:  By going to varargs you've forced the compiler
to generate the large, slow calling sequence, you've added extra arguments
that must be stored, extra execution to sort them out again - after which
the code makes the same calls that would have been made directly.  And the
current approach can be inlined (even though trans is an abstract type)
while the replacement can not.

It doesn't appear to be code size, especially on a SPARC.  (I haven't
looked at compiler output from test cases, so the following is subject
to correction.)

For arguments, the current method uses a two-argument register-only call,
and the first argument and the pointer to the Xcvr's vtable should already
be in registers by the time the argument-passing stage is reached.  So the
additional code for each argument is something like:

	load		register,register
	indirect.call	register[offset.to.function]

This would be replaced by something like:

	load		type.designator.literal,register
	store		register[offset.to.argument.slot]
	load		register,register
	store		register[offset.to.argument.slot]

plus an amortized share of the extra overhead for a varargs-type call.
This may be a net loss (which might have been more visible if the example
had included arguemnts to the member function.)

In the remaining portions, the savings come from elimination of the
extraneous calls for protocol miscelaney.  But about the same (perhaps
more) savings can be realized by collapsing the common sequences into
single shorthand calls:

	commHandler->noArgsResultVoid(category, objnum, "message");


	return CAST(Rational, commHandler->noArgsResultHeaperP(
		cat_Calc, objectNumber, "pop2" , cat_Rational


	(Similarly for noArgsResultVar(), noArgsResultUInt4(), ...)


	SPTR(CommXcvr) trans =
		commHandler->args(category, objnum, "message", arg1);
	trans->send(...);	//use existing stuff for args beyond the first

	CommXcvr * temp = trans;
	delete temp;


	SPTR(Rational) result;

	SPTR(CommXcvr) trans =
		commHandler->args(category, objnum, "message", arg1);
	trans->send(...);	//use existing stuff for args beyond the first
	result = CAST(Rational, trans->resultHeaperP(cat_Rational));

	CommXcvr * temp = trans;
	delete temp;
	return result;


	(similarly for resultVar(), resultUInt4(), ...)


You might get it down further by providing a corresponding shorthand for one-
argument calls, and/or absorbing the last argument into the result postamble.
But we're already down to args+1 calls, and beyond one changing-type item per
call, combinatorial explosions get out-of-hand.  Further, these tricks might
require upgrades to Formic - though they could be added later if the
additional savings warrant them.

I suspect that, even without the additional savings of the previous
paragraph, the code would be about as small as, and perhaps significantly
smaller than, that which would be generated by the varargs approach (even
with the signature-argument modification).

Total changes:

 - Mod to stubble script.  (Note that the naming of the routines may allow
   construction of the routine names by concatenation.)
 - CommXcvr:
    - A new method overResultType(...) corresponding to each receive of a valid
      return type (including void), which does the return-getting sequence
      from "this->over()" to "this->goodbye();".
    - A new method resultType(...) for each return type, approximately:
         return this->overResultType(...);

 - CommHandler:
    - A new private inline method preamble(Category *, IntegerVar, char *),
       - allocates a CommXcvr
       - sends it hello()
       - returns it
      (This might generate less code if done as a macro.)
    - A new method arg(Category *, IntegerVar, char *, Type arg1) for each
      argument type, approximately:
         SPTR(CommXcvr) trans = this->preamble(...);
         return trans;"
    - A new method noArgsResultType(...) corresponding to each
      CommXcvr::overResultType(), approximately:
	 SPTR(CommXcvr) trans = this->preamble(...);
	 SPTR(Type) result = trans->overResultType(...);
	 <delete the CommXcvr>
	 return result;

> Should handleSend go in commHandler instead?  It'd save the three
> lines of code per proxy method that create and throw away the
> CommXcvr.   

I incorporated some of that above.  Thanks for pointing it out.
Offloading the Xcvr allocation, deletion, and strongpointer to the
commHandler is a space saving that can't be matched by the multiple-
call alternatives (though some can be offloaded by various tricks -
see below for one of them).


One of our common idioms (which appears in the proxy code) is:

	SPTR(Type) instance = ...;
	Type * temp = instance;
	delete temp;

Though the way it is used in the proxy is GC-safe (because no allocation
is possible before the SPTR goes out-of-scope), the construct itself is
not.  A safe form would be:

	SPTR(Type) instance = ...;
	Type * temp = instance;
	instance = NULL;
	delete temp;

This is a candidate for an inline member function, thus:

	SPTR(Type) instance = ...;

A related idiom (actually a subset - and the particular subset used in
the proxies) is:

		SPTR(Type) instance = ...;
		Type * temp = instance;
		delete temp;

i.e. instance is a pointer to a transient object, which is to be
deallocated upon exit from the block.  This code works correctly
if the block exits normally, but if flow leaves via a BLAST() it
leaves a "dropping" to be picked up later by the garbage collector.

Yet the SPTR is already a smart bomb.  It has already incurred the
overhead necessary to catch the exception.  It might as well do the
deallocation, too.  (Indeed, this is the problem that inspired
BOMBs in the first place.)

This way the code would read:

		DSPTR(Type) instance = ...;

The code is prettified further.  Nobody has to write the object-deletion
code (beyond that extra "D"), or check that it is correct.  The garbage
collector is run less often, and never has to collect the thing "instance"
holds.  (Something similar could take the object out of the garbage-
collector's overhead alltogether.)  In the DSPTRs-are-not-inline case,
they vanish the code-space overhead that would otherwise appear at the
end of multiple-Xcvr-call proxy methods.  And we get to add another pun
to our collection.

I hope that last doesn't influence any decisions.  B-)