Exercism-এর x86-64 ট্র্যাকটি অ্যাসেম্বলার হিসেবে NASM (The Netwide Assembler) ব্যবহার করে।
NASM-এ কমেন্টের শুরুতে একটি সেমিকোলন (;) বসানো হয়।
কমেন্ট প্রোগ্রামের যেকোনো জায়গায় রাখা যায়, এবং ;-এর পরে যা কিছু আসে তা অ্যাসেম্বলার উপেক্ষা করে।
NASM-এ equ ব্যবহার করে অ্যাসেম্বল-টাইম কনস্ট্যান্ট ডিফাইন করা যায়।
যেমন, এটি UNIVERSE নামের একটি কনস্ট্যান্ট ডিফাইন করে, যার মান 42:
UNIVERSE equ 42
কনস্ট্যান্ট ডিফাইন করার সময় একবারই হিসাব করা হয়, আর পরে পুনরায় ডিফাইন করা যায় না।
অ্যাসেম্বলিতে ভ্যারিয়েবলের বদলে আমরা সাধারণত মান রাখার জন্য রেজিস্টার ব্যবহার করি। একটি CPU রেজিস্টার হলো কম্পিউটারের প্রসেসরের ভেতরে থাকা এক টুকরো দ্রুতগতির মেমোরি। বেশিরভাগ হিসাবনিকাশ CPU-এর রেজিস্টারেই করা হয়।
কিছু রেজিস্টার নানা রকম হিসাবনিকাশে ব্যবহৃত হয়, এদের জেনারেল পারপাস রেজিস্টার (GPRs) বলা হয়। আবার কিছু রেজিস্টারের বিশেষ বা নির্দিষ্ট কাজ থাকে।
x86-64-এ ১৬টি ৬৪-বিট জেনারেল পারপাস রেজিস্টার (GPR) আছে, যেগুলো ৩২-বিট, ১৬-বিট বা ৮-বিট হিসেবেও অ্যাক্সেস করা যায়।
GPR-গুলো নিচে বর্ণনা করা হলো, যেখানে rn-এর n ৮ থেকে ১৫ পর্যন্ত হয়: 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।
রেজিস্টারগুলো সাধারণত push ইন্সট্রাকশন দিয়ে স্ট্যাকে সংরক্ষণ করা হয়।
এরপর ফাংশন শেষ হওয়ার আগে pop ইন্সট্রাকশন দিয়ে সেগুলো আবার ফিরিয়ে আনা হয়।
ইন্সট্রাকশন হলো এমন কিছু হিসাবনিকাশের অংশ যা একটি CPU করতে পারে। এগুলোর সাধারণ রূপ হলো:
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 অংশে রাখা হয়।
একটি ফাংশন ডিক্লারেশন গঠিত হয়:
:।ফাংশনে আর্গুমেন্ট কীভাবে পাঠানো হয় এবং ফাংশন থেকে কীভাবে ফেরত আসে, তার জন্য নির্দিষ্ট কিছু নিয়ম আছে। দুটি প্রধান কলিং কনভেনশন হলো System V AMD64 ABI এবং Microsoft x64।
এই ট্র্যাকটি 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