LuaJIT SSA IR介绍

一、IR基本语法

1.1 IR 概要

LuaJIT资料挺少,可以一手学习的基本上只有官方文档:http://wiki.luajit.org/SSA-IR-2.0,但是目前这个文档的介绍被作者删了。在文档被删之前,我对官网上的部分内容做了记录,基本上是直接翻译,没有加入太多自己理解的解释。等后面我想优化了,再把这一块内容好好优化一些,官文介绍的非常简单,不够深入。

偶尔发现了一个Tarantool的wiki中保存了LuaJIT-SSA-IR,可以作为对官方文档删除的补充。

LuaJIT的JIT模式下使用的IR具有以下特点:

  • 1)IR是SSA(静态单赋值,是一种变量的命名约定,这玩意主要为了方便IR优化而提出的)形式,每条指令都会定义一值(一般都是指令的结果值),多条指令构成数据流图,循环数据流用PHI指令表示。
  • 2)IR指令都可以通过其在线性数组中的位置被唯一引用(IRRef),biased IR可以快速确定const或非const的IRRef(biased IR references allow fast const vs. non-const decisions.)。
  • 3)IR指令都有一个操作码opcode,一个或两个操作数operands。
  • 4)IR指令都有用于定义指令结果值的类型。
  • 5)IR指令可使用操作码和出现过的指令链接,这样则可以反向查找一条指令依赖的所有指令而无需遍历,也方便IR优化。
  • 6)IR一条指令占64位,指令间彼此相邻,这种布局缓存高效,索引和遍历快速。
    在这里插入图片描述
  • 7)IR 存放指令的数组是双向增长的:常量向下增长,所有其他指令向上增长。
  • 8)IR快照,快照捕获bytecode执行栈中modified slots和frames的引用(IRref),每个快照都保存一个特定的bytecod执行状态,以后可以在跟踪退出时恢复。快照也提供了IR和字节码域之间的链接。

1.2 Example IR Dump

在使用LuaJIT时,加上参数-jdump,便可以出现如下格式的代码,下面代码分别是source codeByte codeSSA IR,以及目标机器对应的MCode

$ luajit-2.1.0-beta3 -jdump
LuaJIT 2.1.0-beta3 -- Copyright (C) 2005-2021 Mike Pall. https://luajit.org/
JIT: ON MIPS64R2 fold cse dce fwd dse narrow loop abc sink fuse
> local x = 1.2 for i=1,1e3 do x = x * -3 end
---- TRACE 1 start stdin:1 //
0006  MULVN    0   0   1  ; -3
0007  FORL     1 => 0006
---- TRACE 1 IR
0001    int SLOAD  #3    I
0002 >  num SLOAD  #2    T
0003  + num MUL    0002  -3  
0004  + int ADD    0001  +1  
0005 >  int LE     0004  +1000
0006 ------ LOOP ------------
0007  + num MUL    0003  -3  
0008  + int ADD    0004  +1  
0009 >  int LE     0008  +1000
0010    int PHI    0004  0008
0011    num PHI    0003  0007
---- TRACE 1 mcode 80
// Generate machine instructions, different for each architecture
---- TRACE 1 stop -> loop

针对上面SSA IR作如下解释,其中“文档”指的是官方文档

  • 第1列:IR指令编号,可以作为SSA ref,也就是说,若有指令想要获取当前指令产生的值(如PHI指令),就可以直接ref当前指令的编号到其opt。

  • 第2列:指令标志:
    ">" (IRT_GUARD = 0x80指令标志)有该标志的指令可以理解为是一个分叉指令,可能会是trace路径的出口。该标志一般在关系运算指令前面(见文档Guarded Assertions)。
    "+" (IRT_ISPHI = 0x40指令标志)有该标志的指令可能会是PHI指令的左或右操作数。PHI指令在循环trace的末尾,有两个操作数,分别引用同一变量在循环期间不同时间段的值,左操作数是对初始值的引用,右操作数是对每次循环迭代后该值的引用

  • 第3列:IR类型:int类型指的是32 bit signed integernum类型指的是double(见文档IR Types)。

  • 第4列:IR操作码,主要有以下分类(见文档各种操作码)

    Constants
    Guarded Assertions
    Bit Ops(位操作)
    Arithmetic Ops(算数操作)
    Memory References(内存引用,返回一个指针,供Loads and Stores操作使用)
    Loads and Stores
    Allocations
    Barriers
    Type Conversions(类型转换)
    Calls(函数调用操作码)
    Miscellaneous Ops(其他操作码)
    
  • 第5/6列:IR操作数(SSA ref或字面量)
    '#'+数字,表示该数字是一个stack slot number,可以理解成为对指定栈内存的引用,被用于SLOAD指令的做操作数。
    #0,函数栈帧。
    #1,is the first slot in the first frame (register 0 in the bytecode)。
    '[+-]'+字面量,表示字面量的正负值。
    '[0x%d+]'NULL是内存地址。
    ’”……“是字符串。
    '@' prefixes indicate slots (what is this?).
    Other possible values: "bias" (number 2^52+2^51 ?), "userdata:%p",
    "userdata:%p" (table)–when do these occur?.

1.3 IR类型和常量

IR类型:

IR的每条指令都有用于定义指令结果值的类型,总共有23个,这些类型对应Lua的数据类型或low-level(更适合目标机器的低级类型)类型,源码中关于类型的定义在src/lj_ir.hsrc/lj_obj.h中。

#define IRTDEF(_) \
  _(NIL, 4) _(FALSE, 4) _(TRUE, 4) _(LIGHTUD, LJ_64 ? 8 : 4) \
  _(STR, IRTSIZE_PGC) _(P32, 4) _(THREAD, IRTSIZE_PGC) _(PROTO, IRTSIZE_PGC) \
  _(FUNC, IRTSIZE_PGC) _(P64, 8) _(CDATA, IRTSIZE_PGC) _(TAB, IRTSIZE_PGC) \
  _(UDATA, IRTSIZE_PGC) \
  _(FLOAT, 4) _(NUM, 8) _(I8, 1) _(U8, 1) _(I16, 2) _(U16, 2) \
  _(INT, 4) _(U32, 4) _(I64, 8) _(U64, 8) \
  _(SOFTFP, 4)  /* There is room for 8 more types. */

常量:

常量指令只用于常数操作,IR常量是interned(去重),并且只能通过查看它们的引用(IRref)来比较它们的相等性。常量指令不会出现在转储(dump)中,因为-jdump只显示内联到引用指令中的实际常量值,如0005 > int LE 0004 +1000

32位整数或指针值占用左右两边(共32位)各16位操作数的空间,64位值在一个全局常量表中,并由32位指针间接引用(通过IRref)。

  _(KPRI,       N , ___, ___) \
  _(KINT,       N , cst, ___) \
  _(KGC,        N , cst, ___) \
  _(KPTR,       N , cst, ___) \
  _(KKPTR,      N , cst, ___) \
  _(KNULL,      N , cst, ___) \
  _(KNUM,       N , cst, ___) \
  _(KINT64,     N , cst, ___) \
  _(KSLOT,      N , ref, lit) \

1.4 Guarded Assertions

Guarded assertions也就是条件判断指令,有两个作用:

  • a.提供有关其操作数的断言,编译器可以使用该断言来优化同一跟踪中的所有后续指令。
  • b.被后端用来作为分支比较的发射(emit),比较结果若为true,则继续执行后续指令;结果若为false,则退出trace并恢复到最近的快照。

RETF指令返回的原型(prototype)位于跟踪所覆盖的调用图之下,因此RETF需要锚定(anchor)原型以防止GC回收PC。

  /* Guarded assertions. */ \
  /* Must be properly aligned to flip opposites (^1) and (un)ordered (^4). */ \
  _(LT,         N , ref, ref) \
  _(GE,         N , ref, ref) \
  _(LE,         N , ref, ref) \
  _(GT,         N , ref, ref) \
  \
  _(ULT,        N , ref, ref) \
  _(UGE,        N , ref, ref) \
  _(ULE,        N , ref, ref) \
  _(UGT,        N , ref, ref) \
  \
  _(EQ,         C , ref, ref) \
  _(NE,         C , ref, ref) \
  \
  _(ABC,        N , ref, ref) \
  _(RETF,       S , ref, ref) \

1.5 位运算和算数运算

位运算:

位运算中需要注意的有一个循环左移(left rotate)和循环右移(right rotate),如循环右移:BROR abcdefg 3 => efgabcde

  /* Bit ops. */ \
  _(BNOT,       N , ref, ___) \
  _(BSWAP,      N , ref, ___) \
  _(BAND,       C , ref, ref) \
  _(BOR,        C , ref, ref) \
  _(BXOR,       C , ref, ref) \
  _(BSHL,       N , ref, ref) \
  _(BSHR,       N , ref, ref) \
  _(BSAR,       N , ref, ref) \
  _(BROL,       N , ref, ref) \
  _(BROR,       N , ref, ref) \

算数运算:

算数运算需要注意的则是数据溢出,ADDOVSUBOVMULOV指令均可以在运算时做溢出检查,有符号整数溢出一旦被检查到,就会推出trace。

浮点运算只有一条指令FPMATH ref #fpmref就是操作数,fpm则是被调函数fpmath(ref),被调函数总共有12个。算数运算指令总共有16条。

  /* Arithmetic ops. ORDER ARITH */ \
  _(ADD,        C , ref, ref) \
  _(SUB,        N , ref, ref) \
  _(MUL,        C , ref, ref) \
  _(DIV,        N , ref, ref) \
  _(MOD,        N , ref, ref) \
  _(POW,        N , ref, ref) \
  _(NEG,        N , ref, ref) \
  \
  _(ABS,        N , ref, ref) \
  _(LDEXP,      N , ref, ref) \
  _(MIN,        C , ref, ref) \
  _(MAX,        C , ref, ref) \
  _(FPMATH,     N , ref, lit) \
  \
  /* Overflow-checking arithmetic ops. */ \
  _(ADDOV,      CW, ref, ref) \
  _(SUBOV,      NW, ref, ref) \
  _(MULOV,      CW, ref, ref) \

1.6 内存引用和分配

内存引用:

内存引用(Memory references)会生成一个指针值,供各自对应的load和store使用,为了保留更高级别的语义并简化别名分析,内存引用不会分解为较低级别的操作(如 XLOAD 引用),其中一部分可以融合到load或store指令的操作数中。

  • AREFHREFK 指令的左操作数由FLOAD指令产生的table的array(tab.array)或hash(tab.hash),AREF右操作数是index(int类型),HREFK 右操作数可能是SLOAD指令从stack中取出的str,也可能是str字面量;AREF产生的值则供ALOADASTORE使用,HREFK产生的值则供HLOADHSTORE使用,如:
    0038    p64  FLOAD  0037  tab.array
    0039    p64  AREF   0038  +1
    0040    str  ASTORE 0039  0035
  • HREFNEWREF则直接使用table(可能是FLOAD指令产生的table.node,也可能是SLOAD指令从stack中取出的table,也可能是其他table)作为左操作数;HREFNEWREF的右操作数为一个str常量,这个常量可能是slot中取出的值(SLOAD从stack中取出),也可能是字符串字面量。

内存引用指令总共有8条。

  /* Memory ops. A = array, H = hash, U = upvalue, F = field, S = stack. */ \
  \
  /* Memory references. */ \
  _(AREF,       R , ref, ref) \
  _(HREFK,      R , ref, ref) \
  _(HREF,       L , ref, ref) \
  _(NEWREF,     S , ref, ref) \
  _(UREFO,      LW, ref, lit) \
  _(UREFC,      LW, ref, lit) \
  _(FREF,       R , ref, lit) \
  _(TMPREF,     S , ref, lit) \
  _(STRREF,     N , ref, ref) \
  _(LREF,       L , ___, ___) \

内存分配:

SNEW仅适应于不会变化的数据,如字符串常量,这允许在字符串对象不使用的情况下消除分配(它的数据可能仍然被使用)。

CNEWCNEWI分配的cdata对象的大小是从ctypeid操作数推断出来的,对于变长cdata,其大小由size操作数显式给出,否则size操作数为REF_NIL。

内存分配的指令总共有6条。

  /* Allocations. */ \
  _(SNEW,       N , ref, ref)  /* CSE is ok, not marked as A. */ \
  _(XSNEW,      A , ref, ref) \
  _(TNEW,       AW, lit, lit) \
  _(TDUP,       AW, ref, ___) \
  _(CNEW,       AW, ref, ref) \
  _(CNEWI,      NW, ref, ref)  /* CSE is ok, not marked as A. */ \

1.7 Loads and Stores

loadsstores的指令总共有12条。

  /* Loads and Stores. These must be in the same order. */ \
  _(ALOAD,      L , ref, ___) \
  _(HLOAD,      L , ref, ___) \
  _(ULOAD,      L , ref, ___) \
  _(FLOAD,      L , ref, lit) \
  _(XLOAD,      L , ref, lit) \
  _(SLOAD,      L , lit, lit) \
  _(VLOAD,      L , ref, lit) \
  _(ALEN,       L , ref, ref) \
  \
  _(ASTORE,     S , ref, ref) \
  _(HSTORE,     S , ref, ref) \
  _(USTORE,     S , ref, ref) \
  _(FSTORE,     S , ref, ref) \
  _(XSTORE,     S , ref, ref) \

loads和stores对内存引用进行操作,load一个值(指令的结果)或store一个值(右操作数),为了保持较高层次的语义并简化别名分析,它们没有统一或分解为较低层次的操作。loads和stores很重要,这里详细解释以下:

  • a)FLOADSLOAD内嵌它们的内存引用(dump对比这两条指令与其他指令的区别),其他所有的load和的store都有一个内存引用作为它们的左操作数。除了FLOADXLOAD之外,其他所有load都处理tagged values ,且都具有guarded assertion功能,检查load出来的数据的类型,如果类型检查不匹配,则退出trace并恢复到最近的快照。
  • b)ALOADASTORE指令用于load或store一个array,左操作数是内存引用AREF指令产生的p64指针值。
  • c)HLOADHSTORE用于load或store一个hash,左操作数是内存引用HREFK产生的p64指针值。
  • d)ULOADUSTORE用于load或store一个upvalue,左操作数是内存引用UREFC产生的p64指针值。
  • e)FLOADFSTORE访问复合类型对象中的指定字段,左操作数是复合类型对象的IRref,右操作数是字段id(str字面量或stack slot number)。
  • f)XLOAD 适用于较低级别的类型,内存引用要么是 STRREF,要么分解为较低级别的操作,要么是一个ADDMULBSHL指针、偏移量或索引的组合。
  • g)SLOAD 的slot number与trace的起始栈帧(frame)相关,其中#0表示闭包/帧槽(frame slot),#1表示第一个可变slot(对应bytecode的slot 0)。RETFBASE 向下移动,随后的 SLOAD 指令指的是较低帧的插槽。
  • h)stack slots和vararg slots不支持存储操作,也就是只能load不能store。 All stores to stack slots are effectively sunk into exits or side traces.

1.8 类型转换和Calls

类型转换:

数据类型转化主要有整数和浮点数之间,及数字与字符串之间转换,共有4条。

  /* Type conversions. */ \
  _(CONV,       N , ref, lit) \
  _(TOBIT,      N , ref, ref) \
  _(TOSTR,      N , ref, lit) \
  _(STRTO,      N , ref, ___) \

Calls:

对内部函数的调用分为几类:

  • a)被调函数比较简单,只是简单的算数运算,可消除调用,用CALLN
  • b)对于执行load的调用,编译器会检查其中的store,用CALLL
  • c)对于执行store的调用,不会被消除,用CALLS
  • d)ffi调用使用CALLXS

Dump的代码如下,第一个操作数是函数名,第二个操作数是参数列表:

int CALLN  lj_str_cmp  (0019 0022)
p64 CALLL  lj_strfmt_putfnum  (0006 +53  0004)
num CALLS  lj_prng_u64d  ([0xfff6b1b7d8])
p64 CALLXS [0xfff6bbf6c0](0012 +32 )

函数调用总共5条指令。

  /* Calls. */ \
  _(CALLN,      NW, ref, lit) \
  _(CALLA,      AW, ref, lit) \
  _(CALLL,      LW, ref, lit) \
  _(CALLS,      S , ref, lit) \
  _(CALLXS,     S , ref, ref) \
  _(CARG,       N , ref, ref) \

1.9 其他

这部分指令有几条很重要,需要理解,源码中经常出现:

  • a)BASE是位于REF_BASE的一条固定指令,用于保存BASE指针,被一些指令隐式引用,比如SLOAD
  • b)PHI指令位于一个循环trace的末端,左操作数保存对初始值的引用,右操作数保存对每次循环迭代后的值的引用。PHI就是为了解决SSA带来的麻烦的,看如下IR代码:
    ---- TRACE 1 IR
    0001 >  int SLOAD  #3    T
    0002 >  int SLOAD  #2    T
    0003 >+ int ADDOV  0002  0001
    0004 >+ int SUBOV  0002  +1  
    0005 >  int GT     0004  +1  
    0006 ------ LOOP ------------
    0007 >+ int ADDOV  0004  0003
    0008 >+ int SUBOV  0004  +1  
    0009 >  int GT     0008  +1  
    0010    int PHI    0003  0007
    0011    int PHI    0004  0008
    
    可以将上述代码段划分为3个部分:循环之前必须执行的部分,循环部分,以及PHI部分。如果循环部分没有执行,则0010 = 0003; 0011 = 0004;,如果循环部分执行,则0010= 0007; 0011 = 0008;。可参考LLVM IR PHI指令理解。
  • c)HIOP指令在支持64位的32位机器上会使用到,拆分64位为hiword和lowrd,在soft-fp(软浮点)好像也会用到。
  • d)LOOP指令,只是起到了分隔循环体和循环之前内容的作用。
  • e)当寄存器分配器想要为一个value重命名一个寄存器时(为了提高效率或保留PHI寄存器),就会生成RENAMERENAME指令保存了在给定快照下用于引用的寄存器,相同的引用值可能会有不同的寄存器,如0012 => r14, 0012 => r15,最初引用的指令保存着上面使用的最高快照(如果有的话)的寄存器。

这部分指令总共有9条,选经常遇到的解释一下,需要查询指令语法时请阅读官方文档 Miscellaneous Ops

二、IR在luaJit源码中的定义

luaJit对于IR结构的定义主要在 lj_ir.hlj_ircall.h 这两个个文件,IR emitter主要在 lj_ir.c 文件。以下是lj_ir.h文件,IR基本指令的定义:

1)IR instructions(操作码)

151 /* IR opcodes (max. 256). */
152 typedef enum {
153 #define IRENUM(name, m, m1, m2) IR_##name,
154 IRDEF(IRENUM)              
155 #undef IRENUM              
156   IR__MAX
157 } IROp;

IROp是对IR操作码的枚举定义,需要注意的是每个操作码的枚举顺序很重要(顺序决定了其值),如IR_EQ的值为8IR_NE的值为9,这样则可以通过表达式 (int)IR_EQ^1) == (int)IR_NE 在两者之间相互转换,类似的表达式还有如下:

162 LJ_STATIC_ASSERT(((int)IR_EQ^1) == (int)IR_NE);
163 LJ_STATIC_ASSERT(((int)IR_LT^1) == (int)IR_GE);
164 LJ_STATIC_ASSERT(((int)IR_LE^1) == (int)IR_GT);
165 LJ_STATIC_ASSERT(((int)IR_LT^3) == (int)IR_GT);
166 LJ_STATIC_ASSERT(((int)IR_LT^4) == (int)IR_ULT);
167 
168 /* Delta between xLOAD and xSTORE. */
169 #define IRDELTA_L2S     ((int)IR_ASTORE - (int)IR_ALOAD)
170 
171 LJ_STATIC_ASSERT((int)IR_HLOAD + IRDELTA_L2S == (int)IR_HSTORE);
172 LJ_STATIC_ASSERT((int)IR_ULOAD + IRDELTA_L2S == (int)IR_USTORE);
173 LJ_STATIC_ASSERT((int)IR_FLOAD + IRDELTA_L2S == (int)IR_FSTORE);
174 LJ_STATIC_ASSERT((int)IR_XLOAD + IRDELTA_L2S == (int)IR_XSTORE);

2)Named IR literals

IR使用到的一些name字面量,比如某个字面值代表那个函数等

184 typedef enum {             
185 #define FPMENUM(name)       IRFPM_##name,
186 IRFPMDEF(FPMENUM)          
187 #undef FPMENUM             
188   IRFPM__MAX
189 } IRFPMathOp; 

IRFPMathOp是对 FPMATH操作码 操作数的定义,FPMATH操作码操作码被用来一元浮点运算,其中的浮点运算对应关系如下:

OPDescription
FPM_FLOORfloor(ref)
FPM_CEILceil(ref)
FPM_TRUNCtrunc(ref)
FPM_SQRTsqrt(ref)
FPM_EXPexp(ref)
FPM_EXP2exp2(ref)
FPM_LOGlog(ref)
FPM_LOG2log2(ref)
FPM_LOG10log10(ref)
FPM_SINsin(ref)
FPM_COScos(ref)
FPM_TANtan(ref)

IRFieldID是对 FLOADFREF操作码 的fields的定义:

213 typedef enum {
214 #define FLENUM(name, ofs)   IRFL_##name,
215 IRFLDEF(FLENUM)
216 #undef FLENUM
217   IRFL__MAX
218 } IRFieldID;

下面这些定义分别对应的是表格中操作码的#flags,如下:

220 /* SLOAD mode bits, stored in op2. */
221 #define IRSLOAD_PARENT      0x01    /* Coalesce with parent trace. */
222 #define IRSLOAD_FRAME       0x02    /* Load 32 bits of ftsz. */
223 #define IRSLOAD_TYPECHECK   0x04    /* Needs type check. */
224 #define IRSLOAD_CONVERT     0x08    /* Number to integer conversion. */
225 #define IRSLOAD_READONLY    0x10    /* Read-only, omit slot store. */
226 #define IRSLOAD_INHERIT     0x20    /* Inherited by exits/side traces. */
227 
228 /* XLOAD mode, stored in op2. */
229 #define IRXLOAD_READONLY    1   /* Load from read-only data. */
230 #define IRXLOAD_VOLATILE    2   /* Load from volatile data. */
231 #define IRXLOAD_UNALIGNED   4   /* Unaligned load. */

237 /* CONV mode, stored in op2. */
238 #define IRCONV_SRCMASK      0x001f  /* Source IRType. */
239 #define IRCONV_DSTMASK      0x03e0  /* Dest. IRType (also in ir->t). */
240 #define IRCONV_DSH      5
241 #define IRCONV_NUM_INT      ((IRT_NUM<<IRCONV_DSH)|IRT_INT)
242 #define IRCONV_INT_NUM      ((IRT_INT<<IRCONV_DSH)|IRT_NUM)
243 #define IRCONV_SEXT     0x0800  /* Sign-extend integer to integer. */
244 #define IRCONV_MODEMASK     0x0fff
245 #define IRCONV_CONVMASK     0xf000
246 #define IRCONV_CSH      12
247 /* Number to integer conversion mode. Ordered by strength of the checks. */
248 #define IRCONV_TOBIT  (0<<IRCONV_CSH)   /* None. Cache only: TOBIT conv. */
249 #define IRCONV_ANY    (1<<IRCONV_CSH)   /* Any FP number is ok. */
250 #define IRCONV_INDEX  (2<<IRCONV_CSH)   /* Check + special backprop rules. */
251 #define IRCONV_CHECK  (3<<IRCONV_CSH)   /* Number checked for integerness. */
OPLeftRightDescription
XLOADxref#flagsExtended load
SLOAD#slot#flagsStack slot load
CONVsrc#flagsGeneric type conversion

还有少许其他特殊操作码的定义,可以自行查找对应。

3)IR operands,IR 操作数

以下是对IR操作数的定义,其中IRMode为操作数的kind,分为了四种:

260 /* IR operand mode (2 bit). */
261 typedef enum {
262   IRMref,       /* IR reference. */
263   IRMlit,       /* 16 bit unsigned literal. */
264   IRMcst,       /* Constant literal: i, gcr or ptr. */
265   IRMnone       /* Unused operand. */
266 } IRMode;
267 #define IRM___      IRMnone

4)IR instruction types,IR 指令类型

每个IR指令的都有一个输出结果的类型,IRType则是对其的定义(8 bit)

314 /* IR result type and flags (8 bit). */
315 typedef enum {
316 #define IRTENUM(name, size) IRT_##name,
317 IRTDEF(IRTENUM)
318 #undef IRTENUM
319   IRT__MAX,
320 
321   /* Native pointer type and the corresponding integer type. */
322   IRT_PTR = LJ_64 ? IRT_P64 : IRT_P32,
323   IRT_PGC = LJ_GC64 ? IRT_P64 : IRT_P32,
324   IRT_IGC = LJ_GC64 ? IRT_I64 : IRT_INT,
325   IRT_INTP = LJ_64 ? IRT_I64 : IRT_INT,
326   IRT_UINTP = LJ_64 ? IRT_U64 : IRT_U32,
327 
328   /* Additional flags. */
329   IRT_MARK = 0x20,  /* Marker for misc. purposes. */
330   IRT_ISPHI = 0x40, /* Instruction is left or right PHI operand. */
331   IRT_GUARD = 0x80, /* Instruction is a guard. */
332 
333   /* Masks. */
334   IRT_TYPE = 0x1f,
335   IRT_T = 0xff
336 } IRType;

这里还定义了两个函数,itypeTValuelj_obj.h

  • itype2irt :将itype转成IRType
  • irt_toitype_IRType转成itype

5)IR references

Tagged IR references都有一个IRType,也就是下面的irt,这样方便快速IR type checks。irt is 8 bitflags is 8 bitref is 16 bit

446 /* Fixed references. */
447 enum {
448   REF_BIAS =    0x8000,
449   REF_TRUE =    REF_BIAS-3,
450   REF_FALSE =   REF_BIAS-2,
451   REF_NIL = REF_BIAS-1, /* \--- Constants grow downwards. */
452   REF_BASE =    REF_BIAS,   /* /--- IR grows upwards. */
453   REF_FIRST =   REF_BIAS+1,
454   REF_DROP =    0xffff
455 };
456 

471 /* Tagged IR references (32 bit).
472 **
473 ** +-------+-------+---------------+
474 ** |  irt  | flags |      ref      |
475 ** +-------+-------+---------------+
476 */

6)IR format

524 /* IR instruction format (64 bit).
525 **
526 **    16      16     8   8   8   8
527 ** +-------+-------+---+---+---+---+
528 ** |  op1  |  op2  | t | o | r | s |
529 ** +-------+-------+---+---+---+---+
530 ** |  op12/i/gco32 |   ot  | prev  | (alternative fields in union)
531 ** +-------+-------+---+---+---+---+
532 ** |  TValue/gco64                 | (2nd IR slot for 64 bit constants)
533 ** +---------------+-------+-------+
534 **        32           16      16
535 **
536 ** prev is only valid prior to register allocation and then reused for r + s.
537 */

539 typedef union IRIns {
540   struct {
541     LJ_ENDIAN_LOHI(
542       IRRef1 op1;   /* IR operand 1. */
543     , IRRef1 op2;   /* IR operand 2. */
544     )
545     IROpT ot;       /* IR opcode and type (overlaps t and o). */
546     IRRef1 prev;    /* Previous ins in same chain (overlaps r and s). */
547   };
548   struct {
549     IRRef2 op12;    /* IR operand 1 and 2 (overlaps op1 and op2). */
550     LJ_ENDIAN_LOHI(
551       IRType1 t;    /* IR type. */
552     , IROp1 o;      /* IR opcode. */
553     )
554     LJ_ENDIAN_LOHI(
555       uint8_t r;    /* Register allocation (overlaps prev). */
556     , uint8_t s;    /* Spill slot allocation (overlaps prev). */
557     )
558   };
559   int32_t i;        /* 32 bit signed integer literal (overlaps op12). */
560   GCRef gcr;        /* GCobj constant (overlaps op12 or entire slot). */
561   MRef ptr;     /* Pointer constant (overlaps op12 or entire slot). */
562   TValue tv;        /* TValue constant (overlaps entire slot). */
563 } IRIns;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yelvens

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值