Offset load and store with write-back ldr / str with offset can write the new address (base + offset) back to the address register (in this case r1 ) in two different ways pre-offset: update the index register before doing the store (or load) @ r1 := r1 + 4 str r0, [r1, 4]! @ note the "!" post-offset: update the index register after doing the load (or store) @ r1 := r1 - 8 ldr r0, [r1], -8 @ no "!" for post-offset 62
Pre-offset addressing 63
Post-offset addressing 64
Stack pointer example (again) Pre/post offset addressing means fewer instructions mov r2, 0xbc @ push str r2, [sp, -4]! @ do stuff... @pop ldr r3, [sp], 4 65
push and pop instructions Doing this with the stack pointer ( sp ) as the base address is so common that the ISA even has specific push and pop instructions mov r2, 0xfe @ gives same result as `str r2, [sp, -4]!` push {r2} @ do stuff... @ gives same result as `ldr r3, [sp], 4` pop {r3} note that the sp base address is implicit 66
Register list syntax There was one other difference in the push and pop syntax: the brace ( { } ) syntax around the register name Certain instructions take register lists—they can apply to multiple registers at once, e.g. @ push r0, r1, r2, r9 to stack, decrement sp by 4*4=16 push {r0-r2,r9} @ pop 4 words from the stack into r0, r1, r2, r9 pop {r0-r2,r9} 67
push instruction encoding from A7.7.99 of the reference manual 68
Load/store multiple There are also instructions for loading/storing multiple words using any register as the base register ldmdb load multiple, decrement before ldmia load multiple, increment after stmdb store multiple, decrement before stmia store multiple, increment after But if sp is the base address, then push and pop are probably easier to read be careful about the order! 69
Further reading http://www.davespace.co.uk/arm/introduction-to-arm/stack.html 70
Function prologue & epilogue The beginning (or prologue) of a function should: store (to the stack) lr and any other values (e.g. parameters) in registers which will clobbered during the execution of the function (remember the AAPCS ) make room for any temporary variables by decreasing the stack pointer The end (or epilogue) of a function should: 71
Share house kitchen 72
Function prologue & epilogue example .type my_func, %function @ assume three parameters in r0-r2 my_func: @ prologue push {r0-r2} @ sp decreases by 12 push {lr} @ sp decreases by 4 @ body: do stuff, leave "return value" in r3 @ epilogue mov r0, r3 @ leave return value in the right place pop {lr} @ sp increases by 4 add sp, sp, 12 @ balance out the initial "push" bx lr 73
Function stack frame 74
Nested function calls outer_fn: push {r0,lr} bl middle_fn pop {r0,lr} bx lr middle_fn: push {r0,lr} bl inner_fn pop {r0,lr} bx lr inner_fn: @ do inner function stuff bx lr 75
Nested stack frames 76
the sp “zippers” up and down as the program executes 77
There’s lots more to say … there’s more you can put in your stack frame (e.g. frame pointer fp ) ARMv7/AAPCS is pretty register-heavy (other ISA/CCs use the stack more, e.g. for parameter passing and return addresses) an optimizing compiler will almost certainly not generate the code you expect recursion is an interesting case, which we will now look at shortly 78
These are all conventions It’s the programmer’s job to adhere to them: the operating systems programmer, the compiler programmer, the library programmer, the application programmer, … For bare-metal assembly programming, you’re all of those 79
Recursion A function is recursive if it calls itself from the body of the function. A simple example is: 1 if n = 0 f ( n ) = { nf ( n − 1) otherwise What does this do? 80
Three characteristics of a recursive function: 1. There must be a terminating condition 2. The function must call a clone of itself 3. The parameters to that call must move the function towards the terminating condition 81
@ number passed in r0, result in r1 factorial: push {r0,lr} cmp r0, #0 bne recurse @ what's the potential problem here? mov r1, #1 jmp end recurse: sub r0, #1 bl factorial add r0, #1 mul r1, r0 end: pop {r0,lr} bx lr 82
Some observations about recursion: Recursion incurs a memory overhead (stack frames in the call stack) with each recursive call If values need to be passed back once the recursion terminates, then the resulting unwinding of the call stack and intermediate calculation of values can be expensive Compilers in high level languages can optimize recursive calls so that they run faster In many cases, it can be re-written as an interative solution, which is generally faster 83
Questions? 84
Fibonacci Sequence Write a function that generates a Fibonacci sequence of a certain length. The length and the starting address in memory where the sequence is to be stored are input as arguments to the function. Call the function for different values of length and check that it works correctly. 85
Area Write a function that computes the area of a triangle, given its base and height. Now, write a function that uses the above function to compute the area of a square, given the length of one of its sides. 86
Velocity Write a function that computes the final velocity of an object, given its initial velocity, acceleration and elapsed time. 87
Assembler Macros 88
Outline Godbolt compiler explorer assembly macros 89
Godbolt compiler explorer https://godbolt.org/ : a super-cool interactive resource for exploring stack frames (and code generation in general) A few tips: in the compiler select dropdown, select one of the ARM gcc options in the Compiler options … box, try -O0 (unoptimised) vs -O3 (optimised) try modifying the C code on the left; see how the asm output on the right changes remember the stack frames ! 90
Macros are for automatically copy-pasting code 91
Like this … 92
as macro language The macro language is defined by the assembler ( as ) Two steps: define a macro (with .macro / .endm ) call/use a macro (using the name of the macro) The assembler copy-pastes the macro code (replacing parameters where present) into your program before generating the machine code 93
General macro syntax .macro macro_name arg_a arg_b ... @ to use the argument, prefix with "\" @ e.g. adds r0, \arg_a, \arg_b @ ... .endm 94
Example: swap @ swap the values in two registers @ assumes r12 is free to use as a "scratch" register .macro swap reg_a reg_b mov r12, \reg_a mov \reg_a, \reg_b mov \reg_b, r12 .endm 95
Calling the swap macro If you use swap in your assembly code swap r0, r3 the assembler sees it an “expands” it to mov r12, r0 mov r0, r3 mov r3, r12 it’s exactly like you had used this code in your main.S file in the first place 96
the CPU doesn’t know anything about your macros 97
Recap: if statement Remember the best if statement if: @ set flags here b<c> then @ else b rest_of_program then: @ instruction(s) here rest_of_program: @ continue on... 98
An if macro .macro if condition_code condition then_code else_code \condition_code b\condition then \else_code b end_if then: \then_code end_if: .endm @ usage if "cmp r1, r2", eq, "mov r3, 1", "mov r3, 0" 99
Things to note Macros can “splice” parameters into the middle of instructions, e.g. b\condition becomes e.g. beq or blt Whole instructions can be treated as a single macro parameter (e.g. "cmp r1, r2" as the condition_code parameter) as long as they’re surrounded by double quotes ( " ) This is a blessing and a curse! 100
Recommend
More recommend