[comp.lang.c] Register unions

noise@eneevax.UUCP (Johnson Noise) (02/13/88)

	While testing the availaility of register variables on several
machines and compilers, I thought of this possibility:

	typedef union
	{
		char b;
		short w;
		long l;
	} dreg;

	typedef union
	{
		char *b;
		short *w;
		long *l;
	} areg;

	register dreg d0, d1, d2;
	register areg a0, a1;

which would effectively allow full use of cpu registers.  Note the
similarity to 68k assembly.  Unfortunately, all the compilers I have
tried (including pcc) will not allocate unions in registers.  My initial
suspicion is that most compilers simply do not allow anything other than
basic types to be allocated in registers.  I realize that combining
pointers and non-pointers in 68k systems is not possible, but I don't
intend to do that.  I don't see any immediate problems in allowing
such a declaration (with checks on 68k's).
	Has anyone else considered this possibility? Is there a compiler
which allows this?  If it were possible, one could almost write exact
assembly in C.  It probably is not neccessary for the most part, but
it would further reduce the neccessity of assembly (not to mention tearing
up benchmarks :-). Comments, flames?
	If there aren't any problems, I would like to incorporate these
and other concepts into my copy of gcc.

gwyn@brl-smoke.ARPA (Doug Gwyn ) (02/13/88)

I think it's a valid notion.  Notice that the compiler would have
to determine whether or not to assign a genuine register variable
for the union based on what the member types are (they must all
be compatible with the register).  Also, only non-scratch
registers could be allocated, so for example on a 68000 register
D0, used as a scratch register in the generated code, would not
be assigned to such a union.  Which registers you got would of
course depend on the register allocation strategy of the compiler.
(You may get stack storage instead, if it runs out of registers.)
You may need to add a check for & being applied to such a union,
which should be illegal by normal C rules but may not be checked
for in a compiler that didn't think unions would ever be in real
registers.

On the other hand, I don't think this is nearly as useful as
you seem to think it would be.  Why would you want to twiddle
specific registers when not having to be concerned with such
matters is the whole point of using a higher-level language?

kgregory@bbn.COM (Keith D. Gregory) (02/14/88)

In article <1229@eneevax.UUCP> noise@eneevax.umd.edu.UUCP 
(Johnson Noise) writes:
> [code examples deleted]
>
>which would effectively allow full use of cpu registers.  Note the
>similarity to 68k assembly.  Unfortunately, all the compilers I have
>tried (including pcc) will not allocate unions in registers.  My initial
>suspicion is that most compilers simply do not allow anything other than
>basic types to be allocated in registers.  I realize that combining
>pointers and non-pointers in 68k systems is not possible, but I don't
>intend to do that.  I don't see any immediate problems in allowing
>such a declaration (with checks on 68k's).

I think that the answer becomes apparent with some more code examples :-)

struct mystruct {
	char	field1, field2, field3, field4;
	};

union myunion {
	int	field1;
	char	field2[2];
	};


The first example of course, would fail on the 68000 right away - you
can't arbitrarily split a register into 4 bytes.  The second is simply
more of the same, and the key point is that structures and unions are
manipulated using pointer arithmetic (to access the fields therein), and
pointers to registers can not exist.

-kdg

noise@eneevax.UUCP (Johnson Noise) (02/14/88)

In article <6831@ccv.bbn.COM> kgregory@ccv.bbn.com (Keith D. Gregory) writes:
>In article <1229@eneevax.UUCP> noise@eneevax.umd.edu.UUCP 
>(Johnson Noise) writes:
>> [code examples deleted]
>>
>
>struct mystruct {
>	char	field1, field2, field3, field4;
>	};
>
>union myunion {
>	int	field1;
>	char	field2[2];
>	};
>
>
>The first example of course, would fail on the 68000 right away - you
>can't arbitrarily split a register into 4 bytes.  The second is simply
>more of the same, and the key point is that structures and unions are
>manipulated using pointer arithmetic (to access the fields therein), and
>pointers to registers can not exist.
>

	I understand now, but for slightly different reasons.  In the
code examples I gave in my previous posting, pointer arithmetic was
not necessary to access the different fields.  The problem is with
most/least significant byte/word.

move.b _blah, d0 /* and */
move.w _blah, d0

move data into the least significant part of the register.  Where

move.b _blah, _there /* and */
move.w _blah, _there

move data into the most significant part of _there (assuming _there is
aligned to a longword address).  This is obviously inconsistent.  So
what about address registers (pointers)?  Pointers and adresses are always
32 bits and occupy the same position whether in memory or registers.
Some examples:

68k assembly		C source

movea _i, a0		a0.l = &i;	i must be long
movea _j, a0		a0.w = &j;	j must be short or long
movea _k, a0		a0.b = &k;	k may be char, short, or long

move.b (a0)+, (a1)+	*a1.b++ = *a0.b++;
move.l -(a0), -(a1)	*--a1.w = *--a0.w;

etc.  Again, comments, flames?

tanner@ki4pv.uucp (Dr. T. Andrews) (02/14/88)

In article <6831@ccv.bbn.COM>, kgregory@bbn.COM (Keith D. Gregory) writes:
) In article <1229@eneevax.UUCP> noise@eneevax.umd.edu.UUCP 
) > [code examples deleted]
) I think that the answer becomes apparent with some more code
) examples :-)
) [code examples where a register is not appropriate on machine 'X' ]

You miss the point.  In certain cases, which cases will vary from
machine to machine, it is desirable to put a union in a register.
For instance, to preserve registers or (more likely) to walk through
more than one kind of structure, you might put a union of several
pointers in a register.

The compiler should, when appropriate, put these unions into
registers.  Oh, do I need the standard code examples?

union _ptrs { struct _foo *foo_ptr; struct _bar *bar_ptr; }
	/* .. now, within a function  */
	register union _ptrs my_ptr;
	if (dealing_with_foo)
		my_ptr.foo_ptr = &foo_struct;
	else
		my_ptr.bar_ptr = &bar_struct;

A "good" compiler should know that it can put my_ptr into a register
(unless using some architecture where pointers don't fit into
registers; names omitted here to protect intel).

I suspect that few compilers are actually smart enough to put my_ptr
into a register, however.  The obligatory chorus of compiler vendors
bragging on \fItheir\fP compilers goes in messages immediately
following.
-- 
{allegra clyde!codas decvax!ucf-cs ihnp4!codas killer}!ki4pv!tanner

cjc@ulysses.homer.nj.att.com (Chris Calabrese[rs]) (02/16/88)

In article <1229@eneevax.UUCP>, noise@eneevax.UUCP (Johnson Noise) writes:
> 
> similarity to 68k assembly.  Unfortunately, all the compilers I have
> tried (including pcc) will not allocate unions in registers.  My initial
> suspicion is that most compilers simply do not allow anything other than
> basic types to be allocated in registers.  I realize that combining
> pointers and non-pointers in 68k systems is not possible, but I don't
> intend to do that.  I don't see any immediate problems in allowing
> such a declaration (with checks on 68k's).

The official AT&T version of the K&R portable C compiler only
supports register allocation of int's.  All other types are
silently assigned to core.  This, however, is something which
is supposed to change in the near future, at leas according to
my documentation.

	Chris Calabrese
	AT&T Bell Labs
	ulysses!cjc

guy@gorodish.Sun.COM (Guy Harris) (02/16/88)

> The official AT&T version of the K&R portable C compiler only
> supports register allocation of int's.  All other types are
> silently assigned to core.

I presume you mean "integral types", not "int"s.  The version of PCC2 that
comes with S5R3.1 on 3B2s, for instance, quite cheerfully puts "short"s,
"char"s, and "long"s into registers as well.

daveb@laidbak.UUCP (Dave Burton) (02/16/88)

In article <1229@eneevax.UUCP> noise@eneevax.umd.edu.UUCP (Johnson Noise) writes:
|	typedef union
|	{
|		char b;
|		short w;
|		long l;
|	} dreg;
|
|	typedef union
|	{
|		char *b;
|		short *w;
|		long *l;
|	} areg;
|
|	register dreg d0, d1, d2;
|	register areg a0, a1;
|
|which would effectively allow full use of cpu registers.  Note the
|similarity to 68k assembly. 

Though not specifically disallowed in the original specification of C,
this declaration is not useful in the manner you propose. K&R (pg 197)
and H&S (pg 109) state that storage is allocated from the beginning of the
union. The 68k series put the char type in the LSB, while the union
declarations want the char type in the MSB.
-- 
--------------------"Well, it looked good when I wrote it"---------------------
 Verbal: Dave Burton                        Net: ...!ihnp4!laidbak!daveb
 V-MAIL: (312) 505-9100 x325            USSnail: 1901 N. Naper Blvd.
#include <disclaimer.h>                          Naperville, IL  60540

hankd@pur-ee.UUCP (Hank Dietz) (02/18/88)

According to K&R (page 193), a key constraint on objects of
storage class register is that:

	"the address-of operator & cannot be applied to them"

Now, I certainly admit that the original intent was simply to
give the compiler a hint as to which auto variables should be
placed in registers (if you've got 'em), but the lack of an
address for an object in C means that the object NAME HAS NO
ALIASES, and that is far more important to a good optimizing
or parallelizing compiler.

Consider code like:

	*p = b * c;
	a = b * c;

In this code, unless the compiler can prove that b and c
cannot be pointed-to by p, b * c is NOT a common
subexpression.  Proving that p can't point at b or c is,
however, very difficult under many circumstances --
particularly if b, c, and p are globals accessed in a
separately compiled module.  If b and c are declared as
register variables, then the address can't be taken, hence the
proof is given without need to examine separately compiled
modules, or anything else for that matter.  One might argue
that removing a common subexpression like b * c is not that
important an optimization, but similar constraints apply for
nearly all optimizations and in automatic parallelization.

In summary, for the most efficient and effective compilation,
register OUGHT TO MEAN "HAS NO ALIAS" and nothing else.  Hence,
it makes sense to say register anything -- any type, any
storage class (not just register implies auto, as K&R suggest)
-- and let the compiler assign registers as it sees fit.  In
fact, this is how at least a few optimizing C compilers treat
register declarations anyway.

More details as to treatment of register as a "noalias"-like
storage-class modifier appear in my PhD Thesis, "The
Refined-Language Approach to Compiling for Parallel
Supercomputers," Polytechnic University, Brooklyn NY.  I'm
currently an assistant prof. of EE at Purdue and can supply
reprints of the thesis or related materials directly, if you
prefer (so long as there are not too many of you).

					-Hank Dietz

tainter@ihlpg.ATT.COM (Tainter) (02/20/88)

In article <6831@ccv.bbn.COM>, kgregory@bbn.COM (Keith D. Gregory) writes:
> In article <1229@eneevax.UUCP> noise@eneevax.umd.edu.UUCP 
> (Johnson Noise) writes:
> > [ A STATEMENT THAT REGISTERIZING A SUBSET OF UNIONS WOULD BE USEFUL ]

> I think that the answer becomes apparent with some more code examples :-)

This is a specious argument.  One cannot give examples of problems when the
compiler is free to pick and choose which of your register declarations to
ignore and which to implement.

> struct mystruct {
> 	char	field1, field2, field3, field4;
> 	};

I don't see anything prohibiting this.  To access a field you do byte swaps
or copy to another register and mask etc, depending on what's available in your
instruction set.  This might be less expensive than offset calculation and
memory referencing.  On something without byte addressability it might implement
it with register loads, shifts and masks anyway.  In which case a register to
register copy is bound to be quicker than fecthing from memory.

> union myunion {
> 	int	field1;
> 	char	field2[2];
> 	};

What problems does this have?  field one obviously presents no problems.
field2 is very easy to handle with byte swaps and byte instructions
(assuming the machine has such).  Now, if you intend to pull bytes out of
that int it isn't required that the compiler provide such capability anyway.

> -kdg

--j.a.tainter

jejones@mcrware.UUCP (James Jones) (02/20/88)

In article <7258@brl-smoke.ARPA>, gwyn@brl-smoke.ARPA (Doug Gwyn ) writes:
> On the other hand, I don't think this is nearly as useful as
> you seem to think it would be.  Why would you want to twiddle
> specific registers when not having to be concerned with such
> matters is the whole point of using a higher-level language?

The place I can see people wanting unions to live in registers is something
like

register union {
	woof	*wp;
	char	*cp;
	...
}	mumble;

(Actually, this isn't great insight on my part; I've heard people say,
"Gee, it would be nice if a compiler let you do this...")  There are some
low-level cases in which one would like to add a byte offset to a pointer
that usually points at something bigger than one byte, and still keep the
pointer in a register. 

(Of course, compilers should all be smart enough to figure out what should go
in registers themselves, right?  We don't need no steenkin' register declara-
tions! :-)

(I *do* recognize the advantages of letting the compiler figure out what can
go into a register, what the lifetimes of such variables are, etc.--it's
just that if the compiler *can* really put such a union in a register itself,
it seems to me that the programmer should be able to specify explicitly that
he thinks it's important that the union be in a register.)

		James Jones

gnu@hoptoad.uucp (John Gilmore) (02/21/88)

noise@eneevax.UUCP (Johnson Noise) wrote:
>                              Unfortunately, all the compilers I have
> tried (including pcc) will not allocate unions in registers.
> 	If there aren't any problems, I would like to incorporate these
> and other concepts into my copy of gcc.

I think gcc (the GNU C Compiler) will put unions into registers.  I have
seen it put a struct into a register.  I couldn't get the simple examples
Johnson used to allocate a register -- the optimizer was smart enough
to notice that it was only used a few times or was constant, so why
waste a register on it!

While compiling most of Berkeley Unix with gcc I ran across two programs
that declared "register struct foo bar" and then took the address of bar!
(You cain't do that to register variables).  PCC never complained because it
forgets that you called something "register" if it doesn't actually put
it in a register.

Doug Gwyn noted that you wouldn't get a variable allocated to d0 on the 68000
anyway, since it's a scratch register.  Gcc is smart enough to put variables
in d0 that aren't used across a function call (or any variable, in a "leaf"
routine that makes no function calls).

It's fun having the fastest 68K C compiler be the free one, though that
can't last forever.  If you notice that GCC is generating slow code though,
you could help by improving it, or at least send a note to bug-gcc@prep
to encourage others to do so.
-- 
{pyramid,ptsfa,amdahl,sun,ihnp4}!hoptoad!gnu			  gnu@toad.com
		"Watch me change my world..." -- Liquid Theatre

gnu@l nptoad.uucp (John Gilmore) (02/21/88)

noise@eneevax.UUCP (Johnson Noise) wrote:
>                              Unfortunately, all the compilers I have
> tried (including pcc) will not allocate unions in registers.
> 	If there aren't any problems, I would like to incorporate these
> and other concepts into my copy of gcc.

I think gcc (the GNU C Compiler) will put unions into registers.  I have
seen it put a struct into a register.  I couldn't get the simple examples
Johnson used to allocate a register -- the optimizer was smart enough
to notice that it was only used a few times or was constant, so why
waste a register on it!

While compiling most of Berkeley Unix with gcc I ran across two programs
that declared "register struct foo bar" and then took the address of bar!
(You cain't do that to register variables).  PCC never complained because it
forgets that you called something "register" if it doesn't actually put
it in a register.

Doug Gwyn noted that you wouldn't get a variable allocated to d0 on the 68000
anyway, since it's a scratch register.  Gcc is smart enough to put variables
in d0 that aren't used across a function call (or any variable, in a "leaf"
routine that makes no function calls).

It's fun having the fastest 68K C compiler be the free one, though that
can't last forever.  If you notice that GCC is generating slow code though,
you could help by improving it, or at least send a note to bug-gcc@prep
to encourage others to do so.
-- 
{pyramid,ptsfa,amdahl,sun,ihnp4}!l nptoad!gnu			  gnu@toad.com
		"Watch me change my world..." -- Liquid Theat

ok@quintus.UUCP (Richard A. O'Keefe) (02/22/88)

In article <604@mcrware.UUCP>, jejones@mcrware.UUCP (James Jones) writes:
> The place I can see people wanting unions to live in registers is something
> like
> register union {
> 	woof	*wp;
> 	char	*cp;
> 	...
> }	mumble;
Last time I looked at a PR1ME C manual, most pointers were 32 bits,
but character pointers were 48 bits.  The P400 (the machine I used)
has 32-bit registers.  {There are also the "Field Address" and
"Field Length" registers, one set of which overlaps a floating-point
register...}

If you really need to do this, you can manage it with casts.
The cast version is a wee bit better than the union version,
because Lint will complain about the casts, but the union code
will just quietly do something surprising.

	for (times = 100; --times >= 0; ) {
	    fprintf(stderr, "Byte addressing is not universal.\n");
	    fprintf(stderr, "Pointers may come in different sizes.\n\n");
	}

mwm@eris (Mike (My watch has windows) Meyer) (02/23/88)

In article <677@cresswell.quintus.UUCP> ok@quintus.UUCP (Richard A. O'Keefe) writes:
<In article <604@mcrware.UUCP>, jejones@mcrware.UUCP (James Jones) writes:
<> The place I can see people wanting unions to live in registers is something
<> like
<> register union {
<> 	woof	*wp;
<> 	char	*cp;
<> 	...
<> }	mumble;
<
<	for (times = 100; --times >= 0; ) {
<	    fprintf(stderr, "Byte addressing is not universal.\n");
<	    fprintf(stderr, "Pointers may come in different sizes.\n\n");
<	}

True statements, but they have nothing to do with people wanting to
put unions in registers.

<If you really need to do this, you can manage it with casts.

Do what? You don't know why the union was wanted. Maybe I just want to
reuse that register for multiple things, and have no intention of
using it to copy bits from a (char *) to a (woof *).

<The cast version is a wee bit better than the union version,

Actually, the cast version is a lot better. It gives the compiler the
chance to do something intelligent with the bits before putting it in
the other type of pointer. Doing that kind of thing with a union means
you get raw bits. Then again, that may be what you want.

<Last time I looked at a PR1ME C manual, most pointers were 32 bits, 
<but character pointers were 48 bits.  The P400 (the machine I used)
<has 32-bit registers.

So, because the prime won't let you put (char *) objects in a
register, you think people shouldn't be allowed to declare register
union objects? It'd make more sense to disallow register char *
objects, and any union that they wouldn't fit in. But there are to
many machines where register char * makes sense, so that probably
won't fly.

From the old dpANS, I'd guess you can declare register unions. You
just can't take the address of the union, or any part of it. Machines
that won't support what you're trying to put into the union in the
register type probably ignore the register - just like they do for a
register object decleration where the object won't fit in a register.

	<mike
--
Estant assis, de nuit secrette estude,			Mike Meyer
Seul, repose sur la selle d'airain,   			mwm@berkeley.edu
Flambe exigue, sortant de solitude,   			ucbvax!mwm
Fait proferer qui n'est a croire vain.			mwm@ucbjade.BITNET

ok@quintus.UUCP (Richard A. O'Keefe) (02/24/88)

Someone (I have lost the original posting) suggested that
	'register union { foo* x; baz *y; ... }'
would be a useful construct in C.

Well, it's already legal.  According to the Oct '86 dpANS,
"A declaration with storage-class specifier 'register' is an 'auto'
declaration, with a suggestion that the objects declared be stored
in fast-access machine registers if possible.  The types of objects
that are stored in such registers and the number of such
declaratiopns in each block that are effective are implementation-
defined [footnote: The implementation may treat any 'register'
declaration simply as an 'auto' declarations.  However, ... the
unary & (address-of) operator may not be applied to an object
declared with storage-class specifier 'register', whether or not
a machine register is actually used.]"

The System V Programmer's Guide explicitly says
"excess or invalid 'register' declarations are ignored."
Similar statements appear in other C manuals.
Interestingly enough, DEC's VAX C manual explicitly says
"If the variable requires storage (for example, arrays or structures),
the object of the variable is not placed in a register."

It seems that any C compiler which rejects 'register union foo X'
as an error has always been broken:  this is legal K&R C.  But a
compiler has always been within its rights to ignore 'register' in
this or any other case, though better-quality compilers print a
warning message.

The construct thus already being legal, I assumed the original poster
to be urging that compilers SHOULD put unions in registers if it is
possible for them to do so, and to be alleging that this was particularly
important for pointers.

I posted a message pointing out that this simply doesn't make sense on
some machines (specifically including PR1MEs), and employed a familiar 
humourous device to stress this.  I have received some flaming messages
from people who took exception to this commonplace observation.  Oddly
enough, no-one using a PR1ME has complained to me yet...

Here's why I feel strongly about issues like this:

(1) I did a couple of days consulting once for a company who had found that
    the only practical way of porting 4.2BSD to their machine was to change
    the microcode so that *(char*0) == 0.

(2) I have had the unpleasant experience of porting a program which used
    pointers heavily to a machine where 'int' and 'char*' were not the
    same size.  Even changing the definition of NULL to 0L (which is not,
    strictly speaking, correct) didn't help.  I had to go through more
    lines of code than I care to remember changing 0 to (char*)NULL.

(3) I had to port a program which assumed that, given the declaration
	union two {int a; char *b;} jim;
    the calls
	harry(jim);
    and
	harry(jim.b);
    were identical.  Suffice it to say that they weren't.

(4) I ported a middle-sized program to a machine, and watched someone else
    port a much large program to the same machine, where although character
    pointers and word pointers were both the same size as an integer, they
    had different representations.  So, for example,
	int data[50];
	fwrite(data, sizeof *data, (sizeof data)/(sizeof *data), output);
    wasn't just badly typed (it has always been that), it gave the wrong
    answers.  Again, one had to go though changing things like this to
	fwrite((char*)data, ....);

(5) I had to advise someone that a very large (and *very* useful) program
    of theirs would be too expensive to port to a PR1ME because they had
    assumed throughout that word pointers and character pointers were both
    the same size as an integer.  What was really tragic about this was
    that the program in question had very little real use for character
    pointers, but things had been converted to this "common currency".

What is the point of something like this:
	union ptr { char *c; int *i; long *l; int (*f)(); };
	register union ptr fred;
Surely the point is to say
	fred.c = /* something */;
	... fred.i ...
and have it go fast.

But this is going to give you major porting headaches in the future.
Or more plausibly, it is going to give someone else major porting
headaches.  Too bad that it is already legal...

Is there something comparable which is less trouble for porting?  YES.
Use casts.  Do something like

	#if	....
	typedef	int UsualStorageUnit;
	#elif	...
	typedef short UsualStorageUnit;
	#elif	...
	typedef char UsualStorageUnit;
	#else
	/* if case not handled, syntax error in next declaration */
	#endif
	typedef UsualStorageUnit *UsualPointer;

(void*) is close, but not quite identical.  (void*) has to handle the
worst case, and is usually much the same as (char*).  UsualPointer is to
be the "native" pointer type, just as int is the "native" integer type.

	#define	AsUsualPtr(x) ((UsualPointer)(x))
	#define AsShortPtr(x) ((short*)(x))
	#define AsIfuncPtr(x) ((int (*)())(x))
and so on.

Then you can declare routines like
	void apply1(Fn, Arg)
	    register UsualPointer Fn, Arg;
	    {
		(*AsIfuncPtr(Fn))(*AsShortPtr(Arg));
	    }

What's the difference?  Well, apart from the fact that the compiler is
more likely to put UsualPointers into registers than unions (though
putting both, and putting neither, are both ALREADY legal), the compiler
can now spot each type change, and can tell you about the ones that
aren't going to work.

Look *very* carefully at **any** union in your programs which is not
#ifdeffed by machine or implementation; bugs breed in them like
mosquitos in a swamp.  Using different members of a union at different
times is fine, but putting something into one member of a union and
picking it up again from another member is bad practice in any programming
language.  (My first introduction to the problem was trying to port a
Pascal program from a CDC machine to a B6700.  The Pascal programmer had
assumed that 10 characters = 1 integer, and not only was that not the
case, but the bit pattern of the first N characters often wasn't valid
as an integer.)

Frankly, I am not impressed by people who say
    "don't be so condescending, this is a useful construct on MY machine."
I do not use a PR1ME (or any of the other machines I hinted at above)
myself.  I have done, and hope never to do so again.  Life is difficult
enough for these people without going out of our way to make things worse.

Oh yes, another porting problem:  sizeof *main.  On at least three machines
that I can think of, the function pointers that C programs pass around is
actually a pointer to a control block, *not* a pointer to the code...
Don't expect the bit value C has to be equal to what you see in a load map.

gwyn@brl-smoke.ARPA (Doug Gwyn ) (02/24/88)

In article <686@cresswell.quintus.UUCP> ok@quintus.UUCP (Richard A. O'Keefe) writes:
>(void*) has to handle the worst case, and is usually much the same as (char*).

As of the last draft proposed ANSI C standard, (void *) and (char *) are
required to have the same representation.  This is to grandfather in
existing programs that use routines like fwrite() that really should
always have received generic pointers, but historically have (correctly)
been fed (char *)s.

mike@hpfclq.HP.COM (Mike McNelly) (02/25/88)

Register unions are indeed useful and implementable.  I implemented them
in a pcc based compiler a couple of years ago primarily for our internal
use.  Many programs show negligible speed improvement but some,
particularly those using yacc output gained about 10% speed and space
improvement.  As a consequence we use register unions to generate our
compilers and our assembler, which is also yacc based.

There are difficulties that I never bothered to resolve in our
implementation.  The most obvious one involves multiple assignment.

	e.g.
		register union foo
			{
			int i;
			float f;
			int *ip;
			} a,b,c;

		a.i = b.f = (int)c.ip = ...;

After a series of multiple assignments the internal representation
within pcc becomes very messy.  Since the implementation was for an
internal tool only it was easier to avoid the problem than add lots of
code to fix it. The situation is very rare and not worth slowing the 
compiler down to handle an occasional occurrence.

You may also have problems deciding which register class in which to put
the union.  For the above example, it may be wiser to locate the union
in a data register (MC68000 family) unless the pointer member is to be
accessed frequently in which case an address register may be more
appropriate.  If you guess wrong the resulting code can actually be
somewhat slower than if the union is in memory.  We chose to use the
first union member type to be a clue.


Mike McNelly	hplabs!hpfcla!mike
Hewlett Packard Company

blarson@skat.usc.edu (Bob Larson) (02/25/88)

In article <686@cresswell.quintus.UUCP> ok@quintus.UUCP (Richard A. O'Keefe) writes:
>I posted a message pointing out that this simply doesn't make sense on
>some machines (specifically including PR1MEs), and employed a familiar 
>humourous device to stress this.  I have received some flaming messages
>from people who took exception to this commonplace observation.  Oddly
>enough, no-one using a PR1ME has complained to me yet...

It was quite obvious that you were trying to use Prime as an example
without actually having used the C compiler on one.  The C compiler
designers made some strange desisions (for people used to the machines)
to make porting incorrect code eaiser in some cases.

64v mode:
	sizeof (int) == sizeof (long) == 4
	sizeof (char *) == sizeof (int *) == sizeof (whatever *) == 6
		(last 16 bits of pointers other than char * are unused)

If the first 32 bits of a pointer are 0, it is considered a NULL
pointer, not a pointer to the valid address 0 in kernal space.  (Other
Prime langues use a pointer to segment 7777 with the fault bit set for
NULL pointers.)

Casts from pointers to integers are non-trivial conversions, producing
the number of bytes from address 0, and loosing auxilary information
such as ring and fault bits.

Since 64 v mode C doesn't support 32 bit pointers, calling some other
language routines is difficult.

stdin, stdout, and stderr may only be used other than as arguments to
functions in main.  ("FILE *f = stdin;" will not work other than in
main.)

No variables are ever kept in registers.  Register function paramaters
are copied to local memory for faster access.

32ix mode:

	sizeof (char *) == sizeof (int *) == sizeof (whatever *) == 4

Register variables are supported.  The -ignoreregister option allows
the compiler to choose which variables should go in registers.

Only works on newer primes.  (4-digit except 2250.)

--
Bob Larson	Arpa: Blarson@Ecla.Usc.Edu	blarson@skat.usc.edu
Uucp: {sdcrdcf,cit-vax}!oberon!skat!blarson
Prime mailing list:	info-prime-request%fns1@ecla.usc.edu
			oberon!fns1!info-prime-request

wes@obie.UUCP (Barnacle Wes) (02/26/88)

In article <41964@sun.uucp>, guy@gorodish.Sun.COM (Guy Harris) writes:
> > The official AT&T version of the K&R portable C compiler only
> > supports register allocation of int's.  All other types are
> > silently assigned to core.
> 
> I presume you mean "integral types", not "int"s.  The version of PCC2 that
> comes with S5R3.1 on 3B2s, for instance, quite cheerfully puts "short"s,
> "char"s, and "long"s into registers as well.

A lot of this type of problem is caused by the underlying machine
architecture.  On the M68000, you usually have 5 or 6 address
registers available, so most 68K C compilers let you put pointers in
registers as well as integral types.  The one I use the most (MWC for
Atari ST) does not allow floats of any flavor to be put in registers.

On Intel chips, a pointer in a register isn't really defined; there
are no real general-purpose registers, and there aren't any registers
big enough to hold a full address.  I guess you could probably put an
address is ES:DI or something like that, but I don't know of any
compilers that do.  MicroPort's pcc surely doesn't!

----
	"Segments are for worms!"	-- Landon Dyer

-- 
    /\              -  "Against Stupidity,  -    {backbones}!
   /\/\  .    /\    -  The Gods Themselves  -  utah-cs!utah-gr!
  /    \/ \/\/  \   -   Contend in Vain."   -  uplherc!sp7040!
 / U i n T e c h \  -       Schiller        -     obie!wes

gwyn@smoke.BRL.MIL (Doug Gwyn ) (10/24/88)

>> In article <976@l.cc.purdue.edu> cik@l.cc.purdue.edu (Herman Rubin) writes:
>> >One complaint that I have about the C compilers I have used is that they
>> >do not support register unions.

The point is, on many architectures not all basic data types could actually
share a register.  Your original example was of an integer and a floating
type sharing a register, which doesn't work on any machines I'm aware of.
And obviously a union of large types would not fit into a register.

Simply permitting "register" to be crammed on the front of "union" would
do no good; the hardware would have to actually support the usage for
this to be worthwhile.  If the hardware DOES support this, its C
implementation is allowed to use an actual register (so long as the
address is not taken).  Good optimizing C compilers ignore the "register"
specifiers and do their own register allocation anyway.

cik@l.cc.purdue.edu (Herman Rubin) (10/24/88)

In article <8741@smoke.BRL.MIL>, gwyn@smoke.BRL.MIL (Doug Gwyn ) writes:
> >> In article <976@l.cc.purdue.edu> cik@l.cc.purdue.edu (Herman Rubin) writes:
> >> >One complaint that I have about the C compilers I have used is that they
> >> >do not support register unions.
> 
> The point is, on many architectures not all basic data types could actually
> share a register.  Your original example was of an integer and a floating
> type sharing a register, which doesn't work on any machines I'm aware of.
> And obviously a union of large types would not fit into a register.

On the VAX and PYRAMID, single precision floating point and integer both
take up exactly one register.  But that is not the example I gave.  I have
used the other in a situation where it obviously saved time.

I put down the address of an integer and the address of a floating point
number.  There are very few machines where these take up different amounts
of space.

Of course, if something cannot fit into the registers available, it cannot
be made register.  But this is no reason to ignore the programmer.  And 
there is even less reason not to put a union into a register because it 
is a union when any other pointer would have been put into a register.

> Simply permitting "register" to be crammed on the front of "union" would
> do no good; the hardware would have to actually support the usage for
> this to be worthwhile.  If the hardware DOES support this, its C
> implementation is allowed to use an actual register (so long as the
> address is not taken).  Good optimizing C compilers ignore the "register"
> specifiers and do their own register allocation anyway.

I have not even seen a fair optimizing C compiler.  In a university environ-
ment, one has little say in this.  I can say that the 4.xBSD compiler does
not allow certain registers to be used by the programmer, even if they are
available.  It does not allow the use of symbolic registers in asm line.
There are too many other things it does not allow, and the simple idea of
register unions is one of them.  BTW, in the book, whose title and authors
slip my mind, on the use of lex and yacc in constructing a C compiler, the
authors use register unions.  I have seen register unions used in widely
published C sources.
-- 
Herman Rubin, Dept. of Statistics, Purdue Univ., West Lafayette IN47907
Phone: (317)494-6054
hrubin@l.cc.purdue.edu (Internet, bitnet, UUCP)

renglish@hpisod1.HP.COM (Robert English) (10/26/88)

> / gwyn@smoke.BRL.MIL (Doug Gwyn ) /  2:16 pm  Oct 23, 1988 /

> The point is, on many architectures not all basic data types could actually
> share a register.  Your original example was of an integer and a floating
> type sharing a register, which doesn't work on any machines I'm aware of.
> And obviously a union of large types would not fit into a register.

> Simply permitting "register" to be crammed on the front of "union" would
> do no good; the hardware would have to actually support the usage for
> this to be worthwhile.  If the hardware DOES support this, its C
> implementation is allowed to use an actual register (so long as the
> address is not taken).  Good optimizing C compilers ignore the "register"
> specifiers and do their own register allocation anyway.

Even if the hardware could not support a register union of incompatible
data types, the information would be valuable to an optimizer.
"Register," in addition to expressing a storage preference, informs the
compiler that the variable is non-addressable, and that the compiler
needn't worry about the contents changing.

--bob--

guy@auspex.UUCP (Guy Harris) (10/28/88)

>Even if the hardware could not support a register union of incompatible
>data types, the information would be valuable to an optimizer.
>"Register," in addition to expressing a storage preference, informs the
>compiler that the variable is non-addressable, and that the compiler
>needn't worry about the contents changing.

Well, optimizers of that level of sophistication often completely ignore
"register" declarations and decide for themselves what should go into
registers....  (Wasn't there some other keyword people were talking
about at one point for saying that there wouldn't be any aliases for
some location? :-) :-) :-) :-) :-) :-) :-) :-) :-))

The compiler can generally figure out for itself whether you're taking
the address of some local variable or not; it tends not to need the
assistance of a "register" declaration.