当前位置:  编程技术>综合
本页文章导读:
    ▪探索Lua5.2内部实现:虚拟机指令(3) Upvalues & Globals      原文 在编译期,如果要访问变量a时,会依照以下的顺序决定变量a的类型: a是当前函数的local变量a是外层函数的local变量,那么a是当前函数的upvaluea是全局变量 local变量本身就存在于当前.........
    ▪探索Lua5.2内部实现:虚拟机指令(2) MOVE & LOAD      原文 name args desc OP_MOVE A B R(A) := R(B) OP_MOVE用来将寄存器B中的值拷贝到寄存器A中。由于Lua是register based vm,大部分的指令都是直接对寄存器进行操作,而不需要对数据进行压栈和.........
    ▪探索Lua5.2内部实现:虚拟机指令(1)       原文 Lua一直把虚拟机执行代码的效率作为一个非常重要的设计目标。而采用什么样的指令系统的对于虚拟机的执行效率来说至关重要。 Stack based vs Register based VM 根据指令获取操作数方式的.........

[1]探索Lua5.2内部实现:虚拟机指令(3) Upvalues & Globals
    来源: 互联网  发布时间: 2013-11-10

原文

在编译期,如果要访问变量a时,会依照以下的顺序决定变量a的类型:

  • a是当前函数的local变量
  • a是外层函数的local变量,那么a是当前函数的upvalue
  • a是全局变量
  • local变量本身就存在于当前的register中,所有的指令都可以直接使用它的id来访问。而对于upvalue,lua则有专门的指令负责获取和设置。

    全局变量在lua5.1中也是使用专门的指令,而5.2对这一点做了改变。Lua5.2种没有专门针对全局变量的指令,而是把全局表放到最外层函数的名字为"_ENV"的upvalue中。对于全局变量a,相当于编译期帮你改成了_ENV.a来进行访问。

    name args desc OP_GETUPVAL A B C R(A) := UpValue[B] OP_SETUPVAL A B UpValue[B] := R(A) OP_GETTABUP A B C R(A) := UpValue[B][RK(C)] OP_SETTABUP A B C UpValue[A][RK(B)] := RK(C)

    GETUPVAL将B为索引的upvalue的值装载到A寄存器中。SETUPVAL将A寄存器的值保存到B为索引的upvalue中。

    GETTABUP将B为索引的upvalue当作一个table,并将C做为索引的寄存器或者常量当作key获取的值放入寄存器A。SETTABUP将A为索引的upvalue当作一个table,将C寄存器或者常量的值以B寄存器或常量为key,存入table。

    local u = 0;
    function f() 
    	local l;
    	u = 1; 
    	l = u;
    	g = 1;
    	l = g;
    end
    main <test.lua:0,0> (4 instructions at 0x80048eb0)
    0+ params, 2 slots, 1 upvalue, 1 local, 2 constants, 1 function
    	1	[1]	LOADK    	0 -1	; 0
    	2	[8]	CLOSURE  	1 0	; 0x80049140
    	3	[2]	SETTABUP 	0 -2 1	; _ENV "f"
    	4	[8]	RETURN   	0 1
    constants (2) for 0x80048eb0:
    	1	0
    	2	"f"
    locals (1) for 0x80048eb0:
    	0	u	2	5
    upvalues (1) for 0x80048eb0:
    	0	_ENV	1	0
    
    function <test.lua:2,8> (7 instructions at 0x80049140)
    0 params, 2 slots, 2 upvalues, 1 local, 2 constants, 0 functions
    	1	[3]	LOADNIL  	0 0
    	2	[4]	LOADK    	1 -1	; 1
    	3	[4]	SETUPVAL 	1 0	; u
    	4	[5]	GETUPVAL 	0 0	; u
    	5	[6]	SETTABUP 	1 -2 -1	; _ENV "g" 1
    	6	[7]	GETTABUP 	0 1 -2	; _ENV "g"
    	7	[8]	RETURN   	0 1
    constants (2) for 0x80049140:
    	1	1
    	2	"g"
    locals (1) for 0x80049140:
    	0	l	2	8
    upvalues (2) for 0x80049140:
    	0	u	1	0
    	1	_ENV	0	0  

    上面的代码片段生成一个主函数和一个内嵌函数。根据前面说到的变量规则,在内嵌函数中,l是local变量,u是upvalue,g由于既不是local变量,也不是upvalue,当作全局变量处理。我们先来看内嵌函数,生成的指令从17行开始。

    第17行的LOADNIL前面已经讲过,为local变量赋值。下面的LOADK和SETUPVAL组合,完成了u = 1。因为1是一个常量,存在于常量表中,而lua没有常量与upvalue的直接操作指令,所以需要先把常量1装在到临时寄存器1种,然后将寄存器1的值赋给upvalue 0,也就是u。第20行的GETUPVAL将upvalue u赋给local变量l。第21行开始的SETTABUP和GETTABUP就是前面提到的对全局变量的处理了。g=1被转化为_ENV.g=1。_ENV是系统预先设置在主函数中的upvalue,所以对于全局变量g的访问被转化成对upvalue[_ENV][g]的访问。SETTABUP将upvalue 1(_ENV代表的upvalue)作为一个table,将常量表2(常量"g")作为key的值设置为常量表1(常量1);GETTABUP则是将upvalue 1作为table,将常量表2为key的值赋给寄存器0(local l)。


    作者:yuanlin2008 发表于2013-1-11 18:12:38 原文链接
    阅读:10 评论:0 查看评论

        
    [2]探索Lua5.2内部实现:虚拟机指令(2) MOVE & LOAD
        来源: 互联网  发布时间: 2013-11-10

    原文

    name args desc OP_MOVE A B R(A) := R(B)

    OP_MOVE用来将寄存器B中的值拷贝到寄存器A中。由于Lua是register based vm,大部分的指令都是直接对寄存器进行操作,而不需要对数据进行压栈和弹栈,所以需要OP_MOVE指令的地方并不多。最直接的使用之处就是将一个local变量复制给另一个local变量时:

    local a;
    local b = a;
    	1	[1]	LOADNIL  	0 0
    	2	[2]	MOVE     	1 0
    	3	[2]	RETURN   	0 1
    

    在编译过程中,Lua会将每个local变量都分配到一个指定的寄存器中。在运行期,lua使用local变量所对应的寄存器id来操作local变量,而local变量的名字除了提供debug信息外,没有其他作用。

    在这里a被分配给register 0,b被分配给register 1。第二行的MOVE表示将a(register 0)的值赋给b(register 1)。其他使用的地方基本都是对寄存器的位置有特殊要求的地方,比如函数参数的传递等等。

    name args desc OP_LOADK A Bx R(A) := Kst(Bx)

    LOADK将Bx表示的常量表中的常量值装载到寄存器A中。很多其他指令,比如数学操作指令,其本身可以直接从常量表中索引操作数,所以可以不依赖于LOADK指令。

    local a=1;
    local b="foo";
    	1	[1]	LOADK    	0 -1	; 1
    	2	[2]	LOADK    	1 -2	; "foo"
    	3	[2]	RETURN   	0 1
    constants (2) for 0x80048eb0:
    	1	1
    	2	"foo" 

    name args desc OP_LOADKX A R(A) := Kst(extra arg)

    LOADKX是lua5.2新加入的指令。当需要生成LOADK指令时,如果需要索引的常量id超出了Bx所能表示的有效范围,那么就生成一个LOADKX指令,取代LOADK指令,并且接下来立即生成一个EXTRAARG指令,并用其Ax来存放这个id。5.2的这个改动使得一个函数可以处理超过262143个常量。

    name args desc OP_LOADBOOL A B C R(A) := (Bool)B; if (C) pc++

    LOADBOOL将B所表示的boolean值装载到寄存器A中。B使用0和1分别代表false和true。C也表示一个boolean值,如果C为1,就跳过下一个指令。

    local a = true;

    	1	[1]	LOADBOOL 	0 1 0
    	2	[1]	RETURN   	0 1 
    C在这里的作用比较特殊。要了解C的具体用处,首先要知道lua中对于逻辑和关系表达式是如何处理的,比如:

    local a = 1 < 2

    对于上面的代码,一般我们会认为lua应该先对1<2求出一个boolean值,然后放入到a中。然而实际上产生出来的代码为:

    	1	[1]	LT       	1 -1 -2	; 1 2
    	2	[1]	JMP      	0 1	; to 4
    	3	[1]	LOADBOOL 	0 0 1
    	4	[1]	LOADBOOL 	0 1 0
    	5	[1]	RETURN   	0 1
    constants (2) for 0x80048eb0:
    	1	1
    	2	2 

    可以看到,lua生成了LT和JMP指令,另外再加上两个LOADBOOL对于a赋予不同的boolean值。LT(后面会详细讲解)指令本身并不产生一个boolean结果值,而是配合后面紧跟的JMP实现true和false的不同跳转。如果LT评估为true,就继续执行,也就是执行到JMP,然后调转到4,对a赋予true;否则就跳过下一条指令到达第三行,对a赋予false,并且跳过下一个指令。所以上面的代码实际的意思被转化为:

    local a;
    if 1 < 2 then
        a = true;
    else
        a = false;
    end

    逻辑或者关系表达式之所以被设计成这个样子,主要是为if语句和循环语句所做的优化。不用将整个表达式估值成一个boolean值后再决定跳转路径,而是评估过程中就可以直接跳转,节省了很多指令。

    C的作用就是配合这种使用逻辑或关系表达式进行赋值的操作,他节省了后面必须跟的一个JMP指令。

    name args desc OP_LOADNIL A B R(A), R(A+1), ..., R(A+B) := nil

    LOADNIL将使用A到B所表示范围的寄存器赋值成nil。用范围表示寄存器主要为了对以下情况进行优化:

    local a,b,c;

    	1	[1]	LOADNIL  	0 2
    	2	[1]	RETURN   	0 1 

    对于连续的local变量声明,使用一条LOADNIL指令就可以完成,而不需要分别进行赋值。

    对于一下情况

    local a;
    local b = 0;
    local c;
    
    	1	[1]	LOADNIL  	0 0
    	2	[2]	LOADK    	1 -1	; 0
    	3	[3]	LOADNIL  	2 0 
    在Lua5.2中,a和c不能被合并成一个LOADNIL指令。所以以上写法理论上会生成更多的指令,应该予以避免,而改写成

    local a,c;
    local b = 0;





    作者:yuanlin2008 发表于2013-1-11 18:12:00 原文链接
    阅读:11 评论:0 查看评论

        
    [3]探索Lua5.2内部实现:虚拟机指令(1)
        来源: 互联网  发布时间: 2013-11-10

    原文

    Lua一直把虚拟机执行代码的效率作为一个非常重要的设计目标。而采用什么样的指令系统的对于虚拟机的执行效率来说至关重要。

    Stack based vs Register based VM

    根据指令获取操作数方式的不同,我们可以把虚拟机的实现分为stack based和register based。

    Stack based vm
    对于大多数的虚拟机,比如JVM,Python,都采用传统的stack based vm。

    Stack based vm的指令一般都是在当前stack中获取和保存操作数的。比如一个简单的加法赋值运算:a=b+c,对于stack based vm,一般会被转化成如下的指令:

    push b; // 将变量b的值压入stack
    push c; // 将变量c的值压入stack
    add;    // 将stack顶部的两个值弹出后相加,将结果压入stack
    mov a;  // 将stack顶部结果放到a中

    由于Stack based vm的指令都是基于当前stack来查找操作数的,这就相当于所有操作数的存储位置都是运行期决定的,在编译器的代码生成阶段不需要额外为在哪里存储操作数费心,所以stack based的编译器实现起来相对比较简单直接。也正因为这个原因,每条指令占用的存储空间也比较小。

    但是,对于一个简单的运算,stack based vm会使用过多的指令组合来完成,这样就增加了整体指令集合的长度。vm会使用同样多的迭代次数来执行这些指令,这对于效率来说会有很大的影响。并且,由于操作数都要放到stack上面,使得移动这些操作数的内存复制大大增加,这也会影响到效率。

    Register based vm

    Lua 采用的是register based vm。

    Register based vm的指令都是在已经分配好的寄存器中存取操作数。对于上面的运算,register based vm一般会使用如下的指令:

    add a b c; // 将b与c对应的寄存器的值相加,将结果保存在a对应的寄存器中

    Register based vm的指令可以直接对应标准的3地址指令,用一条指令完成了上面多条指令的计算工作,并且有效地减少了内存复制操作。这样的指令系统对于效率有很大的帮助。

    不过,在编译器设计上,就要在代码生成阶段对寄存器进行分配,增加了实现的复杂度。并且每条指令所占用的存储空间也相应的增加了。

    Lua虚拟机指令简介

    Lua的指令使用一个32bit的unsigned integer表示。所有指令的定义都在lopcodes.h文件中,使用一个enum OpCode代表指令类型。在lua5.2中,总共有40种指令(id从0到39)。根据指令参数的不同,可以将所有指令分为4类:


    除了sBx之外,所有的指令参数都是unsigned integer类型。sBx可以表示负数,但表示方法比较特殊。sBx的18bit可表示的最大整数为262143,这个数的一半131071用来表示0,所以-1可以表示为-1+131071,也就是131070,而+1可以表示为+1+131071,也就是131072。

    ABC一般用来存放指令操作数据的地址,而地址可以分成3种:

    • 寄存器id
    • 常量表id
    • upvalue id
    Lua使用当前函数的stack作为寄存器使用,寄存器id从0开始。当前函数的stack与寄存器数组是相同的概念。stack(n)其实就是register(n)。

    每一个函数prototype都有一个属于本函数的常量表,用于存放编译过程中函数所用到的常量。常量表可以存放nil,boolean,number和string类型的数据,id从1开始。

    每一个函数prototype中都有一个upvalue描述表,用于存放在编译过程中确定的本函数所使用的upvalue的描述。在运行期,通过OP_CLOSURE指令创建一个closure时,会根据prototype中的描述,为这个closure初始化upvalue表。upvalue本身不需要使用名称,而是通过id进行访问。

    A被大多数指令用来指定计算结果的目标寄存器地址。很多指令使用B或C同时存放寄存器地址和常量地址,并通过最左面的一个bit来区分。在指令生成阶段,如果B或C需要引用的常量地址超出了表示范围,则首先会生成指令将常量装载到临时寄存器,然后再将B或C改为使用该寄存器地址。

    在lopcodes.h中,对于每个指令,在源码注释中都有简单的操作描述。本文接下来将针对每一个指令做更详细的描述,并给出关于这个指令的示例代码。示例代码可以帮助我们构建出一个指令使用的具体上下文,有助于进一步理解指令的作用。对指令上下文的理解还可以作为进一步研究lua的编译和代码生成系统的基础。

    在分析过程中,我们使用luac来显示示例代码所生成的指令。luac的具体使用方式为:

    luac -l -l test.lua


    作者:yuanlin2008 发表于2013-1-11 18:11:34 原文链接
    阅读:43 评论:0 查看评论

        
    最新技术文章:
    ▪error while loading shared libraries的解決方法    ▪版本控制的极佳实践    ▪安装多个jdk,多个tomcat版本的冲突问题
    ▪简单选择排序算法    ▪国外 Android资源大集合 和个人学习android收藏    ▪.NET MVC 给loading数据加 ajax 等待loading效果
    ▪http代理工作原理(3)    ▪关注细节-TWaver Android    ▪Spring怎样把Bean实例暴露出来?
    ▪java写入excel2007的操作    ▪http代理工作原理(1)    ▪浅谈三层架构
    ▪http代理工作原理(2)    ▪解析三层架构……如何分层?    ▪linux PS命令
    mysql iis7站长之家
    ▪53个要点提高PHP编程效率    ▪linux僵尸进程    ▪java 序列化到mysql数据库中
    ▪利用ndk编译ffmpeg    ▪活用CSS巧妙解决超长文本内容显示问题    ▪通过DBMS_RANDOM得到随机
    ▪CodeSmith 使用教程(8): CodeTemplate对象    ▪android4.0 进程回收机制    ▪仿天猫首页-产品分类
    ▪从Samples中入门IOS开发(四)------ 基于socket的...    ▪工作趣事 之 重装服务器后的网站不能正常访...    ▪java序列化学习笔记
    ▪Office 2010下VBA Addressof的应用    ▪一起来学ASP.NET Ajax(二)之初识ASP.NET Ajax    ▪更改CentOS yum 源为163的源
    ▪ORACLE 常用表达式    ▪记录一下,AS3反射功能的实现方法    ▪u盘文件系统问题
    ▪java设计模式-观察者模式初探    ▪MANIFEST.MF格式总结    ▪Android 4.2 Wifi Display核心分析 (一)
    ▪Perl 正则表达式 记忆方法    ▪.NET MVC 给loading数据加 ajax 等待laoding效果    ▪java 类之访问权限
    ▪extjs在myeclipse提示    ▪xml不提示问题    ▪Android应用程序运行的性能设计
    ▪sharepoint 2010 自定义列表启用版本记录控制 如...    ▪解决UIScrollView截获touch事件的一个极其简单有...    ▪Chain of Responsibility -- 责任链模式
    ▪运行skyeye缺少libbfd-2.18.50.0.2.20071001.so问题    ▪sharepoint 2010 使用sharepoint脚本STSNavigate方法实...    ▪让javascript显原型!
    ▪kohana基本安装配置    ▪MVVM开发模式实例解析    ▪sharepoint 2010 设置pdf文件在浏览器中访问
    ▪spring+hibernate+事务    ▪MyEclipse中文乱码,编码格式设置,文件编码格...    ▪struts+spring+hibernate用jquery实现数据分页异步加...
    ▪windows平台c++开发"麻烦"总结    ▪Android Wifi几点    ▪Myeclipse中JDBC连接池的配置
    ▪优化后的冒泡排序算法    ▪elasticsearch RESTful搜索引擎-(java jest 使用[入门])...    ▪MyEclipse下安装SVN插件SubEclipse的方法
    ▪100个windows平台C++开发错误之七编程    ▪串口转以太网模块WIZ140SR/WIZ145SR 数据手册(版...    ▪初识XML(三)Schema
    ▪Deep Copy VS Shallow Copy    ▪iphone游戏开发之cocos2d (七) 自定义精灵类,实...    ▪100个windows平台C++开发错误之八编程
    ▪C++程序的内存布局    ▪将不确定变为确定系列~Linq的批量操作靠的住...    ▪DIV始终保持在浏览器中央,兼容各浏览器版本
    ▪Activity生命周期管理之三——Stopping或者Restarti...    ▪《C语言参悟之旅》-读书笔记(八)    ▪C++函数参数小结
    ▪android Content Provider详解九    ▪简单的图片无缝滚动效果    ▪required artifact is missing.
    ▪c++编程风格----读书笔记(1)    ▪codeforces round 160    ▪【Visual C++】游戏开发笔记四十 浅墨DirectX教程...
    ▪【D3D11游戏编程】学习笔记十八:模板缓冲区...    ▪codeforces 70D 动态凸包    ▪c++编程风格----读书笔记(2)
    ▪Android窗口管理服务WindowManagerService计算Activity...    ▪keytool 错误: java.io.FileNotFoundException: MyAndroidKey....    ▪《HTTP权威指南》读书笔记---缓存
    ▪markdown    ▪[设计模式]总结    ▪网站用户行为分析在用户市场领域的应用
     


    站内导航:


    特别声明:169IT网站部分信息来自互联网,如果侵犯您的权利,请及时告知,本站将立即删除!

    ©2012-2021,,E-mail:www_#163.com(请将#改为@)

    浙ICP备11055608号-3