يستخدم مسار x86-64 في Exercism المُجمِّع NASM (The Netwide Assembler).
في NASM، تُسبق التعليقات بفاصلة منقوطة (;).
يمكن وضع التعليقات في أي مكان في البرنامج، وكل ما يأتي بعد ; يتجاهله المُجمِّع.
يمكن تعريف ثابت زمن التجميع في NASM باستخدام equ.
على سبيل المثال، هذا يعرّف ثابتًا اسمه UNIVERSE وقيمته 42:
UNIVERSE equ 42
تُقيَّم الثوابت مرة واحدة عند تعريفها، ولا يمكن إعادة تعريفها لاحقًا.
في لغة التجميع، نستخدم السجلات عادةً لتخزين القيم بدلًا من المتغيرات. سجل المعالج هو قطعة ذاكرة سريعة داخل معالج الحاسوب. وتُجرى معظم العمليات الحسابية في سجلات المعالج.
تُستخدم بعض السجلات في مجموعة متنوعة من العمليات المختلفة وتُسمى السجلات العامة (GPRs). ولسجلات أخرى أغراض خاصة أو مخصصة.
في x86-64، هناك 16 سجلًا عامًا (GPR) بعرض 64 بت، ويمكن الوصول إليها أيضًا بعرض 32 بت أو 16 بت أو 8 بت.
تُوصف السجلات العامة أدناه، حيث يتراوح n في rn من 8 إلى 15: r8 وr9 وr10 وr11 وr12 وr13 وr14 وr15.
| 64-bit | 32-bit | 16-bit | 8-bit |
|---|---|---|---|
| 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، فهي تصل إلى البتات الثمانية العليا من الجزء المكوّن من 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.
لاحقًا في المنهج الدراسي، ستتعلم كيفية الحفاظ على السجلات.
في الوقت الحالي، استخدم فقط النوع الثاني من السجلات: 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 من ملف المصدر.
يتكوّن تعريف الدالة من:
:.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
في هذا التمرين ستكتب بعض الكود لمساعدتك على طهي لازانيا رائعة من كتاب الطبخ المفضل لديك.
لديك أربع مهام، وكلها تتعلق بالوقت المستغرق في طهي اللازانيا.
هذه هي التعليمات المذكورة في هذا المفهوم:
| التعليمة | الوصف |
|---|---|
| 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 | تعود من الدالة |
عرّف الدالة expected_minutes_in_oven التي لا تأخذ أي معاملات وتُرجع عدد الدقائق التي ينبغي أن تبقى فيها اللازانيا في الفرن.
وفقًا لكتاب الطبخ، فإن زمن الفرن المتوقع بالدقائق هو 40:
expected_minutes_in_oven();
// => 40
عرّف الدالة remaining_minutes_in_oven التي تأخذ عدد الدقائق الفعلية التي قضتها اللازانيا في الفرن كمعامل وتُرجع عدد الدقائق التي لا يزال يتعين على اللازانيا أن تبقى فيها في الفرن، بناءً على زمن الفرن المتوقع بالدقائق من المهمة السابقة.
remaining_minutes_in_oven(30);
// => 10
عرّف الدالة preparation_time_in_minutes التي تأخذ عدد الطبقات التي أضفتها إلى اللازانيا كمعامل وتُرجع عدد الدقائق التي قضيتها في تحضير اللازانيا، بافتراض أن تحضير كل طبقة يستغرق منك دقيقتين.
preparation_time_in_minutes(2);
// => 4
عرّف الدالة elapsed_time_in_minutes التي تأخذ معاملين، بهذا الترتيب:
ينبغي أن تُرجع الدالة عدد الدقائق التي عملتها في طهي اللازانيا. وهذا هو مجموع زمن التحضير بالدقائق، والزمن بالدقائق الذي قضته اللازانيا في الفرن في تلك اللحظة.
elapsed_time_in_minutes(3, 20);
// => 26
سجّل في Exercism لتتعلّم وتتقن x86-64 Assembly عبر 22 مفهومًا130 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.