1 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2 * Mupen64plus/PCSX - assem_arm64.c *
3 * Copyright (C) 2009-2011 Ari64 *
4 * Copyright (C) 2009-2018 Gillou68310 *
5 * Copyright (C) 2021 notaz *
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
12 * This program is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
17 * You should have received a copy of the GNU General Public License *
18 * along with this program; if not, write to the *
19 * Free Software Foundation, Inc., *
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
21 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
24 #include "arm_features.h"
26 void do_memhandler_pre();
27 void do_memhandler_post();
30 static void set_jump_target(void *addr, void *target)
32 u_int *ptr = NDRC_WRITE_OFFSET(addr);
33 intptr_t offset = (u_char *)target - (u_char *)addr;
35 if ((*ptr&0xFC000000) == 0x14000000) { // b
36 assert(offset>=-134217728LL&&offset<134217728LL);
37 *ptr=(*ptr&0xFC000000)|((offset>>2)&0x3ffffff);
39 else if ((*ptr&0xff000000) == 0x54000000 // b.cond
40 || (*ptr&0x7e000000) == 0x34000000) { // cbz/cbnz
41 // Conditional branch are limited to +/- 1MB
42 // block max size is 256k so branching beyond the +/- 1MB limit
43 // should only happen when jumping to an already compiled block (see add_jump_out)
44 // a workaround would be to do a trampoline jump via a stub at the end of the block
45 assert(-1048576 <= offset && offset < 1048576);
46 *ptr=(*ptr&0xFF00001F)|(((offset>>2)&0x7ffff)<<5);
48 else if((*ptr&0x9f000000)==0x10000000) { // adr
49 // generated by do_miniht_insert
50 assert(offset>=-1048576LL&&offset<1048576LL);
51 *ptr=(*ptr&0x9F00001F)|(offset&0x3)<<29|((offset>>2)&0x7ffff)<<5;
54 abort(); // should not happen
57 // from a pointer to external jump stub (which was produced by emit_extjump2)
58 // find where the jumping insn is
59 static void *find_extjump_insn(void *stub)
61 int *ptr = (int *)stub + 2;
62 assert((*ptr&0x9f000000) == 0x10000000); // adr
63 int offset = (((signed int)(*ptr<<8)>>13)<<2)|((*ptr>>29)&0x3);
64 return ptr + offset / 4;
68 // find where external branch is liked to using addr of it's stub:
69 // get address that the stub loads (dyna_linker arg1),
70 // treat it as a pointer to branch insn,
71 // return addr where that branch jumps to
72 static void *get_pointer(void *stub)
74 int *i_ptr = find_extjump_insn(stub);
75 if ((*i_ptr&0xfc000000) == 0x14000000) // b
76 return i_ptr + ((signed int)(*i_ptr<<6)>>6);
77 if ((*i_ptr&0xff000000) == 0x54000000 // b.cond
78 || (*i_ptr&0x7e000000) == 0x34000000) // cbz/cbnz
79 return i_ptr + ((signed int)(*i_ptr<<8)>>13);
85 // Allocate a specific ARM register.
86 static void alloc_arm_reg(struct regstat *cur,int i,signed char reg,int hr)
91 // see if it's already allocated (and dealloc it)
92 for(n=0;n<HOST_REGS;n++)
94 if(n!=EXCLUDE_REG&&cur->regmap[n]==reg) {
95 dirty=(cur->dirty>>n)&1;
101 cur->dirty&=~(1<<hr);
102 cur->dirty|=dirty<<hr;
103 cur->isconst&=~(1<<hr);
106 // Alloc cycle count into dedicated register
107 static void alloc_cc(struct regstat *cur,int i)
109 alloc_arm_reg(cur,i,CCREG,HOST_CCREG);
117 static unused const char *regname[32] = {
118 "w0", "w1", "w2", "w3", "w4", "w5", "w6", "w7",
119 "w8", "w9", "w10", "w11", "w12", "w13", "w14", "w15",
120 "ip0", "ip1", "w18", "w19", "w20", "w21", "w22", "w23",
121 "w24", "w25", "w26", "w27", "w28", "wfp", "wlr", "wsp"
124 static unused const char *regname64[32] = {
125 "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7",
126 "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
127 "ip0", "ip1", "x18", "x19", "x20", "x21", "x22", "x23",
128 "x24", "x25", "x26", "x27", "x28", "fp", "lr", "sp"
132 COND_EQ, COND_NE, COND_CS, COND_CC, COND_MI, COND_PL, COND_VS, COND_VC,
133 COND_HI, COND_LS, COND_GE, COND_LT, COND_GT, COND_LE, COND_AW, COND_NV
136 static unused const char *condname[16] = {
137 "eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc",
138 "hi", "ls", "ge", "lt", "gt", "le", "aw", "nv"
141 static void output_w32(u_int word)
143 *((u_int *)NDRC_WRITE_OFFSET(out)) = word;
147 static u_int rn_rd(u_int rn, u_int rd)
151 return (rn << 5) | rd;
154 static u_int rm_rn_rd(u_int rm, u_int rn, u_int rd)
159 return (rm << 16) | (rn << 5) | rd;
162 static u_int rm_ra_rn_rd(u_int rm, u_int ra, u_int rn, u_int rd)
165 return rm_rn_rd(rm, rn, rd) | (ra << 10);
168 static u_int imm7_rt2_rn_rt(u_int imm7, u_int rt2, u_int rn, u_int rt)
174 return (imm7 << 15) | (rt2 << 10) | (rn << 5) | rt;
177 static u_int rm_imm6_rn_rd(u_int rm, u_int imm6, u_int rn, u_int rd)
180 return rm_rn_rd(rm, rn, rd) | (imm6 << 10);
183 static u_int imm16_rd(u_int imm16, u_int rd)
185 assert(imm16 < 0x10000);
187 return (imm16 << 5) | rd;
190 static u_int imm12_rn_rd(u_int imm12, u_int rn, u_int rd)
192 assert(imm12 < 0x1000);
195 return (imm12 << 10) | (rn << 5) | rd;
198 static u_int imm9_rn_rt(u_int imm9, u_int rn, u_int rd)
200 assert(imm9 < 0x200);
203 return (imm9 << 12) | (rn << 5) | rd;
206 static u_int imm19_rt(u_int imm19, u_int rt)
208 assert(imm19 < 0x80000);
210 return (imm19 << 5) | rt;
213 static u_int n_immr_imms_rn_rd(u_int n, u_int immr, u_int imms, u_int rn, u_int rd)
220 return (n << 22) | (immr << 16) | (imms << 10) | (rn << 5) | rd;
223 static u_int genjmp(const u_char *addr)
225 intptr_t offset = addr - out;
226 if ((uintptr_t)addr < 3) return 0; // a branch that will be patched later
227 if (offset < -134217728 || offset > 134217727) {
228 SysPrintf("%s: out of range: %p %lx\n", __func__, addr, offset);
232 return ((u_int)offset >> 2) & 0x03ffffff;
235 static u_int genjmpcc(const u_char *addr)
237 intptr_t offset = addr - out;
238 if ((uintptr_t)addr < 3) return 0;
239 if (offset < -1048576 || offset > 1048572) {
240 SysPrintf("%s: out of range: %p %lx\n", __func__, addr, offset);
244 return ((u_int)offset >> 2) & 0x7ffff;
247 static uint32_t is_mask(u_int value)
249 return value && ((value + 1) & value) == 0;
252 // This function returns true if the argument contains a
253 // non-empty sequence of ones (possibly rotated) with the remainder zero.
254 static uint32_t is_rotated_mask(u_int value)
256 if (value == 0 || value == ~0)
258 if (is_mask((value - 1) | value))
260 return is_mask((~value - 1) | ~value);
263 static void gen_logical_imm(u_int value, u_int *immr, u_int *imms)
265 int lzeros, tzeros, ones;
267 if (is_mask((value - 1) | value)) {
268 lzeros = __builtin_clz(value);
269 tzeros = __builtin_ctz(value);
270 ones = 32 - lzeros - tzeros;
271 *immr = (32 - tzeros) & 31;
276 if (is_mask((value - 1) | value)) {
277 lzeros = __builtin_clz(value);
278 tzeros = __builtin_ctz(value);
279 ones = 32 - lzeros - tzeros;
287 static void emit_mov(u_int rs, u_int rt)
289 assem_debug("mov %s,%s\n", regname[rt], regname[rs]);
290 output_w32(0x2a000000 | rm_rn_rd(rs, WZR, rt));
293 static void emit_mov64(u_int rs, u_int rt)
295 assem_debug("mov %s,%s\n", regname64[rt], regname64[rs]);
296 output_w32(0xaa000000 | rm_rn_rd(rs, WZR, rt));
299 static void emit_add(u_int rs1, u_int rs2, u_int rt)
301 assem_debug("add %s,%s,%s\n", regname[rt], regname[rs1], regname[rs2]);
302 output_w32(0x0b000000 | rm_rn_rd(rs2, rs1, rt));
305 static void emit_adds(u_int rs1, u_int rs2, u_int rt)
307 assem_debug("adds %s,%s,%s\n", regname[rt], regname[rs1], regname[rs2]);
308 output_w32(0x2b000000 | rm_rn_rd(rs2, rs1, rt));
311 static void emit_add64(u_int rs1, u_int rs2, u_int rt)
313 assem_debug("add %s,%s,%s\n", regname64[rt], regname64[rs1], regname64[rs2]);
314 output_w32(0x8b000000 | rm_rn_rd(rs2, rs1, rt));
317 static void emit_adds64(u_int rs1, u_int rs2, u_int rt)
319 assem_debug("adds %s,%s,%s\n",regname64[rt],regname64[rs1],regname64[rs2]);
320 output_w32(0xab000000 | rm_rn_rd(rs2, rs1, rt));
322 #define emit_adds_ptr emit_adds64
324 static void emit_add_lsrimm(u_int rs1, u_int rs2, u_int shift, u_int rt)
326 assem_debug("add %s,%s,%s,lsr #%u\n",regname[rt],regname[rs1],regname[rs2],shift);
327 output_w32(0x0b400000 | rm_imm6_rn_rd(rs2, shift, rs1, rt));
330 static void emit_neg(u_int rs, u_int rt)
332 assem_debug("neg %s,%s\n",regname[rt],regname[rs]);
333 output_w32(0x4b000000 | rm_rn_rd(rs, WZR, rt));
336 static void emit_negs(u_int rs, u_int rt)
338 assem_debug("negs %s,%s\n",regname[rt],regname[rs]);
339 output_w32(0x6b000000 | rm_rn_rd(rs, WZR, rt));
342 static void emit_sub(u_int rs1, u_int rs2, u_int rt)
344 assem_debug("sub %s,%s,%s\n", regname[rt], regname[rs1], regname[rs2]);
345 output_w32(0x4b000000 | rm_imm6_rn_rd(rs2, 0, rs1, rt));
348 static void emit_subs(u_int rs1, u_int rs2, u_int rt)
350 assem_debug("subs %s,%s,%s\n", regname[rt], regname[rs1], regname[rs2]);
351 output_w32(0x6b000000 | rm_imm6_rn_rd(rs2, 0, rs1, rt));
354 static unused void emit_sub_asrimm(u_int rs1, u_int rs2, u_int shift, u_int rt)
356 assem_debug("sub %s,%s,%s,asr #%u\n",regname[rt],regname[rs1],regname[rs2],shift);
357 output_w32(0x4b800000 | rm_imm6_rn_rd(rs2, shift, rs1, rt));
360 static void emit_movz(u_int imm, u_int rt)
362 assem_debug("movz %s,#%#x\n", regname[rt], imm);
363 output_w32(0x52800000 | imm16_rd(imm, rt));
366 static void emit_movz_lsl16(u_int imm, u_int rt)
368 assem_debug("movz %s,#%#x,lsl #16\n", regname[rt], imm);
369 output_w32(0x52a00000 | imm16_rd(imm, rt));
372 static void emit_movn(u_int imm, u_int rt)
374 assem_debug("movn %s,#%#x\n", regname[rt], imm);
375 output_w32(0x12800000 | imm16_rd(imm, rt));
378 static void emit_movn_lsl16(u_int imm,u_int rt)
380 assem_debug("movn %s,#%#x,lsl #16\n", regname[rt], imm);
381 output_w32(0x12a00000 | imm16_rd(imm, rt));
384 static void emit_movk(u_int imm,u_int rt)
386 assem_debug("movk %s,#%#x\n", regname[rt], imm);
387 output_w32(0x72800000 | imm16_rd(imm, rt));
390 static void emit_movk_lsl16(u_int imm,u_int rt)
393 assem_debug("movk %s,#%#x,lsl #16\n", regname[rt], imm);
394 output_w32(0x72a00000 | imm16_rd(imm, rt));
397 static void emit_zeroreg(u_int rt)
402 static void emit_movimm(u_int imm, u_int rt)
406 else if ((~imm) < 65536)
408 else if ((imm&0xffff) == 0)
409 emit_movz_lsl16(imm >> 16, rt);
410 else if (((~imm)&0xffff) == 0)
411 emit_movn_lsl16(~imm >> 16, rt);
412 else if (is_rotated_mask(imm)) {
414 gen_logical_imm(imm, &immr, &imms);
415 assem_debug("orr %s,wzr,#%#x\n", regname[rt], imm);
416 output_w32(0x32000000 | n_immr_imms_rn_rd(0, immr, imms, WZR, rt));
419 emit_movz(imm & 0xffff, rt);
420 emit_movk_lsl16(imm >> 16, rt);
424 static void emit_movimm64(uint64_t imm, u_int rt)
426 u_int shift, op, imm16, insns = 0;
427 for (shift = 0; shift < 4; shift++) {
428 imm16 = (imm >> shift * 16) & 0xffff;
431 op = insns ? 0xf2800000 : 0xd2800000;
432 assem_debug("mov%c %s,#%#x", insns ? 'k' : 'z', regname64[rt], imm16);
434 assem_debug(",lsl #%u", shift * 16);
436 output_w32(op | (shift << 21) | imm16_rd(imm16, rt));
440 assem_debug("movz %s,#0\n", regname64[rt]);
441 output_w32(0xd2800000 | imm16_rd(0, rt));
445 static void emit_readword(void *addr, u_int rt)
447 uintptr_t offset = (u_char *)addr - (u_char *)&dynarec_local;
448 if (!(offset & 3) && offset <= 16380) {
449 assem_debug("ldr %s,[x%d+%#lx]%s\n", regname[rt], FP, offset, fpofs_name(offset));
450 output_w32(0xb9400000 | imm12_rn_rd(offset >> 2, FP, rt));
456 static void emit_readdword(void *addr, u_int rt)
458 uintptr_t offset = (u_char *)addr - (u_char *)&dynarec_local;
459 if (!(offset & 7) && offset <= 32760) {
460 assem_debug("ldr %s,[x%d+%#lx]%s\n", regname64[rt], FP, offset, fpofs_name(offset));
461 output_w32(0xf9400000 | imm12_rn_rd(offset >> 3, FP, rt));
466 #define emit_readptr emit_readdword
468 static void emit_readshword(void *addr, u_int rt)
470 uintptr_t offset = (u_char *)addr - (u_char *)&dynarec_local;
471 if (!(offset & 1) && offset <= 8190) {
472 assem_debug("ldrsh %s,[x%d+%#lx]\n", regname[rt], FP, offset);
473 output_w32(0x79c00000 | imm12_rn_rd(offset >> 1, FP, rt));
479 static void emit_loadreg(u_int r, u_int hr)
487 //case HIREG: addr = &hi; break;
488 //case LOREG: addr = &lo; break;
489 case CCREG: addr = &cycle_count; break;
490 case CSREG: addr = &psxRegs.CP0.n.SR; break;
491 case INVCP: addr = &invc_ptr; is64 = 1; break;
492 case ROREG: addr = &ram_offset; is64 = 1; break;
495 addr = &psxRegs.GPR.r[r];
499 emit_readdword(addr, hr);
501 emit_readword(addr, hr);
505 static void emit_writeword(u_int rt, void *addr)
507 uintptr_t offset = (u_char *)addr - (u_char *)&dynarec_local;
508 if (!(offset & 3) && offset <= 16380) {
509 assem_debug("str %s,[x%d+%#lx]%s\n", regname[rt], FP, offset, fpofs_name(offset));
510 output_w32(0xb9000000 | imm12_rn_rd(offset >> 2, FP, rt));
516 static void emit_writedword(u_int rt, void *addr)
518 uintptr_t offset = (u_char *)addr - (u_char *)&dynarec_local;
519 if (!(offset & 7) && offset <= 32760) {
520 assem_debug("str %s,[x%d+%#lx]%s\n", regname64[rt], FP, offset, fpofs_name(offset));
521 output_w32(0xf9000000 | imm12_rn_rd(offset >> 3, FP, rt));
527 static void emit_storereg(u_int r, u_int hr)
530 void *addr = &psxRegs.GPR.r[r];
532 //case HIREG: addr = &hi; break;
533 //case LOREG: addr = &lo; break;
534 case CCREG: addr = &cycle_count; break;
535 default: assert(r < 34); break;
537 emit_writeword(hr, addr);
540 static void emit_test(u_int rs, u_int rt)
542 assem_debug("tst %s,%s\n", regname[rs], regname[rt]);
543 output_w32(0x6a000000 | rm_rn_rd(rt, rs, WZR));
546 static void emit_testimm(u_int rs, u_int imm)
549 assem_debug("tst %s,#%#x\n", regname[rs], imm);
550 assert(is_rotated_mask(imm)); // good enough for PCSX
551 gen_logical_imm(imm, &immr, &imms);
552 output_w32(0x72000000 | n_immr_imms_rn_rd(0, immr, imms, rs, WZR));
555 static void emit_not(u_int rs,u_int rt)
557 assem_debug("mvn %s,%s\n",regname[rt],regname[rs]);
558 output_w32(0x2a200000 | rm_rn_rd(rs, WZR, rt));
561 static void emit_and(u_int rs1,u_int rs2,u_int rt)
563 assem_debug("and %s,%s,%s\n",regname[rt],regname[rs1],regname[rs2]);
564 output_w32(0x0a000000 | rm_rn_rd(rs2, rs1, rt));
567 static void emit_or(u_int rs1,u_int rs2,u_int rt)
569 assem_debug("orr %s,%s,%s\n",regname[rt],regname[rs1],regname[rs2]);
570 output_w32(0x2a000000 | rm_rn_rd(rs2, rs1, rt));
573 static void emit_bic(u_int rs1,u_int rs2,u_int rt)
575 assem_debug("bic %s,%s,%s\n",regname[rt],regname[rs1],regname[rs2]);
576 output_w32(0x0a200000 | rm_rn_rd(rs2, rs1, rt));
579 static void emit_orrshl_imm(u_int rs,u_int imm,u_int rt)
581 assem_debug("orr %s,%s,%s,lsl #%d\n",regname[rt],regname[rt],regname[rs],imm);
582 output_w32(0x2a000000 | rm_imm6_rn_rd(rs, imm, rt, rt));
585 static void emit_orrshr_imm(u_int rs,u_int imm,u_int rt)
587 assem_debug("orr %s,%s,%s,lsr #%d\n",regname[rt],regname[rt],regname[rs],imm);
588 output_w32(0x2a400000 | rm_imm6_rn_rd(rs, imm, rt, rt));
591 static void emit_orn_asrimm(u_int rs1, u_int rs2, u_int shift, u_int rt)
593 assem_debug("orn %s,%s,%s,asr #%u\n",regname[rt],regname[rs1],regname[rs2],shift);
594 output_w32(0x2aa00000 | rm_imm6_rn_rd(rs2, shift, rs1, rt));
597 static void emit_bicsar_imm(u_int rs,u_int imm,u_int rt)
599 assem_debug("bic %s,%s,%s,asr #%d\n",regname[rt],regname[rt],regname[rs],imm);
600 output_w32(0x0aa00000 | rm_imm6_rn_rd(rs, imm, rt, rt));
603 static void emit_xor(u_int rs1,u_int rs2,u_int rt)
605 assem_debug("eor %s,%s,%s\n",regname[rt],regname[rs1],regname[rs2]);
606 output_w32(0x4a000000 | rm_rn_rd(rs2, rs1, rt));
609 static void emit_xorsar_imm(u_int rs1, u_int rs2, u_int imm, u_int rt)
611 assem_debug("eor %s,%s,%s,asr #%d\n",regname[rt],regname[rs1],regname[rs2],imm);
612 output_w32(0x4a800000 | rm_imm6_rn_rd(rs2, imm, rs1, rt));
615 static void emit_addimm_s(u_int s, u_int is64, u_int rs, uintptr_t imm, u_int rt)
617 unused const char *st = s ? "s" : "";
618 s = s ? 0x20000000 : 0;
619 is64 = is64 ? 0x80000000 : 0;
621 assem_debug("add%s %s,%s,%#lx\n", st, regname[rt], regname[rs], imm);
622 output_w32(0x11000000 | is64 | s | imm12_rn_rd(imm, rs, rt));
624 else if (-imm < 4096) {
625 assem_debug("sub%s %s,%s,%#lx\n", st, regname[rt], regname[rs], -imm);
626 output_w32(0x51000000 | is64 | s | imm12_rn_rd(-imm, rs, rt));
628 else if (imm < 16777216 && (!(imm & 0xfff) || !s)) {
629 assem_debug("add%s %s,%s,#%#lx\n", st, regname[rt], regname[rs], imm&0xfff000);
630 output_w32(0x11400000 | is64 | s | imm12_rn_rd(imm >> 12, rs, rt));
632 assem_debug("add %s,%s,#%#lx\n", regname[rt], regname[rt], imm&0xfff);
633 output_w32(0x11000000 | is64 | imm12_rn_rd(imm & 0xfff, rt, rt));
636 else if (-imm < 16777216 && (!(-imm & 0xfff) || !s)) {
637 assem_debug("sub%s %s,%s,#%#lx\n", st, regname[rt], regname[rs], -imm&0xfff000);
638 output_w32(0x51400000 | is64 | s | imm12_rn_rd(-imm >> 12, rs, rt));
640 assem_debug("sub %s,%s,#%#lx\n", regname[rt], regname[rt], -imm&0xfff);
641 output_w32(0x51000000 | is64 | imm12_rn_rd(-imm & 0xfff, rt, rt));
648 host_tempreg_acquire();
651 emit_movimm(imm, tmp);
652 assem_debug("add%s %s,%s,%s\n", st, regname[rt], regname[rs], regname[tmp]);
653 output_w32(0x0b000000 | s | rm_rn_rd(rs, tmp, rt));
654 if (tmp == HOST_TEMPREG)
655 host_tempreg_release();
659 static void emit_addimm(u_int rs, uintptr_t imm, u_int rt)
665 emit_addimm_s(0, 0, rs, imm, rt);
668 static void emit_addimm64(u_int rs, uintptr_t imm, u_int rt)
670 emit_addimm_s(0, 1, rs, imm, rt);
673 static void emit_addimm_ptr(u_int rs, uintptr_t imm, u_int rt)
675 emit_addimm64(rs, imm, rt);
678 static void emit_addimm_and_set_flags(int imm, u_int rt)
680 emit_addimm_s(1, 0, rt, imm, rt);
683 static void emit_addimm_and_set_flags3(u_int rs, int imm, u_int rt)
685 emit_addimm_s(1, 0, rs, imm, rt);
688 static void emit_logicop_imm(u_int op, u_int rs, u_int imm, u_int rt)
690 const char *names[] = { "and", "orr", "eor", "ands" };
691 const char *name = names[op];
694 if (is_rotated_mask(imm)) {
695 gen_logical_imm(imm, &immr, &imms);
696 assem_debug("%s %s,%s,#%#x\n", name, regname[rt], regname[rs], imm);
697 output_w32(op | 0x12000000 | n_immr_imms_rn_rd(0, immr, imms, rs, rt));
700 if (rs == HOST_TEMPREG || rt != HOST_TEMPREG)
701 host_tempreg_acquire();
702 emit_movimm(imm, HOST_TEMPREG);
703 assem_debug("%s %s,%s,%s\n", name, regname[rt], regname[rs], regname[HOST_TEMPREG]);
704 output_w32(op | 0x0a000000 | rm_rn_rd(HOST_TEMPREG, rs, rt));
705 if (rs == HOST_TEMPREG || rt != HOST_TEMPREG)
706 host_tempreg_release();
711 static void emit_andimm(u_int rs, u_int imm, u_int rt)
716 emit_logicop_imm(0, rs, imm, rt);
719 static void emit_orimm(u_int rs, u_int imm, u_int rt)
726 emit_logicop_imm(1, rs, imm, rt);
729 static void emit_xorimm(u_int rs, u_int imm, u_int rt)
736 emit_logicop_imm(2, rs, imm, rt);
739 static void emit_sbfm(u_int rs,u_int imm,u_int rt)
741 assem_debug("sbfm %s,%s,#0,#%d\n",regname[rt],regname[rs],imm);
742 output_w32(0x13000000 | n_immr_imms_rn_rd(0, 0, imm, rs, rt));
745 static void emit_ubfm(u_int rs,u_int imm,u_int rt)
747 assem_debug("ubfm %s,%s,#0,#%d\n",regname[rt],regname[rs],imm);
748 output_w32(0x53000000 | n_immr_imms_rn_rd(0, 0, imm, rs, rt));
751 static void emit_shlimm(u_int rs,u_int imm,u_int rt)
753 assem_debug("lsl %s,%s,#%d\n",regname[rt],regname[rs],imm);
754 output_w32(0x53000000 | n_immr_imms_rn_rd(0, (31-imm)+1, 31-imm, rs, rt));
757 static void emit_shrimm(u_int rs,u_int imm,u_int rt)
759 assem_debug("lsr %s,%s,#%d\n",regname[rt],regname[rs],imm);
760 output_w32(0x53000000 | n_immr_imms_rn_rd(0, imm, 31, rs, rt));
763 static void emit_shrimm64(u_int rs,u_int imm,u_int rt)
765 assem_debug("lsr %s,%s,#%d\n",regname[rt],regname[rs],imm);
766 output_w32(0xd3400000 | n_immr_imms_rn_rd(0, imm, 63, rs, rt));
769 static void emit_sarimm(u_int rs,u_int imm,u_int rt)
771 assem_debug("asr %s,%s,#%d\n",regname[rt],regname[rs],imm);
772 output_w32(0x13000000 | n_immr_imms_rn_rd(0, imm, 31, rs, rt));
775 static void emit_rorimm(u_int rs,u_int imm,u_int rt)
777 assem_debug("ror %s,%s,#%d\n",regname[rt],regname[rs],imm);
778 output_w32(0x13800000 | rm_imm6_rn_rd(rs, imm, rs, rt));
781 static void emit_signextend16(u_int rs, u_int rt)
783 assem_debug("sxth %s,%s\n", regname[rt], regname[rs]);
784 output_w32(0x13000000 | n_immr_imms_rn_rd(0, 0, 15, rs, rt));
787 static void emit_shl(u_int rs,u_int rshift,u_int rt)
789 assem_debug("lsl %s,%s,%s\n",regname[rt],regname[rs],regname[rshift]);
790 output_w32(0x1ac02000 | rm_rn_rd(rshift, rs, rt));
793 static void emit_shr(u_int rs,u_int rshift,u_int rt)
795 assem_debug("lsr %s,%s,%s\n",regname[rt],regname[rs],regname[rshift]);
796 output_w32(0x1ac02400 | rm_rn_rd(rshift, rs, rt));
799 static void emit_sar(u_int rs,u_int rshift,u_int rt)
801 assem_debug("asr %s,%s,%s\n",regname[rt],regname[rs],regname[rshift]);
802 output_w32(0x1ac02800 | rm_rn_rd(rshift, rs, rt));
805 static void emit_cmpimm(u_int rs, u_int imm)
808 assem_debug("cmp %s,%#x\n", regname[rs], imm);
809 output_w32(0x71000000 | imm12_rn_rd(imm, rs, WZR));
811 else if (-imm < 4096) {
812 assem_debug("cmn %s,%#x\n", regname[rs], imm);
813 output_w32(0x31000000 | imm12_rn_rd(-imm, rs, WZR));
815 else if (imm < 16777216 && !(imm & 0xfff)) {
816 assem_debug("cmp %s,#%#x\n", regname[rs], imm);
817 output_w32(0x71400000 | imm12_rn_rd(imm >> 12, rs, WZR));
820 host_tempreg_acquire();
821 emit_movimm(imm, HOST_TEMPREG);
822 assem_debug("cmp %s,%s\n", regname[rs], regname[HOST_TEMPREG]);
823 output_w32(0x6b000000 | rm_rn_rd(HOST_TEMPREG, rs, WZR));
824 host_tempreg_release();
828 static void emit_cmov_imm(u_int cond0, u_int cond1, u_int imm, u_int rt)
830 assert(imm == 0 || imm == 1);
831 assert(cond0 < 0x10);
832 assert(cond1 < 0x10);
834 assem_debug("csinc %s,%s,%s,%s\n",regname[rt],regname[rt],regname[WZR],condname[cond1]);
835 output_w32(0x1a800400 | (cond1 << 12) | rm_rn_rd(WZR, rt, rt));
837 assem_debug("csel %s,%s,%s,%s\n",regname[rt],regname[WZR],regname[rt],condname[cond0]);
838 output_w32(0x1a800000 | (cond0 << 12) | rm_rn_rd(rt, WZR, rt));
842 static void emit_cmovne_imm(u_int imm,u_int rt)
844 emit_cmov_imm(COND_NE, COND_EQ, imm, rt);
847 static void emit_cmovl_imm(u_int imm,u_int rt)
849 emit_cmov_imm(COND_LT, COND_GE, imm, rt);
852 static void emit_cmovb_imm(int imm,u_int rt)
854 emit_cmov_imm(COND_CC, COND_CS, imm, rt);
857 static void emit_cmoveq_reg(u_int rs,u_int rt)
859 assem_debug("csel %s,%s,%s,eq\n",regname[rt],regname[rs],regname[rt]);
860 output_w32(0x1a800000 | (COND_EQ << 12) | rm_rn_rd(rt, rs, rt));
863 static void emit_cmovne_reg(u_int rs,u_int rt)
865 assem_debug("csel %s,%s,%s,ne\n",regname[rt],regname[rs],regname[rt]);
866 output_w32(0x1a800000 | (COND_NE << 12) | rm_rn_rd(rt, rs, rt));
869 static void emit_cmovl_reg(u_int rs,u_int rt)
871 assem_debug("csel %s,%s,%s,lt\n",regname[rt],regname[rs],regname[rt]);
872 output_w32(0x1a800000 | (COND_LT << 12) | rm_rn_rd(rt, rs, rt));
875 static void emit_cmovb_reg(u_int rs,u_int rt)
877 assem_debug("csel %s,%s,%s,cc\n",regname[rt],regname[rs],regname[rt]);
878 output_w32(0x1a800000 | (COND_CC << 12) | rm_rn_rd(rt, rs, rt));
881 static void emit_cmovs_reg(u_int rs,u_int rt)
883 assem_debug("csel %s,%s,%s,mi\n",regname[rt],regname[rs],regname[rt]);
884 output_w32(0x1a800000 | (COND_MI << 12) | rm_rn_rd(rt, rs, rt));
887 static void emit_csinvle_reg(u_int rs1,u_int rs2,u_int rt)
889 assem_debug("csinv %s,%s,%s,le\n",regname[rt],regname[rs1],regname[rs2]);
890 output_w32(0x5a800000 | (COND_LE << 12) | rm_rn_rd(rs2, rs1, rt));
893 static void emit_csinvne_reg(u_int rs1,u_int rs2,u_int rt)
895 assem_debug("csinv %s,%s,%s,ne\n",regname[rt],regname[rs1],regname[rs2]);
896 output_w32(0x5a800000 | (COND_NE << 12) | rm_rn_rd(rs2, rs1, rt));
899 static void emit_slti32(u_int rs,int imm,u_int rt)
901 if(rs!=rt) emit_zeroreg(rt);
903 if(rs==rt) emit_movimm(0,rt);
904 emit_cmovl_imm(1,rt);
907 static void emit_sltiu32(u_int rs,int imm,u_int rt)
909 if(rs!=rt) emit_zeroreg(rt);
911 if(rs==rt) emit_movimm(0,rt);
912 emit_cmovb_imm(1,rt);
915 static void emit_cmp(u_int rs,u_int rt)
917 assem_debug("cmp %s,%s\n",regname[rs],regname[rt]);
918 output_w32(0x6b000000 | rm_rn_rd(rt, rs, WZR));
921 static void emit_cmpcs(u_int rs,u_int rt)
923 assem_debug("ccmp %s,%s,#0,cs\n",regname[rs],regname[rt]);
924 output_w32(0x7a400000 | (COND_CS << 12) | rm_rn_rd(rt, rs, 0));
927 static void emit_set_gz32(u_int rs, u_int rt)
929 //assem_debug("set_gz32\n");
932 emit_cmovl_imm(0,rt);
935 static void emit_set_nz32(u_int rs, u_int rt)
937 //assem_debug("set_nz32\n");
938 if(rs!=rt) emit_mov(rs,rt);
940 emit_cmovne_imm(1,rt);
943 static void emit_set_if_less32(u_int rs1, u_int rs2, u_int rt)
945 //assem_debug("set if less (%%%s,%%%s),%%%s\n",regname[rs1],regname[rs2],regname[rt]);
946 if(rs1!=rt&&rs2!=rt) emit_zeroreg(rt);
948 if(rs1==rt||rs2==rt) emit_movimm(0,rt);
949 emit_cmovl_imm(1,rt);
952 static void emit_set_if_carry32(u_int rs1, u_int rs2, u_int rt)
954 //assem_debug("set if carry (%%%s,%%%s),%%%s\n",regname[rs1],regname[rs2],regname[rt]);
955 if(rs1!=rt&&rs2!=rt) emit_zeroreg(rt);
957 if(rs1==rt||rs2==rt) emit_movimm(0,rt);
958 emit_cmovb_imm(1,rt);
961 static int can_jump_or_call(const void *a)
963 intptr_t diff = (u_char *)a - out;
964 return (-134217728 <= diff && diff <= 134217727);
967 static void emit_call(const void *a)
969 intptr_t diff = (u_char *)a - out;
970 assem_debug("bl %p (%p+%lx)%s\n", a, out, diff, func_name(a));
972 if (-134217728 <= diff && diff <= 134217727)
973 output_w32(0x94000000 | ((diff >> 2) & 0x03ffffff));
978 static void emit_jmp(const void *a)
980 assem_debug("b %p (%p+%lx)%s\n", a, out, (u_char *)a - out, func_name(a));
981 u_int offset = genjmp(a);
982 output_w32(0x14000000 | offset);
985 static void emit_jne(const void *a)
987 assem_debug("bne %p\n", a);
988 u_int offset = genjmpcc(a);
989 output_w32(0x54000000 | (offset << 5) | COND_NE);
992 static void emit_jeq(const void *a)
994 assem_debug("beq %p\n", a);
995 u_int offset = genjmpcc(a);
996 output_w32(0x54000000 | (offset << 5) | COND_EQ);
999 static void emit_js(const void *a)
1001 assem_debug("bmi %p\n", a);
1002 u_int offset = genjmpcc(a);
1003 output_w32(0x54000000 | (offset << 5) | COND_MI);
1006 static void emit_jns(const void *a)
1008 assem_debug("bpl %p\n", a);
1009 u_int offset = genjmpcc(a);
1010 output_w32(0x54000000 | (offset << 5) | COND_PL);
1013 static void emit_jl(const void *a)
1015 assem_debug("blt %p\n", a);
1016 u_int offset = genjmpcc(a);
1017 output_w32(0x54000000 | (offset << 5) | COND_LT);
1020 static void emit_jge(const void *a)
1022 assem_debug("bge %p\n", a);
1023 u_int offset = genjmpcc(a);
1024 output_w32(0x54000000 | (offset << 5) | COND_GE);
1027 static void emit_jo(const void *a)
1029 assem_debug("bvs %p\n", a);
1030 u_int offset = genjmpcc(a);
1031 output_w32(0x54000000 | (offset << 5) | COND_VS);
1034 static void emit_jno(const void *a)
1036 assem_debug("bvc %p\n", a);
1037 u_int offset = genjmpcc(a);
1038 output_w32(0x54000000 | (offset << 5) | COND_VC);
1041 static void emit_jc(const void *a)
1043 assem_debug("bcs %p\n", a);
1044 u_int offset = genjmpcc(a);
1045 output_w32(0x54000000 | (offset << 5) | COND_CS);
1048 static void emit_cb(u_int isnz, u_int is64, const void *a, u_int r)
1050 assem_debug("cb%sz %s,%p\n", isnz?"n":"", is64?regname64[r]:regname[r], a);
1051 u_int offset = genjmpcc(a);
1052 is64 = is64 ? 0x80000000 : 0;
1053 isnz = isnz ? 0x01000000 : 0;
1054 output_w32(0x34000000 | is64 | isnz | imm19_rt(offset, r));
1057 static void *emit_cbz(u_int r, const void *a)
1060 emit_cb(0, 0, a, r);
1064 static void emit_jmpreg(u_int r)
1066 assem_debug("br %s\n", regname64[r]);
1067 output_w32(0xd61f0000 | rm_rn_rd(0, r, 0));
1070 static void emit_retreg(u_int r)
1072 assem_debug("ret %s\n", r == LR ? "" : regname64[r]);
1073 output_w32(0xd65f0000 | rm_rn_rd(0, r, 0));
1076 static void emit_ret(void)
1081 static void emit_adr(void *addr, u_int rt)
1083 intptr_t offset = (u_char *)addr - out;
1084 assert(-1048576 <= offset && offset < 1048576);
1086 assem_debug("adr x%d,#%#lx\n", rt, offset);
1087 output_w32(0x10000000 | ((offset&0x3) << 29) | (((offset>>2)&0x7ffff) << 5) | rt);
1090 static void emit_adrp(void *addr, u_int rt)
1092 intptr_t offset = ((intptr_t)addr & ~0xfffl) - ((intptr_t)out & ~0xfffl);
1093 assert(-4294967296l <= offset && offset < 4294967296l);
1096 assem_debug("adrp %s,#%#lx(000)\n",regname64[rt],offset);
1097 output_w32(0x90000000 | ((offset&0x3)<<29) | (((offset>>2)&0x7ffff)<<5) | rt);
1100 static void emit_readword_indexed(int offset, u_int rs, u_int rt)
1102 assem_debug("ldur %s,[%s+%#x]\n",regname[rt],regname64[rs],offset);
1103 assert(-256 <= offset && offset < 256);
1104 output_w32(0xb8400000 | imm9_rn_rt(offset&0x1ff, rs, rt));
1107 static void emit_strb_dualindexed(u_int rs1, u_int rs2, u_int rt)
1109 assem_debug("strb %s, [%s,%s]\n",regname[rt],regname64[rs1],regname[rs2]);
1110 output_w32(0x38204800 | rm_rn_rd(rs2, rs1, rt));
1113 static void emit_strh_dualindexed(u_int rs1, u_int rs2, u_int rt)
1115 assem_debug("strh %s, [%s,%s]\n",regname[rt],regname64[rs1],regname[rs2]);
1116 output_w32(0x78204800 | rm_rn_rd(rs2, rs1, rt));
1119 static void emit_str_dualindexed(u_int rs1, u_int rs2, u_int rt)
1121 assem_debug("str %s, [%s,%s]\n",regname[rt],regname64[rs1],regname[rs2]);
1122 output_w32(0xb8204800 | rm_rn_rd(rs2, rs1, rt));
1125 static void emit_readdword_dualindexedx8(u_int rs1, u_int rs2, u_int rt)
1127 assem_debug("ldr %s, [%s,%s, uxtw #3]\n",regname64[rt],regname64[rs1],regname[rs2]);
1128 output_w32(0xf8605800 | rm_rn_rd(rs2, rs1, rt));
1130 #define emit_readptr_dualindexedx_ptrlen emit_readdword_dualindexedx8
1132 static void emit_ldrb_dualindexed(u_int rs1, u_int rs2, u_int rt)
1134 assem_debug("ldrb %s, [%s,%s]\n",regname[rt],regname64[rs1],regname[rs2]);
1135 output_w32(0x38604800 | rm_rn_rd(rs2, rs1, rt));
1138 static void emit_ldrsb_dualindexed(u_int rs1, u_int rs2, u_int rt)
1140 assem_debug("ldrsb %s, [%s,%s]\n",regname[rt],regname64[rs1],regname[rs2]);
1141 output_w32(0x38a04800 | rm_rn_rd(rs2, rs1, rt));
1144 static void emit_ldrh_dualindexed(u_int rs1, u_int rs2, u_int rt)
1146 assem_debug("ldrh %s, [%s,%s, uxtw]\n",regname[rt],regname64[rs1],regname[rs2]);
1147 output_w32(0x78604800 | rm_rn_rd(rs2, rs1, rt));
1150 static void emit_ldrsh_dualindexed(u_int rs1, u_int rs2, u_int rt)
1152 assem_debug("ldrsh %s, [%s,%s, uxtw]\n",regname[rt],regname64[rs1],regname[rs2]);
1153 output_w32(0x78a04800 | rm_rn_rd(rs2, rs1, rt));
1156 static void emit_ldr_dualindexed(u_int rs1, u_int rs2, u_int rt)
1158 assem_debug("ldr %s, [%s,%s, uxtw]\n",regname[rt],regname64[rs1],regname[rs2]);
1159 output_w32(0xb8604800 | rm_rn_rd(rs2, rs1, rt));
1162 static void emit_movsbl_indexed(int offset, u_int rs, u_int rt)
1164 assem_debug("ldursb %s,[%s+%#x]\n",regname[rt],regname64[rs],offset);
1165 assert(-256 <= offset && offset < 256);
1166 output_w32(0x38c00000 | imm9_rn_rt(offset&0x1ff, rs, rt));
1169 static void emit_movswl_indexed(int offset, u_int rs, u_int rt)
1171 assem_debug("ldursh %s,[%s+%#x]\n",regname[rt],regname64[rs],offset);
1172 assert(-256 <= offset && offset < 256);
1173 output_w32(0x78c00000 | imm9_rn_rt(offset&0x1ff, rs, rt));
1176 static void emit_movzbl_indexed(int offset, u_int rs, u_int rt)
1178 assem_debug("ldurb %s,[%s+%#x]\n",regname[rt],regname64[rs],offset);
1179 assert(-256 <= offset && offset < 256);
1180 output_w32(0x38400000 | imm9_rn_rt(offset&0x1ff, rs, rt));
1183 static void emit_movzwl_indexed(int offset, u_int rs, u_int rt)
1185 assem_debug("ldurh %s,[%s+%#x]\n",regname[rt],regname64[rs],offset);
1186 assert(-256 <= offset && offset < 256);
1187 output_w32(0x78400000 | imm9_rn_rt(offset&0x1ff, rs, rt));
1190 static void emit_writeword_indexed(u_int rt, int offset, u_int rs)
1192 if (!(offset & 3) && (u_int)offset <= 16380) {
1193 assem_debug("str %s,[%s+%#x]\n", regname[rt], regname[rs], offset);
1194 output_w32(0xb9000000 | imm12_rn_rd(offset >> 2, rs, rt));
1196 else if (-256 <= offset && offset < 256) {
1197 assem_debug("stur %s,[%s+%#x]\n", regname[rt], regname[rs], offset);
1198 output_w32(0xb8000000 | imm9_rn_rt(offset & 0x1ff, rs, rt));
1204 static void emit_writehword_indexed(u_int rt, int offset, u_int rs)
1206 if (!(offset & 1) && (u_int)offset <= 8190) {
1207 assem_debug("strh %s,[%s+%#x]\n", regname[rt], regname64[rs], offset);
1208 output_w32(0x79000000 | imm12_rn_rd(offset >> 1, rs, rt));
1210 else if (-256 <= offset && offset < 256) {
1211 assem_debug("sturh %s,[%s+%#x]\n", regname[rt], regname64[rs], offset);
1212 output_w32(0x78000000 | imm9_rn_rt(offset & 0x1ff, rs, rt));
1218 static void emit_writebyte_indexed(u_int rt, int offset, u_int rs)
1220 if ((u_int)offset < 4096) {
1221 assem_debug("strb %s,[%s+%#x]\n", regname[rt], regname64[rs], offset);
1222 output_w32(0x39000000 | imm12_rn_rd(offset, rs, rt));
1224 else if (-256 <= offset && offset < 256) {
1225 assem_debug("sturb %s,[%s+%#x]\n", regname[rt], regname64[rs], offset);
1226 output_w32(0x38000000 | imm9_rn_rt(offset & 0x1ff, rs, rt));
1232 static void emit_umull(u_int rs1, u_int rs2, u_int rt)
1234 assem_debug("umull %s,%s,%s\n",regname64[rt],regname[rs1],regname[rs2]);
1235 output_w32(0x9ba00000 | rm_ra_rn_rd(rs2, WZR, rs1, rt));
1238 static void emit_smull(u_int rs1, u_int rs2, u_int rt)
1240 assem_debug("smull %s,%s,%s\n",regname64[rt],regname[rs1],regname[rs2]);
1241 output_w32(0x9b200000 | rm_ra_rn_rd(rs2, WZR, rs1, rt));
1244 static void emit_msub(u_int rs1, u_int rs2, u_int rs3, u_int rt)
1246 assem_debug("msub %s,%s,%s,%s\n",regname[rt],regname[rs1],regname[rs2],regname[rs3]);
1247 output_w32(0x1b008000 | rm_ra_rn_rd(rs2, rs3, rs1, rt));
1250 static void emit_sdiv(u_int rs1, u_int rs2, u_int rt)
1252 assem_debug("sdiv %s,%s,%s\n",regname[rt],regname[rs1],regname[rs2]);
1253 output_w32(0x1ac00c00 | rm_rn_rd(rs2, rs1, rt));
1256 static void emit_udiv(u_int rs1, u_int rs2, u_int rt)
1258 assem_debug("udiv %s,%s,%s\n",regname[rt],regname[rs1],regname[rs2]);
1259 output_w32(0x1ac00800 | rm_rn_rd(rs2, rs1, rt));
1262 static void emit_clz(u_int rs, u_int rt)
1264 assem_debug("clz %s,%s\n",regname[rt],regname[rs]);
1265 output_w32(0x5ac01000 | rn_rd(rs, rt));
1268 // special case for checking invalid_code
1269 static void emit_ldrb_indexedsr12_reg(u_int rbase, u_int r, u_int rt)
1271 emit_shrimm(r, 12, rt);
1272 assem_debug("ldrb %s,[%s,%s,uxtw]\n",regname[rt],regname64[rbase],regname[rt]);
1273 output_w32(0x38604800 | rm_rn_rd(rt, rbase, rt));
1276 // special for loadlr_assemble, rs2 is destroyed
1277 static void emit_bic_lsl(u_int rs1,u_int rs2,u_int shift,u_int rt)
1279 emit_shl(rs2, shift, rs2);
1280 emit_bic(rs1, rs2, rt);
1283 static void emit_bic_lsr(u_int rs1,u_int rs2,u_int shift,u_int rt)
1285 emit_shr(rs2, shift, rs2);
1286 emit_bic(rs1, rs2, rt);
1289 static void emit_ldst(int is_st, int is64, u_int rt, u_int rn, u_int ofs)
1291 u_int op = 0xb9000000;
1292 unused const char *ldst = is_st ? "st" : "ld";
1293 unused char rp = is64 ? 'x' : 'w';
1294 assem_debug("%sr %c%d,[x%d,#%#x]\n", ldst, rp, rt, rn, ofs);
1295 is64 = is64 ? 1 : 0;
1296 assert((ofs & ((1 << (2+is64)) - 1)) == 0);
1297 ofs = (ofs >> (2+is64));
1298 if (!is_st) op |= 0x00400000;
1299 if (is64) op |= 0x40000000;
1300 output_w32(op | imm12_rn_rd(ofs, rn, rt));
1303 static void emit_ldstp(int is_st, int is64, u_int rt1, u_int rt2, u_int rn, int ofs)
1305 u_int op = 0x29000000;
1306 unused const char *ldst = is_st ? "st" : "ld";
1307 unused char rp = is64 ? 'x' : 'w';
1308 assem_debug("%sp %c%d,%c%d,[x%d,#%#x]\n", ldst, rp, rt1, rp, rt2, rn, ofs);
1309 is64 = is64 ? 1 : 0;
1310 assert((ofs & ((1 << (2+is64)) - 1)) == 0);
1311 ofs = (ofs >> (2+is64));
1312 assert(-64 <= ofs && ofs <= 63);
1314 if (!is_st) op |= 0x00400000;
1315 if (is64) op |= 0x80000000;
1316 output_w32(op | imm7_rt2_rn_rt(ofs, rt2, rn, rt1));
1319 static void save_load_regs_all(int is_store, u_int reglist)
1323 for (r = 0; reglist; r++, reglist >>= 1) {
1327 emit_ldstp(is_store, 1, pair[0], pair[1], SP, SSP_CALLEE_REGS + ofs);
1333 emit_ldst(is_store, 1, pair[0], SP, SSP_CALLEE_REGS + ofs);
1336 assert(ofs <= SSP_CALLER_REGS);
1339 // Save registers before function call
1340 static void save_regs(u_int reglist)
1342 reglist &= CALLER_SAVE_REGS; // only save the caller-save registers
1343 save_load_regs_all(1, reglist);
1346 // Restore registers after function call
1347 static void restore_regs(u_int reglist)
1349 reglist &= CALLER_SAVE_REGS;
1350 save_load_regs_all(0, reglist);
1353 /* Stubs/epilogue */
1355 static void literal_pool(int n)
1360 static void literal_pool_jumpover(int n)
1364 // parsed by get_pointer, find_extjump_insn
1365 static void emit_extjump(u_char *addr, u_int target)
1367 assert(((addr[3]&0xfc)==0x14) || ((addr[3]&0xff)==0x54)); // b or b.cond
1369 emit_movz(target & 0xffff, 0);
1370 emit_movk_lsl16(target >> 16, 0);
1372 // addr is in the current recompiled block (max 256k)
1373 // offset shouldn't exceed +/-1MB
1375 emit_far_jump(dyna_linker);
1378 static void check_extjump2(void *src)
1381 assert((ptr[0] & 0xffe0001f) == 0x52800000); // movz r0, #val
1385 // put rt_val into rt, potentially making use of rs with value rs_val
1386 static void emit_movimm_from(u_int rs_val, u_int rs, u_int rt_val, u_int rt)
1388 int diff = rt_val - rs_val;
1389 if ((-4096 < diff && diff < 4096)
1390 || (-16777216 < diff && diff < 16777216 && !(diff & 0xfff)))
1391 emit_addimm(rs, diff, rt);
1392 else if (rt_val == ~rs_val)
1394 else if (is_rotated_mask(rs_val ^ rt_val))
1395 emit_xorimm(rs, rs_val ^ rt_val, rt);
1397 emit_movimm(rt_val, rt);
1400 // return 1 if the above function can do it's job cheaply
1401 static int is_similar_value(u_int v1, u_int v2)
1404 return (-4096 < diff && diff < 4096)
1405 || (-16777216 < diff && diff < 16777216 && !(diff & 0xfff))
1407 || is_rotated_mask(v1 ^ v2);
1410 static void emit_movimm_from64(u_int rs_val, u_int rs, uintptr_t rt_val, u_int rt)
1412 if (rt_val < 0x100000000ull) {
1413 emit_movimm_from(rs_val, rs, rt_val, rt);
1416 // just move the whole thing. At least on Linux all addresses
1417 // seem to be 48bit, so 3 insns - not great not terrible
1418 emit_movimm64(rt_val, rt);
1422 static void pass_args64(u_int a0, u_int a1)
1426 emit_mov64(a0,2); emit_mov64(a1,1); emit_mov64(2,0);
1428 else if(a0!=0&&a1==0) {
1430 if (a0>=0) emit_mov64(a0,0);
1433 if(a0>=0&&a0!=0) emit_mov64(a0,0);
1434 if(a1>=0&&a1!=1) emit_mov64(a1,1);
1438 static void loadstore_extend(enum stub_type type, u_int rs, u_int rt)
1441 case LOADB_STUB: emit_sbfm(rs, 7, rt); break;
1443 case STOREB_STUB: emit_ubfm(rs, 7, rt); break;
1444 case LOADH_STUB: emit_sbfm(rs, 15, rt); break;
1446 case STOREH_STUB: emit_ubfm(rs, 15, rt); break;
1448 case STOREW_STUB: if (rs != rt) emit_mov(rs, rt); break;
1453 #include "pcsxmem.h"
1454 //#include "pcsxmem_inline.c"
1456 static void do_readstub(int n)
1458 assem_debug("do_readstub %x\n",start+stubs[n].a*4);
1459 set_jump_target(stubs[n].addr, out);
1460 enum stub_type type = stubs[n].type;
1462 int rs = stubs[n].b;
1463 const struct regstat *i_regs = (void *)stubs[n].c;
1464 u_int reglist = stubs[n].e;
1465 const signed char *i_regmap = i_regs->regmap;
1467 if(dops[i].itype==C2LS||dops[i].itype==LOADLR) {
1468 rt=get_reg(i_regmap,FTEMP);
1470 rt=get_reg(i_regmap,dops[i].rt1);
1473 int r,temp=-1,temp2=HOST_TEMPREG,regs_saved=0;
1474 void *restore_jump = NULL, *handler_jump = NULL;
1476 for (r = 0; r < HOST_CCREG; r++) {
1477 if (r != EXCLUDE_REG && ((1 << r) & reglist) == 0) {
1482 if(rt>=0&&dops[i].rt1!=0)
1489 if((regs_saved||(reglist&2)==0)&&temp!=1&&rs!=1)
1491 emit_readdword(&mem_rtab,temp);
1492 emit_shrimm(rs,12,temp2);
1493 emit_readdword_dualindexedx8(temp,temp2,temp2);
1494 emit_adds64(temp2,temp2,temp2);
1497 if(dops[i].itype==C2LS||(rt>=0&&dops[i].rt1!=0)) {
1499 case LOADB_STUB: emit_ldrsb_dualindexed(temp2,rs,rt); break;
1500 case LOADBU_STUB: emit_ldrb_dualindexed(temp2,rs,rt); break;
1501 case LOADH_STUB: emit_ldrsh_dualindexed(temp2,rs,rt); break;
1502 case LOADHU_STUB: emit_ldrh_dualindexed(temp2,rs,rt); break;
1503 case LOADW_STUB: emit_ldr_dualindexed(temp2,rs,rt); break;
1509 emit_jmp(0); // jump to reg restore
1512 emit_jmp(stubs[n].retaddr); // return address
1513 set_jump_target(handler_jump, out);
1518 if(type==LOADB_STUB||type==LOADBU_STUB)
1519 handler=jump_handler_read8;
1520 if(type==LOADH_STUB||type==LOADHU_STUB)
1521 handler=jump_handler_read16;
1522 if(type==LOADW_STUB)
1523 handler=jump_handler_read32;
1525 pass_args64(rs,temp2);
1526 int cc=get_reg(i_regmap,CCREG);
1528 emit_loadreg(CCREG,2);
1529 emit_addimm(cc<0?2:cc,(int)stubs[n].d,2);
1530 emit_far_call(handler);
1531 // (no cycle reload after read)
1532 if(dops[i].itype==C2LS||(rt>=0&&dops[i].rt1!=0)) {
1533 loadstore_extend(type,0,rt);
1536 set_jump_target(restore_jump, out);
1537 restore_regs(reglist);
1538 emit_jmp(stubs[n].retaddr);
1541 static void inline_readstub(enum stub_type type, int i, u_int addr,
1542 const signed char regmap[], int target, int adj, u_int reglist)
1544 int rs=get_reg(regmap,target);
1545 int rt=get_reg(regmap,target);
1546 if(rs<0) rs=get_reg_temp(regmap);
1549 uintptr_t host_addr = 0;
1551 int cc=get_reg(regmap,CCREG);
1552 //if(pcsx_direct_read(type,addr,adj,cc,target?rs:-1,rt))
1554 handler = get_direct_memhandler(mem_rtab, addr, type, &host_addr);
1555 if (handler == NULL) {
1556 if(rt<0||dops[i].rt1==0)
1558 if (addr != host_addr)
1559 emit_movimm_from64(addr, rs, host_addr, rs);
1561 case LOADB_STUB: emit_movsbl_indexed(0,rs,rt); break;
1562 case LOADBU_STUB: emit_movzbl_indexed(0,rs,rt); break;
1563 case LOADH_STUB: emit_movswl_indexed(0,rs,rt); break;
1564 case LOADHU_STUB: emit_movzwl_indexed(0,rs,rt); break;
1565 case LOADW_STUB: emit_readword_indexed(0,rs,rt); break;
1570 is_dynamic = pcsxmem_is_handler_dynamic(addr);
1572 if(type==LOADB_STUB||type==LOADBU_STUB)
1573 handler=jump_handler_read8;
1574 if(type==LOADH_STUB||type==LOADHU_STUB)
1575 handler=jump_handler_read16;
1576 if(type==LOADW_STUB)
1577 handler=jump_handler_read32;
1580 // call a memhandler
1581 if(rt>=0&&dops[i].rt1!=0)
1585 emit_movimm(addr,0);
1589 emit_loadreg(CCREG,2);
1590 emit_addimm(cc<0?2:cc,adj,2);
1592 uintptr_t l1 = ((uintptr_t *)mem_rtab)[addr>>12] << 1;
1593 intptr_t offset = (l1 & ~0xfffl) - ((intptr_t)out & ~0xfffl);
1594 if (-4294967296l <= offset && offset < 4294967296l) {
1595 emit_adrp((void *)l1, 1);
1596 emit_addimm64(1, l1 & 0xfff, 1);
1599 emit_movimm64(l1, 1);
1602 emit_far_call(do_memhandler_pre);
1604 emit_far_call(handler);
1606 // (no cycle reload after read)
1607 if(rt>=0&&dops[i].rt1!=0)
1608 loadstore_extend(type, 0, rt);
1609 restore_regs(reglist);
1612 static void do_writestub(int n)
1614 assem_debug("do_writestub %x\n",start+stubs[n].a*4);
1615 set_jump_target(stubs[n].addr, out);
1616 enum stub_type type=stubs[n].type;
1619 struct regstat *i_regs=(struct regstat *)stubs[n].c;
1620 u_int reglist=stubs[n].e;
1621 signed char *i_regmap=i_regs->regmap;
1623 if(dops[i].itype==C2LS) {
1624 rt=get_reg(i_regmap,r=FTEMP);
1626 rt=get_reg(i_regmap,r=dops[i].rs2);
1630 int rtmp,temp=-1,temp2,regs_saved=0;
1631 void *restore_jump = NULL, *handler_jump = NULL;
1632 int reglist2=reglist|(1<<rs)|(1<<rt);
1633 for (rtmp = 0; rtmp < HOST_CCREG; rtmp++) {
1634 if (rtmp != EXCLUDE_REG && ((1 << rtmp) & reglist) == 0) {
1642 for(rtmp=0;rtmp<=3;rtmp++)
1643 if(rtmp!=rs&&rtmp!=rt)
1646 if((regs_saved||(reglist2&8)==0)&&temp!=3&&rs!=3&&rt!=3)
1649 host_tempreg_acquire();
1652 emit_readdword(&mem_wtab,temp);
1653 emit_shrimm(rs,12,temp2);
1654 emit_readdword_dualindexedx8(temp,temp2,temp2);
1655 emit_adds64(temp2,temp2,temp2);
1659 case STOREB_STUB: emit_strb_dualindexed(temp2,rs,rt); break;
1660 case STOREH_STUB: emit_strh_dualindexed(temp2,rs,rt); break;
1661 case STOREW_STUB: emit_str_dualindexed(temp2,rs,rt); break;
1666 emit_jmp(0); // jump to reg restore
1669 emit_jmp(stubs[n].retaddr); // return address (invcode check)
1670 set_jump_target(handler_jump, out);
1676 case STOREB_STUB: handler=jump_handler_write8; break;
1677 case STOREH_STUB: handler=jump_handler_write16; break;
1678 case STOREW_STUB: handler=jump_handler_write32; break;
1684 emit_mov64(temp2,3);
1685 host_tempreg_release();
1687 int cc=get_reg(i_regmap,CCREG);
1689 emit_loadreg(CCREG,2);
1690 emit_addimm(cc<0?2:cc,(int)stubs[n].d,2);
1691 // returns new cycle_count
1692 emit_far_call(handler);
1693 emit_addimm(0,-(int)stubs[n].d,cc<0?2:cc);
1695 emit_storereg(CCREG,2);
1697 set_jump_target(restore_jump, out);
1698 restore_regs(reglist);
1699 emit_jmp(stubs[n].retaddr);
1702 static void inline_writestub(enum stub_type type, int i, u_int addr,
1703 const signed char regmap[], int target, int adj, u_int reglist)
1705 int rs = get_reg_temp(regmap);
1706 int rt = get_reg(regmap,target);
1709 uintptr_t host_addr = 0;
1710 void *handler = get_direct_memhandler(mem_wtab, addr, type, &host_addr);
1711 if (handler == NULL) {
1712 if (addr != host_addr)
1713 emit_movimm_from64(addr, rs, host_addr, rs);
1715 case STOREB_STUB: emit_writebyte_indexed(rt, 0, rs); break;
1716 case STOREH_STUB: emit_writehword_indexed(rt, 0, rs); break;
1717 case STOREW_STUB: emit_writeword_indexed(rt, 0, rs); break;
1723 // call a memhandler
1725 emit_writeword(rs, &address); // some handlers still need it
1726 loadstore_extend(type, rt, 0);
1728 cc = cc_use = get_reg(regmap, CCREG);
1730 emit_loadreg(CCREG, (cc_use = 2));
1731 emit_addimm(cc_use, adj, 2);
1733 emit_far_call(do_memhandler_pre);
1734 emit_far_call(handler);
1735 emit_far_call(do_memhandler_post);
1736 emit_addimm(0, -adj, cc_use);
1738 emit_storereg(CCREG, cc_use);
1739 restore_regs(reglist);
1744 static void c2op_prologue(u_int op, int i, const struct regstat *i_regs, u_int reglist)
1746 save_load_regs_all(1, reglist);
1747 cop2_do_stall_check(op, i, i_regs, 0);
1750 emit_far_call(pcnt_gte_start);
1752 // pointer to cop2 regs
1753 emit_addimm64(FP, (u_char *)&psxRegs.CP2D.r[0] - (u_char *)&dynarec_local, 0);
1756 static void c2op_epilogue(u_int op,u_int reglist)
1760 emit_far_call(pcnt_gte_end);
1762 save_load_regs_all(0, reglist);
1765 static void c2op_assemble(int i, const struct regstat *i_regs)
1767 u_int c2op=source[i]&0x3f;
1768 u_int hr,reglist_full=0,reglist;
1769 int need_flags,need_ir;
1770 for(hr=0;hr<HOST_REGS;hr++) {
1771 if(i_regs->regmap[hr]>=0) reglist_full|=1<<hr;
1773 reglist=reglist_full&CALLER_SAVE_REGS;
1775 if (gte_handlers[c2op]!=NULL) {
1776 need_flags=!(gte_unneeded[i+1]>>63); // +1 because of how liveness detection works
1777 need_ir=(gte_unneeded[i+1]&0xe00)!=0xe00;
1778 assem_debug("gte op %08x, unneeded %016lx, need_flags %d, need_ir %d\n",
1779 source[i],gte_unneeded[i+1],need_flags,need_ir);
1780 if(HACK_ENABLED(NDHACK_GTE_NO_FLAGS))
1782 //int shift = (source[i] >> 19) & 1;
1783 //int lm = (source[i] >> 10) & 1;
1787 c2op_prologue(c2op, i, i_regs, reglist);
1788 emit_movimm(source[i],1); // opcode
1789 emit_writeword(1,&psxRegs.code);
1790 emit_far_call(need_flags?gte_handlers[c2op]:gte_handlers_nf[c2op]);
1793 c2op_epilogue(c2op,reglist);
1797 static void c2op_ctc2_31_assemble(signed char sl, signed char temp)
1799 //value = value & 0x7ffff000;
1800 //if (value & 0x7f87e000) value |= 0x80000000;
1801 emit_andimm(sl, 0x7fffe000, temp);
1802 emit_testimm(temp, 0xff87ffff);
1803 emit_andimm(sl, 0x7ffff000, temp);
1804 host_tempreg_acquire();
1805 emit_orimm(temp, 0x80000000, HOST_TEMPREG);
1806 emit_cmovne_reg(HOST_TEMPREG, temp);
1807 host_tempreg_release();
1808 assert(0); // testing needed
1811 static void do_mfc2_31_one(u_int copr,signed char temp)
1813 emit_readshword(®_cop2d[copr],temp);
1814 emit_bicsar_imm(temp,31,temp);
1815 emit_cmpimm(temp,0xf80);
1816 emit_csinvle_reg(temp,WZR,temp); // if (temp > 0xf80) temp = ~0;
1817 emit_andimm(temp,0xf80,temp);
1820 static void c2op_mfc2_29_assemble(signed char tl, signed char temp)
1823 host_tempreg_acquire();
1824 temp = HOST_TEMPREG;
1826 do_mfc2_31_one(9,temp);
1827 emit_shrimm(temp,7,tl);
1828 do_mfc2_31_one(10,temp);
1829 emit_orrshr_imm(temp,2,tl);
1830 do_mfc2_31_one(11,temp);
1831 emit_orrshl_imm(temp,3,tl);
1832 emit_writeword(tl,®_cop2d[29]);
1834 if (temp == HOST_TEMPREG)
1835 host_tempreg_release();
1838 static void multdiv_assemble_arm64(int i, const struct regstat *i_regs)
1844 if(dops[i].rs1&&dops[i].rs2)
1846 switch(dops[i].opcode2)
1851 signed char m1=get_reg(i_regs->regmap,dops[i].rs1);
1852 signed char m2=get_reg(i_regs->regmap,dops[i].rs2);
1853 signed char hi=get_reg(i_regs->regmap,HIREG);
1854 signed char lo=get_reg(i_regs->regmap,LOREG);
1860 if(dops[i].opcode2==0x18) // MULT
1861 emit_smull(m1,m2,hi);
1863 emit_umull(m1,m2,hi);
1866 emit_shrimm64(hi,32,hi);
1872 signed char numerator=get_reg(i_regs->regmap,dops[i].rs1);
1873 signed char denominator=get_reg(i_regs->regmap,dops[i].rs2);
1874 signed char quotient=get_reg(i_regs->regmap,LOREG);
1875 signed char remainder=get_reg(i_regs->regmap,HIREG);
1876 assert(numerator>=0);
1877 assert(denominator>=0);
1878 assert(quotient>=0);
1879 assert(remainder>=0);
1881 if (dops[i].opcode2 == 0x1A) // DIV
1882 emit_sdiv(numerator,denominator,quotient);
1884 emit_udiv(numerator,denominator,quotient);
1885 emit_msub(quotient,denominator,numerator,remainder);
1887 // div 0 quotient (remainder is already correct)
1888 host_tempreg_acquire();
1889 if (dops[i].opcode2 == 0x1A) { // DIV
1890 emit_add_lsrimm(WZR,numerator,31,HOST_TEMPREG);
1891 emit_orn_asrimm(HOST_TEMPREG,numerator,31,HOST_TEMPREG);
1894 emit_movimm(~0,HOST_TEMPREG);
1895 emit_test(denominator,denominator);
1896 emit_cmoveq_reg(HOST_TEMPREG,quotient);
1897 host_tempreg_release();
1906 signed char hr=get_reg(i_regs->regmap,HIREG);
1907 signed char lr=get_reg(i_regs->regmap,LOREG);
1908 if ((dops[i].opcode2==0x1A || dops[i].opcode2==0x1B) && dops[i].rs2==0) // div 0
1911 signed char numerator = get_reg(i_regs->regmap, dops[i].rs1);
1912 assert(numerator >= 0);
1914 emit_mov(numerator,hr);
1916 if (dops[i].opcode2 == 0x1A) { // DIV
1917 emit_add_lsrimm(WZR,numerator,31,lr);
1918 emit_orn_asrimm(lr,numerator,31,lr);
1925 if (hr >= 0) emit_zeroreg(hr);
1926 if (lr >= 0) emit_movimm(~0,lr);
1929 else if ((dops[i].opcode2==0x1A || dops[i].opcode2==0x1B) && dops[i].rs1==0)
1931 signed char denominator = get_reg(i_regs->regmap, dops[i].rs2);
1932 assert(denominator >= 0);
1933 if (hr >= 0) emit_zeroreg(hr);
1936 emit_test(denominator, denominator);
1937 emit_csinvne_reg(lr, lr, lr);
1942 // Multiply by zero is zero.
1943 if (hr >= 0) emit_zeroreg(hr);
1944 if (lr >= 0) emit_zeroreg(lr);
1948 #define multdiv_assemble multdiv_assemble_arm64
1950 static void do_jump_vaddr(u_int rs)
1954 emit_far_call(ndrc_get_addr_ht);
1958 static void do_preload_rhash(u_int r) {
1959 // Don't need this for ARM. On x86, this puts the value 0xf8 into the
1960 // register. On ARM the hash can be done with a single instruction (below)
1963 static void do_preload_rhtbl(u_int ht) {
1964 emit_addimm64(FP, (u_char *)&mini_ht - (u_char *)&dynarec_local, ht);
1967 static void do_rhash(u_int rs,u_int rh) {
1968 emit_andimm(rs, 0xf8, rh);
1971 static void do_miniht_load(int ht, u_int rh) {
1972 emit_add64(ht, rh, ht);
1973 emit_ldst(0, 0, rh, ht, 0);
1976 static void do_miniht_jump(u_int rs, u_int rh, u_int ht) {
1982 set_jump_target(jaddr, out);
1983 assem_debug("ldr %s,[%s,#8]\n",regname64[ht], regname64[ht]);
1984 output_w32(0xf9400000 | imm12_rn_rd(8 >> 3, ht, ht));
1988 // parsed by set_jump_target?
1989 static void do_miniht_insert(u_int return_address,u_int rt,int temp) {
1990 emit_movz_lsl16((return_address>>16)&0xffff,rt);
1991 emit_movk(return_address&0xffff,rt);
1992 add_to_linker(out,return_address,1);
1994 emit_writedword(temp,&mini_ht[(return_address&0xFF)>>3][1]);
1995 emit_writeword(rt,&mini_ht[(return_address&0xFF)>>3][0]);
1998 static unused void clear_cache_arm64(char *start, char *end)
2000 // Don't rely on GCC's __clear_cache implementation, as it caches
2001 // icache/dcache cache line sizes, that can vary between cores on
2002 // big.LITTLE architectures.
2003 uint64_t addr, ctr_el0;
2004 static size_t icache_line_size = 0xffff, dcache_line_size = 0xffff;
2005 size_t isize, dsize;
2007 __asm__ volatile("mrs %0, ctr_el0" : "=r"(ctr_el0));
2008 isize = 4 << ((ctr_el0 >> 0) & 0xf);
2009 dsize = 4 << ((ctr_el0 >> 16) & 0xf);
2011 // use the global minimum cache line size
2012 icache_line_size = isize = icache_line_size < isize ? icache_line_size : isize;
2013 dcache_line_size = dsize = dcache_line_size < dsize ? dcache_line_size : dsize;
2015 /* If CTR_EL0.IDC is enabled, Data cache clean to the Point of Unification is
2016 not required for instruction to data coherence. */
2017 if ((ctr_el0 & (1 << 28)) == 0x0) {
2018 addr = (uint64_t)start & ~(uint64_t)(dsize - 1);
2019 for (; addr < (uint64_t)end; addr += dsize)
2020 // use "civac" instead of "cvau", as this is the suggested workaround for
2021 // Cortex-A53 errata 819472, 826319, 827319 and 824069.
2022 __asm__ volatile("dc civac, %0" : : "r"(addr) : "memory");
2024 __asm__ volatile("dsb ish" : : : "memory");
2026 /* If CTR_EL0.DIC is enabled, Instruction cache cleaning to the Point of
2027 Unification is not required for instruction to data coherence. */
2028 if ((ctr_el0 & (1 << 29)) == 0x0) {
2029 addr = (uint64_t)start & ~(uint64_t)(isize - 1);
2030 for (; addr < (uint64_t)end; addr += isize)
2031 __asm__ volatile("ic ivau, %0" : : "r"(addr) : "memory");
2033 __asm__ volatile("dsb ish" : : : "memory");
2036 __asm__ volatile("isb" : : : "memory");
2039 // CPU-architecture-specific initialization
2040 static void arch_init(void)
2042 uintptr_t diff = (u_char *)&ndrc->tramp.f - (u_char *)&ndrc->tramp.ops;
2043 struct tramp_insns *ops = NDRC_WRITE_OFFSET(ndrc->tramp.ops);
2045 assert(!(diff & 3));
2046 start_tcache_write(ops, (u_char *)ops + sizeof(ndrc->tramp.ops));
2047 for (i = 0; i < ARRAY_SIZE(ndrc->tramp.ops); i++) {
2048 ops[i].ldr = 0x58000000 | imm19_rt(diff >> 2, 17); // ldr x17, [=val]
2049 ops[i].br = 0xd61f0000 | rm_rn_rd(0, 17, 0); // br x17
2051 end_tcache_write(ops, (u_char *)ops + sizeof(ndrc->tramp.ops));
2054 // vim:shiftwidth=2:expandtab