Used to drive conditional execution of program sections.
(In some languages) may be assigned to boolean variables or passed as parameters.
Two representations may be useful:
Encode true and false numerically, e.g., as 1 and 0, and treat boolean expressions like arithmetic expressions.
Pro: Language may support boolean values.
Con: Bad match to hardware.
Position in generated code represents boolean value.
Pro: Good when ``short-circuit'' evaluation is allowed (or required), e.g., in C expression e1 || e2, e2 should be evaluated only if e1 is false.
Reminder: Some languages mandate short-circuit evaluation; others prohibit it; still others leave it up to the compiler writer.
Pro: Convenient for control statements.
For PCAT, we'll use flow-of-control approach, and convert to values when necessary.
Sample Productions for Value-based Conditional Evaluation
More Sample Value-based Productions
Here | represents bit-wise OR. Note that this implements NON-short-circuiting form of OR.
Example Value-based Code
IF (a > 7) OR (b = 5) THEN x = 7 ELSE y = 2;
Basic Control-flow Representation
Idea: Code generated for boolean and relational expressions has true and false ``exits'', i.e., code evaluates expression and then jumps to one place if true and another place if false.
Relational expressions perform test and jump to true or false exit accordingly.
Boolean variables and constants jump directly to appropriate true or false exit.
Boolean expressions simply adjust/combine true/false exits of their sub-expressions.
Conditional statements define true and false exits of boolean sub-expression to point to appropriate code blocks, e.g., THEN and ELSE branches.
If boolean-typed expression must deliver a value, true and false exits are defined to point to code that loads the value.
Example (assuming short-circuiting)
Conditional Statements - Semi-Naive Approach
Use control flow representation for boolean-typed expressions; define labels on per-statement basis.
Inherit true and false label attributes.
Synthesize code to perform appropriate test and jump to appropriate label.
Code doesn't build a value, so no place attribute.
Inherit true and false label attributes. Pass them down to subexpressions, after suitable manipulation; synthesize code attribute.
Again, no place attribute.
Conversions to and from value form
Boolean-typed identifiers (variables, true
and false constants) must be ``converted''
to control-flow form when tested.
(Assuming 0 = false, non-0 = true)
Similarly, must convert other way when a value is needed, generating code to build a value into a place.
Disadvantages of Naive Approach
Code for each statement always concludes by ``falling through'' to next statement.
There is no information flow between code generation for statements.
This can lead to bad code, e.g.,
We can eliminate problems like this during optimization, but it's easy to avoid some of them in the first place. Idea: Defer Definition of Target Labels
Give each statement an inherited attribute .next, which says where to transfer control after statement.
Code generated for each statement guarantees either to transfer control to .next label or to ``fall through.''
Deferred Label Definition (continued)
Now Get Better Code
What about break or exit statements that can cause jumps out of loops? Add a .break inherited attribute!
All other statement translations must pass the .break attribute through (unchanged) to their children!
Target label attributes (true,false,next) are inherited, so won't work with one-pass bottom-up code generation.
Solution: Instead, keep lists of locations of gotos that need to be filled in (``backpatched'') when final target is known. These backpatch lists are synthesized attributes.
Example (to fill in): (a > 7) OR (b = 5)
At reduction for B := B1 OR B2
Backpatch B1.false list with address of first instruction in B2.
Merge B1.true B2.true to form B.true.
Make B2.false into B.false.
At reduction for conditional statement, backpatch true and false lists for expression.
E.g.: On reducing if B then S1 else S2, backpatch B.true to location of S1 and B.false to location of S2.
Example (to fill in):
Good code generation for case statement depends on analysis of the values on the case labels vi .
List of conditional tests and jumps (linear search).
Binary decision code (binary tree).
Other search code (e.g., hash table).
Jump table (constant time).
Best option depends on range of values (min and max) and their ``density,'' i.e., what percentage of the values in the range are used as labels.
Jump tables work well for dense value sets (even if large), but waste lots of space for sparse sets. Linear search works well for small value sets.