精华内容
下载资源
问答
  • LuaVM:Lua的Lua虚拟机(和各种工具),因此您可以在Lua的同时进行Lua
  • 本篇文章,主要探讨一下lua中的指令系统(涉及到的文件 lopcodes.c )。在lua中,用32位的unsigned int类型来表示一条指令操作码,32位值包含了6位的操作码和26位的指令字段两部分内容。 All instructions have an ...

    本篇文章,主要探讨一下lua中的指令系统(涉及到的文件 lopcodes.c )。

     

    在lua中,用32位的unsigned int类型来表示一条指令操作码,32位值包含了6位的操作码和26位的指令字段两部分内容。

      All instructions have an opcode in the first 6 bits.
      Instructions can have the following fields:
     `A' : 8 bits
     `B' : 9 bits
     `C' : 9 bits
     `Bx' : 18 bits (`B' and `C' together)
     `sBx' : signed Bx

     

     

     

    enum OpMode {iABC, iABx, iAsBx};  /* basic instruction format */

    依据上面的枚举值,指令码目前有三种形式,即iABC,iABx,iAsBx,结构图如下:

     

     

    对于指令字段A,B,C,有以下三种形式的取值来源:

    /*
    ** R(x) - register
    ** Kst(x) - constant (in constant table)
    ** RK(x) == if ISK(x) then Kst(INDEXK(x)) else R(x)
    */
    其中,用R来表示寄存器值,Kst表示常量表,RK表示可能是常量表或者寄存器。

     

     

     

    lua5.14中总共拥有38条指令操作码,操作码名称如下:

     

     

    操作码枚举值,和上面的操作码名称一一对应,方便调用:

     

     

     

    38条操作码对应的操作模式如下:

     

     

     

     

    操作模式计算公式如下:

    #define opmode(t,a,b,c,m) (((t)<<7) | ((a)<<6) | ((b)<<4) | ((c)<<2) | (m))

    用8位值来表示

    /*
    ** masks for instruction properties. The format is:
    ** bits 0-1: op mode
    ** bits 2-3: C arg mode
    ** bits 4-5: B arg mode
    ** bit 6: instruction set register A
    ** bit 7: operator is a test
    */ 

    依次从低位到高位表示如下含义:

    第0,1位表示操作码模式,取值于 enum OpMode {iABC, iABx, iAsBx};  /* basic instruction format */

    第2,3位表示参数C模式,取值于 enum OpArgMask {
      OpArgN,  /* argument is not used */
      OpArgU,  /* argument is used */
      OpArgR,  /* argument is a register or a jump offset */
      OpArgK   /* argument is a constant or register/constant */
    };

    其中OpArgN表示未用参数,OpArgU表示可用参数,OpArgR表示寄存器参数或者跳转指令,OpArgK表示常量表或者寄存器参数,

    第4,5位表示参数B模式,取值于C取值范围一致。

    第6位表示寄存器参数A是否被设置。

    第7位表示是否测试操作。

     

     

     

     

     

     

     

    展开全文
  • 黑暗程序 由 Darkrising 编写的用于 Minecraft mod:Computercraft 的程序。 这些文件写在:Lua
  • 1. Lua虚拟机简介 Lua VM 使用的是 基于寄存器的虚拟机(Register-based)。 指令都是在已经分配好的寄存器中存取操作数。 add a b c 将 寄存器 b 与 寄存器 c 中的值相加,结果存在 寄存器 a 中。 标准的三地址指令,...

    1. Lua虚拟机简介

    Lua VM 使用的是 基于寄存器的虚拟机(Register-based)。 指令都是在已经分配好的寄存器中存取操作数。
    add a b c 将 寄存器 b 与 寄存器 c 中的值相加,结果存在 寄存器 a 中。 标准的三地址指令,每条指令的表达能力很强,并有效的减少了内存赋值操作。

    ADD C,A,B  //将A,B寄存器里面的值相加后赋值给C寄存器
    

    除此之外还有一种虚拟机是基于堆栈的虚拟机(Stack-based)。指令都是通过在栈中取数据,所以这类虚拟机在执行指令的时候都会伴随着数据的入栈和出栈。

    ILOAD A    //将A入栈
    ILOAD B    //将B入栈
    IADD       //将栈顶两个元素弹出并相加,结果入栈
    ISTORE C   //将栈顶弹出赋值给C
    

    由此可见

    • 基于堆栈的虚拟机的指令比基于寄存器的指令要小,因为在指令中不需要指定操作数。基于堆栈的虚拟机使用堆栈来保存中间结果、变量等。
    • 基于寄存器的虚拟机则支持寄存器的指令操作。基于堆栈的虚拟机需要用Push 、Pop 来传送数据,通常,完成同样的工作,基于寄存器的虚拟机所采用的指令数比基于堆栈的虚拟机采用的指令数目少,可以提高执行效率。
    • 堆栈虚拟机指令很低级,基于寄存器的处理器有更强大的指令功能,而且易于调试。 基于堆栈的处理器在处理函数调用、解决递归问题和切换上下文时简单明快。
    • 采用寄存器架构时,虚拟机需要经常保存和恢复寄存器中的内容,还要考虑对操作数的寻址问题等,因此,基于堆栈的虚拟机实现起来更简单,基于寄存器的虚拟机能提供更强大的指令集。

    下面是Lua虚拟机的一个体系结构简图:
    在这里插入图片描述
    如图所示,实际上虚拟机在执行某个函数原型Proto的时候使用的寄存器R[0]~R[n]对应的还是编译阶段预先分配好的栈空间。只是虚拟机在解释过程中当做了寄存器使用。除了寄存器每个Proto还对应有其解析的instruct指令、constan常量、以及外部的upvalue。



    2. Lua虚拟机指令简介

    在lua中,用32位的unsigned int类型来表示一条指令操作码,32位值包含了6位的操作码和26位的指令字段两部分内容。

    26(高位) 6(低位)
    Instructions Opcode

    2.1 指令操作码

    6位的操作码,所以最多支持 262^6 条指令,在Lua5.3中指令数量为47。我们可以在lopcodes.h文件中可以找到指令的定义:

    /*
    ** R(x) - register
    ** Kst(x) - constant (in constant table)
    ** RK(x) == if ISK(x) then Kst(INDEXK(x)) else R(x)
    */
    
    typedef enum {
    
    /*----------------------------------------------------------------------
    name		args	description
    ------------------------------------------------------------------------*/
    OP_MOVE,/*	A B	R(A) := R(B)					*/
    OP_LOADK,/*	A Bx	R(A) := Kst(Bx)					*/
    OP_LOADKX,/*	A 	R(A) := Kst(extra arg)				*/
    OP_LOADBOOL,/*	A B C	R(A) := (Bool)B; if (C) pc++			*/
    OP_LOADNIL,/*	A B	R(A), R(A+1), ..., R(A+B) := nil		*/
    OP_GETUPVAL,/*	A B	R(A) := UpValue[B]				*/
    OP_GETTABUP,/*	A B C	R(A) := UpValue[B][RK(C)]			*/
    OP_GETTABLE,/*	A B C	R(A) := R(B)[RK(C)]				*/
    OP_SETTABUP,/*	A B C	UpValue[A][RK(B)] := RK(C)			*/
    OP_SETUPVAL,/*	A B	UpValue[B] := R(A)				*/
    OP_SETTABLE,/*	A B C	R(A)[RK(B)] := RK(C)				*/
    OP_NEWTABLE,/*	A B C	R(A) := {} (size = B,C)				*/
    OP_SELF,/*	A B C	R(A+1) := R(B); R(A) := R(B)[RK(C)]		*/
    OP_ADD,/*	A B C	R(A) := RK(B) + RK(C)				*/
    OP_SUB,/*	A B C	R(A) := RK(B) - RK(C)				*/
    OP_MUL,/*	A B C	R(A) := RK(B) * RK(C)				*/
    OP_MOD,/*	A B C	R(A) := RK(B) % RK(C)				*/
    OP_POW,/*	A B C	R(A) := RK(B) ^ RK(C)				*/
    OP_DIV,/*	A B C	R(A) := RK(B) / RK(C)				*/
    OP_IDIV,/*	A B C	R(A) := RK(B) // RK(C)				*/
    OP_BAND,/*	A B C	R(A) := RK(B) & RK(C)				*/
    OP_BOR,/*	A B C	R(A) := RK(B) | RK(C)				*/
    OP_BXOR,/*	A B C	R(A) := RK(B) ~ RK(C)				*/
    OP_SHL,/*	A B C	R(A) := RK(B) << RK(C)				*/
    OP_SHR,/*	A B C	R(A) := RK(B) >> RK(C)				*/
    OP_UNM,/*	A B	R(A) := -R(B)					*/
    OP_BNOT,/*	A B	R(A) := ~R(B)					*/
    OP_NOT,/*	A B	R(A) := not R(B)				*/
    OP_LEN,/*	A B	R(A) := length of R(B)				*/
    OP_CONCAT,/*	A B C	R(A) := R(B).. ... ..R(C)			*/
    OP_JMP,/*	A sBx	pc+=sBx; if (A) close all upvalues >= R(A - 1)	*/
    OP_EQ,/*	A B C	if ((RK(B) == RK(C)) ~= A) then pc++		*/
    OP_LT,/*	A B C	if ((RK(B) <  RK(C)) ~= A) then pc++		*/
    OP_LE,/*	A B C	if ((RK(B) <= RK(C)) ~= A) then pc++		*/
    OP_TEST,/*	A C	if not (R(A) <=> C) then pc++			*/
    OP_TESTSET,/*	A B C	if (R(B) <=> C) then R(A) := R(B) else pc++	*/
    OP_CALL,/*	A B C	R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */
    OP_TAILCALL,/*	A B C	return R(A)(R(A+1), ... ,R(A+B-1))		*/
    OP_RETURN,/*	A B	return R(A), ... ,R(A+B-2)	(see note)	*/
    OP_FORLOOP,/*	A sBx	R(A)+=R(A+2);
    			if R(A) <?= R(A+1) then { pc+=sBx; R(A+3)=R(A) }*/
    OP_FORPREP,/*	A sBx	R(A)-=R(A+2); pc+=sBx				*/
    OP_TFORCALL,/*	A C	R(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2));	*/
    OP_TFORLOOP,/*	A sBx	if R(A+1) ~= nil then { R(A)=R(A+1); pc += sBx }*/
    OP_SETLIST,/*	A B C	R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B	*/
    OP_CLOSURE,/*	A Bx	R(A) := closure(KPROTO[Bx])			*/
    OP_VARARG,/*	A B	R(A), R(A+1), ..., R(A+B-2) = vararg		*/
    OP_EXTRAARG/*	Ax	extra (larger) argument for previous opcode	*/
    } OpCode;
    

    通过指令的注释可以简单了解指令的作用以及指令参数的意思:

    /*
    ** R(x) - 寄存器的值
    ** Kst(x) - 常量表的值
    ** RK(x) - 既可以是寄存器的值也可以常量表的值,其中参数的最高位区分 寄存器索引与常量索引。
    */
    

    2.2 指令格式

    高26位的指令字段在不同指令中可能代表不同的情况,其格式定义在lopcodes.h文件中:

    enum OpMode {iABC, iABx, iAsBx, iAx};  /* basic instruction format */
    

    在不同的指令格式中,其解释如下表格:

    31-23 22-14 13-6 OpMode
    B C A iABC
    Bx A iABx
    sBx A iAsBX
    Ax iAx
    • 每条指令都会对一个对象做出影响,受影响的对象被称为 A。它由 8 bits 来表示。 A 通常是一个寄存器的索引,也可能是对 Upvalue 的操作。
    • 作用到 A 的参数一般有两个,每个参数 由 9 bits 表示,分别称为 B 和 C。
    • 一部分指令不需要两个操作参数,这时候可以把 B 和 C 合并为一个 18 bits 的整数 Bx 来适应更大的范围。
    • 当操作码涉及到跳转指令时,这个参数表示跳转偏移量。向前跳转需要设置偏移量为一个负数。这类指令需要带符号信息来区别,记作 sBx。 其中0被表示为 2^17 ; 1 则表示为 2^17 + 1 ; -1 表示为 2^17 - 1 。
    • Lua VM 在运行期,会将需要的常量加载到 寄存器中(Lua 栈),然后利用这些寄存器做相应的工作。 加载常量的操作码 为LOADK,它由两个参数 A ,Bx ,这个操作把Bx 所指的常量加载到 A 所指的寄存器中。 Bx 有 18 bit 长,所以 LOADK 这个操作只能索引到 2^17 个常量。 为了扩大索引常量的上限,提供了LOADKX,它将常量索引号放在了接下来的一条EXTRAARG 指令中。 OP_EXTRAARG 指令 把 opcode所占的 8bit 以外的26 bit 都用于参数表示, 称之为* Ax*。

    参数 A、B、C 所占的位数大小以及偏移量 ,在Lua 中由以下一组宏定义:

    #define SIZE_C		9
    #define SIZE_B		9
    #define SIZE_Bx		(SIZE_C + SIZE_B)
    #define SIZE_A		8
    #define SIZE_Ax		(SIZE_C + SIZE_B + SIZE_A)
    #define SIZE_OP		6
    #define POS_OP		0
    #define POS_A		(POS_OP + SIZE_OP)
    #define POS_C		(POS_A + SIZE_A)
    #define POS_B		(POS_C + SIZE_C)
    #define POS_Bx		POS_C
    #define POS_Ax		POS_A
    

    A、B、C 用来表示指令操作数的数据来源,在Lua 中 值都存储在三个地方:

    1. 存在 Lua 寄存器中(也就是 Lua 的数据栈)局部变量。 Lua 使用当前函数的 栈来作为寄存器使用(Lua 寄存器 = Lua 栈),当前函数的栈等同于寄存器数组,即 stack(n) = register(n)。寄存器的idx 从 0 开始
    2. 常量表中 ,一般存储存储常量。每一个函数的原型 Proto 都有一个属于本函数的常量表,用于存储编译过程中函数所使用到的常量。常量表可以存放 nil、boolean、number、string类型的数据,常量的idx 从 1 开始
    3. 一些既不是常量也不在寄存器的数据 , 存储在 upvalue 表中 或者 Table 表中。每一个函数的原型Proto 中都有一个upvalue 表,用于存储在编译过程中该函数使用的upvalue 。在运行期,通过OP-CLOSURE 指令创建一个 closure时,会根据 Proto 中的描述为这个 closure 初始化upvalue 表。upvalue 也是根据id来索引的。 upvalue 的idx 从 0开始

    2.3 指令格式定义

    对于每个操作码的指令格式,在lopcodes.c文件中也有定义:

    LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = {
    /*       T  A    B       C     mode		   opcode	*/
      opmode(0, 1, OpArgR, OpArgN, iABC)		/* OP_MOVE */
     ,opmode(0, 1, OpArgK, OpArgN, iABx)		/* OP_LOADK */
     ,opmode(0, 1, OpArgN, OpArgN, iABx)		/* OP_LOADKX */
     ,opmode(0, 1, OpArgU, OpArgU, iABC)		/* OP_LOADBOOL */
     ,opmode(0, 1, OpArgU, OpArgN, iABC)		/* OP_LOADNIL */
     ,opmode(0, 1, OpArgU, OpArgN, iABC)		/* OP_GETUPVAL */
     ,opmode(0, 1, OpArgU, OpArgK, iABC)		/* OP_GETTABUP */
     ,opmode(0, 1, OpArgR, OpArgK, iABC)		/* OP_GETTABLE */
     ,opmode(0, 0, OpArgK, OpArgK, iABC)		/* OP_SETTABUP */
     ,opmode(0, 0, OpArgU, OpArgN, iABC)		/* OP_SETUPVAL */
     ,opmode(0, 0, OpArgK, OpArgK, iABC)		/* OP_SETTABLE */
     ,opmode(0, 1, OpArgU, OpArgU, iABC)		/* OP_NEWTABLE */
     ,opmode(0, 1, OpArgR, OpArgK, iABC)		/* OP_SELF */
     ,opmode(0, 1, OpArgK, OpArgK, iABC)		/* OP_ADD */
     ,opmode(0, 1, OpArgK, OpArgK, iABC)		/* OP_SUB */
     ,opmode(0, 1, OpArgK, OpArgK, iABC)		/* OP_MUL */
     ,opmode(0, 1, OpArgK, OpArgK, iABC)		/* OP_MOD */
     ,opmode(0, 1, OpArgK, OpArgK, iABC)		/* OP_POW */
     ,opmode(0, 1, OpArgK, OpArgK, iABC)		/* OP_DIV */
     ,opmode(0, 1, OpArgK, OpArgK, iABC)		/* OP_IDIV */
     ,opmode(0, 1, OpArgK, OpArgK, iABC)		/* OP_BAND */
     ,opmode(0, 1, OpArgK, OpArgK, iABC)		/* OP_BOR */
     ,opmode(0, 1, OpArgK, OpArgK, iABC)		/* OP_BXOR */
     ,opmode(0, 1, OpArgK, OpArgK, iABC)		/* OP_SHL */
     ,opmode(0, 1, OpArgK, OpArgK, iABC)		/* OP_SHR */
     ,opmode(0, 1, OpArgR, OpArgN, iABC)		/* OP_UNM */
     ,opmode(0, 1, OpArgR, OpArgN, iABC)		/* OP_BNOT */
     ,opmode(0, 1, OpArgR, OpArgN, iABC)		/* OP_NOT */
     ,opmode(0, 1, OpArgR, OpArgN, iABC)		/* OP_LEN */
     ,opmode(0, 1, OpArgR, OpArgR, iABC)		/* OP_CONCAT */
     ,opmode(0, 0, OpArgR, OpArgN, iAsBx)		/* OP_JMP */
     ,opmode(1, 0, OpArgK, OpArgK, iABC)		/* OP_EQ */
     ,opmode(1, 0, OpArgK, OpArgK, iABC)		/* OP_LT */
     ,opmode(1, 0, OpArgK, OpArgK, iABC)		/* OP_LE */
     ,opmode(1, 0, OpArgN, OpArgU, iABC)		/* OP_TEST */
     ,opmode(1, 1, OpArgR, OpArgU, iABC)		/* OP_TESTSET */
     ,opmode(0, 1, OpArgU, OpArgU, iABC)		/* OP_CALL */
     ,opmode(0, 1, OpArgU, OpArgU, iABC)		/* OP_TAILCALL */
     ,opmode(0, 0, OpArgU, OpArgN, iABC)		/* OP_RETURN */
     ,opmode(0, 1, OpArgR, OpArgN, iAsBx)		/* OP_FORLOOP */
     ,opmode(0, 1, OpArgR, OpArgN, iAsBx)		/* OP_FORPREP */
     ,opmode(0, 0, OpArgN, OpArgU, iABC)		/* OP_TFORCALL */
     ,opmode(0, 1, OpArgR, OpArgN, iAsBx)		/* OP_TFORLOOP */
     ,opmode(0, 0, OpArgU, OpArgU, iABC)		/* OP_SETLIST */
     ,opmode(0, 1, OpArgU, OpArgN, iABx)		/* OP_CLOSURE */
     ,opmode(0, 1, OpArgU, OpArgN, iABC)		/* OP_VARARG */
     ,opmode(0, 0, OpArgU, OpArgU, iAx)		/* OP_EXTRAARG */
    };
    
    • T: (第 7 bit) 表示这是不是一条逻辑测试相关的指令,这种指令可能会涉及一次条件跳转,将PC指针自增1。(之所以需要这个标记,是因为Lua 中所有涉及条件分支的地方,实际上都在分支指令后紧随着一条 JMP 指令。Lua 没有 为布尔运算单独设计opcode,它让所有的布尔运算都以分支执行流的形式出现。Lua 的 And 与 Or 关键字 支持短路求值,所以在VM 中以分支跳转的形式实现)。分支指令和之后的 JMP 跳转指令是一体的,是因为32bit 的 Instruction 无法全部描述才分拆为两条指令。这个指令可以用来检测是不是分支指令。 当遇到 JMP 指令时,可以回溯到前面的一条指令来分辨是否是一次条件跳转。 这对 生成Lua 的bytecode 模块有帮助。
    • A: (第 6 bit)表示这个指令是否会修改 register A,这个标记在 debug模块被用于跟踪最后改变register 内容的指令位置,帮助生成debug info。
    • B : (第 4-5 bit) B arg mode。
    • C : (第 2-3 bit) C arg mode。
    • opcode:(第 0-1 bit)OpCode的格式,这些分类信息,用于luac 反编译字节码时的输出,对于Lua 的运行时没有实际意义。

    因为不是每个指令都需要2-3个参数,所以Lua中定义了一个枚举值OpArgMask来表示参数内存的解释。

    enum OpArgMask {
      OpArgN,  /* 参数没有被使用 */
      OpArgU,  /* 参数被使用 */
      OpArgR,  /* 表示寄存器索引或者跳转偏移量 */
      OpArgK   /* 表示寄存器索引或者常量表索引 */
    };
    

    OpArgN 好理解,就是指这个参数对应的内存没有被使用。例如:

    opmode(0, 1, OpArgR, OpArgN, iABC)		/* OP_MOVE A B	R(A) := R(B) */
    

    指令格式是iABC,只需要两个参数,所以C参数是没有被使用的。(注意:指令格式只代表每个参数的内存范围,并不表示一定都会用上,也不表示参数的实际含义)


    OpArgU 表示这个参数的值就是参数本身(有点绕,区别于OpArgR,OpArgK表示的是索引)

    opmode(0, 1, OpArgU, OpArgU, iABC)		/* OP_NEWTABLE A B C	R(A) := {} (size = B,C) */
    

    OP_NEWTABLE指令创建一个数组部分大小为B,hash表部分大小为C的table放置于寄存器A中;这里可以到:B,C两个参数自身值在指令中作为参数。


    OpArgR 表示这个参数是寄存器上的值,为寄存器的索引。

    opmode(0, 1, OpArgR, OpArgN, iABC)		/* OP_MOVE A B	R(A) := R(B) */
    

    OP_MOVE表示将B寄存器的值拷贝到A寄存器中,可以看见这里A,B并不表示值本身,而是表示寄存器上索引。


    OpArgK 表示这个参数是寄存器上的值也可能是常量表里面的值。

    RK(x) == if ISK(x) then Kst(INDEXK(x)) else R(x)
    opmode(0, 1, OpArgK, OpArgK, iABC)		/* OP_ADD   A B C	R(A) := RK(B) + RK(C)*/
    

    OP_ADD 表示将B,C 索引所对应的值相加放到寄存器A中,这里可以看到B,C可能是寄存器的索引也可能是常量表里的索引,在Lua中可以用ISK宏来判断是否是寄存器的值。



    3. Lua编译指令

    在Lua官网下载Lua5.3的源码:Lua5.3源码
    将源码添加到工程后,注意添加luac.c不要添加lua.c。编译好luac.exe。
    下面是一段简单的Lua代码:

    local a = 1
    local b = a + 2
    print(b)
    

    将Lua代码文件放置于luac.exe同一目录,使用:luac.exe(exe)llmylua.lua(lua)luac.exe_{(你编译好后的exe名字)} -l -l \quad mylua.lua_{(lua文件名)}

    luac.exe -l -l mylua.lua
    

    可以得到如下的输出:

    main <mylua.lua:0,0> (6 instructions at 007EB310)
    0+ params, 4 slots, 1 upvalue, 2 locals, 3 constants, 0 functions
    	1	[1]	LOADK    	0 -1	; 1
    	2	[2]	ADD      	1 0 -2	; - 2
    	3	[3]	GETTABUP 	2 0 -3	; _ENV "print"
    	4	[3]	MOVE     	3 1
    	5	[3]	CALL     	2 2 1
    	6	[3]	RETURN   	0 1
    constants (3) for 007EB310:
    	1	1
    	2	2
    	3	"print"
    locals (2) for 007EB310:
    	0	a	2	7
    	1	b	3	7
    upvalues (1) for 007EB310:
    	0	_ENV	1	0
    
    

    从上面编译指令可以看到
    0+ params, 4 slots, 1 upvalue, 2 locals, 3 constants, 0 functions
    0个参数,4个寄存器,1个upvalue,2个局部变量,3个常量,0个函数。

    在指令的下面分别是常量表、局部变量表、upvalue表。三个表中第一列都是表中的索引,不过constants的索引在指令中都为对应的负数。

    还有值得注意的是:在局部变量表中的第3、4列,分别表示当前局部变量的有效域,比如第0个局部变量a的有效域为第2个指令到第7个指令(上面只有6个指令,说明都有效)。

    至于上面的指令含义,我们在第二部分会继续细讲。



    4.参考

    本博客参考了Bloger TTC的虚拟机指令分析一系列文章: Lua5.3 虚拟机指令分析

    展开全文
  • gopher-lua:GopherLua:Go中Lua虚拟机和编译器
  • 任何一个函数,一个 lua 文件, 经过编译之后都会对应变成Lua的一个LClosure(闭包)对象,一个闭包包含一个函数原型(Proto)和一个upvalues列表。 typedef struct LClosure { ClosureHeader; struct Proto *p; ...

    1. Proto函数原型

    任何一个函数,一个 lua 文件, 经过编译之后都会对应变成Lua的一个LClosure(闭包)对象,一个闭包包含一个函数原型(Proto)和一个upvalues列表。

    typedef struct LClosure {
      ClosureHeader;
      struct Proto *p;
      UpVal *upvals[1];  /* list of upvalues */
    } LClosure;
    

    比较重要的就是生成的 Proto 这个数据结构, 这个结构的定义在 lobject.h 中。

    /*
    ** Function Prototypes
    */
    typedef struct Proto {
      CommonHeader;
      lu_byte numparams;  /* 固定参数个数 */
      lu_byte is_vararg;  /* 是否是可变参数列表 */
      lu_byte maxstacksize;  /* 这个函数最多需要寄存器的数量(就是函数栈) */
      int sizeupvalues;  /* upvalues的个数 */
      int sizek;  /* 常量的个数 */
      int sizecode; /* 指令的个数 */
      int sizelineinfo;  /* 字段表示当前函数所有的行总数目 */
      int sizep;  /* 定义的函数(proto)的个数 */
      int sizelocvars; /* 局部变量的个数 */
      int linedefined;  /* 定义的首行(Debug信息)  */
      int lastlinedefined;  /* 定义的尾行(Debug信息)  */
      TValue *k;  /* 常量表 */
      Instruction *code;  /* 指令表 */
      struct Proto **p;  /* 定义的内部函数 */
      int *lineinfo;  /* 每个指令对应的行数 (Debug信息) */
      LocVar *locvars;  /* 局部变量的信息 (Debug信息) */
      Upvaldesc *upvalues;  /* Upvalue的信息 */
      struct LClosure *cache;  /* last-created closure with this prototype */
      TString  *source;  /* lua完整文件路径名(Debug信息) */
      GCObject *gclist;
    } Proto;
    



    2. Upvalue

    完整的函数是包括Proto和Upvalues,在介绍Proto之前,先介绍Upvalue的概念。在Lua中,函数视为一种对象,那么我们可以在函数内部定义新的函数,如下:

    function fun1()
    	local a = 100
    	local test_fun = function(num)
    		if num > a then
    			print("true")
    		else
    			print("false")
    		end
    	end
    	test_fun(50)   -- output : false
    	test_fun(200)  -- output : true
    end
    

    如上,内部函数test_fun函数在执行过程中可以访问外部函数的局部变量a,那么a变量对于函数test_fun而言就是一个Upvalue。

    在Proto的结构中放了Upvalue 的名称(用于调试),是否在父函数栈上,索引位置等信息:

    /*  Description of an upvalue for function prototypes */
    typedef struct Upvaldesc {
      TString *name;  /*  名字(Debug信息) */
      lu_byte instack;  /* 是否在栈中 */
      lu_byte idx;  /* 索引(可能是外部函寄存器索引,也可能是外部函数的upvalue索引) */
    } Upvaldesc;
    

    Lua编译器在编译阶段是如何认定一个变量是Upvalue呢?

    1. 会先在当前函数中找,找到则标记为 Local 变量,否则递归的查找父函数,找到则为 Upvalue, 否则为 Global
    2. 对当前函数的 Upvalue来说,有两种情况:是父函数的 Upvalue;是父函数的 Local;
      当是父函数的 Upvalue时,将 instack 标记置为 0
      否则是父函数的 Local,将 instack 标记置为 1

    对于在父函数栈中的Upvalue,值为L->base + idx,否则就是父函数的Upvalue,值为Upvalue[idx]



    3. 指令的生成

    这篇文章不重点讲述指令的生成流程,只是简单介绍指令产生的地方。

    #define CREATE_ABC(o,a,b,c)	((cast(Instruction, o)<<POS_OP) \
    			| (cast(Instruction, a)<<POS_A) \
    			| (cast(Instruction, b)<<POS_B) \
    			| (cast(Instruction, c)<<POS_C))
    
    #define CREATE_ABx(o,a,bc)	((cast(Instruction, o)<<POS_OP) \
    			| (cast(Instruction, a)<<POS_A) \
    			| (cast(Instruction, bc)<<POS_Bx))
    
    #define CREATE_Ax(o,a)		((cast(Instruction, o)<<POS_OP) \
    			| (cast(Instruction, a)<<POS_Ax))
    

    Lua是通过上面三个宏来生成对应的指令,并通过luaK_code添加到对应的FuncState中。(在 LexState 中,成员 struct FuncState *fs, 记录了当前正在解析 FuncState,它相当于一系列被编译的函数栈的栈顶。 每当新解析一个函数时,就会新建一个 FuncState 来记录当前函数编译信息。)



    4. 虚拟机的执行

    虚拟机相关的代码是在lvm.clvm.h两个文件中。

    最核心的函数就是void luaV_execute (lua_State L) ,参数lua_State实际上是每一个LUA线程创建独立的函数栈和线程栈,以及线程执行过程中需要用到的内存管理、字符串管理、gc等信息。

    /*
    ** 'per thread' state
    ** Lua 主线程 栈 数据结构
    ** 作用:管理整个栈和当前函数使用的栈的情况,最主要的功能就是函数调用以及和c的通信
    */
    struct lua_State {
      CommonHeader;
      lu_byte status; /* 解析容器的用于记录中间状态*/
     
      /* 全局状态机 */
      global_State *l_G;
     
      /* 调用栈:调用栈信息管理(CallInfo 为双向链表结构) */
      unsigned short nci;  /* number of items in 'ci' list - 存储一共多少个CallInfo */
      CallInfo base_ci;  /* CallInfo for first level (C calling Lua) - 调用栈的头部指针 */
      CallInfo *ci;  /* call info for current function - 当前运行函数信息 */
     
      /* 数据栈:栈指针地址管理  StkId = TValue 的数组 */
      StkId top;  /* first free slot in the stack - 线程栈的栈顶指针 */
      StkId stack_last;  /* last free slot in the stack  - 线程栈的最后一个位置 */
      StkId stack;  /* stack base - 栈的指针,当前执行的位置*/
     
     
      const Instruction *oldpc;  /* last pc traced 在当前thread 的解释执行指令的过程中,指向最后一次执行的指令的指针 */
      UpVal *openupval;  /* list of open upvalues in this stack */
      GCObject *gclist; /* GC列表 */
      struct lua_State *twups;  /* list of threads with open upvalues */
      struct lua_longjmp *errorJmp;  /* current error recover point */
     
      /* Hook 相关管理 - 服务于debug模块 */
      volatile lua_Hook hook;
      ptrdiff_t errfunc;  /* current error handling function (stack index) */
      int stacksize;
      int basehookcount;
      int hookcount;
      l_signalT hookmask;
      lu_byte allowhook;
     
      /* 跟C语言通信 管理*/
      unsigned short nCcalls;  /* number of nested C calls */
      unsigned short nny;  /* number of non-yieldable calls in stack */
    };
    

    可以看出lua_State里面存储了当前运行的函数信息,以及函数栈、数据栈等等;而luaV_execute也是从lua_State里面取数据和指令进行运行。

    而在luaV_execute函数中,可以看到有一个无限循环,从ci中不断的取出指令,然后通过switch (GET_OPCODE(i))来根据不同的opcode进行不同的过程:

    void luaV_execute (lua_State *L) {
      CallInfo *ci = L->ci;
      LClosure *cl;
      TValue *k;
      StkId base;
      ci->callstatus |= CIST_FRESH;  /* fresh invocation of 'luaV_execute" */
     newframe:  /* reentry point when frame changes (call/return) */
      lua_assert(ci == L->ci);
      cl = clLvalue(ci->func);  /* local reference to function's closure */
      k = cl->p->k;  /* local reference to function's constant table */
      base = ci->u.l.base;  /* local copy of function's base */
      /* main loop of interpreter */
      for (;;) {
      	Instruction i;
        StkId ra;
        vmfetch();   //获取指令,并且准备其执行的相关数据初始化
        vmdispatch (GET_OPCODE(i)) {
        	vmcase(...) {   //对于不同的指令处理
    		...
            vmbreak;
          }
      	}
      }
    }
    
    展开全文
  • lua 源码阅读一虚拟机

    2014-08-01 15:18:18
     lua虚拟机是基于寄存器的,我们所了解的虚拟机中大部分是基于堆栈的(包括c#、python、jvm等),android的davik虚拟机是基于寄存器的,那么基于寄存器和基于堆栈的虚拟机有什么区别和优缺点呢?   ...

            lua的虚拟机是基于寄存器的,我们所了解的虚拟机中大部分是基于堆栈的(包括c#、python、jvm等),android的davik虚拟机是基于寄存器的,那么基于寄存器和基于堆栈的虚拟机有什么区别和优缺点呢?

        

    展开全文
  • Lua中的赋值指令有如下几个: OP_MOVE A B R(A) := R(B) OP_LOADK A Bx R(A) := Kst(Bx) OP_LOADKX A R(A) := Kst(extra arg) OP_LOADBOOL A B C R(A) := (Bool)B; if (C) pc++ OP_LOADNIL A B R(A), R(A+1), …, ...

空空如也

空空如也

1 2 3 4 5 ... 9
收藏数 170
精华内容 68
关键字:

lua虚拟机源码