In this segment we'll pick up where we left off, with the call of max on line 37 of Prg5.s.
You know enough now to figure out most of max on your own, with the probable exception of lines 15 and 16. It starts by setting up a stack frame, though its stack frame differs a bit from that of maxof3.
There is one notable omission in max's stack frame vs maxOf3's stack frame. What is it? And why is this omission OK in max's case? (Note: a bx <reg> instruction branches to a location held address register
Answer 1 It doesn't save lr on the RTS. Instead, it leaves lr unchanged throughout the function call, and uses it directly to jump back at the end, on line 21.
When a function doesn't call any other function, it can just leave its lr value in the register, since no other call will overwrite lr.
Since we're looking at line 21, can you rewrite it to do the same thing, but with a different
Answer 2
bx instruction is a relatively recent addition to the ARM ISA. It's slightly faster than a mov on newer processors, and is preferred.
How many words of local state does max's stack frame have, and what is in each word, in ascending order within the RTS (descending memory order)? Use names from the C code to describe the contents.
Answer 3
Let's draw the max stack frame in the diagram, now.
Question 4
What part of the C code for max is implemented by lines 12-14? (It's just one part of an expression, not a whole C statement.)
Answer 4
Lines 15 and 16 need some explanation. You already know that the result of a cmp can be used to control a branch operation in the next instruction, e.g. bgt will branch if the result of the preceding cmp was "greater than". But the result of a cmp can be used in more powerful ways.
First, any instruction may be made conditional on the result of the cmp by adding the correct two letters to the opcode. So movge means "move only if the cmp result was greater-than-or-equal. And, as another example, an addlt would mean "add only if the cmp result was less-than.
Second, a cmp result stays around for more than just the next instruction. Any instruction after a cmp, even several steps later, can still use that result. The result stays until replaced. For now, the only way we can replace it is via another cmp. We'll see other ways later. Thus, the result of the cmp is still available for testing on line 16.
Question 5
You know know enough to decipher what lines 15 and 16 are up to. What do they do? Tie it back to the C code.
Answer 5
This completes the implementation of the x > y ? x : y expression.
Question 6
And why is it necessary to move that result from r1 to r0 on line 17? (Think about what the C code does with the result of the expression.)
Answer 6
So, lines 12-17, all together, implement return x > y ? x : y.
You may also be thinking that they do so very inefficiently, and you'd be right. Compilers produce dumb but accurate assembly code, unless you ask them to optimize. We'll do that in a later module, and then we'll find that compilers produce very smart but hard to understand assembly code, filled with interesting efficiency tricks. This is a great way to learn about compilers and ARM architecture, but it's a poor way to learn assembly code at first. So we'll stick with the slightly dumb, but unoptimized, compiler-generated code for now.
However, even this assembly code has one nice optimization: lines 15 and 16. The same job could have been done with a couple of branches.
Question 7
Write a replacement of lines 15 and 16 using branches, and simple mov instructions. You'll need to add two labels.
Answer 7
cmp r2, r3 bge r2IsBigger mov r1, r3 b allDone r2IsBigger: mov r1, r2 allDone:
Now, that's a good bit longer, but even worse, it has a couple of branches. As we mentioned earlier when explaining why the compiler puts while-loop tests after the loop body, branches will slow down a RISC processor. We'll see why in a later module, but for now we can appreciate the elegance of lines 15 and 16 in avoiding any branches.
So, to wrap up, explain how lines 19-21 tear down our current stack frame, and return to the caller. And, specifically, could we replace ldr fp, [sp], #4 with just ldr fp, [sp]?
Line 19 removes the local state from the frame, returning sp to the bottom of the frame. Line 20 loads the old fp value back into fp, and moves the sp forward in memory to eliminate the last word in the current frame. That #4 addition is essential, since the caller expects the sp to be left as it was when max was called. Finally, line 21 returns to the caller, with the result in r0.
In the next segment, we'll look at main.