日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

          歡迎訪問 生活随笔!

          生活随笔

          當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

          编程问答

          c语言尚未实现的虚拟函数,编译原理之学习 lua 1.1 笔记 (二) 函数调用与局部变量...

          發布時間:2024/10/8 编程问答 40 豆豆
          生活随笔 收集整理的這篇文章主要介紹了 c语言尚未实现的虚拟函数,编译原理之学习 lua 1.1 笔记 (二) 函数调用与局部变量... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

          函數(過程)是程序中重要的抽象, 過程調用一般用棧實現. Lua 1.1 中尚未實現閉包(closure),

          對于函數使用棧實現即已滿足需求了.在理論上, 在棧中要保存為實現調用以及返回調用處的足夠

          信息, 這些信息當前是返回地址(return-address,棧基址指針(base-pointer).

          在虛擬機指令層次, 指令 CALLFUNC, RETCODE 用于函數調用的核心實現. 另有一些與調用參數, 局部

          變量相關的指令, 稍后遇到的時候研究.

          對于一個函數, 如例子 function f(a,b) ... end, 對其的調用代碼為:

          PUSH f? -- 將函數對象 f 壓入堆棧.

          PUSH MARK? --- 將特殊標記值 MARK 壓入堆棧, 作用下面描述.

          PUSH a --- 計算得到 a 的代碼, 最終為將 a 的值壓入堆棧.

          PUSH b --- 同 a 的情況, 此時棧中數據為: [f, MARK, a, b??)

          CALLFUNC --- 實際產生調用.

          指令 CALLFUNC 的執行如下:

          case CALLFUNC:? --- 位于 opcode.c 的虛擬機執行函數 lua_execute() 中.

          1. 從棧頂向上找 MARK 標記, 這一標記的前面是函數 f, 后面是第一個函數參數 a.

          2. 從函數對象 f 中獲得該函數的代碼地址 new-pc;

          把當前 pc?(也即返回地址)保存在函數對象 f 中. (注1)

          設置 pc = new-pc,?此即實現執行地址的改變.

          3. 將棧 base 指針保存在 MARK 的值中, 設置新的 base = MARK+1, 即指向棧中第一個

          參數 a 的位置.

          此后再執行的下一條指令就是函數的入口指令了.

          上述的步驟, 所做工作就是保存 , 設置新的運行地址和基址指針. 與機器碼實現

          函數調用幾乎是一致的. 略有不同的是由于調用者提供的參數數量可能是0個或多個(可變數量的), 所以

          通過查找 MARK 方式找到, 同時 MARK 還用作保存 base 指針, 這種方式較為巧妙.

          注1: 將返回地址保存到函數對象中, 這是一個"不好的"方式, 這樣函數就不能重入,遞歸了?

          指令 RETCODE 的執行與 CALLFUNC 想對應:

          case RETCODE:? --- 虛擬機函數 lua_execute() 中.

          1. 由于當前 base 指針-1, -2 分別是保存了 base 的MARK, 和保存了返回地址的函數 f 對象,

          故此從中恢復 pc, base 的值.

          2. 復制/移動函數返回值 (函數的多返回值機制以后分析, 此處暫略)

          此后下一條指令即返回到原調用處的下一條指令位置.

          對函數產生調用的代碼, 在如下產生式中生成(代碼生成):

          1. stat1 -> functioncall

          2. expr -> functioncall

          3. functioncall -> functionvalue {代碼塊1} '(' exprlist ')' {代碼塊2}

          對上面的產生式3略作記錄:

          1. functionvalue 產生計算函數 f 的值的代碼, 例如壓入全局變量 f 到堆棧中. PUSHGLOBAL?f

          2. 代碼塊1: 產生代碼 PUSHMARK, 更新 ntemp 值(其用于跟蹤堆棧使用量)

          3. exprlist 產生所有參數的計算和壓棧代碼, 如 PUSH a, PUSH b

          4. 代碼塊2: 產生代碼 CALLFUNC, 更新 ntemp 值.

          這里生成了調用函數 f 的前述代碼序列.

          在前面提到過基址指針 base, 相當于 80x86 體系中的寄存器 BP, 用于尋址位于堆棧上的地址.

          在 Lua 中, 函數的參數和局部變量存放在棧中, 并使用 base 指針尋址. 下面研究對局部變量訪問的

          指令, 及其代碼生成.

          在 Lua 中, 讀取局部變量的指令為 PUSHLOCAL(及其同系列的 PUSHLOCAL0~~9),

          寫入局部變量的指令為 STORELOCAL(及其同系列的 STORELOCAL0~~9). 那些同系列的指令僅僅

          是減少(優化)了指令大小, 語義是一致的, 因此只需要研究 PUSHLOCAL, STORELOCAL 即可.

          case PUSHLOCAL:? --- 帶一個字節的指令立即數 i, 局部變量壓入棧中.

          *top++ = stack[base + i];

          case STORELOCAL: --- 帶一個字節的指令立即數 i, 棧頂值彈出存入局部變量.

          stack[base + i] = *(--top);

          PUSHLOCAL, STORELOCAL 都帶有一個字節的指令參數, 表示所訪問的局部變量的索引 i, 從 0 開始,

          尋址到堆棧 stack[base + i] 位置. 由于只有一個字節, 也即限制最多只能有 256 個局部變量.

          由于調用函數時 base 被自動維護, 因此每函數都有自己的局部變量. 又 base 指針從第一個參數開始,

          因此參數實現上也是被當做局部變量看待的. 這隱含的幾個問題, 第一個是如果函數所需參數數量,

          和實際調用者傳遞的參數數量不一致的問題.

          舉例說明, 設函數聲明為 function f(a,b), 而調用者調用為 f(1,2,3), 或 f(4), 即函數參數多或少的情況.

          在函數的入口代碼中, 會產生一條 ADJUST n?指令, 其中 n 是函數聲明時的參數數量, 該指令執行如下:

          case ADJUST:

          在語義上:

          1. 如果調用者提供的參數不足 n 個, 則不足的部分以 NIL 值填充.

          2. 如果調用者提供的參數多于 n 個, 則多出的被裁剪掉.

          最終設置棧頂指針 top = base + n (即多出的參數被裁剪, 如果有的話)

          這一指令在產生式 function -> FUNCTION NAME '(' parlist ')' block END 中生成.

          隱含的第二個問題是, 由于多出的參數被裁剪掉了, 這樣表示無法提供 f(args, ...) 后面可變參數語義的實現.

          預計 lua 以后的版本會使用某種方法實現.

          局部變量的聲明, 例如 local x, y, 相關產生式為:

          stat1 -> LOCAL localdeclist decinit

          localdeclist -> NAME {代碼塊1} | localdeclist ',' NAME

          其中代碼塊1:

          localvar[nlocalvar]=lua_findsymbol($1);?? --- 查找 $1(即NAME), 并加入到局部變量表中.

          $$ = 1;? --- 已聲明的局部變量數.

          在函數中聲明的局部變量被放置在 localvar[] 表中, 值是 lua_findsymbol() 的返回值, 即到符號表

          symtab 的索引.

          在產生式 var -> NAME 中, 第一篇研究全局變量的文章中也有遇見, 其中代碼塊為:

          Word s = lua_findsymbol(NAME) --- 查找 NAME 在符號表中的索引.

          int local = lua_localname(s)? ---? 在 localvar[] 表中查找是否有 s, 如果有則表示這是個局部

          --- 變量.

          if (是局部變量)?$$ =?-(local+1) --- 是一個負數, 從而與全局變量的 正的索引 區分開.

          在 lua_pushvar() 為訪問 var 生成代碼時, 前一篇文章研究全局變量時也碰到,

          為全局變量生成代碼為: PUSHGLOBAL idx-of-symtab

          為局部變量生成代碼為: PUSHLOCAL idx-of-localvar, 也即表示在 stack[base+idx]

          在產生式 varlist1 -> var ... 中, 記錄 var.$$ 到 varbuffer[] 表中, 在為其生成寫入指令時,

          根據是正數生成 全局變量的(STOREGLOBAL), 是負數生成局部變量訪問指令 (STORELOCAL).

          生成指令的時候, 如果 idx 在 0~9, 則產生較短的指令 PUSHLOCAL0~9, STORELOCAL0~9.

          函數的返回值也是放在棧頂的, 是在 RETCODE 指令中設置好返回給調用者的, 由于 lua 支持多賦值,多返回值, 將它們單獨放一個地方再研究也許更合適一些.

          總結

          以上是生活随笔為你收集整理的c语言尚未实现的虚拟函数,编译原理之学习 lua 1.1 笔记 (二) 函数调用与局部变量...的全部內容,希望文章能夠幫你解決所遇到的問題。

          如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。