1/*
2 * This file is part of the MicroPython project, http://micropython.org/
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (c) 2014 Fabian Vogt
7 * Copyright (c) 2013, 2014 Damien P. George
8 *
9 * Permission is hereby granted, free of charge, to any person obtaining a copy
10 * of this software and associated documentation files (the "Software"), to deal
11 * in the Software without restriction, including without limitation the rights
12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 * copies of the Software, and to permit persons to whom the Software is
14 * furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 * THE SOFTWARE.
26 */
27
28#include <stdio.h>
29#include <assert.h>
30#include <string.h>
31
32#include "py/mpconfig.h"
33
34// wrapper around everything in this file
35#if MICROPY_EMIT_ARM
36
37#include "py/asmarm.h"
38
39#define SIGNED_FIT24(x) (((x) & 0xff800000) == 0) || (((x) & 0xff000000) == 0xff000000)
40
41void asm_arm_end_pass(asm_arm_t *as) {
42 if (as->base.pass == MP_ASM_PASS_EMIT) {
43 #if defined(__linux__) && defined(__GNUC__)
44 char *start = mp_asm_base_get_code(&as->base);
45 char *end = start + mp_asm_base_get_code_size(&as->base);
46 __builtin___clear_cache(start, end);
47 #elif defined(__arm__)
48 // flush I- and D-cache
49 asm volatile (
50 "0:"
51 "mrc p15, 0, r15, c7, c10, 3\n"
52 "bne 0b\n"
53 "mov r0, #0\n"
54 "mcr p15, 0, r0, c7, c7, 0\n"
55 : : : "r0", "cc");
56 #endif
57 }
58}
59
60// Insert word into instruction flow
61STATIC void emit(asm_arm_t *as, uint op) {
62 uint8_t *c = mp_asm_base_get_cur_to_write_bytes(&as->base, 4);
63 if (c != NULL) {
64 *(uint32_t *)c = op;
65 }
66}
67
68// Insert word into instruction flow, add "ALWAYS" condition code
69STATIC void emit_al(asm_arm_t *as, uint op) {
70 emit(as, op | ASM_ARM_CC_AL);
71}
72
73// Basic instructions without condition code
74STATIC uint asm_arm_op_push(uint reglist) {
75 // stmfd sp!, {reglist}
76 return 0x92d0000 | (reglist & 0xFFFF);
77}
78
79STATIC uint asm_arm_op_pop(uint reglist) {
80 // ldmfd sp!, {reglist}
81 return 0x8bd0000 | (reglist & 0xFFFF);
82}
83
84STATIC uint asm_arm_op_mov_reg(uint rd, uint rn) {
85 // mov rd, rn
86 return 0x1a00000 | (rd << 12) | rn;
87}
88
89STATIC uint asm_arm_op_mov_imm(uint rd, uint imm) {
90 // mov rd, #imm
91 return 0x3a00000 | (rd << 12) | imm;
92}
93
94STATIC uint asm_arm_op_mvn_imm(uint rd, uint imm) {
95 // mvn rd, #imm
96 return 0x3e00000 | (rd << 12) | imm;
97}
98
99STATIC uint asm_arm_op_add_imm(uint rd, uint rn, uint imm) {
100 // add rd, rn, #imm
101 return 0x2800000 | (rn << 16) | (rd << 12) | (imm & 0xFF);
102}
103
104STATIC uint asm_arm_op_add_reg(uint rd, uint rn, uint rm) {
105 // add rd, rn, rm
106 return 0x0800000 | (rn << 16) | (rd << 12) | rm;
107}
108
109STATIC uint asm_arm_op_sub_imm(uint rd, uint rn, uint imm) {
110 // sub rd, rn, #imm
111 return 0x2400000 | (rn << 16) | (rd << 12) | (imm & 0xFF);
112}
113
114STATIC uint asm_arm_op_sub_reg(uint rd, uint rn, uint rm) {
115 // sub rd, rn, rm
116 return 0x0400000 | (rn << 16) | (rd << 12) | rm;
117}
118
119STATIC uint asm_arm_op_mul_reg(uint rd, uint rm, uint rs) {
120 // mul rd, rm, rs
121 assert(rd != rm);
122 return 0x0000090 | (rd << 16) | (rs << 8) | rm;
123}
124
125STATIC uint asm_arm_op_and_reg(uint rd, uint rn, uint rm) {
126 // and rd, rn, rm
127 return 0x0000000 | (rn << 16) | (rd << 12) | rm;
128}
129
130STATIC uint asm_arm_op_eor_reg(uint rd, uint rn, uint rm) {
131 // eor rd, rn, rm
132 return 0x0200000 | (rn << 16) | (rd << 12) | rm;
133}
134
135STATIC uint asm_arm_op_orr_reg(uint rd, uint rn, uint rm) {
136 // orr rd, rn, rm
137 return 0x1800000 | (rn << 16) | (rd << 12) | rm;
138}
139
140void asm_arm_bkpt(asm_arm_t *as) {
141 // bkpt #0
142 emit_al(as, 0x1200070);
143}
144
145// locals:
146// - stored on the stack in ascending order
147// - numbered 0 through num_locals-1
148// - SP points to first local
149//
150// | SP
151// v
152// l0 l1 l2 ... l(n-1)
153// ^ ^
154// | low address | high address in RAM
155
156void asm_arm_entry(asm_arm_t *as, int num_locals) {
157 assert(num_locals >= 0);
158
159 as->stack_adjust = 0;
160 as->push_reglist = 1 << ASM_ARM_REG_R1
161 | 1 << ASM_ARM_REG_R2
162 | 1 << ASM_ARM_REG_R3
163 | 1 << ASM_ARM_REG_R4
164 | 1 << ASM_ARM_REG_R5
165 | 1 << ASM_ARM_REG_R6
166 | 1 << ASM_ARM_REG_R7
167 | 1 << ASM_ARM_REG_R8;
168
169 // Only adjust the stack if there are more locals than usable registers
170 if (num_locals > 3) {
171 as->stack_adjust = num_locals * 4;
172 // Align stack to 8 bytes
173 if (num_locals & 1) {
174 as->stack_adjust += 4;
175 }
176 }
177
178 emit_al(as, asm_arm_op_push(as->push_reglist | 1 << ASM_ARM_REG_LR));
179 if (as->stack_adjust > 0) {
180 emit_al(as, asm_arm_op_sub_imm(ASM_ARM_REG_SP, ASM_ARM_REG_SP, as->stack_adjust));
181 }
182}
183
184void asm_arm_exit(asm_arm_t *as) {
185 if (as->stack_adjust > 0) {
186 emit_al(as, asm_arm_op_add_imm(ASM_ARM_REG_SP, ASM_ARM_REG_SP, as->stack_adjust));
187 }
188
189 emit_al(as, asm_arm_op_pop(as->push_reglist | (1 << ASM_ARM_REG_PC)));
190}
191
192void asm_arm_push(asm_arm_t *as, uint reglist) {
193 emit_al(as, asm_arm_op_push(reglist));
194}
195
196void asm_arm_pop(asm_arm_t *as, uint reglist) {
197 emit_al(as, asm_arm_op_pop(reglist));
198}
199
200void asm_arm_mov_reg_reg(asm_arm_t *as, uint reg_dest, uint reg_src) {
201 emit_al(as, asm_arm_op_mov_reg(reg_dest, reg_src));
202}
203
204size_t asm_arm_mov_reg_i32(asm_arm_t *as, uint rd, int imm) {
205 // Insert immediate into code and jump over it
206 emit_al(as, 0x59f0000 | (rd << 12)); // ldr rd, [pc]
207 emit_al(as, 0xa000000); // b pc
208 size_t loc = mp_asm_base_get_code_pos(&as->base);
209 emit(as, imm);
210 return loc;
211}
212
213void asm_arm_mov_reg_i32_optimised(asm_arm_t *as, uint rd, int imm) {
214 // TODO: There are more variants of immediate values
215 if ((imm & 0xFF) == imm) {
216 emit_al(as, asm_arm_op_mov_imm(rd, imm));
217 } else if (imm < 0 && imm >= -256) {
218 // mvn is "move not", not "move negative"
219 emit_al(as, asm_arm_op_mvn_imm(rd, ~imm));
220 } else {
221 asm_arm_mov_reg_i32(as, rd, imm);
222 }
223}
224
225void asm_arm_mov_local_reg(asm_arm_t *as, int local_num, uint rd) {
226 // str rd, [sp, #local_num*4]
227 emit_al(as, 0x58d0000 | (rd << 12) | (local_num << 2));
228}
229
230void asm_arm_mov_reg_local(asm_arm_t *as, uint rd, int local_num) {
231 // ldr rd, [sp, #local_num*4]
232 emit_al(as, 0x59d0000 | (rd << 12) | (local_num << 2));
233}
234
235void asm_arm_cmp_reg_i8(asm_arm_t *as, uint rd, int imm) {
236 // cmp rd, #imm
237 emit_al(as, 0x3500000 | (rd << 16) | (imm & 0xFF));
238}
239
240void asm_arm_cmp_reg_reg(asm_arm_t *as, uint rd, uint rn) {
241 // cmp rd, rn
242 emit_al(as, 0x1500000 | (rd << 16) | rn);
243}
244
245void asm_arm_setcc_reg(asm_arm_t *as, uint rd, uint cond) {
246 emit(as, asm_arm_op_mov_imm(rd, 1) | cond); // movCOND rd, #1
247 emit(as, asm_arm_op_mov_imm(rd, 0) | (cond ^ (1 << 28))); // mov!COND rd, #0
248}
249
250void asm_arm_add_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) {
251 // add rd, rn, rm
252 emit_al(as, asm_arm_op_add_reg(rd, rn, rm));
253}
254
255void asm_arm_sub_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) {
256 // sub rd, rn, rm
257 emit_al(as, asm_arm_op_sub_reg(rd, rn, rm));
258}
259
260void asm_arm_mul_reg_reg_reg(asm_arm_t *as, uint rd, uint rs, uint rm) {
261 // rs and rm are swapped because of restriction rd!=rm
262 // mul rd, rm, rs
263 emit_al(as, asm_arm_op_mul_reg(rd, rm, rs));
264}
265
266void asm_arm_and_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) {
267 // and rd, rn, rm
268 emit_al(as, asm_arm_op_and_reg(rd, rn, rm));
269}
270
271void asm_arm_eor_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) {
272 // eor rd, rn, rm
273 emit_al(as, asm_arm_op_eor_reg(rd, rn, rm));
274}
275
276void asm_arm_orr_reg_reg_reg(asm_arm_t *as, uint rd, uint rn, uint rm) {
277 // orr rd, rn, rm
278 emit_al(as, asm_arm_op_orr_reg(rd, rn, rm));
279}
280
281void asm_arm_mov_reg_local_addr(asm_arm_t *as, uint rd, int local_num) {
282 // add rd, sp, #local_num*4
283 emit_al(as, asm_arm_op_add_imm(rd, ASM_ARM_REG_SP, local_num << 2));
284}
285
286void asm_arm_mov_reg_pcrel(asm_arm_t *as, uint reg_dest, uint label) {
287 assert(label < as->base.max_num_labels);
288 mp_uint_t dest = as->base.label_offsets[label];
289 mp_int_t rel = dest - as->base.code_offset;
290 rel -= 12 + 8; // adjust for load of rel, and then PC+8 prefetch of add_reg_reg_reg
291
292 // To load rel int reg_dest, insert immediate into code and jump over it
293 emit_al(as, 0x59f0000 | (reg_dest << 12)); // ldr rd, [pc]
294 emit_al(as, 0xa000000); // b pc
295 emit(as, rel);
296
297 // Do reg_dest += PC
298 asm_arm_add_reg_reg_reg(as, reg_dest, reg_dest, ASM_ARM_REG_PC);
299}
300
301void asm_arm_lsl_reg_reg(asm_arm_t *as, uint rd, uint rs) {
302 // mov rd, rd, lsl rs
303 emit_al(as, 0x1a00010 | (rd << 12) | (rs << 8) | rd);
304}
305
306void asm_arm_lsr_reg_reg(asm_arm_t *as, uint rd, uint rs) {
307 // mov rd, rd, lsr rs
308 emit_al(as, 0x1a00030 | (rd << 12) | (rs << 8) | rd);
309}
310
311void asm_arm_asr_reg_reg(asm_arm_t *as, uint rd, uint rs) {
312 // mov rd, rd, asr rs
313 emit_al(as, 0x1a00050 | (rd << 12) | (rs << 8) | rd);
314}
315
316void asm_arm_ldr_reg_reg(asm_arm_t *as, uint rd, uint rn, uint byte_offset) {
317 // ldr rd, [rn, #off]
318 emit_al(as, 0x5900000 | (rn << 16) | (rd << 12) | byte_offset);
319}
320
321void asm_arm_ldrh_reg_reg(asm_arm_t *as, uint rd, uint rn) {
322 // ldrh rd, [rn]
323 emit_al(as, 0x1d000b0 | (rn << 16) | (rd << 12));
324}
325
326void asm_arm_ldrb_reg_reg(asm_arm_t *as, uint rd, uint rn) {
327 // ldrb rd, [rn]
328 emit_al(as, 0x5d00000 | (rn << 16) | (rd << 12));
329}
330
331void asm_arm_str_reg_reg(asm_arm_t *as, uint rd, uint rm, uint byte_offset) {
332 // str rd, [rm, #off]
333 emit_al(as, 0x5800000 | (rm << 16) | (rd << 12) | byte_offset);
334}
335
336void asm_arm_strh_reg_reg(asm_arm_t *as, uint rd, uint rm) {
337 // strh rd, [rm]
338 emit_al(as, 0x1c000b0 | (rm << 16) | (rd << 12));
339}
340
341void asm_arm_strb_reg_reg(asm_arm_t *as, uint rd, uint rm) {
342 // strb rd, [rm]
343 emit_al(as, 0x5c00000 | (rm << 16) | (rd << 12));
344}
345
346void asm_arm_str_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) {
347 // str rd, [rm, rn, lsl #2]
348 emit_al(as, 0x7800100 | (rm << 16) | (rd << 12) | rn);
349}
350
351void asm_arm_strh_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) {
352 // strh doesn't support scaled register index
353 emit_al(as, 0x1a00080 | (ASM_ARM_REG_R8 << 12) | rn); // mov r8, rn, lsl #1
354 emit_al(as, 0x18000b0 | (rm << 16) | (rd << 12) | ASM_ARM_REG_R8); // strh rd, [rm, r8]
355}
356
357void asm_arm_strb_reg_reg_reg(asm_arm_t *as, uint rd, uint rm, uint rn) {
358 // strb rd, [rm, rn]
359 emit_al(as, 0x7c00000 | (rm << 16) | (rd << 12) | rn);
360}
361
362void asm_arm_bcc_label(asm_arm_t *as, int cond, uint label) {
363 assert(label < as->base.max_num_labels);
364 mp_uint_t dest = as->base.label_offsets[label];
365 mp_int_t rel = dest - as->base.code_offset;
366 rel -= 8; // account for instruction prefetch, PC is 8 bytes ahead of this instruction
367 rel >>= 2; // in ARM mode the branch target is 32-bit aligned, so the 2 LSB are omitted
368
369 if (SIGNED_FIT24(rel)) {
370 emit(as, cond | 0xa000000 | (rel & 0xffffff));
371 } else {
372 printf("asm_arm_bcc: branch does not fit in 24 bits\n");
373 }
374}
375
376void asm_arm_b_label(asm_arm_t *as, uint label) {
377 asm_arm_bcc_label(as, ASM_ARM_CC_AL, label);
378}
379
380void asm_arm_bl_ind(asm_arm_t *as, uint fun_id, uint reg_temp) {
381 // The table offset should fit into the ldr instruction
382 assert(fun_id < (0x1000 / 4));
383 emit_al(as, asm_arm_op_mov_reg(ASM_ARM_REG_LR, ASM_ARM_REG_PC)); // mov lr, pc
384 emit_al(as, 0x597f000 | (fun_id << 2)); // ldr pc, [r7, #fun_id*4]
385}
386
387void asm_arm_bx_reg(asm_arm_t *as, uint reg_src) {
388 emit_al(as, 0x012fff10 | reg_src);
389}
390
391#endif // MICROPY_EMIT_ARM
392