হিসাবরক্ষণ

হিসাবরক্ষণ

লার্নিং অনুশীলনী

ভূমিকা

থাঙ্ক

আগের একটি কনসেপ্টে বলা হয়েছিল যে লোকাল লেবেল এবং ফাংশন 둘다 এক্সিকিউটেবল কোডের একটি সেকশনে থাকা শুধুই অ্যাড্রেস, যেমন 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. আর্গুমেন্ট হিসেবে একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা নেয়
  2. এবং একইসাথে একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা রিটার্ন করে।

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-এর জন্য:

  • আর্গুমেন্টটি একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা।
  • রিটার্ন ভ্যালুটি একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা।

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-এর জন্য:

  • প্রথম আর্গুমেন্টটি একটি ডিসপ্যাচ টেবিলের মেমোরি অ্যাড্রেস।
  • দ্বিতীয় আর্গুমেন্টটি একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা (ইনডেক্স)।
  • তৃতীয় আর্গুমেন্টটি একটি ট্রানজ্যাকশন।
  • কোনো রিটার্ন ভ্যালু নেই।

select_transaction-এর জন্য:

  • প্রথম আর্গুমেন্টটি একটি ডিসপ্যাচ টেবিলের মেমোরি অ্যাড্রেস।
  • দ্বিতীয় আর্গুমেন্টটি একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা (ইনডেক্স)।
  • তৃতীয় আর্গুমেন্টটি একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা (ব্যালেন্স)।
  • রিটার্ন ভ্যালুটি একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা।

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 ৫ যোগ করে আর service_fee ২ বিয়োগ করে:

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

প্রথম আর্গুমেন্টটি একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা। দ্বিতীয় আর্গুমেন্টটি ট্রানজ্যাকশনের একটি অ্যারের মেমোরি অ্যাড্রেস। তৃতীয় আর্গুমেন্টটি একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা (অ্যারের দৈর্ঘ্য)। রিটার্ন ভ্যালুটি একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা।

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 এমন একটি ট্রানজ্যাকশন যা ৫ যোগ করে এবং service_fee আরেকটি ট্রানজ্যাকশন যা ২ বিয়োগ করে
  2. at_least_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-এর জন্য:

  • প্রথম আর্গুমেন্টটি একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা (শুরুর ব্যালেন্স)।
  • দ্বিতীয় আর্গুমেন্টটি ট্রানজ্যাকশনের একটি অ্যারের মেমোরি অ্যাড্রেস।
  • তৃতীয় আর্গুমেন্টটি একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা (অ্যারের দৈর্ঘ্য)।
  • চতুর্থ আর্গুমেন্টটি এমন একটি গার্ড ফাংশন যা একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা নেয় এবং একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা রিটার্ন করে।
  • রিটার্ন ভ্যালু দুটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা: চূড়ান্ত ব্যালেন্স rax-এ, এবং অনুমোদিত ট্রানজ্যাকশনের সংখ্যা rdx-এ।
GitHub-এর মাধ্যমে সম্পাদনা করুন লিংকটি একটি নতুন উইন্ডো বা ট্যাবে খোলে
x86-64 Assembly Exercism

হিসাবরক্ষণ শুরু করতে প্রস্তুত?

Exercism-এ সাইন আপ করুন, x86-64 Assembly ট্র্যাকের 22টি কনসেপ্ট130টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।