1/*
2 * This file is part of the MicroPython project, http://micropython.org/
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (c) 2016 Damien P. George
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 * THE SOFTWARE.
25 */
26
27#include <stdio.h>
28#include <assert.h>
29
30#include "py/mpconfig.h"
31
32// wrapper around everything in this file
33#if MICROPY_EMIT_XTENSA || MICROPY_EMIT_INLINE_XTENSA || MICROPY_EMIT_XTENSAWIN
34
35#include "py/asmxtensa.h"
36
37#define WORD_SIZE (4)
38#define SIGNED_FIT8(x) ((((x) & 0xffffff80) == 0) || (((x) & 0xffffff80) == 0xffffff80))
39#define SIGNED_FIT12(x) ((((x) & 0xfffff800) == 0) || (((x) & 0xfffff800) == 0xfffff800))
40
41void asm_xtensa_end_pass(asm_xtensa_t *as) {
42 as->num_const = as->cur_const;
43 as->cur_const = 0;
44
45 #if 0
46 // make a hex dump of the machine code
47 if (as->base.pass == MP_ASM_PASS_EMIT) {
48 uint8_t *d = as->base.code_base;
49 printf("XTENSA ASM:");
50 for (int i = 0; i < ((as->base.code_size + 15) & ~15); ++i) {
51 if (i % 16 == 0) {
52 printf("\n%08x:", (uint32_t)&d[i]);
53 }
54 if (i % 2 == 0) {
55 printf(" ");
56 }
57 printf("%02x", d[i]);
58 }
59 printf("\n");
60 }
61 #endif
62}
63
64void asm_xtensa_entry(asm_xtensa_t *as, int num_locals) {
65 // jump over the constants
66 asm_xtensa_op_j(as, as->num_const * WORD_SIZE + 4 - 4);
67 mp_asm_base_get_cur_to_write_bytes(&as->base, 1); // padding/alignment byte
68 as->const_table = (uint32_t *)mp_asm_base_get_cur_to_write_bytes(&as->base, as->num_const * 4);
69
70 // adjust the stack-pointer to store a0, a12, a13, a14, a15 and locals, 16-byte aligned
71 as->stack_adjust = (((ASM_XTENSA_NUM_REGS_SAVED + num_locals) * WORD_SIZE) + 15) & ~15;
72 if (SIGNED_FIT8(-as->stack_adjust)) {
73 asm_xtensa_op_addi(as, ASM_XTENSA_REG_A1, ASM_XTENSA_REG_A1, -as->stack_adjust);
74 } else {
75 asm_xtensa_op_movi(as, ASM_XTENSA_REG_A9, as->stack_adjust);
76 asm_xtensa_op_sub(as, ASM_XTENSA_REG_A1, ASM_XTENSA_REG_A1, ASM_XTENSA_REG_A9);
77 }
78
79 // save return value (a0) and callee-save registers (a12, a13, a14, a15)
80 asm_xtensa_op_s32i_n(as, ASM_XTENSA_REG_A0, ASM_XTENSA_REG_A1, 0);
81 for (int i = 1; i < ASM_XTENSA_NUM_REGS_SAVED; ++i) {
82 asm_xtensa_op_s32i_n(as, ASM_XTENSA_REG_A11 + i, ASM_XTENSA_REG_A1, i);
83 }
84}
85
86void asm_xtensa_exit(asm_xtensa_t *as) {
87 // restore registers
88 for (int i = ASM_XTENSA_NUM_REGS_SAVED - 1; i >= 1; --i) {
89 asm_xtensa_op_l32i_n(as, ASM_XTENSA_REG_A11 + i, ASM_XTENSA_REG_A1, i);
90 }
91 asm_xtensa_op_l32i_n(as, ASM_XTENSA_REG_A0, ASM_XTENSA_REG_A1, 0);
92
93 // restore stack-pointer and return
94 if (SIGNED_FIT8(as->stack_adjust)) {
95 asm_xtensa_op_addi(as, ASM_XTENSA_REG_A1, ASM_XTENSA_REG_A1, as->stack_adjust);
96 } else {
97 asm_xtensa_op_movi(as, ASM_XTENSA_REG_A9, as->stack_adjust);
98 asm_xtensa_op_add_n(as, ASM_XTENSA_REG_A1, ASM_XTENSA_REG_A1, ASM_XTENSA_REG_A9);
99 }
100
101 asm_xtensa_op_ret_n(as);
102}
103
104void asm_xtensa_entry_win(asm_xtensa_t *as, int num_locals) {
105 // jump over the constants
106 asm_xtensa_op_j(as, as->num_const * WORD_SIZE + 4 - 4);
107 mp_asm_base_get_cur_to_write_bytes(&as->base, 1); // padding/alignment byte
108 as->const_table = (uint32_t *)mp_asm_base_get_cur_to_write_bytes(&as->base, as->num_const * 4);
109
110 as->stack_adjust = 32 + ((((ASM_XTENSA_NUM_REGS_SAVED_WIN + num_locals) * WORD_SIZE) + 15) & ~15);
111 asm_xtensa_op_entry(as, ASM_XTENSA_REG_A1, as->stack_adjust);
112 asm_xtensa_op_s32i_n(as, ASM_XTENSA_REG_A0, ASM_XTENSA_REG_A1, 0);
113}
114
115void asm_xtensa_exit_win(asm_xtensa_t *as) {
116 asm_xtensa_op_l32i_n(as, ASM_XTENSA_REG_A0, ASM_XTENSA_REG_A1, 0);
117 asm_xtensa_op_retw_n(as);
118}
119
120STATIC uint32_t get_label_dest(asm_xtensa_t *as, uint label) {
121 assert(label < as->base.max_num_labels);
122 return as->base.label_offsets[label];
123}
124
125void asm_xtensa_op16(asm_xtensa_t *as, uint16_t op) {
126 uint8_t *c = mp_asm_base_get_cur_to_write_bytes(&as->base, 2);
127 if (c != NULL) {
128 c[0] = op;
129 c[1] = op >> 8;
130 }
131}
132
133void asm_xtensa_op24(asm_xtensa_t *as, uint32_t op) {
134 uint8_t *c = mp_asm_base_get_cur_to_write_bytes(&as->base, 3);
135 if (c != NULL) {
136 c[0] = op;
137 c[1] = op >> 8;
138 c[2] = op >> 16;
139 }
140}
141
142void asm_xtensa_j_label(asm_xtensa_t *as, uint label) {
143 uint32_t dest = get_label_dest(as, label);
144 int32_t rel = dest - as->base.code_offset - 4;
145 // we assume rel, as a signed int, fits in 18-bits
146 asm_xtensa_op_j(as, rel);
147}
148
149void asm_xtensa_bccz_reg_label(asm_xtensa_t *as, uint cond, uint reg, uint label) {
150 uint32_t dest = get_label_dest(as, label);
151 int32_t rel = dest - as->base.code_offset - 4;
152 if (as->base.pass == MP_ASM_PASS_EMIT && !SIGNED_FIT12(rel)) {
153 printf("ERROR: xtensa bccz out of range\n");
154 }
155 asm_xtensa_op_bccz(as, cond, reg, rel);
156}
157
158void asm_xtensa_bcc_reg_reg_label(asm_xtensa_t *as, uint cond, uint reg1, uint reg2, uint label) {
159 uint32_t dest = get_label_dest(as, label);
160 int32_t rel = dest - as->base.code_offset - 4;
161 if (as->base.pass == MP_ASM_PASS_EMIT && !SIGNED_FIT8(rel)) {
162 printf("ERROR: xtensa bcc out of range\n");
163 }
164 asm_xtensa_op_bcc(as, cond, reg1, reg2, rel);
165}
166
167// convenience function; reg_dest must be different from reg_src[12]
168void asm_xtensa_setcc_reg_reg_reg(asm_xtensa_t *as, uint cond, uint reg_dest, uint reg_src1, uint reg_src2) {
169 asm_xtensa_op_movi_n(as, reg_dest, 1);
170 asm_xtensa_op_bcc(as, cond, reg_src1, reg_src2, 1);
171 asm_xtensa_op_movi_n(as, reg_dest, 0);
172}
173
174size_t asm_xtensa_mov_reg_i32(asm_xtensa_t *as, uint reg_dest, uint32_t i32) {
175 // load the constant
176 uint32_t const_table_offset = (uint8_t *)as->const_table - as->base.code_base;
177 size_t loc = const_table_offset + as->cur_const * WORD_SIZE;
178 asm_xtensa_op_l32r(as, reg_dest, as->base.code_offset, loc);
179 // store the constant in the table
180 if (as->const_table != NULL) {
181 as->const_table[as->cur_const] = i32;
182 }
183 ++as->cur_const;
184 return loc;
185}
186
187void asm_xtensa_mov_reg_i32_optimised(asm_xtensa_t *as, uint reg_dest, uint32_t i32) {
188 if (SIGNED_FIT12(i32)) {
189 asm_xtensa_op_movi(as, reg_dest, i32);
190 } else {
191 asm_xtensa_mov_reg_i32(as, reg_dest, i32);
192 }
193}
194
195void asm_xtensa_mov_local_reg(asm_xtensa_t *as, int local_num, uint reg_src) {
196 asm_xtensa_op_s32i(as, reg_src, ASM_XTENSA_REG_A1, local_num);
197}
198
199void asm_xtensa_mov_reg_local(asm_xtensa_t *as, uint reg_dest, int local_num) {
200 asm_xtensa_op_l32i(as, reg_dest, ASM_XTENSA_REG_A1, local_num);
201}
202
203void asm_xtensa_mov_reg_local_addr(asm_xtensa_t *as, uint reg_dest, int local_num) {
204 uint off = local_num * WORD_SIZE;
205 if (SIGNED_FIT8(off)) {
206 asm_xtensa_op_addi(as, reg_dest, ASM_XTENSA_REG_A1, off);
207 } else {
208 asm_xtensa_op_movi(as, reg_dest, off);
209 asm_xtensa_op_add_n(as, reg_dest, reg_dest, ASM_XTENSA_REG_A1);
210 }
211}
212
213void asm_xtensa_mov_reg_pcrel(asm_xtensa_t *as, uint reg_dest, uint label) {
214 // Get relative offset from PC
215 uint32_t dest = get_label_dest(as, label);
216 int32_t rel = dest - as->base.code_offset;
217 rel -= 3 + 3; // account for 3 bytes of movi instruction, 3 bytes call0 adjustment
218 asm_xtensa_op_movi(as, reg_dest, rel); // imm has 12-bit range
219
220 // Use call0 to get PC+3 into a0
221 // call0 destination must be aligned on 4 bytes:
222 // - code_offset&3=0: off=0, pad=1
223 // - code_offset&3=1: off=0, pad=0
224 // - code_offset&3=2: off=1, pad=3
225 // - code_offset&3=3: off=1, pad=2
226 uint32_t off = as->base.code_offset >> 1 & 1;
227 uint32_t pad = (5 - as->base.code_offset) & 3;
228 asm_xtensa_op_call0(as, off);
229 mp_asm_base_get_cur_to_write_bytes(&as->base, pad);
230
231 // Add PC to relative offset
232 asm_xtensa_op_add_n(as, reg_dest, reg_dest, ASM_XTENSA_REG_A0);
233}
234
235void asm_xtensa_call_ind(asm_xtensa_t *as, uint idx) {
236 if (idx < 16) {
237 asm_xtensa_op_l32i_n(as, ASM_XTENSA_REG_A0, ASM_XTENSA_REG_FUN_TABLE, idx);
238 } else {
239 asm_xtensa_op_l32i(as, ASM_XTENSA_REG_A0, ASM_XTENSA_REG_FUN_TABLE, idx);
240 }
241 asm_xtensa_op_callx0(as, ASM_XTENSA_REG_A0);
242}
243
244void asm_xtensa_call_ind_win(asm_xtensa_t *as, uint idx) {
245 if (idx < 16) {
246 asm_xtensa_op_l32i_n(as, ASM_XTENSA_REG_A8, ASM_XTENSA_REG_FUN_TABLE_WIN, idx);
247 } else {
248 asm_xtensa_op_l32i(as, ASM_XTENSA_REG_A8, ASM_XTENSA_REG_FUN_TABLE_WIN, idx);
249 }
250 asm_xtensa_op_callx8(as, ASM_XTENSA_REG_A8);
251}
252
253#endif // MICROPY_EMIT_XTENSA || MICROPY_EMIT_INLINE_XTENSA || MICROPY_EMIT_XTENSAWIN
254