On a processor with a sign flag N, an overflow flag O, and a zero flag Z, we can compute x op y by doing x + (-y) and examining the flags as follows:
| Operation | Signed | Unsigned |
|---|---|---|
| < | N xor O | N |
| ≤ | (N xor O) or Z | N or Z |
| = | Z | Z |
| ≠ | not Z | not Z |
| > | not Z and not(N xor O) | not N and not Z |
| ≥ | not(N xor O) | not N |
This answer is done under the most reasonable assumption that the processor has a single ADD instruction that sets flags according to a signed addition interpretation. If you had a processor that distinguished between signed and unsigned additions, then it would never set the N flag and would use O to signal that an unsigned addition caused an overflow. Replacing N with O throughout the unsigned case, therefore, is an acceptable answer, but not the most realistic.
1. r1 := K
2. r4 := &A
3. r6 := &B
4. r2 := r1 × 4
5. r3 := r4 + r2
6. r3 := *r3
7. r5 := *(r3 + 12)
8. r3 := r6 + r2
9. r3 := *r3
10. r7 := *(r3 + 12)
11. r3 := r5 + r7
12. S := r3
This code is adding the two structure fields at offset 12 within arrays A and B.
Flow dependencies are
Anti-dependencies are
Output dependencies are
1. r1 := K
2. r4 := &A
3. r2 := r1 × 4
4. r3 := r4 + r2
5. r3 := *r3
6. r6 := &B
7. r5 := *(r3 + 12)
8. r3 := r6 + r2
9. r3 := *r3
10. r7 := *(r3 + 12)
11. r3 := r5 + r7
12. S := r3
To fill the one after instruction 9, we realize that we should rename r3 to r8 for all the instructions from line 8 down. This means that instruction 7 can move down to fill the slot.
1. r1 := K
2. r4 := &A
3. r2 := r1 × 4
4. r3 := r4 + r2
5. r3 := *r3
6. r6 := &B
7. r8 := r6 + r2
8. r8 := *r8
9. r5 := *(r3 + 12)
10. r7 := *(r8 + 12)
11. r8 := r5 + r7
12. S := r8
Most of these disadvantages are not terrible. The first might be the most severe but is overcome by using a version of C that allows embedded assembly language. Points b), c), and e) are problems with raw assembly language as well, so "so what?" For d), just use C99.
test reg trueGraph falseGraph
where trueGraph and falseGraph are (obviously) node references; if you draw pictures, draw them as pointers. The goto instruction has the form
goto Graph
Again, draw it as a pointer. Also, write [i] to denote the graph node with a single instruction i, and [] for the node with a zero-length sequence of instructions. Finally, introduce the notation
g1 ^ g2
to mean the graph whose instruction sequence is all of g1's instructions followed by g2's.
This problem isn't so bad if you've been trained to think functionally, but it is still very difficult to get right. Knowing where and when to use the goto instructions makes all the difference. (Note how the goto and the test instructions "finish off" a node. Here's the grammar (oh, notice I used "nfr" for next_free_reg, which is strange because I usually hate abbreviation.... Go figure! And I simplified the 'stp' thing too.):
global reg_names = ["r1", "r2", ..., "rk"]
program -> id stmt
stmt.nfr := 0
program.name := id.name
program.graph := stmt.graph
assign: stmt1 := id expr stmt2
expr.nfr := stmt2.nfr := stmt1.nfr
stmt1.graph := expr.graph ^ [id.name := expr.reg] ^ stmt2.graph
if: stmt1 -> expr stmt2 stmt3 stmt4
expr.nfr := stmt2.nfr := stmt3.nfr := stmt4.nfr := stmt1.nfr
stmt1.graph :=
expr.graph ^
[test expr.reg
stmt2.graph^[goto stmt4.graph]
stmt3.graph^[goto stmt4.graph]]
while: stmt1 -> expr stmt2 stmt3
expr.nfr := stmt2.nfr := stmt3.nfr := stmt1.nfr
stmt1.graph := [goto g]
where g =
expr.graph ^
[test expr.reg stmt3.graph stmt2.graph^[goto g]]
null: stmt ->
stmt.graph := []
The rest of this problem is straightforward; change "code" to
"graph", "+" to "^", "next_free_reg" to "nfr" and so on.
To get started, let's look at the simple case where we don't worry about spilling and don't worry about saving the result.
if : stmt1 -> expr stmt2 stmt3 stmt4
expr.nfr := stmt2.nfr := stmt3.nfr := stmt4.nfr := stmt1.nfr
THEN_LABEL := new_label(); ELSE_LABEL := new_label(); END_LABEL := new_label()
expr.trueloc := THEN_LABEL
expr.falseloc := ELSE_LABEL
expr.do_not_save := true
stmt1.code := expr.code
+ [THEN_LABEL ":"] + stmt2.code + ["goto" END_LABEL]
+ [ELSE_LABEL ":"] + stmt3.code + [END_LABEL ":"]
+ stmt4.code
> : expr1 -> expr2 expr3
expr1.code := expr2.code + expr3.code
+ ["if expr2.reg "<=" expr3.reg "goto" expr1.falseloc]
+ ["goto" expr1.trueloc]
-- The other relational operators are analagous
or : expr1 -> expr2 expr3
expr1.code := expr2.code
+ ["if" expr2.reg "goto" expr1.trueloc]
+ expr3.code
+ ["if !" expr3.reg "goto" expr1.falseloc]
+ ["goto" expr1.trueloc]
and : expr1 -> expr2 expr3
expr1.code := expr2.code
+ ["if !" expr2.reg "goto" expr1.falseloc]
+ expr3.code
+ ["if !" expr3.reg "goto" expr1.falseloc]
+ ["goto" expr1.trueloc]
Integrating saving and spilling should be done as part of this assignment, too. Also, knowing whether to test for false or true could be sent into an inherited attribute. There are many ways to do all this.
For StartExpression
public void analyze(Log log, SymbolTable table) {
// Analyze all the arguments
for (Expression e: args) {
e.analyze(log, table);
}
// Find out which function we're calling. If it turns out
// to be null, an error will be logged anyway, but there's
// nothing that has to be done here.
function = table.lookupFunction(functionName, args, log);
type = Type.THREAD;
}
For DieStatement
public void analyze(Log log, SymbolTable table, Function f, boolean inLoop) {
argument.analyze(log, table);
if (argument.type != Type.STRING) {
log.error("die_arg_must_be_string");
}
}
For UnlessStatement
public void analyze(Log log, SymbolTable table, Function f, boolean inLoop) {
condition.analyze(log, table);
condition.assertBoolean("unless_condition_not_boolean", log);
body.analyze(log, table, f, true);
}
For SliceVariable
public void analyze(Log log, SymbolTable table) {
array.analyze(log, table);
low.analyze(log, table);
high.analyze(log, table);
array.assertArrayOrString("[...]", log);
low.assertInteger("[...]", log);
high.assertInteger("[...]", log);
type = array.type;
}