बहीखाता

बहीखाता

सीखने का अभ्यास

परिचय

थंक

पिछले कॉन्सेप्ट में बताया गया था कि लोकल लेबल और फंक्शन, दोनों असल में किसी ऐसे सेक्शन में रखे एड्रेस होते हैं जिसमें चलाने योग्य कोड होता है, जैसे section .text।

दरअसल, फंक्शन के साथ भी वैसा ही काम किया जा सकता है जैसा किसी भी मेमोरी एड्रेस के साथ किया जाता है। फंक्शन को रजिस्टर में लोड किया जा सकता है, एक जगह से दूसरी जगह भेजा जा सकता है और मेमोरी में संग्रहीत किया जा सकता है। call या jmp की मदद से कोड को किसी रजिस्टर या मेमोरी में रखे फंक्शन पर चलाया भी जा सकता है:

section .text
sum_op:
    lea rax, [rdi + rsi] ; loads the sum rdi + rsi into rax
    ret

apply_sum:
    lea rax, [rel sum_op]
    jmp rax   ; tail call

जो फंक्शन एड्रेस किसी वैल्यू की तरह एक जगह से दूसरी जगह भेजा जाता है, उसे थंक कहते हैं। असेंबली में हायर-ऑर्डर प्रोग्रामिंग की बुनियाद थंक ही हैं, यानी ऐसा कोड जो दूसरे कोड पर काम करता है।

डेटा के रूप में कोड

फंक्शन एड्रेस को मेमोरी में संग्रहीत करके बाद में निकाला भी जा सकता है:

section .bss
    cached_fn resq 1

section .text
save_op:
    mov qword [rel cached_fn], rdi
    ret

apply_op:
    ; arguments are already set up according to the ABI
    jmp qword [rel cached_fn] ; tail call

save_op को जो फंक्शन एड्रेस मिलता है, उसे यह cached_fn में लिख देता है। save_op लौटने के बाद भी यह वैल्यू बनी रहती है, इसलिए apply_op को बाद में कभी भी कॉल करने पर वह उसी एड्रेस पर टेल-जंप करता है जो सबसे बाद में संग्रहीत हुआ था। इससे यह मुमकिन हो जाता है कि रनटाइम पर बदला जा सके कि apply_op किस फंक्शन को कॉल करेगा।

डिस्पैच टेबल

फंक्शन एड्रेस को एक ऐरे में संग्रहीत करने से किसी इंडेक्स के हिसाब से अलग-अलग फंक्शन चुने जा सकते हैं, और वह इंडेक्स रनटाइम की किसी शर्त पर निर्भर भी हो सकता है। इसे डिस्पैच टेबल कहते हैं:

section .data
    dispatch_table dq add_op, sub_op, mul_op

section .text
dispatch:
    ; this function takes two arguments in rdi and rsi, and an index in rdx
    ; it then applies the function corresponding to the index in rdx to the arguments
    lea rax, [rel dispatch_table]
    jmp qword [rax + 8*rdx]   ; tail-call the function address for the index

स्टेटफुल थंक

जो थंक कॉल के बीच-बीच में किसी स्थायी मेमोरी को पढ़ता या बदलता है, उसका व्यवहार इस बात पर बदल सकता है कि उससे पहले क्या हुआ था। ऐसे थंक का परिणाम सिर्फ उसके आर्गुमेंट पर निर्भर नहीं होता, कुछ और भी उसमें भूमिका निभाता है।

उदाहरण के लिए, एक काउंटर लीजिए जो एक फंक्शन लेता है और उसे मौजूदा काउंट के साथ कॉल करता है, और हर बार काउंट आगे बढ़ा देता है:

section .data
    count dq 0

section .text
tick:
    mov rax, rdi               ; saves the function address
    mov rdi, [rel count]       ; loads the current count as the function's argument
    inc qword [rel count]      ; advances the count
    jmp rax                    ; tail-calls the function

tick दिए गए फंक्शन को मौजूदा काउंट आर्गुमेंट के साथ कॉल करता है और फिर काउंट आगे बढ़ा देता है। इसलिए पहली बार tick(square) कॉल करने पर square(0) चलता है, अगली बार tick(square) कॉल करने पर square(1), उसके बाद square(2), और इसी तरह।

एक और उदाहरण विलंबित गणना का होगा:

section .bss
    captured_fn resq 1
    argument resq 1

section .text
delay:
    mov qword [rel captured_fn], rdi ; saves the function
    mov qword [rel argument], rsi    ; saves the argument
    lea rax, [rel invoke]            ; returns the `invoke` function
    ret

invoke:
    mov rdi, qword [rel argument]    ; loads the saved argument into `rdi`
    jmp qword [rel captured_fn]      ; tail-calls the saved function

delay एक फंक्शन और एक वैल्यू लेता है, उन दोनों को संग्रहीत करता है, और invoke लौटाता है। जब invoke को कॉल किया जाता है, तो यह कैप्चर किए गए फंक्शन को सहेजे गए आर्गुमेंट के साथ चलाता है।

हायर-लेवल भाषाओं में आम कई पैटर्न, जैसे कॉलबैक, वर्चुअल मेथड, जनरेटर, करीइंग, फंक्शन कंपोज़िशन और ऐसे ही कई और, स्थायी स्थिति के साथ जोड़े गए थंक पर ही टिके होते हैं।

निर्देश

आप एक छोटे से गाँव के बैंक में हिसाब-किताब रखते हैं। हर ग्राहक का एक खाता होता है, और आप उसका बैलेंस अपनी बही में रखते हैं। साल भर इन बैलेंसों पर तरह-तरह के लेन-देन लागू होते हैं: ब्याज जमा होता है, फीस कटती है, बोनस बाँटा जाता है, और जुर्माना लगाया जाता है। हर लेन-देन एक बैलेंस लेता है और एक नया बैलेंस बनाता है।

आपके सामने चार काम हैं।

Note

आप यह मान सकते हैं कि इस अभ्यास में हर थंक (लेन-देन और गार्ड) एक ऐसा फंक्शन है जो:

  1. आर्गुमेंट के रूप में एक 64-बिट ऋणेतर पूर्णांक लेता है
  2. और एक 64-बिट ऋणेतर पूर्णांक भी लौटाता है।

1. एक लेन-देन याद रखिए

कैशियर दिन की शुरुआत में एक नया लेन-देन जान लेता है और उसे लिख लेता है, ताकि ग्राहक के आने पर उसे बाद में लागू किया जा सके।

दो फंक्शन बनाइए:

  • remember_transaction एक लेन-देन लेता है और उसे मेमोरी में संग्रहीत करता है।
  • apply_remembered एक बैलेंस लेता है और पहले संग्रहीत लेन-देन उस पर लागू करता है।

उदाहरण, यह मानते हुए कि add_interest एक ऐसा लेन-देन है जो पाँच यूनिट ब्याज जमा करता है:

remember_transaction(add_interest);
apply_remembered(100);
// => 105

remember_transaction(service_fee);
apply_remembered(100);
// => 98   (assuming service_fee deducts 2)

remember_transaction के लिए:

  • आर्गुमेंट एक लेन-देन है जिसे बाद के इस्तेमाल के लिए सहेजना है।
  • कोई रिटर्न वैल्यू नहीं होती है।

apply_remembered के लिए:

  • आर्गुमेंट एक 64-बिट ऋणेतर पूर्णांक है।
  • रिटर्न वैल्यू एक 64-बिट ऋणेतर पूर्णांक है।

2. बैंक का मैनुअल

बैंक के मैनुअल में बार-बार होने वाले लेन-देन की एक सूची होती है, जो एक डिस्पैच टेबल में रखी जाती है। हर शाखा सूची की अपनी प्रति रखती है, और स्थानीय नीति के अनुसार अलग-अलग लेन-देन दर्ज कर सकती है।

कॉलर द्वारा दी गई डिस्पैच टेबल पर काम करने वाले दो फंक्शन बनाइए:

  • register_transaction एक डिस्पैच टेबल का मेमोरी एड्रेस, एक इंडेक्स और एक लेन-देन लेता है। यह उस लेन-देन को टेबल में दिए गए इंडेक्स पर संग्रहीत करता है।
  • select_transaction एक डिस्पैच टेबल का मेमोरी एड्रेस, एक इंडेक्स और एक बैलेंस लेता है। यह दिए गए इंडेक्स पर मौजूद लेन-देन को ढूँढकर उसे बैलेंस पर लागू करता है, और नया बैलेंस लौटाता है।

select_transaction को ढूँढे गए लेन-देन तक सिर्फ एक अप्रत्यक्ष टेल कॉल से पहुँचना चाहिए।

उदाहरण, यह मानते हुए कि manual चार खाली स्लॉट वाली एक डिस्पैच टेबल का मेमोरी एड्रेस है:

register_transaction(manual, 0, monthly_interest);
register_transaction(manual, 1, service_fee);

select_transaction(manual, 0, 100);
// applies monthly_interest to 100

select_transaction(manual, 1, 100);
// applies service_fee to 100

register_transaction के लिए:

  • पहला आर्गुमेंट एक डिस्पैच टेबल का मेमोरी एड्रेस है।
  • दूसरा आर्गुमेंट एक 64-बिट ऋणेतर पूर्णांक है (इंडेक्स)।
  • तीसरा आर्गुमेंट एक लेन-देन है।
  • कोई रिटर्न वैल्यू नहीं होती है।

select_transaction के लिए:

  • पहला आर्गुमेंट एक डिस्पैच टेबल का मेमोरी एड्रेस है।
  • दूसरा आर्गुमेंट एक 64-बिट ऋणेतर पूर्णांक है (इंडेक्स)।
  • तीसरा आर्गुमेंट एक 64-बिट ऋणेतर पूर्णांक है (बैलेंस)।
  • रिटर्न वैल्यू एक 64-बिट ऋणेतर पूर्णांक है।

3. मासिक स्टेटमेंट संसाधित कीजिए

महीने के अंत में ग्राहक के खाते का मिलान किया जाता है। महीने भर में हुए हर लेन-देन को शुरुआती बैलेंस पर एक के बाद एक लागू किया जाता है, और इसका नतीजा नया बैलेंस होता है।

एक फंक्शन process_statement बनाइए। यह तीन चीज़ें लेता है: एक शुरुआती बैलेंस, लेन-देन के एक ऐरे का मेमोरी एड्रेस, और ऐरे में मौजूद लेन-देन की संख्या। यह क्रम में हर लेन-देन को चालू बैलेंस पर लागू करता है, और फिर नतीजे को अगले लेन-देन के लिए बैलेंस बना लेता है। अंतिम बैलेंस लौटाया जाता है।

स्यूडोकोड में, process_statement(balance, transactions, n) यह गणना करता है:

for each transaction in transactions:
    balance = transaction(balance)
return balance

उदाहरण, यह मानते हुए कि transactions उस ऐरे का मेमोरी एड्रेस है जिसमें इसी क्रम में add_interest, service_fee और add_interest लेन-देन हैं, जहाँ add_interest 5 जोड़ता है और service_fee 2 घटाता है:

process_statement(100, transactions, 3);
// add_interest(100) = 105
// service_fee(105)  = 103
// add_interest(103) = 108
// => 108

पहला आर्गुमेंट एक 64-बिट ऋणेतर पूर्णांक है। दूसरा आर्गुमेंट लेन-देन के एक ऐरे का मेमोरी एड्रेस है। तीसरा आर्गुमेंट एक 64-बिट ऋणेतर पूर्णांक है (ऐरे की लंबाई)। रिटर्न वैल्यू एक 64-बिट ऋणेतर पूर्णांक है।

4. गार्ड के साथ संसाधित कीजिए

बैंक की नीति के अनुसार कुछ लेन-देन को अंतिम रूप देने से पहले जाँचना ज़रूरी है। एक गार्ड एक ऐसा फंक्शन है जो प्रस्तावित बैलेंस को देखकर तय करता है कि वह स्वीकार्य है या नहीं। यह गार्ड फंक्शन स्वीकृति के लिए गैर-शून्य वैल्यू लौटाता है, और अस्वीकार करने के लिए शून्य लौटाता है।

process_with_guard बनाइए। यह चार चीज़ें लेता है: एक शुरुआती बैलेंस, लेन-देन के एक ऐरे का मेमोरी एड्रेस, ऐरे में लेन-देन की संख्या, और एक गार्ड फंक्शन। क्रम में हर लेन-देन के लिए:

  1. चालू बैलेंस पर लेन-देन लागू करके एक अस्थायी नया बैलेंस निकालिए।
  2. अस्थायी बैलेंस के साथ गार्ड को कॉल कीजिए।
  3. अगर गार्ड गैर-शून्य लौटाता है, तो लेन-देन को स्वीकार कीजिए: चालू बैलेंस अस्थायी बैलेंस बन जाता है।
  4. अगर गार्ड शून्य लौटाता है, तो चालू बैलेंस वैसा ही रहता है और लेन-देन छोड़ दिया जाता है।

सारे लेन-देन संसाधित हो जाने के बाद, अंतिम बैलेंस के साथ-साथ स्वीकृत लेन-देन की संख्या भी लौटाइए।

स्यूडोकोड में, process_with_guard(balance, transactions, n, guard) यह गणना करता है:

approved = 0
for each transaction in transactions:
    tentative = transaction(balance)
    if guard(tentative) is non-zero:
        balance = tentative
        approved = approved + 1
return balance, approved

उदाहरण के लिए, मान लीजिए कि:

  1. add_interest एक ऐसा लेन-देन है जो 5 जोड़ता है, और service_fee एक और लेन-देन है जो 2 घटाता है
  2. at_least_10 एक ऐसा गार्ड है जो बैलेंस 10 या उससे अधिक होने पर गैर-शून्य वैल्यू लौटाता है

तो:

process_with_guard(5, {add_interest, service_fee, add_interest}, 3, at_least_10);
// add_interest(5) = 10; at_least_10(10) != 0;
// => balance = 10, approved = 1
//
// service_fee(10) = 8; at_least_10(8) = 0;
// => balance = 10, approved = 1
//
// add_interest(10) = 15; at_least_10(15) != 0;
// => balance = 15, approved = 2
//
// final balance (15) is returned in rax
// number of approved transactions (2) is returned in rdx

process_with_guard के लिए:

  • पहला आर्गुमेंट एक 64-बिट ऋणेतर पूर्णांक है (शुरुआती बैलेंस)।
  • दूसरा आर्गुमेंट लेन-देन के एक ऐरे का मेमोरी एड्रेस है।
  • तीसरा आर्गुमेंट एक 64-बिट ऋणेतर पूर्णांक है (ऐरे की लंबाई)।
  • चौथा आर्गुमेंट एक गार्ड फंक्शन है जो एक 64-बिट ऋणेतर पूर्णांक लेता है और एक 64-बिट ऋणेतर पूर्णांक लौटाता है।
  • रिटर्न वैल्यू दो 64-बिट ऋणेतर पूर्णांक हैं: अंतिम बैलेंस rax में, और स्वीकृत लेन-देन की संख्या rdx में।
GitHub के ज़रिए संपादित करें यह लिंक एक नई विंडो या टैब में खुलता है
x86-64 Assembly Exercism

बहीखाता शुरू करने के लिए तैयार हैं?

Exercism पर साइन अप कीजिए और x86-64 Assembly को 22 कॉन्सेप्ट130 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।