--- language: "MIPS Assembly" filename: MIPS.asm contributors: - ["Stanley Lim", "https://github.com/Spiderpig86"] translators: - ["AstiaSun", "https://github.com/AstiaSun"] lang: uk-ua --- Мова ассемблера MIPS (англ. Microprocessor without Interlocked Pipeline Stages) була написана для роботи з мікропроцесорами MIPS, парадигма яких була описана в 1981 році [Джоном Геннессі](https://uk.wikipedia.org/wiki/Джон_Лерой_Геннессі). Ці RISC процесори використовуються у таких вбудованих системах, як маршрутизатори та мережеві шлюзи. [Детальніше](https://en.wikipedia.org/wiki/MIPS_architecture) ```asm # Коментарі позначені як'#' # Всі символи після '#' ігноруються лексичним аналізатором асемблера. # Зазвичай програми поділяються на .data та .text частини .data # У цьому розділі дані зберігаються у пам'яті, виділеній в RAM, подібно до змінних # в мовах програмування вищого рівня # Змінна оголошується наступним чином: [назва]: .[тип] [значення] # Наприклад: hello_world: .asciiz "Hello World\n" # Оголосити текстову змінну num1: .word 42 # word - це чисельний тип 32-бітного розряду arr1: .word 1, 2, 3, 4, 5 # Масив чисел arr2: .byte 'a', 'b' # Масив буквених символів (розмір кожного - 1 байт) buffer: .space 60 # Виділити місце в RAM # (не очищується, тобто не заповнюється 0) # Розміри типів даних _byte: .byte 'a' # 1 байт _halfword: .half 53 # 2 байти _word: .word 3 # 4 байти _float: .float 3.14 # 4 байти _double: .double 7.0 # 8 байтів .align 2 # Вирівнювання пам'яті даних, де число # показує кількість байтів, вирівнених # у степені 2. (.align 2 означає # чисельне (word) вирівнювання оскільки # 2^2 = 4 байти) .text # Розділ, що містить інструкції та # логіку програми .globl _main # Оголошує назву інструкції як # глобальну, тобто, яка є доступною для # всіх інших файлів _main: # програми MIPS виконують інструкції # послідовно, тобто першочергово код # буде виконуватись після цієї позначки # Виведемо на екран "hello world" la $a0, hello_world # Завантажує адресу тексту у пам'яті li $v0, 4 # Завантажує значення системної # команди (вказуючи тип функціоналу) syscall # Виконує зазначену системну команду # з обраним аргументом ($a0) # Регістри (використовуються, щоб тримати дані протягом виконання програми) # $t0 - $t9 # Тимчасові регістри використовуються # для проміжних обчислень всередині # підпрограм (не зберігаються між # викликами функцій) # $s0 - $s7 # Збережені регістри, у яких значення # зберігаються між викликами підпрограм. # Зазвичай зберігаються у стеку. # $a0 - $a3 # Регістри для передачі аргументів для # підпрограм # $v0 - $v1 # Регістри для значень, що повертаються # від викликаної функції # Типи інструкції завантаження / збереження la $t0, label # Скопіювати адресу в пам'яті, де # зберігається значення змінної label # в регістр $t0 lw $t0, label # Скопіювати чисельне значення з пам'яті lw $t1, 4($s0) # Скопіювати чисельне значення з адреси # пам'яті регістра зі зміщенням в # 4 байти (адреса + 4) lb $t2, label # Скопіювати буквений символ в частину # нижчого порядку регістра $t2 lb $t2, 0($s0) # Скопіювати буквений символ з адреси # в $s0 із зсувом 0 # Подібне використання і 'lh' для halfwords sw $t0, label # Зберегти чисельне значення в адресу в # пам'яті, що відповідає змінній label sw $t0, 8($s0) # Зберегти чисельне значення в адресу, # що зазначена у $s0, та зі зсувом у 8 байтів # Така ж ідея використання 'sb' та 'sh' для буквених символів та halfwords. # 'sa' не існує ### Математичні операції ### _math: # Пам'ятаємо, що попередньо потрібно завантажити дані в пам'ять lw $t0, num # Із розділа з даними li $t0, 5 # Або безпосередньо з константи li $t1, 6 add $t2, $t0, $t1 # $t2 = $t0 + $t1 sub $t2, $t0, $t1 # $t2 = $t0 - $t1 mul $t2, $t0, $t1 # $t2 = $t0 * $t1 div $t2, $t0, $t1 # $t2 = $t0 / $t1 (Може не підтримуватись # деякими версіями MARS) div $t0, $t1 # Виконує $t0 / $t1. Отримати частку можна # за допомогою команди 'mflo', остаток - 'mfhi' # Бітовий зсув sll $t0, $t0, 2 # Побітовий зсув вліво на 2. Біти вищого порядку # не зберігаються, нищого - заповнюються 0 sllv $t0, $t1, $t2 # Зсув вліво зі змінною кількістю у # регістрі srl $t0, $t0, 5 # Побітовий зсув вправо на 5 (не зберігає # біти, біти зліва заповнюються 0) srlv $t0, $t1, $t2 # Зсув вправо зі змінною кількістю у # регістрі sra $t0, $t0, 7 # Побітовий арифметичний зсув вправо # (зберігає біти) srav $t0, $t1, $t2 # Зсув вправо зі змінною кількістю у # регістрі зі збереження значеннь бітів # Побітові операції and $t0, $t1, $t2 # Побітове І (AND) andi $t0, $t1, 0xFFF # Побітове І з безпосереднім значенням or $t0, $t1, $t2 # Побітове АБО (OR) ori $t0, $t1, 0xFFF # Побітове АБО з безпосереднім значенням xor $t0, $t1, $t2 # Побітова виключна диз'юнкція (XOR) xori $t0, $t1, 0xFFF # Побітове XOR з безпосереднім значенням nor $t0, $t1, $t2 # Побітова стрілка Пірса (NOR) ## Розгалуження ## _branching: # В основному інструкції розгалуження мають наступну форму: # <instr> <reg1> <reg2> <label> # де label - це назва змінної, в яку ми хочемо перейти, якщо зазначене твердження # правдиве beq $t0, $t1, reg_eq # Перейдемо у розгалуження reg_eq # якщо $t0 == $t1, інакше - # виконати наступний рядок bne $t0, $t1, reg_neq # Розгалужується, якщо $t0 != $t1 b branch_target # Розгалуження без умови завжди виконується beqz $t0, req_eq_zero # Розгалужується, якщо $t0 == 0 bnez $t0, req_neq_zero # Розгалужується, якщо $t0 != 0 bgt $t0, $t1, t0_gt_t1 # Розгалужується, якщо $t0 > $t1 bge $t0, $t1, t0_gte_t1 # Розгалужується, якщо $t0 >= $t1 bgtz $t0, t0_gt0 # Розгалужується, якщо $t0 > 0 blt $t0, $t1, t0_gt_t1 # Розгалужується, якщо $t0 < $t1 ble $t0, $t1, t0_gte_t1 # Розгалужується, якщо $t0 <= $t1 bltz $t0, t0_lt0 # Розгалужується, якщо $t0 < 0 slt $s0, $t0, $t1 # Інструкція, що посилає сигнал коли # $t0 < $t1, результат зберігається в $s0 # (1 - правдиве твердження) # Просте твердження якщо (if) # if (i == j) # f = g + h; # f = f - i; # Нехай $s0 = f, $s1 = g, $s2 = h, $s3 = i, $s4 = j bne $s3, $s4, L1 # if (i !=j) add $s0, $s1, $s2 # f = g + h L1: sub $s0, $s0, $s3 # f = f - i # Нижче наведений приклад знаходження максимального значення з 3 чисел # Пряма трансляція в Java з логіки MIPS: # if (a > b) # if (a > c) # max = a; # else # max = c; # else # max = b; # else # max = c; # Нехай $s0 = a, $s1 = b, $s2 = c, $v0 = повернути регістр ble $s0, $s1, a_LTE_b # якщо (a <= b) розгалуження(a_LTE_b) ble $s0, $s2, max_C # якщо (a > b && a <=c) розгалуження(max_C) move $v0, $s0 # інакше [a > b && a > c] max = a j done # Перейти в кінець програми a_LTE_b: # Мітка розгалуження, коли a <= b ble $s1, $s2, max_C # якщо (a <= b && b <= c) розгалуження(max_C) move $v0, $s1 # якщо (a <= b && b > c) max = b j done # Перейти в кінець програми max_C: move $v0, $s2 # max = c done: # Кінець програми ## Цикли ## _loops: # Цикл складається з умови виходу та з інструкції переходу після його завершення li $t0, 0 while: bgt $t0, 10, end_while # Коли $t0 менше 10, продовжувати ітерації addi $t0, $t0, 1 # Збільшити значення j while # Перейти на початок циклу end_while: # Транспонування 2D матриці # Припустимо, що $a0 зберігає адресу цілочисельної матриці розмірністю 3 x 3 li $t0, 0 # Лічильник для i li $t1, 0 # Лічильник для j matrix_row: bgt $t0, 3, matrix_row_end matrix_col: bgt $t1, 3, matrix_col_end # ... addi $t1, $t1, 1 # Збільшити лічильник стовпця (col) matrix_col_end: # ... addi $t0, $t0, 1 matrix_row_end: ## Функції ## _functions: # Функції - це процедури, що викликаються, приймають аргументи та повертають значення main: # Програма починається з головної функції jal return_1 # jal збереже поточний ПЦ (програмний центр) в $ra, # а потім перейде до return_1 # Як передати аргументи? # По-перше, ми маємо передати значення аргументів у регістри аргументів li $a0, 1 li $a1, 2 jal sum # Тепер ми можемо викликати функцію # Як щодо рекурсії? # Тут потрібно дещо більше роботи оскільки ми маємо впевнитись, що ми збережемо # та зчитаємо попередній ПЦ в $ra, оскільки jal автоматично перепише її при виклику li $a0, 3 jal fact li $v0, 10 syscall # Ця функція повертає 1 return_1: li $v0, 1 # Завантажити val в регіст $v0 jr $ra # Повернутись до попереднього ПЦ і продовжити виконання # Функція з двома аргументами sum: add $v0, $a0, $a1 jr $ra # Повернутись # Рекурсивна функція, яка знаходить факторіал fact: addi $sp, $sp, -8 # Виділити місце в стеку sw $s0, ($sp) # Зберегти регістр, що містить поточне число sw $ra, 4($sp) # Зберегти попередній ПЦ li $v0, 1 # Проініціалізувати значення, що повертатиметься beq $a0, 0, fact_done # Закінчити, якщо параметр 0 # Інакше, продовжити рекурсію move $s0, $a0 # Скопіювати $a0 в $s0 sub $a0, $a0, 1 jal fact mul $v0, $s0, $v0 # Множення fact_done: lw $s0, ($sp) lw $ra, ($sp) # Відновити ПЦ addi $sp, $sp, 8 jr $ra ## Макроси ## _macros: # Макроси надзвичайно корисні для заміни блоків коду, що повторюються, за допомогою # однієї змінної, для покращення читабельності # Це не заміна функцій. # Вони мають бути оголошені перед використанням # Макрос для виведення нових рядків (оскільки операція досить часто виконується) .macro println() la $a0, newline # Значення нового рядка зберігатиметься тут li $v0, 4 syscall .end_macro println() # Асемблер скопіює цей блок коду сюди # перед тим, як виконувати його # Можна передавати параметри у макроси. # Параметри позначаються знаком '%' з довільною назвою .macro print_int(%num) li $v0, 1 lw $a0, %num syscall .end_macro li $t0, 1 print_int($t0) # Значення також можна передавати безпосередньо в макроси .macro immediates(%a, %b) add $t0, %a, %b .end_macro immediates(3, 5) # Одночасно із назвами змінних .macro print(%string) la $a0, %string li $v0, 4 syscall .end_macro print(hello_world) ## Масиви ## .data list: .word 3, 0, 1, 2, 6 # Це масив чисел char_arr: .asciiz "hello" # Це текстовий масив buffer: .space 128 # Виділяє блок пам'яті, що # автоматично не очищується # Ці блоки пам'яті вирівнені # вирівнені поруч один з одним .text la $s0, list # Завантажити адресу списку li $t0, 0 # Лічильник li $t1, 5 # Довжина списку loop: bgt $t0, $t1, end_loop lw $a0, ($s0) li $v0, 1 syscall # Вивести число addi $s0, $s0, 4 # Розмір числа - 4 байти addi $t0, $t0, 1 # Збільшити j loop end_loop: ## Включення ## # Потрібно для імпорту сторонніх файлів у програму (насправді, код з цього файлу # копіюється та вставляється в місце, де оголошений імпорт) .include "somefile.asm" ```