Лазанья

Лазанья

Навчальна вправа

Вступ

Основи

Коментарі

Трек x86-64 в Exercism використовує NASM (The Netwide Assembler) як асемблер. У NASM коментарі позначаються крапкою з комою (;).

Коментарі можна розміщувати будь-де в програмі, і все, що йде після ;, асемблер ігнорує.

Константи

Константу, що визначається під час асемблювання, можна задати в NASM за допомогою equ. Наприклад, ось так визначається константа з назвою UNIVERSE зі значенням 42:

UNIVERSE equ 42

Константи обчислюються один раз, під час визначення, і їх не можна перевизначити пізніше.

Регістри загального призначення (GPRs)

В асемблері замість змінних для зберігання значень ми зазвичай використовуємо регістри. Регістр процесора - це ділянка швидкої памʼяті всередині процесора компʼютера. Більшість обчислень відбувається в регістрах процесора.

Деякі з регістрів використовуються для різноманітних обчислень і називаються регістрами загального призначення (GPRs). Інші мають спеціальне або вузьке призначення.

У x86-64 є 16 64-бітних регістрів загального призначення (GPRs), до яких можна також звертатися як до 32-бітних, 16-бітних або 8-бітних. Ці регістри загального призначення описано нижче, де n у rn набуває значень від 8 до 15: r8, r9, r10, r11, r12, r13, r14 і r15.

64-бітний 32-бітний 16-бітний 8-бітний
rax eax ax ah/al
rbx ebx bx bh/bl
rcx ecx cx ch/cl
rdx edx dx dh/dl
rsi esi si sil
rdi edi di dil
rbp ebp bp bpl
rsp esp sp spl
rn rnd rnw rnb

Коли використовується менше ніж 64 біти, доступ зазвичай відбувається до молодшої частини регістра. Виняток із цього правила становлять ah, bh, ch і dh, які дають доступ до старших 8 бітів 16-бітної частини регістра.

Ілюстрація того, як відбувається доступ до бітів регістра rax:

+--------+---------------------------------------+
| 64-bit |                  rax                  |
+--------+-------------------+-------------------+
| 32-bit |                   |        eax        |
+--------+-------------------+---------+---------+
| 16-bit |                             |    ax   |
+--------+-----------------------------+----+----+
| 8-bit  |                             | ah | al |
+--------+-----------------------------+----+----+

Деякі з цих регістрів потрібно зберігати під час викликів функцій: rbp, rsp, rbx, r12, r13, r14 і r15. Якщо їх не зберегти, це може призвести до помилки або до невизначеної поведінки.

Решту зберігати не потрібно, і їх можна використовувати вільно: rax, rcx, rdx, rdi, rsi, r8, r9, r10 і r11.

Caution

Пізніше в силабусі ми навчимося зберігати регістри. А поки що використовуйте лише другий тип регістрів: rax, rcx, rdx, rdi, rsi, r8, r9, r10 і r11.

Інструкції

Інструкції - це частини обчислень, які може виконувати процесор. Зазвичай вони мають такий вигляд:

name destination, source

Отже, спочатку йде назва інструкції, потім щонайменше один пробільний символ, далі операнд призначення, кома (,) і нарешті операнд джерела. Зазвичай інструкція не змінює операнд джерела, лише операнд призначення.

Обидва операнди повинні мати однаковий розмір, якщо не зазначено інше. Наприклад, якщо операнд джерела має 16 бітів, то й операнд призначення повинен мати 16 бітів.

Наприклад, щоб зберегти значення в регістрі, можна використати інструкцію mov:

mov rax, rdx  ; rax = rdx
              ; mov is the name, rax is the destination operand and rdx is the source operand

Наведений фрагмент копіює вміст усіх 64 бітів rdx, операнда джерела, до всіх 64 бітів rax, який є операндом призначення цієї інструкції.

У більшості інструкцій можна використовувати сталі цілі числа як операнди джерела (але не як операнди призначення):

mov rcx, 42 ; rcx = 42
            ; rcx is the destination operand and 42 is the source operand

Зауважмо, що запис у 32-бітний регістр також очищає старші біти, тож mov eax, 42 рівнозначно mov rax, 42. Для 16-бітних і 8-бітних регістрів це не так.

Для арифметичних операцій додавання, віднімання та множення можна використовувати інструкції add, sub та imul:

add rax, rsi ; rax = rax + rsi
imul rax, rdi ; rax = rax * rdi
sub rax, r8 ; rax = rax - r8

Функції

Інструкції організовано у функції. Усі функції розміщуються в секції section .text вихідного файлу.

Оголошення функції складається з:

  1. Мітки з назвою функції, після якої йде :.
  2. Інструкцій, які визначають функцію.
  3. Інструкції повернення, ret.

Цей трек використовує угоду про виклики System V AMD64 ABI, і перші шість цілочисельних аргументів передаються у функцію в регістрах. Вони передаються в такому порядку: rdi, rsi, rdx, rcx, r8 і r9.

Цілочисельне значення повертається з функції в регістрі rax.

Щоб викликати функцію, ми використовуємо інструкцію call. Функцію можна викликати будь-де в тому самому вихідному файлі. Щоб зробити функцію видимою для інших вихідних файлів, потрібно використати директиву global.

Наприклад, ось так оголошується функція sum:

section .text ; functions are placed here

global sum ; sum is visible to other source files

sum:
    ; first argument is passed in rdi
    ; second argument is passed in rsi
    ; return value is placed in rax

    mov rax, rdi ; rax is now equal to rdi
    add rax, rsi ; rax = rax + rsi

    ret ; function return

А ось так викликається наша функція sum з аргументами 3 і 5:

mov rdi, 3  ; First argument in rdi
mov rsi, 5  ; Second argument in rsi
call sum

; The rax register now contains the value 8 (3 + 5), after sum returns

Вказівки

У цій вправі ми напишемо трохи коду, який допоможе приготувати чудову лазанью з улюбленої кулінарної книги.

На нас чекають чотири завдання, і всі вони повʼязані з часом, витраченим на приготування лазаньї.

Note

Це інструкції, згадані в цій концепції:

Інструкція Опис
mov a, b копіює вміст з b в a
add a, b a = a + b
sub a, b a = a - b
imul a, b a = a * b
call a викликає функцію a
ret повертається з функції

1. Визначте очікуваний час у духовці в хвилинах

Визначте функцію expected_minutes_in_oven, яка не приймає жодних параметрів і повертає, скільки хвилин лазанья має провести в духовці. Згідно з кулінарною книгою, очікуваний час у духовці становить 40 хвилин:

expected_minutes_in_oven();
// => 40

2. Обчисліть залишок часу в духовці в хвилинах

Визначте функцію remaining_minutes_in_oven, яка приймає як параметр фактичну кількість хвилин, які лазанья вже провела в духовці, і повертає, скільки хвилин їй ще залишилося там провести, виходячи з очікуваного часу в духовці з попереднього завдання.

remaining_minutes_in_oven(30);
// => 10

3. Обчисліть час підготовки в хвилинах

Визначте функцію preparation_time_in_minutes, яка приймає як параметр кількість шарів, доданих до лазаньї, і повертає, скільки хвилин ми витратили на підготовку лазаньї, якщо на кожен шар іде 2 хвилини.

preparation_time_in_minutes(2);
// => 4

4. Обчисліть витрачений час у хвилинах

Визначте функцію elapsed_time_in_minutes, яка приймає два параметри в такому порядку:

  1. Кількість шарів, доданих до лазаньї.
  2. Кількість хвилин, які лазанья провела в духовці.

Функція має повернути, скільки хвилин ми витратили на приготування лазаньї. Це сума часу підготовки в хвилинах і часу в хвилинах, який лазанья на цей момент провела в духовці.

elapsed_time_in_minutes(3, 20);
// => 26
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
x86-64 Assembly Exercism

Час розпочати Лазанья?

Зареєструйтеся на Exercism, щоб вивчати й опановувати x86-64 Assembly, а також 22 концепції130 вправ та справжнє наставництво від людей, і все це безкоштовно.