2 ** ARM instruction emitter.
3 ** Copyright (C) 2005-2012 Mike Pall. See Copyright Notice in luajit.h
6 /* -- Constant encoding --------------------------------------------------- */
8 static uint8_t emit_invai
[16] = {
9 /* AND */ (ARMI_AND
^ARMI_BIC
) >> 21,
11 /* SUB */ (ARMI_SUB
^ARMI_ADD
) >> 21,
13 /* ADD */ (ARMI_ADD
^ARMI_SUB
) >> 21,
14 /* ADC */ (ARMI_ADC
^ARMI_SBC
) >> 21,
15 /* SBC */ (ARMI_SBC
^ARMI_ADC
) >> 21,
19 /* CMP */ (ARMI_CMP
^ARMI_CMN
) >> 21,
20 /* CMN */ (ARMI_CMN
^ARMI_CMP
) >> 21,
22 /* MOV */ (ARMI_MOV
^ARMI_MVN
) >> 21,
23 /* BIC */ (ARMI_BIC
^ARMI_AND
) >> 21,
24 /* MVN */ (ARMI_MVN
^ARMI_MOV
) >> 21
27 /* Encode constant in K12 format for data processing instructions. */
28 static uint32_t emit_isk12(ARMIns ai
, int32_t n
)
30 uint32_t invai
, i
, m
= (uint32_t)n
;
31 /* K12: unsigned 8 bit value, rotated in steps of two bits. */
32 for (i
= 0; i
< 4096; i
+= 256, m
= lj_rol(m
, 2))
33 if (m
<= 255) return ARMI_K12
|m
|i
;
34 /* Otherwise try negation/complement with the inverse instruction. */
35 invai
= emit_invai
[((ai
>> 21) & 15)];
36 if (!invai
) return 0; /* Failed. No inverse instruction. */
38 if (invai
== ((ARMI_SUB
^ARMI_ADD
) >> 21) ||
39 invai
== (ARMI_CMP
^ARMI_CMN
) >> 21) m
++;
40 for (i
= 0; i
< 4096; i
+= 256, m
= lj_rol(m
, 2))
41 if (m
<= 255) return ARMI_K12
|(invai
<<21)|m
|i
;
42 return 0; /* Failed. */
45 /* -- Emit basic instructions --------------------------------------------- */
47 static void emit_dnm(ASMState
*as
, ARMIns ai
, Reg rd
, Reg rn
, Reg rm
)
49 *--as
->mcp
= ai
| ARMF_D(rd
) | ARMF_N(rn
) | ARMF_M(rm
);
52 static void emit_dm(ASMState
*as
, ARMIns ai
, Reg rd
, Reg rm
)
54 *--as
->mcp
= ai
| ARMF_D(rd
) | ARMF_M(rm
);
57 static void emit_dn(ASMState
*as
, ARMIns ai
, Reg rd
, Reg rn
)
59 *--as
->mcp
= ai
| ARMF_D(rd
) | ARMF_N(rn
);
62 static void emit_nm(ASMState
*as
, ARMIns ai
, Reg rn
, Reg rm
)
64 *--as
->mcp
= ai
| ARMF_N(rn
) | ARMF_M(rm
);
67 static void emit_d(ASMState
*as
, ARMIns ai
, Reg rd
)
69 *--as
->mcp
= ai
| ARMF_D(rd
);
72 static void emit_n(ASMState
*as
, ARMIns ai
, Reg rn
)
74 *--as
->mcp
= ai
| ARMF_N(rn
);
77 static void emit_m(ASMState
*as
, ARMIns ai
, Reg rm
)
79 *--as
->mcp
= ai
| ARMF_M(rm
);
82 static void emit_lsox(ASMState
*as
, ARMIns ai
, Reg rd
, Reg rn
, int32_t ofs
)
84 lua_assert(ofs
>= -255 && ofs
<= 255);
85 if (ofs
< 0) ofs
= -ofs
; else ai
|= ARMI_LS_U
;
86 *--as
->mcp
= ai
| ARMI_LS_P
| ARMI_LSX_I
| ARMF_D(rd
) | ARMF_N(rn
) |
87 ((ofs
& 0xf0) << 4) | (ofs
& 0x0f);
90 static void emit_lso(ASMState
*as
, ARMIns ai
, Reg rd
, Reg rn
, int32_t ofs
)
92 lua_assert(ofs
>= -4095 && ofs
<= 4095);
93 /* Combine LDR/STR pairs to LDRD/STRD. */
94 if (*as
->mcp
== (ai
|ARMI_LS_P
|ARMI_LS_U
|ARMF_D(rd
^1)|ARMF_N(rn
)|(ofs
^4)) &&
95 (ai
& ~(ARMI_LDR
^ARMI_STR
)) == ARMI_STR
&& rd
!= rn
&&
96 (uint32_t)ofs
<= 252 && !(ofs
& 3) && !((rd
^ (ofs
>>2)) & 1) &&
97 as
->mcp
!= as
->mcloop
) {
99 emit_lsox(as
, ai
== ARMI_LDR
? ARMI_LDRD
: ARMI_STRD
, rd
&~1, rn
, ofs
&~4);
102 if (ofs
< 0) ofs
= -ofs
; else ai
|= ARMI_LS_U
;
103 *--as
->mcp
= ai
| ARMI_LS_P
| ARMF_D(rd
) | ARMF_N(rn
) | ofs
;
107 static void emit_vlso(ASMState
*as
, ARMIns ai
, Reg rd
, Reg rn
, int32_t ofs
)
109 lua_assert(ofs
>= -1020 && ofs
<= 1020 && (ofs
&3) == 0);
110 if (ofs
< 0) ofs
= -ofs
; else ai
|= ARMI_LS_U
;
111 *--as
->mcp
= ai
| ARMI_LS_P
| ARMF_D(rd
& 15) | ARMF_N(rn
) | (ofs
>> 2);
115 /* -- Emit loads/stores --------------------------------------------------- */
117 /* Prefer spills of BASE/L. */
118 #define emit_canremat(ref) ((ref) < ASMREF_L)
120 /* Try to find a one step delta relative to another constant. */
121 static int emit_kdelta1(ASMState
*as
, Reg d
, int32_t i
)
123 RegSet work
= ~as
->freeset
& RSET_GPR
;
125 Reg r
= rset_picktop(work
);
126 IRRef ref
= regcost_ref(as
->cost
[r
]);
128 if (emit_canremat(ref
)) {
129 int32_t delta
= i
- (ra_iskref(ref
) ? ra_krefk(as
, ref
) : IR(ref
)->i
);
130 uint32_t k
= emit_isk12(ARMI_ADD
, delta
);
133 emit_dm(as
, ARMI_MOV
, d
, r
);
135 emit_dn(as
, ARMI_ADD
^k
, d
, r
);
141 return 0; /* Failed. */
144 /* Try to find a two step delta relative to another constant. */
145 static int emit_kdelta2(ASMState
*as
, Reg d
, int32_t i
)
147 RegSet work
= ~as
->freeset
& RSET_GPR
;
149 Reg r
= rset_picktop(work
);
150 IRRef ref
= regcost_ref(as
->cost
[r
]);
152 if (emit_canremat(ref
)) {
153 int32_t other
= ra_iskref(ref
) ? ra_krefk(as
, ref
) : IR(ref
)->i
;
155 int32_t delta
= i
- other
;
156 uint32_t sh
, inv
= 0, k2
, k
;
157 if (delta
< 0) { delta
= -delta
; inv
= ARMI_ADD
^ARMI_SUB
; }
158 sh
= lj_ffs(delta
) & ~1;
159 k2
= emit_isk12(0, delta
& (255 << sh
));
160 k
= emit_isk12(0, delta
& ~(255 << sh
));
162 emit_dn(as
, ARMI_ADD
^k2
^inv
, d
, d
);
163 emit_dn(as
, ARMI_ADD
^k
^inv
, d
, r
);
170 return 0; /* Failed. */
173 /* Load a 32 bit constant into a GPR. */
174 static void emit_loadi(ASMState
*as
, Reg r
, int32_t i
)
176 uint32_t k
= emit_isk12(ARMI_MOV
, i
);
177 lua_assert(rset_test(as
->freeset
, r
) || r
== RID_TMP
);
179 /* Standard K12 constant. */
180 emit_d(as
, ARMI_MOV
^k
, r
);
181 } else if ((as
->flags
& JIT_F_ARMV6T2
) && (uint32_t)i
< 0x00010000u
) {
182 /* 16 bit loword constant for ARMv6T2. */
183 emit_d(as
, ARMI_MOVW
|(i
& 0x0fff)|((i
& 0xf000)<<4), r
);
184 } else if (emit_kdelta1(as
, r
, i
)) {
185 /* One step delta relative to another constant. */
186 } else if ((as
->flags
& JIT_F_ARMV6T2
)) {
187 /* 32 bit hiword/loword constant for ARMv6T2. */
188 emit_d(as
, ARMI_MOVT
|((i
>>16) & 0x0fff)|(((i
>>16) & 0xf000)<<4), r
);
189 emit_d(as
, ARMI_MOVW
|(i
& 0x0fff)|((i
& 0xf000)<<4), r
);
190 } else if (emit_kdelta2(as
, r
, i
)) {
191 /* Two step delta relative to another constant. */
193 /* Otherwise construct the constant with up to 4 instructions. */
194 /* NYI: use mvn+bic, use pc-relative loads. */
196 uint32_t sh
= lj_ffs(i
) & ~1;
197 int32_t m
= i
& (255 << sh
);
200 emit_d(as
, ARMI_MOV
^ emit_isk12(0, m
), r
);
203 emit_dn(as
, ARMI_ORR
^ emit_isk12(0, m
), r
, r
);
208 #define emit_loada(as, r, addr) emit_loadi(as, (r), i32ptr((addr)))
210 static Reg
ra_allock(ASMState
*as
, int32_t k
, RegSet allow
);
212 /* Get/set from constant pointer. */
213 static void emit_lsptr(ASMState
*as
, ARMIns ai
, Reg r
, void *p
)
215 int32_t i
= i32ptr(p
);
216 emit_lso(as
, ai
, r
, ra_allock(as
, (i
& ~4095), rset_exclude(RSET_GPR
, r
)),
221 /* Load a number constant into an FPR. */
222 static void emit_loadn(ASMState
*as
, Reg r
, cTValue
*tv
)
225 if ((as
->flags
& JIT_F_VFPV3
) && !tv
->u32
.lo
) {
226 uint32_t hi
= tv
->u32
.hi
;
227 uint32_t b
= ((hi
>> 22) & 0x1ff);
228 if (!(hi
& 0xffff) && (b
== 0x100 || b
== 0x0ff)) {
229 *--as
->mcp
= ARMI_VMOVI_D
| ARMF_D(r
& 15) |
230 ((tv
->u32
.hi
>> 12) & 0x00080000) |
231 ((tv
->u32
.hi
>> 4) & 0x00070000) |
232 ((tv
->u32
.hi
>> 16) & 0x0000000f);
237 emit_vlso(as
, ARMI_VLDR_D
, r
,
238 ra_allock(as
, (i
& ~1020), RSET_GPR
), (i
& 1020));
242 /* Get/set global_State fields. */
243 #define emit_getgl(as, r, field) \
244 emit_lsptr(as, ARMI_LDR, (r), (void *)&J2G(as->J)->field)
245 #define emit_setgl(as, r, field) \
246 emit_lsptr(as, ARMI_STR, (r), (void *)&J2G(as->J)->field)
248 /* Trace number is determined from pc of exit instruction. */
249 #define emit_setvmstate(as, i) UNUSED(i)
251 /* -- Emit control-flow instructions -------------------------------------- */
253 /* Label for internal jumps. */
254 typedef MCode
*MCLabel
;
256 /* Return label pointing to current PC. */
257 #define emit_label(as) ((as)->mcp)
259 static void emit_branch(ASMState
*as
, ARMIns ai
, MCode
*target
)
262 ptrdiff_t delta
= (target
- p
) - 1;
263 lua_assert(((delta
+ 0x00800000) >> 24) == 0);
264 *--p
= ai
| ((uint32_t)delta
& 0x00ffffffu
);
268 #define emit_jmp(as, target) emit_branch(as, ARMI_B, (target))
270 static void emit_call(ASMState
*as
, void *target
)
272 MCode
*p
= --as
->mcp
;
273 ptrdiff_t delta
= ((char *)target
- (char *)p
) - 8;
274 if ((((delta
>>2) + 0x00800000) >> 24) == 0) {
276 *p
= ARMI_BLX
| ((uint32_t)(delta
>>2) & 0x00ffffffu
) | ((delta
&2) << 27);
278 *p
= ARMI_BL
| ((uint32_t)(delta
>>2) & 0x00ffffffu
);
279 } else { /* Target out of range: need indirect call. But don't use R0-R3. */
280 Reg r
= ra_allock(as
, i32ptr(target
), RSET_RANGE(RID_R4
, RID_R12
+1));
281 *p
= ARMI_BLXr
| ARMF_M(r
);
285 /* -- Emit generic operations --------------------------------------------- */
287 /* Generic move between two regs. */
288 static void emit_movrr(ASMState
*as
, IRIns
*ir
, Reg dst
, Reg src
)
291 lua_assert(!irt_isnum(ir
->t
)); UNUSED(ir
);
293 if (dst
>= RID_MAX_GPR
) {
294 emit_dm(as
, irt_isnum(ir
->t
) ? ARMI_VMOV_D
: ARMI_VMOV_S
,
295 (dst
& 15), (src
& 15));
299 if (as
->mcp
!= as
->mcloop
) { /* Swap early registers for loads/stores. */
300 MCode ins
= *as
->mcp
, swp
= (src
^dst
);
301 if ((ins
& 0x0c000000) == 0x04000000 && (ins
& 0x02000010) != 0x02000010) {
302 if (!((ins
^ (dst
<< 16)) & 0x000f0000))
303 *as
->mcp
= ins
^ (swp
<< 16); /* Swap N in load/store. */
304 if (!(ins
& 0x00100000) && !((ins
^ (dst
<< 12)) & 0x0000f000))
305 *as
->mcp
= ins
^ (swp
<< 12); /* Swap D in store. */
308 emit_dm(as
, ARMI_MOV
, dst
, src
);
311 /* Generic load of register from stack slot. */
312 static void emit_spload(ASMState
*as
, IRIns
*ir
, Reg r
, int32_t ofs
)
315 lua_assert(!irt_isnum(ir
->t
)); UNUSED(ir
);
317 if (r
>= RID_MAX_GPR
)
318 emit_vlso(as
, irt_isnum(ir
->t
) ? ARMI_VLDR_D
: ARMI_VLDR_S
, r
, RID_SP
, ofs
);
321 emit_lso(as
, ARMI_LDR
, r
, RID_SP
, ofs
);
324 /* Generic store of register to stack slot. */
325 static void emit_spstore(ASMState
*as
, IRIns
*ir
, Reg r
, int32_t ofs
)
328 lua_assert(!irt_isnum(ir
->t
)); UNUSED(ir
);
330 if (r
>= RID_MAX_GPR
)
331 emit_vlso(as
, irt_isnum(ir
->t
) ? ARMI_VSTR_D
: ARMI_VSTR_S
, r
, RID_SP
, ofs
);
334 emit_lso(as
, ARMI_STR
, r
, RID_SP
, ofs
);
337 /* Emit an arithmetic/logic operation with a constant operand. */
338 static void emit_opk(ASMState
*as
, ARMIns ai
, Reg dest
, Reg src
,
339 int32_t i
, RegSet allow
)
341 uint32_t k
= emit_isk12(ai
, i
);
343 emit_dn(as
, ai
^k
, dest
, src
);
345 emit_dnm(as
, ai
, dest
, src
, ra_allock(as
, i
, allow
));
348 /* Add offset to pointer. */
349 static void emit_addptr(ASMState
*as
, Reg r
, int32_t ofs
)
352 emit_opk(as
, ARMI_ADD
, r
, r
, ofs
, rset_exclude(RSET_GPR
, r
));
355 #define emit_spsub(as, ofs) emit_addptr(as, RID_SP, -(ofs))