Exercism के x86-64 ट्रैक में असेंबलर के रूप में NASM (The Netwide Assembler) इस्तेमाल होता है।
NASM में टिप्पणी से पहले सेमीकोलन (;) लगाया जाता है।
टिप्पणी प्रोग्राम में कहीं भी रखी जा सकती है, और ; के बाद जो कुछ आता है उसे असेंबलर अनदेखा कर देता है।
NASM में equ की मदद से असेंबलर के समय का स्थिरांक परिभाषित किया जा सकता है।
उदाहरण के लिए, यह UNIVERSE नाम का एक स्थिरांक बनाता है जिसकी वैल्यू 42 है:
UNIVERSE equ 42
स्थिरांक परिभाषित होते समय एक ही बार मूल्यांकित किए जाते हैं, और बाद में दोबारा परिभाषित नहीं किए जा सकते।
असेंबली में हम वेरिएबल के बजाय आम तौर पर वैल्यू रखने के लिए रजिस्टर इस्तेमाल करते हैं। CPU रजिस्टर कंप्यूटर के प्रोसेसर के अंदर तेज़ मेमोरी का एक छोटा हिस्सा होता है। अधिकतर गणनाएँ CPU के रजिस्टरों में ही होती हैं।
कुछ रजिस्टर तरह-तरह की गणनाओं के लिए इस्तेमाल होते हैं और उन्हें जनरल परपस रजिस्टर (GPRs) कहा जाता है। बाकी रजिस्टरों के अपने खास या निर्धारित काम होते हैं।
x86-64 में 16 तरह के 64-बिट जनरल परपस रजिस्टर (GPRs) होते हैं, जिन्हें 32-बिट, 16-बिट या 8-बिट के रूप में भी एक्सेस किया जा सकता है।
GPRs का ब्यौरा नीचे दिया गया है, जहाँ rn में n का मान 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-बिट वाले हिस्से के ऊपरी 8 बिट एक्सेस करते हैं।
इस चित्र में दिखाया गया है कि 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
यानी पहले अनुदेश का नाम आता है, फिर कम से कम एक खाली जगह, उसके बाद डेस्टिनेशन ऑपरेंड, एक कॉमा (,) और अंत में सोर्स ऑपरेंड।
आम तौर पर अनुदेश सोर्स ऑपरेंड को नहीं बदलता, सिर्फ डेस्टिनेशन ऑपरेंड को बदलता है।
जब तक कुछ और न कहा जाए, दोनों ऑपरेंड का आकार एक जैसा होना चाहिए। उदाहरण के लिए, अगर सोर्स ऑपरेंड 16 बिट का है तो डेस्टिनेशन ऑपरेंड भी 16 बिट का होना चाहिए।
उदाहरण के लिए, किसी रजिस्टर में वैल्यू रखने के लिए हम mov अनुदेश इस्तेमाल कर सकते हैं:
mov rax, rdx ; rax = rdx
; mov is the name, rax is the destination operand and rdx is the source operand
ऊपर का स्निपेट rdx (सोर्स ऑपरेंड) के सभी 64 बिट का कॉन्टेंट rax के सभी 64 बिट में कॉपी करता है, जो इस अनुदेश का डेस्टिनेशन ऑपरेंड है।
अधिकतर अनुदेशों में स्थिर पूर्णांक संख्याओं को सोर्स ऑपरेंड के रूप में इस्तेमाल किया जा सकता है (लेकिन डेस्टिनेशन ऑपरेंड के रूप में नहीं):
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 में रखे जाते हैं।
फंक्शन की घोषणा में ये चीज़ें होती हैं:
:।फंक्शन को आर्गुमेंट कैसे भेजे जाते हैं और फंक्शन से वैल्यू कैसे लौटाई जाती है, इसके लिए कुछ खास कन्वेंशन होते हैं। दो मुख्य कॉलिंग कन्वेंशन हैं: 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 फंक्शन को 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