Unlike most languages, LC3 assembly is a case insensitive language. You can
"spell" things in upper case, lower case, or mixed case.
You may use different "spellings" in different places, but all refer to
the same thing. For example, blah, BLAH, Blah, BlaH
are all
identical. The only place where case matters is in the
.STRINGZ
pseudo-op which is used to initialize memory with a
quoted string (e.g. .STRINGZ "Hello World"
). The case of the quoted
string is preserved.
malloc()/free()
) because no one has written routine(s) to do it.
C code | LC3 code |
|
|
LD
and
ST
.
C code | LC3 code |
|
|
|
|
When using LD/ST
, the location of the variable is fixed at
assembly time and the location must be within 256 of the instruction.
If either of these conditions is not met, pointers must be used.
C code | LC3 code |
|
|
|
|
|
|
Like the LD/ST
instructions, the LDI/STI/LEA
instructions have a limited range. The pointer variable itself must be
within 256 of the instruction. The pointer can address anywhere in the
memory.
LDR/STR
instructions also access memory via pointers. However,
the value of the pointer is stored in a register instead of a pointer variable.
This is useful for accessing structures and variable on the stack. Consider a
simple C structure that represents a date. It contains three integers
representing a day, month and year. Such a structure might be declared as
C code | LC3 code |
|
No LC3 equivalent |
|
|
|
|
|
|
Note how the address is stored in a register and then different offsets are
used to access the different fields of a structure. In fact, C has a construct
offsetof()
that is used to compute the address of a field within
a structure. In LC3 assembly, you must compute the offsets yourself (or write
your own compiler :( ).
The LDR/STR
instructions are also important in accessing method
parameters and local variables. This is done using offset from the frame
pointer.
<
, >
, ==
,
!=
, ...). In assembly language we can only test a value and
determine if it is negative, zero or positive. Therefore, the logical
expressions in C must be converted to an operation with respect to zero. The
following table shows the conversion.
C logical expr | C numeric expression | LC3 branch instruction | LC3 negated branch instruction |
if (a < b) |
if ((a - b) < 0) |
BRn |
BRzp |
if (a <= b) |
if ((a - b) <= 0) |
BRnz |
BRp |
if (a == b) |
if ((a - b) == 0) |
BRz |
BRnp |
if (a >= b) |
if ((a - b) >= 0) |
BRzp |
BRn |
if (a > b) |
if ((a - b) > 0) |
BRp |
BRnz |
if (a != b) |
if ((a - b) != 0) |
BRnp |
BRz |
if (a) |
if (a != 0) |
BRnp |
BRz |
if (! a) |
if (a == 0) |
BRz |
BRnp |
Converting a typical C code with a condition is a two part process. First one must generate an LC3 operation that sets the condition code in a way that can be used to make a decision. Then one has to conditionally execute or not execute some code.
Comparison in LC3 often require subtraction. But, the LC3 has no subtract operation, so one must change this to addition with a negated value.. Negation is two's complement. The logical structure of a condition in C is if condition is true, do the following. If we want the structure of assembly language code to mimic the form of the high level code (i.e. the code to execute directly follows the test), the test must be changed to if condition is not true, skip the following. This is done by using the negated test.
C code | LC3 code |
if (a < b) { // do something } |
LD R0, a ; load a LD R1, b ; load b NOT R1, R1 ; begin 2's complement of b ADD R1, R1, #1 ; R1 now has -b ADD R0, R0, R1 ; R0 = a + (-b) ; condition code now set BRzp SKIP ; if false, skip over code ; code to do something (the true clause) SKIP ; remainder of code after if |
if/else
. Now one selects between two
pieces of code. One is executed if the condition is true, the other if the
condition is false. To make this change, the else
is added as is
a branch to skip the else
if the condition is true. The final code
becomes:
C code | LC3 code |
if (a < b) { // do something } else { // do something else } |
LD R0, a ; load a LD R1, b ; load b NOT R1, R1 ; begin 2's complement of b ADD R1, R1, #1 ; R1 now has -b ADD R0, R0, R1 ; R0 = a + (-b) ; condition code now set BRzp ELSE ; if false, skip over code ; code to do something (the true clause) BR END_ELSE ; don't execute else code ELSE ; code for else clause here END_ELSE ; remainder of code after else |
if (score >= 90) {
grade = 'A'
}
When converting this to assembly, one needs to compute score -90
.
One can store the value 90 using an .FILL
.
However, the code will still need to negate it and add 1 to get the negative
value. An easy alternative is to store the negative value.
Then the final code will only need to add. Compare the two possibilities in
the following table.
code using positive constant | code using negative constant |
|
|
&&/||/!
). Code for each individual condition is required.
In between the code for each condition are branch instructions that branch
to the appropriate location as soon as the final result (true/false) is
known. For example, when you use use a logical and (&&
), if
the first clause is false, the result is false regardless of the second clause.
Similarly, if you use logical or (||
), and the
first clause is true, the result is true regardless of the second clause.
This is know as short circuiting the evaluation. Of course, if the first
clause of an and (&&
) is true, or the first clause of an or
(||
) is false, the second clause must be evaluated to determine
the result.
The following code uses "boolean" variables with C's notation of zero being false and non-zero being true. If you are translating code with comparisons, you must have code to do the subtraction to generate the comparison result.
C code | LC3 code |
if (a || (b && !c)) { // do something } |
LD R0, a ; load a BRnp DO_IT ; left clause of or is true so execute ; get here if a is false LD R0, b ; load b BRz SKIP ; left side of and is false, so skip ; get here if b is true LD R0, c ; load C NOT R0,R0 ; negate it (! operation) BRz SKIP ; right side of and is false, so skip DO_IT ; code to do something (the true clause) SKIP ; remainder of code after if |
for
loop of C. The syntax for the for
is:
for (initialization; termination condition; increment) {
// body of loop
}
The for
has three distinct parts:
for
to LC3 assembly.
for (int i = 0; i < limit; i++) {
// body of loop
}
In the following table, the numbers at the beginning of the line are used for reference in the discussion. They do not actually occur in the code.
C code | LC3 code | Optimized LC3 code |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
There are redundant instructions in the above code. In general, it is the job
of the optimizer to fix this, but if you are writing in assembly language, you
may choose to do it yourself. For example, consider the use of R0
to hold the value i
. Note that at line 02:
the
value of R0
is stored in i
, only to be immediately
reloaded at statement 03:
. And, at line 11:
the
updated value of i
is also in R0
. Thus, the
LD
at line 03:
is redundant and can be removed. There
is also an redundant ST
instruction.
A word to the wise, first get it correct, then optimize (but
only if necessary).
break
, you would use
a BRnzp END
in the body of the loop. If you wanted to use the
equivalent of C's continue
, you would put a
BRnzp INCR
in the body of the loop.
for
loop is just a while
loop with some extra
code. The extra code is the initialization and the increment sections.
The initialization code is removed. The only code at the end of the loop is an
unconditional branch (BR TEST
) back to the termination test.
for
loop from the previous section. The syntax for the
countdown for
loop is:
for (int i = limit; i > 0; i--) {
// body of loop
}
In the following table, the numbers at the beginning of the line are used for reference in the discussion. They do not actually occur in the code.
C code | LC3 code |
|
|
|
|
|
|
|
|
|
|
If the body of the loop does not use R0
, the code can be
optimized further by removing lines 02
and 04
.
This leaves four lines of LC3 code: a) initialize counter; b) check for
termination; c) decrement counter; d) go back to check.
do/while
loop the termination test occurs at the end of the
loop. This means the body of the loop will be executed at least once. Since
the test is at the bottom, one uses the "positive" condition for the test.
This means that if the condition is true, the code branches back to the
beginning.
Consider the C code
do {
// body of the loop
} while (i < limit);
C code | LC3 code |
|
|
|
|
|
|
C code | LC3 code |
|
|
|
|
In C, array access (e.g. c[i]
) is equivalent to pointer
access (e.g. *(c + i)
).
(c + i*sizeof(element))
.
Each element of the array takes
the same amount of space, but the size of an element may not be 1.
C provides the construct sizeof()
to do this computation.
Here is an example where the size of each element is greater that 1.
LC3 code |
|