الدوال و المكدس

الدوال و المكدس

الدالة هي كتلة تعليمات مُسمّاة يمكنك استخدامها أكثر من مرة. تنتقل إليها، فتنفّذ مهمتها، ثم تعيد التحكم إلى الموضع الذي استدعيتها منه. يتيح لك ذلك كتابة جزء من المنطق مرة واحدة واستخدامه من أماكن كثيرة بدلًا من تكرار الأسطر نفسها في كل أنحاء برنامجك.

استخدم لاستدعاء دالة: إذ ينتقل إلى التسمية ويخزّن موضع العودة في ra. وتنتهي الدالة بـ ، الذي يعود إلى العنوان الموجود في ra. إن و هما تعليمات شبهية: أسماء مريحة يوسّعها المجمّع إلى التعليمات الفعلية و.

توضع الوسائط في a0 وa1 والسجلات التي تليهما. وتوضع القيمة المُعادة في a0. هذا الترتيب هو اصطلاح الاستدعاء، أي الاتفاق المشترك الذي يتيح لأجزاء منفصلة من الشيفرة أن تعمل معًا.

دالة square()
li a0, 6 # وسيط
call square # استدعاء، وra يتذكر إلى أين يعود
li a7, 1 # يترك square نتيجته في a0
ecall
li a7, 10
ecall
square:
mul a0, a0, a0 # a0 = a0 * a0
ret # يعود إلى ra

اتبع الاستدعاء. يضع الوسيطة 6 في a0. وينتقل إلى التسمية square ويخزّن عنوان العودة في ra. داخل الدالة، يحسب ناتج 6 * 6 = 36 ويترك النتيجة في a0، وهو السجل المستخدم لقيم الإعادة. ويعود إلى التعليمة التي تلي الاستدعاء مباشرة، حيث تطبع و القيمة 36، ثم ينتهي البرنامج.

استدعاء الدالة وإرجاع القيمة
caller
call A
...
A
addi sp, sp, -4
sw ra, 0(sp)
call B
lw ra, 0(sp)
addi sp, sp, 4
ret
B
mul a0, a0, a0
ret
STACK
sp →
0x7ffffff0
0x7fffffec

المستدعي على وشك استدعاء A.

شاهد السطر المميز وهو يقوم بالرحلة الكاملة ذهابًا وإيابًا. ينفذ caller الأمر ، الذي يحفظ تلقائيًا نقطة العودة إلى caller في السجل ra. قبل أن يتمكن A من تنفيذ ، يواجه مشكلة: الأمر على وشك الكتابة فوق ra بعنوان عودة جديد، مما يدمر العنوان الذي يحتاجه A للعودة إلى موطنه. لإصلاح ذلك، يخصص A مساحة على المكدس () ويحفظ ra الأصلي هناك (). الآن أصبح من الآمن تنفيذ . يعمل B ويعود بأمر عادي، ليهبط مرة أخرى داخل A. أخيرًا، يستعيد A قيمة ra الأصلية من المكدس ()، وينظف مؤشر المكدس ()، ويستخدم أمر الخاص به القيمة المستعادة للقفز بنجاح إلى caller. إذا تخطى A هذه الخطوة، لكان الأمر قد كتب فوق ra بشكل دائم، ولكان أمر النهائي لـ A قد قفز إلى المكان الخطأ بدلاً من العودة إلى موطنه.

المكدس

لا يوجد سوى عدد قليل من السجلات، لذا فإن الدوال التي تستدعي دوال أخرى تحتاج إلى مساحة مؤقتة. المكدس هو ذاكرة تستعيرها عبر مؤشر المكدس sp: اطرح منها لتوفير مساحة، وخزّن ما يجب الاحتفاظ به، وأعده قبل .

المكدس بنية آخر ما يدخل أول ما يخرج (LIFO): أحدث قيمة مخزّنة هي أول ما تسترجعه. في RISC-V، ينمو المكدس إلى الأسفل عبر الذاكرة. ويؤدي الطرح من sp إلى حجز مساحة جديدة آمنة للاستخدام. وعند استرجاع القيم، يجب أن تفعل ذلك بالترتيب العكسي لطريقة تخزينها، وإلا فسيفسد تدفق تنفيذ البرنامج.

حفظ واستعادة ra عبر استدعاء
addi sp, sp, -4 # يفسح مكانًا لكلمة واحدة
sw ra, 0(sp) # يحفظ عنوان العودة
# ... استدعِ دوالًا أخرى هنا ...
lw ra, 0(sp) # يستعيده
addi sp, sp, 4 # يحرر المكان
ret

كل / يستبدل ra، لذا إذا استدعت دالتك دالة أخرى، فإن الاستدعاء الداخلي يدمّر عنوان عودتك ويقفز الخاص بك إلى المكان الخطأ. لمنع ذلك، يحجز مساحة 4 بايتات على المكدس، ويحفظ قيمة ra هناك. وبعد انتهاء الاستدعاءات الداخلية، يستعيد عنوان العودة، ويعيد المساحة قبل .

⚠
إذا استدعيت أي شيء، فاستعد ra قبل ، وأعد sp دائمًا إلى القيمة التي كانت عليه في البداية. إن مكدسًا لا يطابق حالته الابتدائية هو السبب المعتاد لانهيار برامج التجميع.