home *** CD-ROM | disk | FTP | other *** search
/ The C Users' Group Library 1994 August / wc-cdrom-cusersgrouplibrary-1994-08.iso / vol_300 / 357_01 / cstar1.exe / DESIGN.DOC < prev    next >
Text File  |  1991-06-20  |  17KB  |  426 lines

  1. DESIGN.DOC:  Design issues pertaining to the CSTAR translator
  2.  
  3. June 25, 1991
  4.  
  5. The following notes were written in 1987, I believe.  The subject of these 
  6. notes are various issues concerning the design of the CSTAR translator, and 
  7. in some cases the CSTAR language.  Be warned: these notes  are extremely 
  8. technical in nature and assume a deep level of knowledge of the CSTAR 
  9. translator.  They are likely to be of interest only to *serious* students of 
  10. CSTAR.
  11.  
  12. Parts of this file have the flavor of cryptic "notes to myself."  If you have a 
  13. question, please don't be intimidated by the obscure nature of these notes.  
  14. Just give me a call and I'll be happy to tell you what I know.  By the way, 
  15. much of this file was written by a brilliant programmer named Paul Schick, 
  16. who worked on CSTAR for about a year with me.  Parts of this file are in 
  17. dialog form, with my comments being marked by EKR:
  18.  
  19. Another way of looking at this file is as pages from an engineering 
  20. notebook.  Some of the issues discusses here never really got satisfactorily 
  21. resolved.  The section called goals, principles and problems was written by 
  22. me, and the rest of the file is really a discussion of the problems in meeting 
  23. those goals and principles in any kind of clean way.
  24.  
  25. Some terminology:  LHS stands for the Left Hand Side of an assignment 
  26. statement and RHS stands for the Right Hand Side.  A "star expression" is 
  27. an expression containing the * operator.  A "loc node" is the part of the 
  28. parse tree containing all information for a "location," which usually 
  29. corresponds to some identifier.  Understanding how loc nodes are created 
  30. and used is a key to understanding how the compiler works.
  31.  
  32.  
  33. GOALS, PRINCIPLES AND PROBLEMS
  34.  
  35. The primary goal is to make available all of the efficiency of assembly 
  36. language in a reasonable manner.  
  37.  
  38. 1.  Register allocation must be done in an obvious and efficient way.
  39.  
  40. 2.  As much as possible, consistent with other principles, we want to allow 
  41. the programmer to experiment with register allocation without rewriting the 
  42. form of expressions.
  43.  
  44. 3.  The details of the 68000 architecture  should be separate from the actual 
  45. code.  I see no necessity of embedding all the details of which addressing 
  46. modes are allowed in which instruction all through the compiler code.  This 
  47. suggests avoiding flaky rules on expression composition.
  48.  
  49. 4.  The code generator ought to function independently of the the precise 
  50. details of what kinds of expressions are allowed, but that may be wishful 
  51. thinking.  The code generation process can be broken down in concept into 
  52. three phases:
  53.  
  54. a) Assign result location to each operator.  Once this is done, all other 
  55. decisions are completely determined.  For binary operators, one operand 
  56. will, in general, be a result location from another operator.
  57.  
  58. b) Determine whether a machine instruction exists to apply the indicated 
  59. operator to the two operands and produce the required result.  Again, the 
  60. previous phase will have tried to ensure that the result location is the same 
  61. as one of the arguments.
  62.  
  63. c) A peephole optimizer pass operates to handle flow-of-control problems.  
  64. At present, I do not see a peephole pass being applied to arithmetic 
  65. expressions, though I have not really looked into this possibility.
  66.  
  67. Consider the following expression.
  68.  
  69.     a = b + (c = d + (e = 2 + f)));
  70.  
  71. The CSTAR translator treats this as exactly equivalent to 
  72.  
  73.     e = 2 + f;
  74.     c = d + e;
  75.     a = b + c;
  76.  
  77. with the restriction that c may not be a and e may not be c.  But C compilers 
  78. are free to rearrange the order of evaluation of operands, so additional 
  79. restrictions are needed to ensure that the corresponding C expression always 
  80. is evaluated correctly.  In the above example, d should not be e and b should 
  81. not be c.  Isn't the assignment a sequence point?
  82.  
  83.  
  84. ON PREDICTABILITY
  85.  
  86. We are entitled to predict what we have specified, and no more (or not very 
  87. much more.)   It seems to me, right now, that a statement like:
  88.  
  89.     m1 = (m2*m3) + (m4*m5);
  90.  
  91. really is, even in CSTAR a specification which says:
  92.  
  93. 1. put the LHS into the RHS and don't tell me how you did it.
  94.  
  95. 2. don't touch anything except the authorized temporaries.
  96.  
  97. I'm really not entitled to know more, because I didn't say more.
  98.  
  99. Now, in practice, I ought to help myself to a little bit more, and say that as 
  100. long as every non-assignment operator corresponds to an assignment 
  101. operator, one for one, e. g.:
  102.  
  103.     m1 = (d2 = m2 * m3) + (d3 = m4 + m5);
  104.  
  105. then there really aren't any choices left (except for whatever moves are 
  106. necessary to reconcile operands and results), so now--and only now--I 
  107. haven't asked for any surprises, and so I'm entitled to not get them. 
  108.  
  109. EKR:  doesn't this still leave open the question of whether to use the LHS 
  110. as a temporary or not?
  111.  
  112. What's new and useful is that I have that choice.  I didn't have that choice in 
  113. full C, because I can only specify these things loosely in full C, and the 
  114. compiler is entitled to ignore me.  I didn't have that choice in macro 
  115. assembler, but for a different reason:  I was obliged to specify everything all 
  116. the time, whether I needed to or not, which is why programming was so 
  117. impossibly tedious.
  118.  
  119. So I don't want to give up the right to specify register designators, and to 
  120. know how function calls are done, and go back to  C, because I want the 
  121. option to handle those things in a specific, assembly-like way.  Nor do I to 
  122. eschew all compiler decisions all the time and go back to macro assembler, 
  123. because I want relief from the burden of specifying everything all the time, 
  124. needed or not.
  125.  
  126. In a way, I want in-line assembly code, but in-line assembly code that 
  127. works and is workable.  Normally it is not workable, because the register 
  128. designators can't be treated as variables, so you have to go through a big 
  129. song and dance with lots of overhead in order to feed something to the in-
  130. line code, and then another big song and dance to get it out again; nobody 
  131. could ever optimize anything that way.
  132.  
  133. It might be helpful to have the compiler issue a message if a "ternary" 
  134. assignment, etc., compiles to a surprise.  The programmer would be 
  135. informed about an incorrect assumption (possibly resulting from an 
  136. overlooked instruction-set quirk), and we'd have to decide what a 
  137. "surprise" is, or whether there's any such thing.
  138.  
  139.  
  140. HOW CAN WE EXPERIMENT WITH REGISTER/MEMORY ALLOCATION?
  141.  
  142. We can't, unless the rules for composing expressions are blind to register 
  143. vs. memory allocations.  They need not be blind with respect to pointers vs. 
  144. nonpointers, since that is a question of type.
  145.  
  146. For assignments, there is always at least one data temporary register and 
  147. one address temporary register, as specified in the command line or in the 
  148. function.  We will refer to the first pair as as and ds for convenience.  The 
  149. programmer should act as though all of these scratch registers are clobbered 
  150. by any assignment.   Any assumption to the contrary may not be portable, 
  151. which may be a good reason to allow for specifying a temporary-count 
  152. individually for each function.  (E.g. it might be that it is infeasible to put a 
  153. result into local memory without loading a component of the address into 
  154. the address temporary.)
  155.  
  156. On the 68000, and other processors that distinguish address and data 
  157. registers, the type of a hardware address register shall always be pointer.  
  158. On the 8086/80186/80286, this is guaranteed to get weird.
  159.  
  160. The type of the result of an operator can be calculated in a machine 
  161. independent way as the tree for the expression is built bottom up by the 
  162. reduce() routine.  The rules for doing this depend only on the rules stated in 
  163. K & R.
  164.  
  165. Functions which produce a non-pointer result  leave that result in ds.  
  166. Functions which produce a pointer result leave that result in as.
  167.  
  168. Temporaries will be used in a canonical way in coding any expression 
  169. which invokes them.  The compiler will do no rearrangements, and will 
  170. evaluate operands by order of association unless there are contradicting 
  171. parentheses.  In order to enhance speed and simplify code generation, a data 
  172. register will be used in lieu of the LHS to accumulate the result, if the LHS 
  173. is in memory and the RHS is nontrivial.  Complex expressions which are 
  174. speed- or space-critical should (as a matter of style) either be decomposed, 
  175. or have intermediate assignments explicitly specified on the RHS.
  176.  
  177. In CSTAR, stack temporaries are not used, so there is no indefinitely 
  178. extensible set of temporaries available.  It is therefore advisable to keep 
  179. expressions as simple as possible consistent with readability, in order to 
  180. know that there are enough temporaries.
  181.  
  182. In the simple, ternary assignment statement, the LHS will not be used as an 
  183. accumulator [if there is any alternative], [or maybe never at all, but what 
  184. about exclusive-or?] because of the following:
  185.  
  186.     m = d1 + d2;    
  187.  
  188. option1: treat as:
  189.  
  190.     m = d1;    
  191.     m += d2;    one move, one add, two addresses, 32 clocks
  192.  
  193. option 2:  treat as:
  194.  
  195.     m = dt = d1 + d2;
  196.     dt = d1;    
  197.     dt += d2;    
  198.     m = dt;    two moves, one add, one address, 20 clocks
  199.  
  200. Most assignments will have implied moves under the covers since the 
  201. processors generally require results to be placed in at least one, and possibly 
  202. in both, operands, while the operator form actually written in source code 
  203. implies no such thing.  If speed or compactness is highly critical, the 
  204. programmer must use forms that are more specific, and correspond more 
  205. closely to the hardware.  However, such forms tend to be less readable, so 
  206. there is no requirement to use them in non-critical code.
  207.  
  208. EKR:  One solution [?]   Have assignment operators (+=, *=, etc.) always 
  209. produce exactly the indicated code.
  210.  
  211.  
  212. STAR EXPRESSIONS
  213.  
  214. Star expressions arise out of the P> and [ ] operators, but with luck that 
  215. should not affect this discussion.
  216.  
  217. Star expressions have 3 parts, 1) an address register, 2) a data register and 
  218. 3) a constant.  At least one of these parts must be present.  The parser will 
  219. probably rearrange the parse tree so that the unary * operator has these 3 
  220. subtrees under it.
  221.  
  222. Before the parser rearranges the tree, the operand of a unary * operator is 
  223. either:
  224.  
  225. 1) another unary * operator or
  226.  
  227. 2) a tree containing a star expression whose operands are joined by + 
  228. operators (or a P operator if the P operator applies to the constant.)
  229.  
  230. The a0 register is used to do multiple indirections as required.  For example, 
  231. the star expression ***a5 generates
  232.  
  233.     a0 = *a5;
  234.     a0 = *a5;
  235.  
  236. and *a0  is then used as the equivalent operand.
  237.  
  238. Scaling is done in d0.  Scaling is done if the data register is present and the 
  239. scale factor is not 1.  For example, the star expression
  240.  
  241.     *(a5 + d5)
  242.  
  243. which is equivalent to
  244.  
  245.     a5 [d5]
  246.  
  247. is done as
  248.  
  249.     d0 = d5;    /* assembly language (no scaling) */
  250.     d0 *= scale_factor;
  251.     a0 = a5 + d0
  252.  
  253. and then *a0 is used as the equivalent operand. Note that scaling by 
  254. constants between 2 and 10 (?) is done by masking and shifting instead of 
  255. multiplying.
  256.  
  257. Similarly, the star expression
  258.  
  259.     * (d5 + base_constant)
  260.  
  261. which is equivalent to 
  262.  
  263.     base_constant [d5]
  264.  
  265. is done as 
  266.     
  267.     d0 = d5
  268.     d0 *= scale_factor;
  269.     a0 = base_constant + d0;
  270.     
  271. and then *a0 is used as the equivalent operand.
  272.  
  273. When scaling is not required at run time, i.e., when the data register is not 
  274. present or when the scale factor is one, star expressions are equivalent to 
  275. 68000 addressing modes.  For example,
  276.  
  277.     char * a1;
  278.     *(a1 + d1 + 5);
  279.  
  280. the star expression is the same as the equivalent operand and generates the
  281.  
  282.     #5(a1,d1) 
  283.  
  284. addressing mode.
  285.  
  286. One could, in theory, allow more complex star expressions by substituting 
  287. an inner expression for the data register.  For example:
  288.  
  289.     * (a1 + (d1 = *a2) + 5);
  290.  
  291. This would be unwrapped as:
  292.  
  293.     d1 = *a2;
  294.     *(a1 + d1 + 5);
  295.  
  296. At present, I see no particular problem with allowing this generality.  
  297. Indeed, one could use d0 as an implicit d register and write
  298.  
  299.     *(a1 + *a2 + 5);
  300.  
  301. which would be the same as
  302.  
  303.     d0 = *a2;
  304.     *(a1 + d0 + 5);
  305.  
  306. It turns out that finding the three separate parts of a star expression is not 
  307. difficult.  At the top level of a tree for the star expression, look for a subtree 
  308. with pointer type.  That subtree contains the address register. If the subtree 
  309. contains a binary operator, one subtree of the binary operator will contain 
  310. the pointer and the other will contain the data register and/or the constant.  
  311. Again, if that subtree contains a binary operator, one subtree must contain 
  312. the data register and the other subtree must contain the constant or constant 
  313. expression.
  314.  
  315.  
  316. WHEN ARE EXPRESSIONS TOO COMPLEX?
  317.  
  318. When we run out of temporaries given the expression and the current 
  319. settings.
  320.  
  321. Associated with each assignment is a list of forbidden registers which may 
  322. not appear in the RHS of the assignment.  This list is created by the 
  323. unwrapping program.  Actually, it's probably more complicated than this.  
  324. There must be some canonical tools.
  325.  
  326. The result of a subtree need not be either the LHS or the result temporary if 
  327. the subtree consists of a primitive.  In that case, that subtree does not limit 
  328. in any way the form of the complexity of the other subtree.  Eh?
  329.  
  330.  
  331. REWRITING RULES
  332.  
  333. An expression of the form
  334.  
  335.     a = arg1 op1 arg2 op2 ... opn argn+1
  336.  
  337. is, by definition, equivalent to
  338.  
  339.     a     = arg1
  340.     a op1 = arg2
  341.     a opn = argn+1
  342.  
  343. I don't know anything about this from here down.
  344.  
  345. As the result of this equivalence, the LHS becomes a hidden common 
  346. subexpression.  The LHS temporary may be used to store this hidden 
  347. common subexpression.  For example,  if the LHS temporary is available, 
  348. the expression
  349.  
  350.     ***a5 = a + b;
  351.  
  352. generates
  353.  
  354.     a1 = **a5;
  355.     *a1 = a;
  356.     *a1 += b;
  357.  
  358. If the LHS temporary is not used, the following code would have to be 
  359. generated (In general, *a0 might be the effective operand for a or b).
  360.  
  361.     a1 = *a5;
  362.     a1 = *a5;
  363.     *a1 = a;
  364.     a1 = *a5;
  365.     a1 = *a5;
  366.     *a1 += b;
  367.  
  368. In actuality, the expression ***a5 = a + b would not be allowed if the LHS 
  369. temporary is not used.
  370.  
  371. Another way around this problem would be to write the expression this way
  372.  
  373.     ***a5 = d0 = a + b;
  374.  
  375. In general, if the effective LHS of an expression is *an, the only  
  376. non-primitive operators allowed on the RHS are +  P  and  ^.
  377.  
  378. Rewriting rules move a lot of "code generation" into the parser.
  379.  
  380.  
  381. MANAGEMENT OF LOC_NODES DESIGNATING SIMPLE REGISTERS
  382.  
  383. The management of loc_nodes is not as simple as we once thought it was.
  384. At this time, in fact, they are not managed at all, but created at will
  385. in great profusion.  The essence of the problem is that because of the
  386. way the parser makes loc_nodes, it is permissible to alter them at will--
  387. until they get attached to the code tree.  Once they get attached to the
  388. code tree, they can no longer be altered because to do so would cause
  389. retroactive changes earlier in the code.  This fact underlaid our bafflement
  390. at the initial behavior of the code generator.
  391.  
  392. Two techniques are proposed to minimize proliferation, and are already
  393. provided for (and a compile-time switch should switch them out, to help
  394. us track down the inevitable bugs!)  The simpler one is to mark nodes when
  395. they get attached to the code list, so that conditional routines like
  396. locn_xdupl need not copy them unless they are marked.  In most cases,
  397. this means that extra copies won't be made except when inner-level
  398. assignments are used as values, as in a = b = c;
  399.  
  400. The more complex one involves the locn_chmod routine, which is meant to
  401. manage a set of simple register and register-indirect nodes.  These
  402. permanent nodes will be marked, so that they never get altered, and
  403. routines like locn_chmod will operate by substituting one for another.
  404. This will limit most copying of temporaries to cases where nodes are
  405. actually combined, rather than just referred to.
  406.  
  407. Provision is also made for locn_xconst to do something similar if it
  408. turns out that we are generating lots of internal ones.  Note that we
  409. may want to improve the folding routines in the parser eventually to
  410. eliminate the creation of superfluous zeros, in particular.  These
  411. nodes do need to carry their types, at least transitorily, but perhaps
  412. there is some practical way to work this out.
  413.  
  414.  
  415. VARIABLES AND VARIABLE NAMES
  416.  
  417. Variables are set up in the parser to be suitable for their intended
  418. storage class.  This fact implies some coupling of the parser to the
  419. code generator; the parser is not totally machine-independent.
  420.  
  421. There is as yet no mechanism for properly determining the range of
  422. a symbolic name; that is, whether it can be combined into an address
  423. mode or not, although portions are in place.  In particular, globals
  424. have to be always regarded as out-of-range, since the linker may place them
  425. anywhere at its own option.
  426.