لازانیا

لازانیا

تمرین یادگیری

مقدمه

مبانی

کامنت‌ها

مسیر x86-64 در Exercism از NASM (The Netwide Assembler) به عنوان اسمبلر خود استفاده می‌کند. در NASM، کامنت‌ها با یک نقطه‌ویرگول (;) شروع می‌شوند.

کامنت‌ها می‌توانند هرجای برنامه قرار بگیرند و هر چیزی که بعد از ; بیاید توسط اسمبلر نادیده گرفته می‌شود.

ثابت‌ها

یک ثابت زمان اسمبل می‌تواند در NASM با استفاده از equ تعریف شود. برای مثال، این یک ثابت به اسم UNIVERSE با مقدار 42 تعریف می‌کند:

UNIVERSE equ 42

ثابت‌ها یک بار، هنگام تعریف، ارزیابی می‌شوند و بعداً نمی‌توانند دوباره تعریف شوند.

رجیسترهای همه‌منظوره (GPR)

در اسمبلی، به جای متغیرها، معمولاً از رجیسترها برای ذخیره‌ی مقادیر استفاده می‌کنیم. یک رجیستر پردازنده قطعه‌ای از حافظه‌ی سریع در داخل پردازنده‌ی کامپیوتر است. بیشتر محاسبات در رجیسترهای پردازنده انجام می‌شوند.

برخی از رجیسترها برای انواع مختلفی از محاسبات استفاده می‌شوند و رجیسترهای همه‌منظوره (GPR) نامیده می‌شوند. برخی دیگر اهداف خاص یا اختصاصی دارند.

در x86-64، ۱۶ رجیستر همه‌منظوره‌ی ۶۴ بیتی وجود دارد که می‌توان به آن‌ها به صورت ۳۲ بیتی، ۱۶ بیتی یا ۸ بیتی هم دسترسی داشت. رجیسترهای همه‌منظوره در ادامه توصیف شده‌اند، که در آن n در rn از ۸ تا ۱۵ متغیر است: r8، r9، r10، r11، r12، r13، r14 و r15.

۶۴ بیتی ۳۲ بیتی ۱۶ بیتی ۸ بیتی
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

وقتی از کمتر از ۶۴ بیت استفاده می‌کنید، بیت‌های دسترسی‌یافته معمولاً از بخش پایینی رجیستر هستند. استثنای این قاعده ah، bh، ch و dh هستند که به ۸ بیت بالایی از بخش ۱۶ بیتی رجیستر دسترسی دارند.

تصویری از نحوه‌ی دسترسی به بیت‌ها برای رجیستر 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

بنابراین، اسم دستور اول قرار می‌گیرد، سپس حداقل یک فاصله، بعد عملوند مقصد، یک ویرگول (,) و در نهایت یک عملوند مبدأ. عملوند مبدأ معمولاً توسط دستور تغییر نمی‌کند، فقط عملوند مقصد.

هر دو عملوند باید اندازه‌ی یکسانی داشته باشند مگر اینکه خلاف آن ذکر شود. برای مثال، اگر عملوند مبدأ ۱۶ بیت داشته باشد، عملوند مقصد هم باید ۱۶ بیت داشته باشد.

برای مثال، برای ذخیره‌ی یک مقدار در یک رجیستر، می‌توانیم از دستور mov استفاده کنیم:

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

قطعه‌کد بالا محتویات تمام ۶۴ بیت rdx، یعنی عملوند مبدأ، را در تمام ۶۴ بیت rax، که عملوند مقصد این دستور است، کپی می‌کند.

در بیشتر دستورها می‌توان از اعداد صحیح ثابت به عنوان عملوند مبدأ (اما نه به عنوان عملوند مقصد) استفاده کرد:

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

توجه کنید که نوشتن در یک رجیستر ۳۲ بیتی، بیت‌های بالایی را هم پاک می‌کند، بنابراین mov eax, 42 معادل mov rax, 42 است. این برای رجیسترهای ۱۶ بیتی و ۸ بیتی صادق نیست.

برای عملیات حسابی جمع، تفریق و ضرب، می‌توانیم از دستورهای 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 ما را با آرگومان‌های ۳ و ۵ فراخوانی می‌کند:

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

این‌ها دستورهایی هستند که در این مفهوم به آن‌ها اشاره شده است:

Instruction Description
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 را تعریف کنید که هیچ پارامتری نمی‌گیرد و برمی‌گرداند که لازانیا باید چند دقیقه در فر بماند. طبق کتاب آشپزی، زمان مورد انتظار در فر ۴۰ دقیقه است:

expected_minutes_in_oven();
// => 40

2. زمان باقی‌مانده در فر را بر حسب دقیقه محاسبه کنید

تابع remaining_minutes_in_oven را تعریف کنید که تعداد دقیقه‌های واقعی ماندن لازانیا در فر را به عنوان پارامتر می‌گیرد و برمی‌گرداند که لازانیا بر اساس زمان مورد انتظار در فر از وظیفه‌ی قبلی، چند دقیقه‌ی دیگر باید در فر بماند.

remaining_minutes_in_oven(30);
// => 10

3. زمان آماده‌سازی را بر حسب دقیقه محاسبه کنید

تابع preparation_time_in_minutes را تعریف کنید که تعداد لایه‌هایی را که به لازانیا اضافه کرده‌اید به عنوان پارامتر می‌گیرد و برمی‌گرداند که برای آماده کردن لازانیا چند دقیقه صرف کرده‌اید، با این فرض که آماده کردن هر لایه ۲ دقیقه طول می‌کشد.

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 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.