967e44126c
From-SVN: r2535
449 lines
18 KiB
Plaintext
449 lines
18 KiB
Plaintext
0. Improved efficiency.
|
|
|
|
* Parse and output array initializers an element at a time, freeing
|
|
storage after each, instead of parsing the whole initializer first and
|
|
then outputting. This would reduce memory usage for large
|
|
initializers.
|
|
|
|
* See if the techniques describe in Oct 1991 SIGPLAN Notices
|
|
(Frazer and Hanson) are applicable to GCC.
|
|
|
|
1. Better optimization.
|
|
|
|
* Constants in unused inline functions
|
|
|
|
It would be nice to delay output of string constants so that string
|
|
constants mentioned in unused inline functions are never generated.
|
|
Perhaps this would also take care of string constants in dead code.
|
|
|
|
The difficulty is in finding a clean way for the RTL which refers
|
|
to the constant (currently, only by an assembler symbol name)
|
|
to point to the constant and cause it to be output.
|
|
|
|
* More cse
|
|
|
|
The techniques for doing full global cse are described in the red
|
|
dragon book, or (a different version) in Frederick Chow's thesis from
|
|
Stanford. It is likely to be slow and use a lot of memory, but it
|
|
might be worth offering as an additional option.
|
|
|
|
It is probably possible to extend cse to a few very frequent cases
|
|
without so much expense.
|
|
|
|
For example, it is not very hard to handle cse through if-then
|
|
statements with no else clauses. Here's how to do it. On reaching a
|
|
label, notice that the label's use-count is 1 and that the last
|
|
preceding jump jumps conditionally to this label. Now you know it
|
|
is a simple if-then statement. Remove from the hash table
|
|
all the expressions that were entered since that jump insn
|
|
and you can continue with cse.
|
|
|
|
It is probably not hard to handle cse from the end of a loop
|
|
around to the beginning, and a few loops would be greatly sped
|
|
up by this.
|
|
|
|
* Optimize a sequence of if statements whose conditions are exclusive.
|
|
|
|
It is possible to optimize
|
|
|
|
if (x == 1) ...;
|
|
if (x == 2) ...;
|
|
if (x == 3) ...;
|
|
|
|
into
|
|
|
|
if (x == 1) ...;
|
|
else if (x == 2) ...;
|
|
else if (x == 3) ...;
|
|
|
|
provided that x is not altered by the contents of the if statements.
|
|
|
|
It's not certain whether this is worth doing. Perhaps programmers
|
|
nearly always write the else's themselves, leaving few opportunities
|
|
to improve anything.
|
|
|
|
* Un-cse.
|
|
|
|
Perhaps we should have an un-cse step right after cse, which tries to
|
|
replace a reg with its value if the value can be substituted for the
|
|
reg everywhere, if that looks like an improvement. Which is if the
|
|
reg is used only a few times. Use rtx_cost to determine if the
|
|
change is really an improvement.
|
|
|
|
* Clean up how cse works.
|
|
|
|
The scheme is that each value has just one hash entry. The
|
|
first_same_value and next_same_value chains are no longer needed.
|
|
|
|
For arithmetic, each hash table elt has the following slots:
|
|
|
|
* Operation. This is an rtx code.
|
|
* Mode.
|
|
* Operands 0, 1 and 2. These point to other hash table elements.
|
|
|
|
So, if we want to enter (PLUS:SI (REG:SI 30) (CONST_INT 104)), we
|
|
first enter (CONST_INT 104) and find the entry that (REG:SI 30) now
|
|
points to. Then we put these elts into operands 0 and 1 of a new elt.
|
|
We put PLUS and SI into the new elt.
|
|
|
|
Registers and mem refs would never be entered into the table as such.
|
|
However, the values they contain would be entered. There would be a
|
|
table indexed by regno which points at the hash entry for the value in
|
|
that reg.
|
|
|
|
The hash entry index now plays the role of a qty number.
|
|
We still need qty_first_reg, reg_next_eqv, etc. to record which regs
|
|
share a particular qty.
|
|
|
|
When a reg is used whose contents are unknown, we need to create a
|
|
hash table entry whose contents say "unknown", as a place holder for
|
|
whatever the reg contains. If that reg is added to something, then
|
|
the hash entry for the sum will refer to the "unknown" entry. Use
|
|
UNKNOWN for the rtx code in this entry. This replaces make_new_qty.
|
|
|
|
For a constant, a unique hash entry would be made based on the
|
|
value of the constant.
|
|
|
|
What about MEM? Each time a memory address is referenced, we need a
|
|
qty (a hash table elt) to represent what is in it. (Just as for a
|
|
register.) If this isn't known, create one, just as for a reg whose
|
|
contents are unknown.
|
|
|
|
We need a way to find all mem refs that still contain a certain value.
|
|
Do this with a chain of hash elts (for memory addresses) that point to
|
|
locations that hold the value. The hash elt for the value itself should
|
|
point to the start of the chain. It would be good for the hash elt
|
|
for an address to point to the hash elt for the contents of that address
|
|
(but this ptr can be null if the contents have never been entered).
|
|
|
|
With this data structure, nothing need ever be invalidated except
|
|
the lists of which regs or mems hold a particular value. It is easy
|
|
to see if there is a reg or mem that is equiv to a particular value.
|
|
If the value is constant, it is always explicitly constant.
|
|
|
|
* Support more general tail-recursion among different functions.
|
|
|
|
This might be possible under certain circumstances, such as when
|
|
the argument lists of the functions have the same lengths.
|
|
Perhaps it could be done with a special declaration.
|
|
|
|
You would need to verify in the calling function that it does not
|
|
use the addresses of any local variables and does not use setjmp.
|
|
|
|
* Put short statics vars at low addresses and use short addressing mode?
|
|
|
|
Useful on the 68000/68020 and perhaps on the 32000 series,
|
|
provided one has a linker that works with the feature.
|
|
This is said to make a 15% speedup on the 68000.
|
|
|
|
* Keep global variables in registers.
|
|
|
|
Here is a scheme for doing this. A global variable, or a local variable
|
|
whose address is taken, can be kept in a register for an entire function
|
|
if it does not use non-constant memory addresses and (for globals only)
|
|
does not call other functions. If the entire function does not meet
|
|
this criterion, a loop may.
|
|
|
|
The VAR_DECL for such a variable would have to have two RTL expressions:
|
|
the true home in memory, and the pseudo-register used temporarily.
|
|
It is necessary to emit insns to copy the memory location into the
|
|
pseudo-register at the beginning of the function or loop, and perhaps
|
|
back out at the end. These insns should have REG_EQUIV notes so that,
|
|
if the pseudo-register does not get a hard register, it is spilled into
|
|
the memory location which exists in any case.
|
|
|
|
The easiest way to set up these insns is to modify the routine
|
|
put_var_into_stack so that it does not apply to the entire function
|
|
(sparing any loops which contain nothing dangerous) and to call it at
|
|
the end of the function regardless of where in the function the
|
|
address of a local variable is taken. It would be called
|
|
unconditionally at the end of the function for all relevant global
|
|
variables.
|
|
|
|
For debugger output, the thing to do is to invent a new binding level
|
|
around the appropriate loop and define the variable name as a register
|
|
variable with that scope.
|
|
|
|
* Live-range splitting.
|
|
|
|
Currently a variable is allocated a hard register either for the full
|
|
extent of its use or not at all. Sometimes it would be good to
|
|
allocate a variable a hard register for just part of a function; for
|
|
example, through a particular loop where the variable is mostly used,
|
|
or outside of a particular loop where the variable is not used. (The
|
|
latter is nice because it might let the variable be in a register most
|
|
of the time even though the loop needs all the registers.)
|
|
|
|
It might not be very hard to do this in global.c when a variable
|
|
fails to get a hard register for its entire life span.
|
|
|
|
The first step is to find a loop in which the variable is live, but
|
|
which is not the whole life span or nearly so. It's probably best to
|
|
use a loop in which the variable is heavily used.
|
|
|
|
Then create a new pseudo-register to represent the variable in that loop.
|
|
Substitute this for the old pseudo-register there, and insert move insns
|
|
to copy between the two at the loop entry and all exits. (When several
|
|
such moves are inserted at the same place, some new feature should be
|
|
added to say that none of those registers conflict merely because of
|
|
overlap between the new moves. And the reload pass should reorder them
|
|
so that a store precedes a load, for any given hard register.)
|
|
|
|
After doing this for all the reasonable candidates, run global-alloc
|
|
over again. With luck, one of the two pseudo-registers will be fit
|
|
somewhere. It may even have a much higher priority due to its reduced
|
|
life span.
|
|
|
|
There will be no room in general for the new pseudo-registers in
|
|
basic_block_live_at_start, so there will need to be a second such
|
|
matrix exclusively for the new ones. Various other vectors indexed by
|
|
register number will have to be made bigger, or there will have to be
|
|
secondary extender vectors just for global-alloc.
|
|
|
|
A simple new feature could arrange that both pseudo-registers get the
|
|
same stack slot if they both fail to get hard registers.
|
|
|
|
Other compilers split live ranges when they are not connected, or
|
|
try to split off pieces `at the edge'. I think splitting around loops
|
|
will provide more speedup.
|
|
|
|
Creating a fake binding block and a new like-named variable with
|
|
shorter life span and different address might succeed in describing
|
|
this technique for the debugger.
|
|
|
|
* Detect dead stores into memory?
|
|
|
|
A store into memory is dead if it is followed by another store into
|
|
the same location; and, in between, there is no reference to anything
|
|
that might be that location (including no reference to a variable
|
|
address).
|
|
|
|
* Loop optimization.
|
|
|
|
Strength reduction and iteration variable elimination could be
|
|
smarter. They should know how to decide which iteration variables are
|
|
not worth making explicit because they can be computed as part of an
|
|
address calculation. Based on this information, they should decide
|
|
when it is desirable to eliminate one iteration variable and create
|
|
another in its place.
|
|
|
|
It should be possible to compute what the value of an iteration
|
|
variable will be at the end of the loop, and eliminate the variable
|
|
within the loop by computing that value at the loop end.
|
|
|
|
When a loop has a simple increment that adds 1,
|
|
instead of jumping in after the increment,
|
|
decrement the loop count and jump to the increment.
|
|
This allows aob insns to be used.
|
|
|
|
* Using constraints on values.
|
|
|
|
Many operations could be simplified based on knowledge of the
|
|
minimum and maximum possible values of a register at any particular time.
|
|
These limits could come from the data types in the tree, via rtl generation,
|
|
or they can be deduced from operations that are performed. For example,
|
|
the result of an `and' operation one of whose operands is 7 must be in
|
|
the range 0 to 7. Compare instructions also tell something about the
|
|
possible values of the operand, in the code beyond the test.
|
|
|
|
Value constraints can be used to determine the results of a further
|
|
comparison. They can also indicate that certain `and' operations are
|
|
redundant. Constraints might permit a decrement and branch
|
|
instruction that checks zeroness to be used when the user has
|
|
specified to exit if negative.
|
|
|
|
* Smarter reload pass.
|
|
|
|
The reload pass as currently written can reload values only into registers
|
|
that are reserved for reloading. This means that in order to use a
|
|
register for reloading it must spill everything out of that register.
|
|
|
|
It would be straightforward, though complicated, for reload1.c to keep
|
|
track, during its scan, of which hard registers were available at each
|
|
point in the function, and use for reloading even registers that were
|
|
free only at the point they were needed. This would avoid much spilling
|
|
and make better code.
|
|
|
|
* Change the type of a variable.
|
|
|
|
Sometimes a variable is declared as `int', it is assigned only once
|
|
from a value of type `char', and then it is used only by comparison
|
|
against constants. On many machines, better code would result if
|
|
the variable had type `char'. If the compiler could detect this
|
|
case, it could change the declaration of the variable and change
|
|
all the places that use it.
|
|
|
|
* Better handling for very sparse switches.
|
|
|
|
There may be cases where it would be better to compile a switch
|
|
statement to use a fixed hash table rather than the current
|
|
combination of jump tables and binary search.
|
|
|
|
* Order of subexpressions.
|
|
|
|
It might be possible to make better code by paying attention
|
|
to the order in which to generate code for subexpressions of an expression.
|
|
|
|
* More code motion.
|
|
|
|
Consider hoisting common code up past conditional branches or
|
|
tablejumps.
|
|
|
|
* Trace scheduling.
|
|
|
|
This technique is said to be able to figure out which way a jump
|
|
will usually go, and rearrange the code to make that path the
|
|
faster one.
|
|
|
|
* Distributive law.
|
|
|
|
The C expression *(X + 4 * (Y + C)) compiles better on certain
|
|
machines if rewritten as *(X + 4*C + 4*Y) because of known addressing
|
|
modes. It may be tricky to determine when, and for which machines, to
|
|
use each alternative.
|
|
|
|
Some work has been done on this, in combine.c.
|
|
|
|
* Can optimize by changing if (x) y; else z; into z; if (x) y;
|
|
if z and x do not interfere and z has no effects not undone by y.
|
|
This is desirable if z is faster than jumping.
|
|
|
|
* For a two-insn loop on the 68020, such as
|
|
foo: movb a2@+,a3@+
|
|
jne foo
|
|
it is better to insert dbeq d0,foo before the jne.
|
|
d0 can be a junk register. The challenge is to fit this into
|
|
a portable framework: when can you detect this situation and
|
|
still be able to allocate a junk register?
|
|
|
|
2. Simpler porting.
|
|
|
|
Right now, describing the target machine's instructions is done
|
|
cleanly, but describing its addressing mode is done with several
|
|
ad-hoc macro definitions. Porting would be much easier if there were
|
|
an RTL description for addressing modes like that for instructions.
|
|
Tools analogous to genflags and genrecog would generate macros from
|
|
this description.
|
|
|
|
There would be one pattern in the address-description file for each
|
|
kind of addressing, and this pattern would have:
|
|
|
|
* the RTL expression for the address
|
|
* C code to verify its validity (since that may depend on
|
|
the exact data).
|
|
* C code to print the address in assembler language.
|
|
* C code to convert the address into a valid one, if it is not valid.
|
|
(This would replace LEGITIMIZE_ADDRESS).
|
|
* Register constraints for all indeterminates that appear
|
|
in the RTL expression.
|
|
|
|
3. Other languages.
|
|
|
|
Front ends for Pascal, Fortran, Algol, Cobol, Modula-2 and Ada are
|
|
desirable.
|
|
|
|
Pascal, Modula-2 and Ada require the implementation of functions
|
|
within functions. Some of the mechanisms for this already exist.
|
|
|
|
4. More extensions.
|
|
|
|
* Generated unique labels. Have some way of generating distinct labels
|
|
for use in extended asm statements. I don't know what a good syntax would
|
|
be.
|
|
|
|
* A way of defining a structure containing a union, in which the choice of
|
|
union alternative is controlled by a previous structure component.
|
|
|
|
Here is a possible syntax for this.
|
|
|
|
struct foo {
|
|
enum { INT, DOUBLE } code;
|
|
auto union { case INT: int i; case DOUBLE: double d;} value : code;
|
|
};
|
|
|
|
* Allow constructor expressions as lvalues, like this:
|
|
|
|
(struct foo) {a, b, c} = foo();
|
|
|
|
This would call foo, which returns a structure, and then store the
|
|
several components of the structure into the variables a, b, and c.
|
|
|
|
5. Generalize the machine model.
|
|
|
|
* Some new compiler features may be needed to do a good job on machines
|
|
where static data needs to be addressed using base registers.
|
|
|
|
* Some machines have two stacks in different areas of memory, one used
|
|
for scalars and another for large objects. The compiler does not
|
|
now have a way to understand this.
|
|
|
|
6. Useful warnings.
|
|
|
|
* Warn about statements that are undefined because the order of
|
|
evaluation of increment operators makes a big difference. Here is an
|
|
example:
|
|
|
|
*foo++ = hack (*foo);
|
|
|
|
7. Better documentation of how GCC works and how to port it.
|
|
|
|
Here is an outline proposed by Allan Adler.
|
|
|
|
I. Overview of this document
|
|
II. The machines on which GCC is implemented
|
|
A. Prose description of those characteristics of target machines and
|
|
their operating systems which are pertinent to the implementation
|
|
of GCC.
|
|
i. target machine characteristics
|
|
ii. comparison of this system of machine characteristics with
|
|
other systems of machine specification currently in use
|
|
B. Tables of the characteristics of the target machines on which
|
|
GCC is implemented.
|
|
C. A priori restrictions on the values of characteristics of target
|
|
machines, with special reference to those parts of the source code
|
|
which entail those restrictions
|
|
i. restrictions on individual characteristics
|
|
ii. restrictions involving relations between various characteristics
|
|
D. The use of GCC as a cross-compiler
|
|
i. cross-compilation to existing machines
|
|
ii. cross-compilation to non-existent machines
|
|
E. Assumptions which are made regarding the target machine
|
|
i. assumptions regarding the architecture of the target machine
|
|
ii. assumptions regarding the operating system of the target machine
|
|
iii. assumptions regarding software resident on the target machine
|
|
iv. where in the source code these assumptions are in effect made
|
|
III. A systematic approach to writing the files tm.h and xm.h
|
|
A. Macros which require special care or skill
|
|
B. Examples, with special reference to the underlying reasoning
|
|
IV. A systematic approach to writing the machine description file md
|
|
A. Minimal viable sets of insn descriptions
|
|
B. Examples, with special reference to the underlying reasoning
|
|
V. Uses of the file aux-output.c
|
|
VI. Specification of what constitutes correct performance of an
|
|
implementation of GCC
|
|
A. The components of GCC
|
|
B. The itinerary of a C program through GCC
|
|
C. A system of benchmark programs
|
|
D. What your RTL and assembler should look like with these benchmarks
|
|
E. Fine tuning for speed and size of compiled code
|
|
VII. A systematic procedure for debugging an implementation of GCC
|
|
A. Use of GDB
|
|
i. the macros in the file .gdbinit for GCC
|
|
ii. obstacles to the use of GDB
|
|
a. functions implemented as macros can't be called in GDB
|
|
B. Debugging without GDB
|
|
i. How to turn off the normal operation of GCC and access specific
|
|
parts of GCC
|
|
C. Debugging tools
|
|
D. Debugging the parser
|
|
i. how machine macros and insn definitions affect the parser
|
|
E. Debugging the recognizer
|
|
i. how machine macros and insn definitions affect the recognizer
|
|
|
|
ditto for other components
|
|
|
|
VIII. Data types used by GCC, with special reference to restrictions not
|
|
specified in the formal definition of the data type
|
|
IX. References to the literature for the algorithms used in GCC
|
|
|