文档章节

Lua1.1 虚拟机指令分析(一)

晓寒
 晓寒
发布于 2014/09/14 00:57
字数 1859
阅读 732
收藏 5

在语法分析 lua_parse 之后,调用 lua_execute 来执行语法分析生成的字节码。
虚拟机的指令是一个枚举型,就是在 opcode.h 中的 OpCode, 通过 lua_execute 中的那个 switch case 来看下指令对应的操作。
> PUSHNIL

   case PUSHNIL: tag(top++) = T_NIL; break;

  设置栈顶的 Object 类型为 T_NIL,这个栈就是之前所说的那个 Lua 和 C 之间交互的那个栈。

> PUSH0, PUSH1, PUSH2

   case PUSH0: tag(top) = T_NUMBER; nvalue(top++) = 0; break;
   case PUSH1: tag(top) = T_NUMBER; nvalue(top++) = 1; break;
   case PUSH2: tag(top) = T_NUMBER; nvalue(top++) = 2; break;

  设置栈顶的 Object 类型为 T_NUMBER,值为 0/1/2。 这个是指令优化,把操作数放到指令里,从而让指令更短,执行的更快些。
  这几个指令是 PUSHBYTE 指令的优化版,或者叫特化版更合适一些。

> PUSHBYTE

   case PUSHBYTE: tag(top) = T_NUMBER; nvalue(top++) = *pc++; break;

  设置栈顶的 Object 类型为 T_NUMBER,值从当前字节码处(也就是 pc 指针处的一个字节)取得。值在字节码中占一个字节。

> PUSHWORD

   case PUSHWORD:
   {
    CodeWord code;
    get_word(code,pc);
    tag(top) = T_NUMBER; nvalue(top++) = code.w;
   }
   break;

  设置栈顶的 Object 类型为 T_NUMBER,值从当前字节码处(也就是 pc 指针处的两个字节)取得。值在字节码中占两个字节。
  get_word 是 y.tab.c 中的 code_word 的相反过程,CodeWord 是个联合体

   typedef union
   {
    struct {char c1; char c2;} m;
    Word w;
   } CodeWord;

  在 code_word 方法

   static void code_word (Word n)
   {
    CodeWord code;
    code.w = n;
    code_byte(code.m.c1);
    code_byte(code.m.c2);
   }

  可以看到,设置的时候把值设置给 w, 之后调用 code_byte 分别对 w 的两个字节生成字节码。
  由于 CodeWord 是个联合体,这里的 w 和 m 是同一片内存,所以 code_word 可以按预期正确执行。
  这样的联合体操作是 C 语言里的一个小技巧,比如测字节序(例如你的处理器体系结构是大端序还是小端序)的时候就可以用这样的技巧。

> PUSHFLOAT

   case PUSHFLOAT:
   {
    CodeFloat code;
    get_float(code,pc);
    tag(top) = T_NUMBER; nvalue(top++) = code.f;
   }
   break;

  设置栈顶的 Object 类型为 T_NUMBER,值从当前字节码处(也就是 pc 指针处的四个字节)取得。值在字节码中占四个字节。
  get_float 和上面的 get_word 情况一样,不再重复。

> PUSHSTRING

   case PUSHSTRING:
   {
    CodeWord code;
    get_word(code,pc);
    tag(top) = T_STRING; svalue(top++) = lua_constant[code.w];
   }
   break;

  设置栈顶的 Object 类型为 T_STRING,值从常量表从取得,下标从当前字节码处取得。下标占两个字节。

> PUSHLOCAL0, PUSHLOCAL1, PUSHLOCAL2, PUSHLOCAL3, PUSHLOCAL4,
  PUSHLOCAL5, PUSHLOCAL6, PUSHLOCAL7, PUSHLOCAL8, PUSHLOCAL9,

   case PUSHLOCAL0: case PUSHLOCAL1: case PUSHLOCAL2:
   case PUSHLOCAL3: case PUSHLOCAL4: case PUSHLOCAL5:
   case PUSHLOCAL6: case PUSHLOCAL7: case PUSHLOCAL8:
   case PUSHLOCAL9: *top++ = *(base + (int)(opcode-PUSHLOCAL0)); break;

  设置栈顶的 Object 为局部变量 N (0<=N<=9),这个也是指令优化。把当前指令 opcode 减去 PUSHLOCAL0 取得偏移量 N。
  局部变量是从栈的 base 算起的偏移量,也可以理解为数组的下标。
  这几个指令是 PUSHLOCAL 的特化版。

> PUSHLOCAL

   case PUSHLOCAL: *top++ = *(base + (*pc++)); break;

  设置栈顶的 Object 为局部变量 N (N 的偏移量从字节码中取得)。

> PUSHGLOBAL,

   case PUSHGLOBAL:
   {
    CodeWord code;
    get_word(code,pc);
    *top++ = s_object(code.w);
   }
   break;

  设置栈顶的 Object 为全局变量 N (N 从全局符号表中取得,它的下标从字节码中取得,占两个字节)。

> PUSHINDEXED,

   case PUSHINDEXED:
    --top;
    if (tag(top-1) != T_ARRAY)
    {
     lua_reportbug ("indexed expression not a table");
     return 1;
    }
    {
     Object *h = lua_hashdefine (avalue(top-1), top);
     if (h == NULL) return 1;
     *(top-1) = *h;
    }
   break;

  设置数组元素索引,当前的栈的 top 处为要设置的元素索引,top-1 为数组。
  通过 lua_hashdefine 把 top 做为索引设置进数组,返回其所在键值对儿 Node 值 val 地址。
  设置到 top-1 处,以备后续使用。

> PUSHMARK

   case PUSHMARK: tag(top++) = T_MARK; break;

  设置栈顶的 Object 为 T_MARK,这个用于标记,比如在函数调用时会在函数入栈后再入栈一个 T_MARK。

> PUSHOBJECT

   case PUSHOBJECT: *top = *(top-3); top++; break;

  设置栈顶的 Object 为栈顶的倒数第 4 个 Object。
 

> STORELOCAL0, STORELOCAL1, STORELOCAL2, STORELOCAL3, STORELOCAL4,
  STORELOCAL5, STORELOCAL6, STORELOCAL7, STORELOCAL8, STORELOCAL9,

   case STORELOCAL0: case STORELOCAL1: case STORELOCAL2:
   case STORELOCAL3: case STORELOCAL4: case STORELOCAL5:
   case STORELOCAL6: case STORELOCAL7: case STORELOCAL8:
   case STORELOCAL9: *(base + (int)(opcode-STORELOCAL0)) = *(--top); break;

  设置局部变量 N (0<=N<=9)为栈顶的 Object,这个也是指令优化。把当前指令 opcode 减去 STORELOCAL0 取得偏移量 N。
  局部变量是从栈的 base 算起的偏移量,也可以理解为数组的下标。
  这几个指令是 STORELOCAL 的特化版。

> STORELOCAL

   case STORELOCAL: *(base + (*pc++)) = *(--top); break;

  设置局部变量 N (N 的偏移量从字节码中取得)为栈顶的 Object。

> STOREGLOBAL

   case STOREGLOBAL:
   {
    CodeWord code;
    get_word(code,pc);
    s_object(code.w) = *(--top);
   }
   break;

  设置全局变量 N (N 从全局符号表中取得,它的下标从字节码中取得,占两个字节)为栈顶的 Object。

> STOREINDEXED0

   case STOREINDEXED0:
    if (tag(top-3) != T_ARRAY)
    {
     lua_reportbug ("indexed expression not a table");
     return 1;
    }
    {
     Object *h = lua_hashdefine (avalue(top-3), top-2);
     if (h == NULL) return 1;
     *h = *(top-1);
    }
    top -= 3;
   break;

  设置数组元素,数组位于 top-3, 数组索引位于 top-2, 数组值位于 top-1。
  设置完成后,这三个 Object 出栈。

> STOREINDEXED

   case STOREINDEXED:
   {
    int n = *pc++;
    if (tag(top-3-n) != T_ARRAY)
    {
     lua_reportbug ("indexed expression not a table");
     return 1;
    }
    {
     Object *h = lua_hashdefine (avalue(top-3-n), top-2-n);
     if (h == NULL) return 1;
     *h = *(top-1);
    }
    top--;
   }
   break;

  设置指定偏移量的数组元素,偏移量从字节码中取得,数组位于 top-3-n,索引位于 top-2-n,值位于栈顶。
  设置完成后,栈顶值出栈。

> STORELIST0,
  STORELIST

   case STORELIST0:
   case STORELIST:
   {
    int m, n;
    Object *arr;
    if (opcode == STORELIST0) m = 0;
    else m = *(pc++) * FIELDS_PER_FLUSH;
    n = *(pc++);
    arr = top-n-1;
    if (tag(arr) != T_ARRAY)
    {
     lua_reportbug ("internal error - table expected");
     return 1;
    }
    while (n)
    {
     tag(top) = T_NUMBER; nvalue(top) = n+m;
     *(lua_hashdefine (avalue(arr), top)) = *(top-1);
     top--;
     n--;
    }
   }
   break;

  设置数组值,m 为下标,n 为数组元素个数。要设置的数组值都位于栈上,栈顶为最后一个元素。
  所以在 while 循环里给数组赋值是先给下标大的赋值,再给小的赋值,每赋值一个就出栈一个。

> STORERECORD

   case STORERECORD:
   {
    int n = *(pc++);
    Object *arr = top-n-1;
    if (tag(arr) != T_ARRAY)
    {
     lua_reportbug ("internal error - table expected");
     return 1;
    }
    while (n)
    {
     CodeWord code;
     get_word(code,pc);
     tag(top) = T_STRING; svalue(top) = lua_constant[code.w];
     *(lua_hashdefine (avalue(arr), top)) = *(top-1);
     top--;
     n--;
    }
   }
   break;

  给记录赋值,n 为要赋值的个数。被赋值的元素索引从字节码中取得,右值从栈上取得,赋值后出栈。
  记录是指下标为常量字符串的表,数组是指下标为整数的表。具体的区别请参见手册。

> ADJUST

   case ADJUST:
   {
    Object *newtop = base + *(pc++);
    while (top < newtop) tag(top++) = T_NIL;
    top = newtop; /* top could be bigger than newtop */
   }
   break;

  调整栈元素个数,元素个数从字节码取出。如果新的栈顶高于当前栈顶,当前栈顶到新的栈顶之间的元素赋空。
  一般函数调用之后会调用它调整栈。 ADJUST 的下一个字节码(pc)是需要返回的函数返回值个数。
  while 补空的意义就是如果需要返回的函数个数大于实际返回的,则补空。
  (反之,需要的返回值少于实际返回的个数的话,多余的会被丢弃,这是通过设置 top 实现的。)

> CREATEARRAY

   case CREATEARRAY:
    if (tag(top-1) == T_NIL)
     nvalue(top-1) = 101;
    else
    {
     if (tonumber(top-1)) return 1;
     if (nvalue(top-1) <= 0) nvalue(top-1) = 101;
    }
    avalue(top-1) = lua_createarray(nvalue(top-1));
    if (avalue(top-1) == NULL)
     return 1;
    tag(top-1) = T_ARRAY;
   break;

  新建数组,元素个数从栈顶取得,如果没有指定或者指定一个负数,把它修正为 101。如果栈顶不是个数字,出错。
  新建数组,赋值给栈顶元素,并设置栈顶 Object 类型为 T_ARRAY。
(未完待续)

© 著作权归作者所有

晓寒
粉丝 35
博文 119
码字总数 133745
作品 0
海淀
私信 提问
Lua1.1 公开发布的第一版

Lua1.1 是官方公开发布的第一版,是事实上的第一版 ,也是最早发布的一版。 代码从这里 www.lua.org/ftp/lua-1.1.tar.gz 下载,事实上在 www.lua.org/versions.html 页面,有所有的可以下下载...

晓寒
2014/09/02
333
1
Lua1.1 Lua 的设计和实现 (二)

(接上篇) -------------------------------------- 实现 -------------------------------------- 扩展语言总是由应用程序以某种方式解释执行的。简单的扩展语言可以直接从源代码进行解释执...

晓寒
2014/09/03
1K
0
Lua1.1 虚拟机指令分析(二)

(接上篇) > EQOP case EQOP: { Object *l = top-2; Object *r = top-1; --top; if (tag(l) != tag(r)) tag(top-1) = T_NIL; else { switch (tag(l)) { case TNIL: tag(top-1) = TNUMBER; b......

晓寒
2014/09/15
144
0
使用 Antlr 开发领域语言 - 开发一个完整的应用

简介: 为了使应用系统能够快速地响应复杂多变的业务规则,通过让用户使用领域语言来自定义业务规则是一种常用的选 择。 然而实现领域语言并非易事,本文使用 Antlr 语言识别工具,从语言定义...

IBMdW
2011/11/03
2.7K
0
Lua1.0 代码分析 opcode.c

opcode.c 代码分析 Lua1.0 虚拟机的实现,语法分析中生成的字节码交给它 luaexecute 来执行。 这个文件的主要部分就是 luaexecute 函数,而它就是很大的 switch case,Lua1.0 中定义的字节码...

晓寒
2014/08/31
473
0

没有更多内容

加载失败,请刷新页面

加载更多

web前端开发高级

前端高效开发框架技术与应用 Vue 基础 Vue 框架简介 MVX 模式介绍 Vue 框架概述 如何使用 Vue.js 基础语法 实例对象 生命周期 模板语法 计算属性 Methods 方法 渲染 列表渲染 条件渲染 事件与...

达达前端小酒馆
31分钟前
5
0
PostgreSQL 11.3 locking

rudi
今天
5
0
Mybatis Plus sql注入器

一、继承AbstractMethod /** * @author beth * @data 2019-10-23 20:39 */public class DeleteAllMethod extends AbstractMethod { @Override public MappedStatement injectMap......

一个yuanbeth
今天
21
1
一次写shell脚本的经历记录——特殊字符惹的祸

本文首发于微信公众号“我的小碗汤”,扫码文末二维码即可关注,欢迎一起交流! redis在容器化的过程中,涉及到纵向扩pod实例cpu、内存以及redis实例的maxmemory值,statefulset管理的pod需要...

码农实战
今天
4
0
为什么阿里巴巴Java开发手册中不建议在循环体中使用+进行字符串拼接?

之前在阅读《阿里巴巴Java开发手册》时,发现有一条是关于循环体中字符串拼接的建议,具体内容如下: 那么我们首先来用例子来看看在循环体中用 + 或者用 StringBuilder 进行字符串拼接的效率...

武培轩
今天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部