Thirrja e funksioneve Analiza dhe optimizimi

Nga Wikipedia, enciklopedia e lirë

Një trup me procedurë të vetme përdor një numër të fundmë të ndryshoreve.[1] Të gjitha ndryshoret nuk mund të ruhen në regjistra, kështu që është shfaqur ideja që vetëm ndryshoret që i duhen funksionit (ose ato qe janë thirrur së fundi) të qëndrojnë në regjistra, ndërsa ndryshoret e tjera të qëndrojnë në memorie. Të gjitha ndryshoret e funksionit thirrës ( caller ) ruhen në memorie, kështu që regjistrat do të jenë të lirë për përdorim nga funksioni i thirrur ( callee ). Kur i thirruri kthen rezultat, ndryshoret e ruajtura kthehen prapë në regjistra. Është e përshtatshme të përdoret një stack për ruajtje dhe kthim, duke futur përmbajtjen e regjistrave në stack kur ata duhet të ruhen dhe duke i nxjerr prapë në regjistra kur duhet të ruhen përsëri në regjistra. Pasi që një stack është i pakufizuar, kjo përshtatet mirë me idenë e rekursionit të pakufizuar.

1. 2 Regjistrimi i aktivizimit

Çdo thirrje e funksionit do të rezervojë një pjesë të memories në stack për të mbuluar çdo nevojë të funksionit për të ruajtur vlera në stack. Kjo copë quhet “regjistrimi i aktivizimit” ose korniza për thirrjen e funksioneve. Regjistrimi i aktivizimit do të ketë strukturën e njëjtë në përgjithësi për çdo funksion në një program, megjithëse madhësitë e ndryshme të fushave në regjistrime mund të ndryshojnë. Shpesh, në arkitekturën e makinës diktohet një marrëveshje të thirrjes që standardizon planin për aktivizimin e regjistrimeve. Kjo lejon një program që të thërras funksionet që janë të kompiluara me një kompilator tjetër ose të shkruara edhe në gjuhë tjetër, me kusht që të dy kompilatorët të ndjekin të njëjtën marrëveshje të thirrjes.FP është shkurtesë për FramePointer dhe thekson fjalën e parë të aktivizimit të regjistrimeve. Në këtë strukturë, fjala e parë mban adresën e kthimit. Sipër kësaj, parametrat hyrës janë të ruajtur. Funksioni do të zhvendosë automatikisht parametrat nëpër regjistra ( përveç parametrave që kanë qenë të shpërndarë nga Register-Allocator) para se të ekzekutohet trupi i tij. Hapësira e shfrytëzuar për parametrin e parë hyrës është gjithashtu e shfrytëzuar për ruajtjen e vlerës kthyese të thirrjes së funksionit (nëse ka). Sipër parametrave hyrës, regjistrimi i aktivizimit ka hapësirë për të ruajtur ndryshore të tjera lokale, si p.sh: për shpërndarjen ose për ruajtjen e të gjitha thirrjeve të mëvonshme të funksioneve. 1.3 Prologu, epilogu dhe vargu i thirrjeve

Pasi që parametrat dhe rezultatet kalojnë nëpër aktivizimin e regjistrimeve, duhet të shtojmë para kodit trupin e funksionit kodues që lexon parametra në ndryshoret e regjistrit të aktivizimit. Ky kod është i quajtur prolog i funksionit. Gjithashtu, pas kodit për trupin e funksionit, na duhet kod për ta ruajtur vlerën kthyese të llogaritur në aktivizimin e regjistrimeve dhe kërcimin tek adresa kthyese që ka qenë e ruajtur në aktivizimin e regjistrave nga thirrësi. Kjo quhet epilog i një funksioni. Instruksioni i thirrjes së funksionit duhet të përkthehet në një sekuencë të thirrjes së instruksioneve që do t'i ruajnë regjistrat, vendosin parametrat në regjistrimin e aktivizimit etj. Një sekuencë e thirrjes është treguar në Fig.1 . Kodi paraqet një përpunim të instruksionit të gjuhës së mesme x:= CALL f (a_1,….,a_n). Të gjithë regjistrat që mund të përdoren për të ruajtur ndryshore mund të ruhen në kornizë. Kur kthehet rezultati pas thirrjes së funksionit, rezultati lexohet nga korniza në ndryshorenx, FP ruhet përsëri në ndryshoren e mëparshme të saj si dhe regjistrat lexohen prapa nga korniza e kaluar.Në këtë sekuencë të thirrjes, ruajmë në stack të gjithë regjistrat që mund të mbajnë ndryshoret. Kjo mund të ruajë më shumë regjistra sesa që nevojitet, pasi që të gjithë regjistrat nuk mbajnë vlera të duhura pas thirrjes (p.sh: disa regjistra mund të jenë jo-funksionalë).

1.4 Përdorimi i regjistrave për kalimin e parametrave

Disa regjistra përdoren për kalimin e parametrave. Këta përdoren për parametrat e parë të një funksioni, përderisa parametrat e mbetur kalojnë në stack, ashtu siç kemi bërë më sipër. Pasiqë shumica e funksioneve kanë lista mjaft të shkurta të parametrave, shumica e parametrave do të kalojnë në regjistra. Shumica e arkitekturave RISC kanë instruksionet jump-and-link, të cilat vendosin adresën kthyese në regjistër. Nëse thirrja e funksionit është bërë në brendi të trupit, ky regjistër është mbishkruar, kështu që adresa kthyese duhet të ruhet në regjistrimin e aktivizimit para ndonjë thirrjeje. Në këtë mënyrë, regjistri i adresës kthyese është i njëjtë sikurse cilado ndryshore që duhet të ruhet në kornizë, nëse përdoret në trup. 1.5 Përdorimi i ndryshoreve jo-lokale

Ne deri tani kemi supozuar që të gjitha ndryshoret e përdorura në një funksion janë lokale, por shumica e gjuhëve të nivelit të lartë lejojnë që funksionet të kenë si hyrje ndryshore që nuk janë deklaruar në nivel lokal tek vetë funksionet. 1.6 Ndryshoret globale

Ndryshoret lokale ruhen në një regjistër. Ndryshoret globale nga ana tjetër duhet të ruhen në memorie. Vendndodhja e çdo ndryshoreje globale përcaktohet sipas kohës kur bëhet kompilimi. Nëse një ndryshore globale është përdorur shpesh brenda një funksioni, ajo mund të ngarkohet në një ndryshore lokale në fillim të funksionit dhe të ruhet përsëri kur funksioni kthen rezultat. Ndryshorja duhet të ruhet përsëri në memorie sa herë që një funksion thirret, i cili funksion mund të lexojë ose ndryshojë ndryshore globale. 1.7 Parametrat që referencohen në bazë të thirrjes dhe përfshirjet e mbivendosura

Disa gjuhë, si p.sh. Pascal, mundësojnë që parametrat të barten nëpërmjet thirrjes me referencë. Një parametër i bartur nga thirrja-me-referencë duhet të jetë një ndryshore, një element vektori, një fushë në një regjistër ose, në përgjithësi, çdo gjë që lejohet në anën e majtë të një urdhri gjatë të cilit shoqërohet vlera. 1.8 Kornizat me madhësi të ndryshoreve

Nëse vargjet janë të vendosura në stack, madhësia e regjistrit aktiv varet nga madhësia e vargjeve. Nëse këto madhësi nuk njihen në kohën e kompilimit, nuk do të njihet as madhësia e regjistrave aktivë. Prandaj, duhet ta kemi një ndryshore kohore për të treguar për fundin e kornizës. Ky quhet pointer-i në stack, për shkak se fundi i kornizës është po ashtu pjesa e sipërme e stack-ut. Kur vendosen parametra në një thirrje të re, këta janë të vendosur në vende relative në SP në vend se të jenë në FP. Kur të bëhet thirrja e një funksioni, një FP i ri e merr vlerën e SP-së së vjetër, por duhet ta ruajmë vlerën e FP-së, pasi që më nuk mund ta ruajmë atë duke e larguar një konstantë nga FP-ja i tanishëm. Prandaj FP-ja i vjetër kalohet si parametër (në një regjistër ose kornizë) në funksionin e ri, që kthen këtë vlerë pak para kthimit. 1.9 Numri i ndryshoreve si parametra

Disa gjuhë ia mundësojnë funksionit që të ketë një numër të ndryshoreve si parametra. Kjo nënkupton që funksioni mund të thirret me një numër të ndryshëm të parametrave në çdo thirrje. Nëse numri i parametrave mund të ndryshojë, kjo më nuk është e vërtetë. Një zgjidhje e mundshme është që të jenë të pranishëm dy pointer për korniza: njëri që tregon pozitën e parametrit të parë dhe njëri që tregon për pjesën e kornizës që vjen pas parametrave. Sidoqoftë, manipulimi i dy FP-ve kushton, kështu që përdoret një tjetër truk: FP-ja tregon tek pjesa e kornizës që vjen pas parametrave. [2]

  1. ^ "Kopje e arkivuar" (PDF). Arkivuar nga origjinali (PDF) më 7 mars 2016. Marrë më 9 qershor 2014. {{cite web}}: Mungon ose është bosh parametri |language= (Ndihmë!)Mirëmbajtja CS1: Archived copy si titull (lidhja)
  2. ^ Williams, Lance R. Evolution of Tail-Call Optimization in a Population of Self-Hosting Compilers.