arm32_inline_test.c (12807B)
1 /* Public-API unit test for the ARM32 Thumb-2 inline-asm backend. */ 2 3 #include <stdint.h> 4 #include <string.h> 5 6 #include <kit/disasm.h> 7 8 #include "inline_public_test.h" 9 10 static void arm32_body(KitCompiler* c, KitCg* cg, KitCgTypeId i64_ty) { 11 (void)i64_ty; 12 13 it_inline_asm(c, cg, "nop ; nop", NULL, 0, NULL, 0, NULL, 0); 14 it_inline_asm(c, cg, "add.w r0, r0, #7", NULL, 0, NULL, 0, NULL, 0); 15 it_inline_asm(c, cg, "movw r1, #0x1234", NULL, 0, NULL, 0, NULL, 0); 16 it_inline_asm(c, cg, "bkpt #0xab", NULL, 0, NULL, 0, NULL, 0); 17 /* ARMv7E-M DSP saturating (no dsp-feature gate; assembler is permissive). */ 18 it_inline_asm(c, cg, "ssat r0, #16, r1", NULL, 0, NULL, 0, NULL, 0); 19 it_inline_asm(c, cg, "qadd r3, r1, r2", NULL, 0, NULL, 0, NULL, 0); 20 } 21 22 static void arm32_bad_operand(KitCompiler* c, KitCg* cg, KitCgTypeId i64_ty) { 23 (void)i64_ty; 24 it_inline_asm(c, cg, "mov %9, r0", NULL, 0, NULL, 0, NULL, 0); 25 } 26 27 static void arm32_named_r4_clobber(KitCompiler* c, KitCg* cg, 28 KitCgTypeId i64_ty) { 29 static const char* const clobbers[] = {"r4"}; 30 (void)i64_ty; 31 it_inline_asm(c, cg, "movs r4, #99", NULL, 0, NULL, 0, clobbers, 1); 32 } 33 34 static void arm32_bad_frame_clobber(KitCompiler* c, KitCg* cg, 35 KitCgTypeId i64_ty) { 36 static const char* const clobbers[] = {"r7"}; 37 (void)i64_ty; 38 it_inline_asm(c, cg, "", NULL, 0, NULL, 0, clobbers, 1); 39 } 40 41 /* r0-r3 are the direct binder's caller-saved operand pool. Five simultaneous 42 * generic inputs therefore put the fifth value in callee-saved r4. */ 43 static void arm32_generic_callee_saved_operand(KitCompiler* c, KitCg* cg, 44 KitCgTypeId i64_ty) { 45 KitCgTypeId i32_ty = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I32); 46 KitCgAsmOperand ins[5]; 47 KitCgLocal locals[5]; 48 KitCgLocalAttrs attrs; 49 KitCgMemAccess mem; 50 uint32_t i; 51 (void)i64_ty; 52 53 memset(&attrs, 0, sizeof attrs); 54 memset(&mem, 0, sizeof mem); 55 mem.type = i32_ty; 56 mem.align = kit_cg_type_align(c, i32_ty); 57 for (i = 0; i < 5u; ++i) { 58 locals[i] = kit_cg_local(cg, i32_ty, attrs); 59 kit_cg_push_int(cg, i + 1u, i32_ty); 60 kit_cg_local_write(cg, locals[i], mem); 61 } 62 for (i = 0; i < 5u; ++i) { 63 kit_cg_local_read(cg, locals[i], mem); 64 ins[i] = it_asm_op(c, "r", NULL, i32_ty, KIT_CG_ASM_IN); 65 } 66 it_inline_asm(c, cg, "", NULL, 0, ins, 5, NULL, 0); 67 } 68 69 static int arm32_is_r4_mem(const KitInsn* insn, const char* mnemonic) { 70 size_t n = strlen(mnemonic); 71 return insn->mnemonic.s && insn->mnemonic.len >= n && 72 memcmp(insn->mnemonic.s, mnemonic, n) == 0 && insn->operands.s && 73 insn->operands.len >= 2u && insn->operands.s[0] == 'r' && 74 insn->operands.s[1] == '4'; 75 } 76 77 static int arm32_is_r4_reglist(const KitInsn* insn, const char* mnemonic) { 78 size_t n = strlen(mnemonic); 79 if (!insn->mnemonic.s || insn->mnemonic.len < n || 80 memcmp(insn->mnemonic.s, mnemonic, n) != 0 || !insn->operands.s) 81 return 0; 82 for (size_t i = 0; i + 1u < insn->operands.len; ++i) 83 if (insn->operands.s[i] == 'r' && insn->operands.s[i + 1u] == '4') 84 return 1; 85 return 0; 86 } 87 88 static int arm32_has_known_frame_r4_preservation(InlineTestEnv* env, 89 const InlineText* text) { 90 KitTargetOptions opts; 91 KitTarget* target = NULL; 92 KitDisasmContext dc; 93 KitDisasmIter* it = NULL; 94 KitInsn insn; 95 int saw_push = 0; 96 int saw_pop = 0; 97 98 memset(&opts, 0, sizeof opts); 99 opts.spec = it_target(KIT_ARCH_ARM_32); 100 if (kit_target_new(&env->ctx, &opts, &target) != KIT_OK || !target) 101 goto done; 102 memset(&dc, 0, sizeof dc); 103 dc.target = target; 104 dc.context = env->ctx; 105 if (kit_disasm_iter_new(&dc, text->data, text->len, 0, text->file, &it) != 106 KIT_OK || 107 !it) 108 goto done; 109 while (kit_disasm_iter_next(it, &insn) == KIT_ITER_ITEM) { 110 if (arm32_is_r4_reglist(&insn, "push")) saw_push = 1; 111 if (arm32_is_r4_reglist(&insn, "pop")) saw_pop = 1; 112 } 113 114 done: 115 if (it) kit_disasm_iter_free(it); 116 kit_target_free(target); 117 return saw_push && saw_pop; 118 } 119 120 static int arm32_has_generic_r4_preservation(InlineTestEnv* env, 121 const InlineText* text) { 122 KitTargetOptions opts; 123 KitTarget* target = NULL; 124 KitDisasmContext dc; 125 KitDisasmIter* it = NULL; 126 KitInsn insn; 127 uint32_t loads_after_save = 0; 128 int saw_save = 0; 129 int ok = 0; 130 131 memset(&opts, 0, sizeof opts); 132 opts.spec = it_target(KIT_ARCH_ARM_32); 133 if (kit_target_new(&env->ctx, &opts, &target) != KIT_OK || !target) 134 goto done; 135 memset(&dc, 0, sizeof dc); 136 dc.target = target; 137 dc.context = env->ctx; 138 if (kit_disasm_iter_new(&dc, text->data, text->len, 0, text->file, &it) != 139 KIT_OK || 140 !it) 141 goto done; 142 while (kit_disasm_iter_next(it, &insn) == KIT_ITER_ITEM) { 143 if (!saw_save && arm32_is_r4_mem(&insn, "str")) { 144 saw_save = 1; 145 } else if (saw_save && arm32_is_r4_mem(&insn, "ldr")) { 146 loads_after_save++; 147 } 148 } 149 /* The first load stages operand five; the second restores caller r4. */ 150 ok = saw_save && loads_after_save >= 2u; 151 152 done: 153 if (it) kit_disasm_iter_free(it); 154 kit_target_free(target); 155 return ok; 156 } 157 158 static int arm32_emit_file_asm(InlineTestEnv* env, const char* src, 159 InlineText* text) { 160 KitCompiler* c = NULL; 161 KitTarget* target = NULL; 162 KitTargetOptions target_opts; 163 KitObjBuilder* ob = NULL; 164 KitCg* cg = NULL; 165 KitCodeOptions opts; 166 KitSlice bytes; 167 size_t len = 0; 168 int ok = 0; 169 170 memset(text, 0, sizeof *text); 171 memset(&target_opts, 0, sizeof target_opts); 172 target_opts.spec = it_target(KIT_ARCH_ARM_32); 173 if (kit_target_new(&env->ctx, &target_opts, &target) != KIT_OK || !target) { 174 snprintf(env->last_diag, sizeof env->last_diag, "kit_target_new failed"); 175 return 0; 176 } 177 if (kit_compiler_new(target, &env->ctx, &c) != KIT_OK || !c) { 178 snprintf(env->last_diag, sizeof env->last_diag, "kit_compiler_new failed"); 179 goto done; 180 } 181 if (kit_obj_builder_new(c, &ob) != KIT_OK || !ob) { 182 snprintf(env->last_diag, sizeof env->last_diag, 183 "kit_obj_builder_new failed"); 184 goto done; 185 } 186 if (kit_cg_new(c, &cg) != KIT_OK || !cg) { 187 snprintf(env->last_diag, sizeof env->last_diag, "kit_cg_new failed"); 188 goto done; 189 } 190 memset(&opts, 0, sizeof opts); 191 if (kit_cg_begin(cg, ob, &opts) != KIT_OK) { 192 snprintf(env->last_diag, sizeof env->last_diag, "kit_cg_begin failed"); 193 goto done; 194 } 195 kit_cg_file_scope_asm(cg, kit_slice_cstr(src)); 196 if (kit_cg_finish(cg, NULL) != KIT_OK) { 197 if (!env->last_diag[0]) 198 snprintf(env->last_diag, sizeof env->last_diag, "kit_cg_finish failed"); 199 goto done; 200 } 201 if (kit_cg_detach(cg) != KIT_OK) { 202 snprintf(env->last_diag, sizeof env->last_diag, "kit_cg_detach failed"); 203 goto done; 204 } 205 kit_cg_free(cg); 206 cg = NULL; 207 208 if (kit_writer_mem(&env->heap, &text->writer) != KIT_OK || !text->writer) { 209 snprintf(env->last_diag, sizeof env->last_diag, "kit_writer_mem failed"); 210 goto done; 211 } 212 if (kit_obj_builder_emit(ob, text->writer) != KIT_OK) { 213 if (!env->last_diag[0]) 214 snprintf(env->last_diag, sizeof env->last_diag, 215 "kit_obj_builder_emit failed"); 216 goto done; 217 } 218 bytes.data = kit_writer_mem_bytes(text->writer, &len); 219 bytes.len = len; 220 if (kit_obj_open(&env->ctx, KIT_SLICE_LIT("<arm32-file-asm>"), &bytes, 221 &text->file) != KIT_OK) { 222 if (!env->last_diag[0]) 223 snprintf(env->last_diag, sizeof env->last_diag, "kit_obj_open failed"); 224 goto done; 225 } 226 ok = 1; 227 228 done: 229 if (cg) kit_cg_free(cg); 230 if (ob) kit_obj_builder_free(ob); 231 kit_compiler_free(c); 232 kit_target_free(target); 233 if (!ok) { 234 if (text->file) kit_obj_free(text->file); 235 if (text->writer) kit_writer_close(text->writer); 236 memset(text, 0, sizeof *text); 237 } 238 return ok; 239 } 240 241 static int arm32_has_abs32_to(KitObjFile* file, KitSlice name) { 242 KitObjRelocIter* it = NULL; 243 KitObjReloc r; 244 int found = 0; 245 if (kit_obj_reliter_new(file, &it) != KIT_OK || !it) return 0; 246 while (kit_obj_reliter_next(it, &r) == KIT_ITER_ITEM) { 247 if (r.kind.arch == KIT_ARCH_ARM_32 && r.kind.code == KIT_RELOC_ABS32 && 248 r.sym_name.len == name.len && 249 memcmp(r.sym_name.s, name.s, name.len) == 0) { 250 found = 1; 251 break; 252 } 253 } 254 kit_obj_reliter_free(it); 255 return found; 256 } 257 258 static int arm32_check_thumb_func(InlineTestEnv* env) { 259 static const char src[] = 260 ".syntax unified\n" 261 ".thumb\n" 262 ".section .text.thumb_fn, \"ax\"\n" 263 ".globl thumb_target\n" 264 ".thumb_func\n" 265 ".type thumb_target, %function\n" 266 "thumb_target:\n" 267 " bx lr\n" 268 ".size thumb_target, .-thumb_target\n" 269 ".section .rodata.thumb_fn, \"a\"\n" 270 ".globl thumb_target_ptr\n" 271 "thumb_target_ptr:\n" 272 " .word thumb_target\n"; 273 InlineText obj; 274 KitObjSymInfo sym; 275 KitSlice target_name = KIT_SLICE_LIT("thumb_target"); 276 int ok; 277 278 ok = arm32_emit_file_asm(env, src, &obj); 279 if (!ok) return 0; 280 ok = kit_obj_symbol_by_name(obj.file, target_name, &sym) == KIT_OK && 281 sym.kind == KIT_SK_FUNC && (sym.value & 1u) != 0 && sym.size == 2u && 282 arm32_has_abs32_to(obj.file, target_name); 283 if (!ok && !env->last_diag[0]) 284 snprintf(env->last_diag, sizeof env->last_diag, 285 ".thumb_func did not produce odd FUNC symbol plus ABS32 reloc"); 286 it_text_close(&obj); 287 return ok; 288 } 289 290 int main(void) { 291 static const uint8_t nops[] = {0x00u, 0xbfu, 0x00u, 0xbfu}; 292 static const uint8_t add_w_r0_7[] = {0x00u, 0xf1u, 0x07u, 0x00u}; 293 static const uint8_t movw_r1_1234[] = {0x41u, 0xf2u, 0x34u, 0x21u}; 294 static const uint8_t bkpt_ab[] = {0xabu, 0xbeu}; 295 static const uint8_t ssat_r0_16_r1[] = {0x01u, 0xf3u, 0x0fu, 0x00u}; 296 static const uint8_t qadd_r3_r1_r2[] = {0x82u, 0xfau, 0x81u, 0xf3u}; 297 InlineTestEnv env; 298 InlineText text; 299 300 it_env_init(&env); 301 IT_EXPECT(&env, 302 it_emit_text(&env, KIT_ARCH_ARM_32, "arm32_inline_public", 303 arm32_body, &text), 304 "failed to emit arm32 inline-asm object: %s", env.last_diag); 305 if (text.data) { 306 IT_EXPECT(&env, it_contains(text.data, text.len, nops, sizeof nops), 307 "missing two-nop inline asm encoding"); 308 IT_EXPECT(&env, 309 it_contains(text.data, text.len, add_w_r0_7, sizeof add_w_r0_7), 310 "missing add.w r0, r0, #7 encoding"); 311 IT_EXPECT(&env, 312 it_contains(text.data, text.len, movw_r1_1234, 313 sizeof movw_r1_1234), 314 "missing movw r1, #0x1234 encoding"); 315 IT_EXPECT(&env, it_contains(text.data, text.len, bkpt_ab, sizeof bkpt_ab), 316 "missing bkpt #0xab encoding"); 317 IT_EXPECT(&env, 318 it_contains(text.data, text.len, ssat_r0_16_r1, 319 sizeof ssat_r0_16_r1), 320 "missing ssat r0, #16, r1 encoding"); 321 IT_EXPECT(&env, 322 it_contains(text.data, text.len, qadd_r3_r1_r2, 323 sizeof qadd_r3_r1_r2), 324 "missing qadd r3, r1, r2 encoding"); 325 } 326 it_text_close(&text); 327 328 IT_EXPECT(&env, 329 it_expect_panic(&env, KIT_ARCH_ARM_32, "arm32_bad_operand", 330 arm32_bad_operand, "operand index"), 331 "expected out-of-range arm32 asm operand to panic"); 332 IT_EXPECT(&env, 333 it_expect_panic(&env, KIT_ARCH_ARM_32, 334 "arm32_bad_frame_clobber", 335 arm32_bad_frame_clobber, 336 "structural register cannot be clobbered"), 337 "expected arm32 r7 clobber to panic"); 338 IT_EXPECT(&env, arm32_check_thumb_func(&env), 339 "arm32 .thumb_func symbol regression failed: %s", env.last_diag); 340 341 IT_EXPECT(&env, 342 it_emit_text(&env, KIT_ARCH_ARM_32, 343 "arm32_generic_callee_saved_operand", 344 arm32_generic_callee_saved_operand, &text), 345 "failed to emit generic callee-saved asm operand: %s", 346 env.last_diag); 347 if (text.data) 348 IT_EXPECT(&env, arm32_has_generic_r4_preservation(&env, &text), 349 "generic r4 asm operand was not saved and restored"); 350 it_text_close(&text); 351 352 IT_EXPECT(&env, 353 it_emit_text_opt(&env, KIT_ARCH_ARM_32, 354 "arm32_named_r4_clobber_o1", 355 arm32_named_r4_clobber, 1, &text), 356 "failed to emit O1 named r4 clobber: %s", env.last_diag); 357 if (text.data) 358 IT_EXPECT(&env, arm32_has_known_frame_r4_preservation(&env, &text), 359 "O1 known frame did not preserve named r4 clobber"); 360 it_text_close(&text); 361 362 if (env.fails) { 363 fprintf(stderr, "%d failure(s)\n", env.fails); 364 return 1; 365 } 366 printf("arm32_inline_test: ok\n"); 367 return 0; 368 }