文章目录
一、IR代码书写结构理解
LuaJIT资料挺少,可以一手学习的基本上只有官方文档,我也是按照自己的理解,对官网上的部分内容做了记录。下面代码分别是Byte code
,SSA IR
,以及目标机器对应的MCode
,此时的目标机器架构是LoongArch
:
loongson@loongson-pc:~/workspace$ 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
555558bbff80 lui ra, 49160
555558bbff84 dsll32 ra, ra, 0
555558bbff88 dmtc1 ra, f0
555558bbff8c lw r23, 8(r16)
555558bbff90 ld ra, 0(r16)
555558bbff94 dsra32 ra, ra, 15
555558bbff98 ldc1 f1, 0(r16)
555558bbff9c sltiu ra, ra, -14
555558bbffa0 beqz ra, 0x555558bbffd4 ->exit
555558bbffa4 li ra, 0
555558bbffa8 mul.d f31, f1, f0
555558bbffac addiu r23, r23, 1
555558bbffb0 slti ra, r23, 1001
555558bbffb4 beqz ra, 0x555558bbffd4 ->exit
555558bbffb8 li ra, 1
->LOOP:
555558bbffbc mul.d f31, f31, f0
555558bbffc0 addiu r23, r23, 1
555558bbffc4 slti ra, r23, 1001
555558bbffc8 bnez ra, 0x555558bbffbc ->LOOP
555558bbffcc li ra, 3
---- 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?.
二、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;