# 2019-MPSL Lab2 > 2019 Fall Microprocessor System Lab > Lab2 ARM Assembly II > [name=0516009 吳宗達、0616015 劉姿利] ## What to Do - Learning how to use stack pointer `SP` - Learning how to implement recursion with stack in assembly language - Finishing 2 tasks - Postfix arithmetic - GCD and the Stack Size ## How to Do ### Postfix Arithmetic ```cpp= .syntax unified .cpu cortex-m4 .thumb .data user_stack_bottom: .zero 128 expr_result: .word 0 .text .global main postfix_expr: .asciz "-100 10 20 + - 10 +" // R0: address // R1: ptr // R2: value // R3: pop, push // R4: pop // R5: ten main: ldr SP, =user_stack_bottom add SP, #128 ldr R0, =postfix_expr mov R1, #0 LOOP: ldrb R2, [R0, R1] // \0 cmp R2, #0 it eq bleq END // space cmp R2, #32 itt eq addeq R1, #1 beq LOOP // add cmp R2, #43 it eq beq ADD // sub cmp R2, #45 it eq beq SUB b NUM ADD: pop {R3, R4} add R3, R4 push {R3} add R1, #1 b LOOP SUB: add R1, #1 ldrb R2, [R0, R1] cmp R2, #32 it eq beq SUB_OP cmp R2, #0 it eq beq SUB_OP it ne bne SUB_NUM SUB_NUM: bl atoi sub R12, #1 mvn R12, R12 push {R12} b LOOP SUB_OP: pop {R3, R4} sub R4, R3 push {R4} b LOOP NUM: bl atoi push {R12} b LOOP END: pop {R2} ldr R0, =expr_result str R2, [R0] L: nop b L // R12: return value // R5: ten atoi: mov R12, #0 mov R5, #10 ATOI_LOOP: ldrb R2, [R0, R1] add R1, #1 // break cmp R2, #32 it eq bxeq LR cmp R2, #0 it eq bxeq LR mul R12, R5 sub R2, #48 add R12, R2 b ATOI_LOOP bx LR ``` #### Sample Testcase ``` 45 49 48 48 32 49 48 32 50 48 32 43 32 45 32 49 48 32 43 00 - 1 0 0 1 0 2 0 + - 1 0 + \0 ``` #### `.zero` 意義 ```cpp user_stack_bottom: .zero 128 ``` 宣告 128 個 bytes 的空間並把他們初始化為 0。 #### `SP` Configuration ```cpp ldr SP, =user_stack_bottom add SP, #128 ``` 將 `SP` 移到 `user_stack_bottom` 的位址並往後移 128 個 byte,因為 `stack` `push` 和 `pop` 的操作是以 `SP` 為 base,從高位開始使用,所以要先加到高位的 address 上去。 #### `atoi` Implementation - `R0`: Begin address of `postfix_expr` - `R1`: Index of `postfix_expr` (`postfix_expr` 中第幾個 byte) - `R2`: 位於 `R0` + `R1` 位址的 value - `R5`: Const integer 10 - `R12`: Return value ```cpp atoi: mov R12, #0 // 初始化 return value 為 0 mov R5, #10 // 設 R5 為 10 ATOI_LOOP: ldrb R2, [R0, R1] // 取出位於 R0 + R1 位址的 value add R1, #1 // index++ // break: 遇到 space 或 \0 則返回 cmp R2, #32 // space it eq bxeq LR cmp R2, #0 // \0 it eq bxeq LR mul R12, R5 // R12 *= 10 sub R2, #48 // R2 -= '0' add R12, R2 // R12 += R2 b ATOI_LOOP bx LR ``` #### Get 2's Complement ```cpp sub R12, #1 mvn R12, R12 ``` 如果要將 `R12` 中的字轉成 2's complement,要先將 `R12` 減 1 然後取 1's complement。 #### Postfix Arithmetic Using Stack `push` 和 `pop` 在 memory 位址中與目標 register index 的大小關係為: - `push`: 由高位到低位,優先將 register index 大的 push 到 memory 中 - `pop`: 由低位到高位,優先 pop 到 index 小的 register 上 ##### `push` Number ```cpp push {R12} ``` ##### `pop`: 加法 ```cpp pop {R3, R4} add R3, R4 push {R3} ``` ##### `push`: 減法 ```cpp pop {R3, R4} sub R4, R3 push {R4} ``` 因為 `pop` 會將後丟進 stack 的值優先放入 register index 小的 `R3` 中,但是我們要用「先放入 stack 的值」減去「後放入 stack 的值」,所以要拿 `R4` 減去 `R3`,至於加法有交換律則沒有這個問題。 ### GCD and the Stack Size ```cpp= .syntax unified .cpu cortex-m4 .data result: .word 0 max_size: .word 0 .text m: .word 0x5E // 94 n: .word 0x60 // 96 .global main // R0: address // R1: a // R2: b // R3: modified a // R4: modified b // R5: a % 2 // R6: b % 2 // R7: return value // R8: SP /* start GCD */ GCD: // push push {LR} mov R8, SP // return cases cmp R1, #0 itt eq moveq R7, R2 popeq {PC} cmp R2, #0 itt eq moveq R7, R1 popeq {PC} // compare and R5, R1, #1 and R6, R2, #1 orr R3, R5, R6 cmp R3, #0 asr R3, R1, #1 asr R4, R2, #1 // recursion cases case1: it ne bne case2 mov R1, R3 mov R2, R4 bl GCD lsl R7, #1 pop {PC} case2: cmp R5, #0 it ne bne case3 mov R1, R3 bl GCD pop {PC} case3: cmp R6, #0 it ne bne case4 mov R2, R4 bl GCD pop {PC} case4: cmp R1, R2 ittee ge subge R3, R1, R2 movge R4, R2 sublt R3, R2, R1 movlt R4, R1 mov R1, R3 mov R2, R4 bl GCD pop {PC} /* end GCD */ main: ldr R0, =m ldr R1, [R0] ldr R0, =n ldr R2, [R0] bl GCD ldr R0, =result str R7, [R0] sub R8, SP, R8 ldr R0, =max_size str R8, [R0] nop nop L: b L ``` #### GCD Code in C ```cpp int GCD(int a, int b) { // return cases if (a == 0) return b; if (b == 0) return a; // recursion cases if (a % 2 == 0 && b % 2 == 0) // case1 return 2 * GCD (a >> 1, b >> 1); else if (a % 2 == 0) // case2 return GCD(a >> 1, b); else if (b % 2 == 0) // case3 return GCD(a, b >> 1); else // case4 return GCD(abs(a - b), min(a, b)); } ``` #### GCD 的 parameters 使用 我們將 `R1` 和 `R2` 分別代表 GCD 的 parameters `a` 和 `b`,在進入 recursion `bl GCD` 之前,要先將需要的 parameters 移進 `R1` 和 `R2` 中。 以下以 case4 為例。 ```cpp // e.g. case4 mov R1, R3 // R3 = (a >> 1) mov R2, R4 // R4 = (b >> 1) bl GCD ``` #### push 和 pop 的使用 ##### push ```cpp GCD: push {LR} ... ``` 每次一進入 GCD 就馬上把 return link `LR` 存起來,因為 recursion 時再次呼叫 `bl GCD` 會把原本在 `LR` 上的值覆蓋掉。 ##### pop ```cpp pop {PC} ``` 當要 return 的時候直接使用上面這行讓 `PC` 直接跳回 return link `LR`。 #### max_size 的計算 ```cpp push {LR} mov R8, SP ``` 每次 push 完將新的 `SP` 值存到 `R8` 裡面。 因為每一層 `GCD` 的 recursion 只會往下長出一個下一層的 `GCD`,所以 `R8` 的值只會越來越小。 ```cpp sub R8, SP, R8 ldr R0, =max_size str R8, [R0] ``` 計算 `max_size` 要將 `SP` 和 `R8` (遞迴途中 `SP` 的最小值)相減,再存到記憶體中。 ## Feedback 這次 Lab 完成之後有更了解 `push` 跟 `pop` 的運作,尤其是他們在 recursion 中扮演的角色。 原先以為 push 到 stack 上的值是要傳遞到下一層的 parameters,後來才發現真正的目的是要保存目前的狀態,return 之後才可以繼續用。 另外一個比較有收穫的就是 ascii 在 memory 中的配置還有字串的處理,對於在 assembly language 中沒有型態只有記憶體與數值的架構感覺有點新奇。 ## Reference - [Assembler Directives](https://docs.oracle.com/cd/E26502_01/html/E28388/eoiyg.html)