اسمبل

اسمبل

دشوار

دستورالعمل‌ها

در این تمرین، نحوه‌ی نگارش و مدل اجرای یک زبان کوچک را تعریف می‌کنید که از زبان اسمبلی x86-64 الهام گرفته شده است.

هدف، رسمی‌سازی این است که کد در یک محیط محدود چگونه نوشته، تجزیه و اجرا می‌شود.

مدل اجرا

کد اسمبلی شما درون یک ساختار ویژه نوشته می‌شود:

let program := assemble!(
    -- assembly code here
)

این ساختار باید یک برنامه تولید کند که تابعی را شبیه‌سازی می‌کند؛ تابعی که کد اسمبلی را اجرا می‌کند و مقداری برمی‌گرداند. سپس این برنامه با نحوه‌ی نگارش زیر فراخوانی می‌شود، که در آن a، b، c و غیره آرگومان هستند:

let result := program(a, b, c, ...)

همه‌ی آرگومان‌ها و مقدار بازگشتی، اعداد صحیح علامت‌دار ۶۴ بیتی هستند. حداکثر ۶ آرگومان مجاز است.

ثبات‌ها

مقدار بازگشتی و همه‌ی آرگومان‌ها در ثبات‌ها منتقل می‌شوند. زبان شما باید از ثبات‌های زیر پشتیبانی کند:

ثبات نقش
rdi آرگومان اول
rsi آرگومان دوم
rdx آرگومان سوم
rcx آرگومان چهارم
r8 آرگومان پنجم
r9 آرگومان ششم
rax مقدار بازگشتی

ثبات‌ها با مقدار پیش‌فرض 0 شروع می‌کنند، مگر اینکه برای انتقال آرگومان‌ها به تابع استفاده شوند.

برای مثال، در برنامه‌ی زیر، همه‌ی ثبات‌ها مقدار 0 دارند، به‌جز rdi که با 10 مقداردهی اولیه می‌شود و rsi که با -20 مقداردهی اولیه می‌شود:

program(10, -20)

مقدار بازگشتی برنامه همیشه در rax ذخیره می‌شود.

Note

اسم ثبات‌ها به بزرگی و کوچکی حروف حساس نیست. این یعنی rax، RAX و rAx همگی به یک ثبات اشاره می‌کنند.

کد اسمبلی

هر خط از کد اسمبلی می‌تواند به یکی از دو شکل زیر باشد:

  • برچسب‌ها مکان‌های مشخصی از برنامه را نشانه‌گذاری می‌کنند تا برخی دستورها از آن‌ها استفاده کنند.
  • دستورها عملیات‌هایی را نشان می‌دهند که برنامه اجرا می‌کند.

جریان اجرای برنامه، مگر آنکه دستوری آن را تغییر دهد، به‌صورت خطی پیش می‌رود. این یعنی هر خط پیش از خط بعدی به‌طور کامل اجرا می‌شود.

برچسب‌ها

برچسب‌ها نحوه‌ی نگارش زیر را دارند:

<label>:

آن‌ها مقدار هیچ ثباتی را تغییر نمی‌دهند و هیچ تأثیری بر برنامه ندارند جز اینکه مکان‌های مشخصی از کد را نشانه‌گذاری می‌کنند تا دستورها بتوانند از آن‌ها استفاده کنند.

Note

اسم برچسب‌ها به بزرگی و کوچکی حروف حساس است. این یعنی Start، start و sTart همگی برچسب‌های متفاوتی هستند.

دستورهای دو عملوندی

بیشتر دستورها نحوه‌ی نگارش زیر را دارند:

<opcode> <destination>, <source>

opcode عملیاتی را نشان می‌دهد که اجرا می‌شود و از مقادیر عملوندهای destination و source استفاده می‌کند. نتیجه‌ی عملیات در عملوند destination ذخیره می‌شود.

برای مثال، دستور add مقادیر عملوندهای destination و source را جمع می‌کند و این نتیجه را در عملوند destination ذخیره می‌کند.

عملوند destination همیشه یک ثبات است، در حالی که عملوند source می‌تواند یک ثبات یا یک عدد ثابت باشد.

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

کد عملیات عملیات انجام‌شده
mov destination := source
add destination := destination + source
sub destination := destination - source
mul destination := destination * source
div destination := destination / source
xor destination := destination ^^^ source
and destination := destination &&& source
or destination := destination ||| source
shl destination := destination <<< source
shr destination := destination >>> source

علاوه بر این‌ها، برنامه‌ی شما باید از دستور دو عملوندی cmp پشتیبانی کند.

این دستور هیچ ثباتی را تغییر نمی‌دهد، بلکه عملوندهای destination و source را مقایسه می‌کند و حالت داخلی برنامه را روی یکی از سه گزینه تنظیم می‌کند:

  • بزرگ‌تر بودن، اگر destination > source
  • برابر بودن، اگر destination == source
  • کوچک‌تر بودن، اگر destination < source

نحوه‌ی پیگیری داخلی این حالت به خودتان بستگی دارد.

دستورهای یک عملوندی

برخی دستورها جریان برنامه را تغییر می‌دهند و اجرا را به نقطه‌ی دیگری از کد منتقل می‌کنند. به آن‌ها دستورهای پرش می‌گویند.

همه‌ی آن‌ها فقط یک عملوند می‌گیرند که برچسبی است و مقصد پرش، یعنی نقطه‌ای از کد که اجرا در آن ادامه می‌یابد، را نشان می‌دهد:

<opcode> <label>

دستور jmp همیشه پرش به برچسب مقصد را انجام می‌دهد.

سایر دستورهای پرش تنها زمانی پرش می‌کنند که حالت داخلی برنامه روی مقدار مشخصی تنظیم شده باشد. این یعنی معمولاً پیش از آن‌ها یک دستور cmp می‌آید.

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

دستور عملیات انجام‌شده
jmp پرش بی‌قیدوشرط. پرش همیشه انجام می‌شود
je اگر حالت داخلی برابر باشد، پرش می‌کند
jl اگر حالت داخلی کوچک‌تر باشد، پرش می‌کند
jg اگر حالت داخلی بزرگ‌تر باشد، پرش می‌کند
Note

کدهای عملیات دستورها، چه دو عملوندی و چه یک عملوندی، به بزرگی و کوچکی حروف حساس نیستند. این یعنی add، ADD و aDd همگی به یک دستور اشاره می‌کنند.

ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
Lean Exercism

آماده‌اید اسمبل را شروع کنید؟

در Exercism ثبت‌نام کنید تا Lean را همراه با 100 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.