আগের একটি কনসেপ্টে বলা হয়েছিল যে লোকাল লেবেল এবং ফাংশন 둘다 এক্সিকিউটেবল কোডের একটি সেকশনে থাকা শুধুই অ্যাড্রেস, যেমন 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 কল করা হলে সেটি সংরক্ষিত আর্গুমেন্ট দিয়ে ক্যাপচার করা ফাংশনটি রান করে।
উচ্চস্তরের ভাষায় কমন অনেক প্যাটার্ন, যেমন কলব্যাক, ভার্চুয়াল মেথড, জেনারেটর, কারিং, ফাংশন কম্পোজিশন এবং আরও অনেক কিছু, স্থায়ী স্টেটের সঙ্গে জোড়া থাঙ্কের উপর গড়ে ওঠে।
আপনি একটি ছোট গ্রামীণ ব্যাংকের হিসাবরক্ষক। প্রতিটি গ্রাহকের নিজের অ্যাকাউন্ট থাকে, আর আপনি আপনার খতিয়ানে তার ব্যালেন্স লিখে রাখেন। বছরজুড়ে এই ব্যালেন্সগুলোর উপর নানা ট্রানজ্যাকশন প্রয়োগ করা হয়: সুদ জমা হয়, ফি কাটা হয়, বোনাস পরিশোধ করা হয়, জরিমানা ধার্য হয়। প্রতিটি ট্রানজ্যাকশন একটি ব্যালেন্স নেয় এবং একটি নতুন ব্যালেন্স তৈরি করে।
আপনার সামনে চারটি কাজ আছে।
আপনি ধরে নিতে পারেন যে এই অনুশীলনীর প্রতিটি থাঙ্ক (ট্রানজ্যাকশন ও গার্ড) এমন একটি ফাংশন, যা:
দিনের শুরুতে ক্যাশিয়ার নতুন একটি ট্রানজ্যাকশন জেনে নেয় এবং তা লিখে রাখে, যাতে পরে কোনো গ্রাহক এলে সেটি প্রয়োগ করা যায়।
দুটি ফাংশন ডিফাইন করুন:
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-এর জন্য:
ব্যাংকের ম্যানুয়ালে ঘন ঘন ব্যবহৃত ট্রানজ্যাকশনের একটি তালিকা একটি ডিসপ্যাচ টেবিলে সংরক্ষিত থাকে। প্রতিটি শাখা তালিকাটির নিজস্ব কপি রাখে, এবং স্থানীয় নীতি অনুযায়ী ভিন্ন ভিন্ন ট্রানজ্যাকশন নিবন্ধন করতে পারে।
কলকারীর দেওয়া একটি ডিসপ্যাচ টেবিলের উপর কাজ করে এমন দুটি ফাংশন ডিফাইন করুন:
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-এর জন্য:
মাসের শেষে একজন গ্রাহকের অ্যাকাউন্টের হিসাব মেলানো হয়। মাসজুড়ে ঘটে যাওয়া প্রতিটি ট্রানজ্যাকশন একটির পর একটি শুরুর ব্যালেন্সের উপর প্রয়োগ করা হয়, আর সেই ফলাফলই নতুন ব্যালেন্স।
এমন একটি ফাংশন 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
প্রথম আর্গুমেন্টটি একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা। দ্বিতীয় আর্গুমেন্টটি ট্রানজ্যাকশনের একটি অ্যারের মেমোরি অ্যাড্রেস। তৃতীয় আর্গুমেন্টটি একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা (অ্যারের দৈর্ঘ্য)। রিটার্ন ভ্যালুটি একটি ৬৪-বিটের অঋণাত্মক পূর্ণসংখ্যা।
ব্যাংকের নীতি অনুযায়ী কিছু ট্রানজ্যাকশন চূড়ান্ত করার আগে যাচাই করা আবশ্যক। একটি গার্ড হলো এমন একটি ফাংশন যা প্রস্তাবিত ব্যালেন্স পরীক্ষা করে এবং তা গ্রহণযোগ্য কি না তা নির্ধারণ করে। এই গার্ড ফাংশনটি অনুমোদন করতে একটি অশূন্য মান রিটার্ন করে, আর প্রত্যাখ্যান করতে শূন্য রিটার্ন করে।
process_with_guard ডিফাইন করুন যা একটি শুরুর ব্যালেন্স, ট্রানজ্যাকশনের একটি অ্যারের মেমোরি অ্যাড্রেস, অ্যারেতে থাকা ট্রানজ্যাকশনের সংখ্যা এবং একটি গার্ড ফাংশন নেয়।
পরপর প্রতিটি ট্রানজ্যাকশনের জন্য:
সব ট্রানজ্যাকশন প্রসেস হওয়ার পর, চূড়ান্ত ব্যালেন্সের সাথে অনুমোদিত ট্রানজ্যাকশনের সংখ্যাও রিটার্ন করুন।
সিউডোকোডে, 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
উদাহরণস্বরূপ, ধরে নিন যে:
add_interest এমন একটি ট্রানজ্যাকশন যা ৫ যোগ করে এবং service_fee আরেকটি ট্রানজ্যাকশন যা ২ বিয়োগ করে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-এ।Exercism-এ সাইন আপ করুন, x86-64 Assembly ট্র্যাকের 22টি কনসেপ্ট130টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।