| 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 |
| |