<
,
>
, <=
, >=
,
==
, !=
)
Of these, while and if statements introduce interesting control flow, for the first time allowing programs that do something other than continue in a straight-line path. Compound statements are important in conjunction with while and if statements, in order to allow a loop body (for example) to perform more than one action. Moreover, introducing compound statements gives us a natural context for variable scoping. The comparison operators are the only addition of no real substance: they are fundamentally similar to the arithmetic operators, and could equally well have been in the compiler the whole time. The only reason to add them now is that they become particularly handy to have now that we have loops and conditional statements.
The syntax of while and if statements should be as in C (and C++
and Java). For example, the controlling expression in each case must
be parenthesized, and there is no keyword (like then or do) between
the parenthesized expression and the following statement. Remember that
any statement can be used as the body of a loop, or as an alternative
in an if
statement: there is no requirement of curly
braces. They have nothing to do with the while
or
if
statement, and are only present if a compound
statement is used, as part of the compound statement. For
example, the following three while
loops are all legal:
while(x >= 10) x = x / 10; // body is an assignment statement while(x >= 10){ // body is a compound statement, which x = x / 10; // happens to only have one statement in it } while(x >= 10){ // body is a more general compound statement int d = x % 10; x = x / 10; print_int(d); }
Each brace-enclosed compound statement constitutes a new scope, nested inside the surrounding scope. It is legal to redeclare a variable that was declared in some outer, surrounding scope, but not legal to redeclare a variable in the exact same scope. Each variable reference refers to a declaration that textually precedes the reference and is in a scope that has not yet ended at the point of the reference. If there is no such declaration, the variable reference is in error. If there is more than one such declaration, the one in the innermost scope is used. To determine whether a declaration textually precedes a reference, consider any declaration that has an initializer to take place at the end of the initializer. For example, in the program:
main(){ int x; // declaration 1 x = 3; // reference (a) while(x){ // reference (b) x = x - 1; // references (c) and (d) int x = x + 5;// reference (e) and declaration 2 print_int(x); // reference (f) } { print_int(x); // reference (g) } { int x = 2; // declaration 3 } print_int(x); // reference (h) }The
x
declared by declaration 1 is used by references
(a), (b), (c), (d), (e), (g), and (h), while the x
declared by
declaration 2 is used only by reference (f), and the x
declared by declaration 3 isn't used at all.
If you do have the new features and the prior optimizations working,
it would be desirable to also turn relational operators around when
appropriate to be able to take advantage of immediate operands. For
example, if the source code contains 3>x
, it would be
better to turn this around to x<3
so that you can
generate an instruction like slt $t0, $s0, 3
.
For while
loops, it is preferable to generate code like
this:
jump to label1 label2: code for the body goes here label1: code for the test goes here branch if true to label2rather than:
label1: code for the test goes here branch if false to label2 code for the body goes here jump to label1 label2:
Normally if
statements translate into something like
code for the test goes here branch if false to label1 code for the first alternative goes here jump to label2 label1: code for the "else" part (2nd alternative) goes here label2:However, when the
if
statement has no else
part, or where the else
part is sufficiently vacuous that
it translates to an empty string of assembly code, it would be
preferable not to generate the jump to label2, since it is a waste of
time to jump to the next instruction.
Don't worry if there are unusual circumstances under which your code will jump (or branch) to a jump instruction, or jump (or branch) to the next instruction, so long as you've taken care to not generate routinely stupid code. (Routinely stupid means something like jumping around a non-existent else part.)
Of course, I don't know the status for those students who didn't turn in the previous lab on time.
If any of you thinks your difficulties with the prior lab would put you at a disadvantage for doing the new lab, you are welcome to ask me for my code from the prior lab, to use as a starting point.
If you are looking for another learning opportunity, this would be a great class to write yourself.
It is important for your compiler not to die if the program contains divisions by zero, because they might be in a non-executed part of the program. Thus, you'll have to not constant-fold any divisions where the divisor is zero.
Worse yet, it turns out that SPIM (our MIPS simulator) doesn't
recognize that some instructions aren't executed, and so it won't load
in an assembly language program that has a division instruction with
an immediate 0 operand. Thus you'll need to be careful not to
generate such instructions. (You can use register $zero
instead.)
c4
modules, highlighting
the differences from the code for c3
. You should also
describe the tests you ran, and any problems uncovered.