文章目录
一、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 code
,Byte code
,SSA 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 integer
,num
类型指的是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.h
和src/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) \
算数运算:
算数运算需要注意的则是数据溢出,ADDOV
、SUBOV
、MULOV
指令均可以在运算时做溢出检查,有符号整数溢出一旦被检查到,就会推出trace。
浮点运算只有一条指令FPMATH ref #fpm
,ref
就是操作数,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指令的操作数中。
AREF
和HREFK
指令的左操作数由FLOAD
指令产生的table的array(tab.array
)或hash(tab.hash
),AREF
右操作数是index(int类型),HREFK
右操作数可能是SLOAD
指令从stack中取出的str,也可能是str字面量;AREF
产生的值则供ALOAD
或ASTORE
使用,HREFK
产生的值则供HLOAD
或HSTORE
使用,如:0038 p64 FLOAD 0037 tab.array 0039 p64 AREF 0038 +1 0040 str ASTORE 0039 0035。
HREF
和NEWREF
则直接使用table(可能是FLOAD指令产生的table.node
,也可能是SLOAD
指令从stack中取出的table,也可能是其他table)作为左操作数;HREF
和NEWREF
的右操作数为一个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
仅适应于不会变化的数据,如字符串常量,这允许在字符串对象不使用的情况下消除分配(它的数据可能仍然被使用)。
CNEW
和CNEWI
分配的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
loads
和stores
的指令总共有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)
FLOAD
和SLOAD
内嵌它们的内存引用(dump对比这两条指令与其他指令的区别),其他所有的load和的store都有一个内存引用作为它们的左操作数。除了FLOAD
和XLOAD
之外,其他所有load都处理tagged values ,且都具有guarded assertion功能,检查load出来的数据的类型,如果类型检查不匹配,则退出trace并恢复到最近的快照。 - b)
ALOAD
和ASTORE
指令用于load或store一个array
,左操作数是内存引用AREF指令产生的p64
指针值。 - c)
HLOAD
和HSTORE
用于load或store一个hash
,左操作数是内存引用HREFK产生的p64
指针值。 - d)
ULOAD
和USTORE
用于load或store一个upvalue
,左操作数是内存引用UREFC
产生的p64
指针值。 - e)
FLOAD
和FSTORE
访问复合类型对象中的指定字段,左操作数是复合类型对象的IRref
,右操作数是字段id
(str字面量或stack slot number)。 - f)
XLOAD
适用于较低级别的类型,内存引用要么是STRREF
,要么分解为较低级别的操作,要么是一个ADD
、MUL
、BSHL
指针、偏移量或索引的组合。 - g)
SLOAD
的slot number与trace的起始栈帧(frame)相关,其中#0
表示闭包/帧槽(frame slot),#1
表示第一个可变slot(对应bytecode的slot 0)。RETF
将BASE
向下移动,随后的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寄存器),就会生成
RENAME
。RENAME
指令保存了在给定快照下用于引用的寄存器,相同的引用值可能会有不同的寄存器,如0012 => r14, 0012 => r15
,最初引用的指令保存着上面使用的最高快照(如果有的话)的寄存器。
这部分指令总共有9
条,选经常遇到的解释一下,需要查询指令语法时请阅读官方文档 Miscellaneous Ops。
二、IR在luaJit源码中的定义
luaJit对于IR结构的定义主要在 lj_ir.h
、 lj_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
的值为8
,IR_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
操作码操作码被用来一元浮点运算,其中的浮点运算对应关系如下:
OP | Description |
---|---|
FPM_FLOOR | floor(ref) |
FPM_CEIL | ceil(ref) |
FPM_TRUNC | trunc(ref) |
FPM_SQRT | sqrt(ref) |
FPM_EXP | exp(ref) |
FPM_EXP2 | exp2(ref) |
FPM_LOG | log(ref) |
FPM_LOG2 | log2(ref) |
FPM_LOG10 | log10(ref) |
FPM_SIN | sin(ref) |
FPM_COS | cos(ref) |
FPM_TAN | tan(ref) |
IRFieldID
是对 FLOAD
、FREF
操作码 的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. */
OP | Left | Right | Description |
---|---|---|---|
XLOAD | xref | #flags | Extended load |
SLOAD | #slot | #flags | Stack slot load |
CONV | src | #flags | Generic 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;
这里还定义了两个函数,itype
和TValue
见lj_obj.h
:
itype2irt
:将itype
转成IRType
irt_toitype_
:IRType
转成itype
5)IR references
Tagged IR references
都有一个IRType
,也就是下面的irt
,这样方便快速IR type checks。irt is 8 bit
,flags is 8 bit
,ref 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;