1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
|
---
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"
```
|