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 
asm_xtensa_end_pass(asm_xtensa_t * as)41 void 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 
asm_xtensa_entry(asm_xtensa_t * as,int num_locals)64 void 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 
asm_xtensa_exit(asm_xtensa_t * as)86 void 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 
asm_xtensa_entry_win(asm_xtensa_t * as,int num_locals)104 void 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 
asm_xtensa_exit_win(asm_xtensa_t * as)115 void 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 
get_label_dest(asm_xtensa_t * as,uint label)120 STATIC 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 
asm_xtensa_op16(asm_xtensa_t * as,uint16_t op)125 void 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 
asm_xtensa_op24(asm_xtensa_t * as,uint32_t op)133 void 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 
asm_xtensa_j_label(asm_xtensa_t * as,uint label)142 void 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 
asm_xtensa_bccz_reg_label(asm_xtensa_t * as,uint cond,uint reg,uint label)149 void 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 
asm_xtensa_bcc_reg_reg_label(asm_xtensa_t * as,uint cond,uint reg1,uint reg2,uint label)158 void 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]
asm_xtensa_setcc_reg_reg_reg(asm_xtensa_t * as,uint cond,uint reg_dest,uint reg_src1,uint reg_src2)168 void 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 
asm_xtensa_mov_reg_i32(asm_xtensa_t * as,uint reg_dest,uint32_t i32)174 size_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 
asm_xtensa_mov_reg_i32_optimised(asm_xtensa_t * as,uint reg_dest,uint32_t i32)187 void 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 
asm_xtensa_mov_local_reg(asm_xtensa_t * as,int local_num,uint reg_src)195 void 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 
asm_xtensa_mov_reg_local(asm_xtensa_t * as,uint reg_dest,int local_num)199 void 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 
asm_xtensa_mov_reg_local_addr(asm_xtensa_t * as,uint reg_dest,int local_num)203 void 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 
asm_xtensa_mov_reg_pcrel(asm_xtensa_t * as,uint reg_dest,uint label)213 void 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 
asm_xtensa_call_ind(asm_xtensa_t * as,uint idx)235 void 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 
asm_xtensa_call_ind_win(asm_xtensa_t * as,uint idx)244 void 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