x64_inline_test.c (7389B)
1 /* Public-API unit test for the x86_64 inline-asm backend. */ 2 3 #include <stdint.h> 4 #include <string.h> 5 6 #include "inline_public_test.h" 7 8 /* Collect the architectural XMM registers named by `ucomisd %xmmN,%xmmN`. 9 * The inline case below emits one such non-destructive compare per input. A 10 * full mask therefore proves that all sixteen simultaneous constraints were 11 * bound independently; merely compiling an empty template cannot detect an 12 * accidental many-to-one placement. */ 13 static uint32_t x64_self_ucomisd_bindings(const uint8_t* p, size_t n, 14 uint32_t* count_out) { 15 uint32_t mask = 0; 16 uint32_t count = 0; 17 for (size_t i = 0; i + 3u < n; ++i) { 18 size_t op = i; 19 uint8_t rex = 0; 20 uint8_t modrm; 21 uint32_t lhs; 22 uint32_t rhs; 23 if (p[op++] != 0x66u) continue; 24 if (op < n && p[op] >= 0x40u && p[op] <= 0x4fu) rex = p[op++]; 25 if (op + 2u >= n || p[op] != 0x0fu || p[op + 1u] != 0x2eu) 26 continue; 27 modrm = p[op + 2u]; 28 if ((modrm & 0xc0u) != 0xc0u) continue; 29 lhs = ((modrm >> 3u) & 7u) | ((rex & 4u) ? 8u : 0u); 30 rhs = (modrm & 7u) | ((rex & 1u) ? 8u : 0u); 31 if (lhs != rhs) continue; 32 mask |= 1u << lhs; 33 ++count; 34 } 35 if (count_out) *count_out = count; 36 return mask; 37 } 38 39 static void x64_body(KitCompiler* c, KitCg* cg, KitCgTypeId i64_ty) { 40 KitCgAsmOperand imm; 41 (void)i64_ty; 42 43 it_inline_asm(c, cg, "nop ; nop", NULL, 0, NULL, 0, NULL, 0); 44 it_inline_asm(c, cg, "movq %%rcx, %%rax", NULL, 0, NULL, 0, NULL, 0); 45 46 kit_cg_push_int(cg, 7, i64_ty); 47 imm = it_asm_op(c, "i", "imm", i64_ty, KIT_CG_ASM_IN); 48 it_inline_asm(c, cg, "addq %[imm], %%rax", NULL, 0, &imm, 1, NULL, 0); 49 } 50 51 static void x64_bad_operand(KitCompiler* c, KitCg* cg, KitCgTypeId i64_ty) { 52 (void)i64_ty; 53 it_inline_asm(c, cg, "movq %9, %%rax", NULL, 0, NULL, 0, NULL, 0); 54 } 55 56 /* A GNU local register variable pinned to %rax — the Linux syscall idiom 57 * (syscall number in rax). rax is reserved (return / div-mul), but no longer 58 * an emit-internal scratch register, so an asm operand may pin it while the 59 * allocator still leaves it alone. Clobbers rcx/r11 as `syscall` does. */ 60 static void x64_rax_pin(KitCompiler* c, KitCg* cg, KitCgTypeId i64_ty) { 61 KitCgAsmOperand in; 62 const char* clob[] = {"rcx", "r11", "memory"}; 63 in = it_asm_op(c, "r", "n", i64_ty, KIT_CG_ASM_IN); 64 in.reg = kit_sym_intern(c, kit_slice_cstr("rax")); 65 kit_cg_push_int(cg, 60, i64_ty); /* SYS_exit */ 66 it_inline_asm(c, cg, "syscall", NULL, 0, &in, 1, clob, 3); 67 } 68 69 static void x64_push_local_inputs(KitCompiler* c, KitCg* cg, KitCgTypeId ty, 70 const char* constraint, 71 KitCgAsmOperand* ins, uint32_t n) { 72 KitCgLocal locals[18]; 73 KitCgLocalAttrs attrs; 74 KitCgMemAccess mem; 75 memset(&attrs, 0, sizeof attrs); 76 memset(&mem, 0, sizeof mem); 77 mem.type = ty; 78 mem.align = kit_cg_type_align(c, ty); 79 for (uint32_t i = 0; i < n; ++i) { 80 locals[i] = kit_cg_local(cg, ty, attrs); 81 if (ty == kit_cg_type_builtin(c, KIT_CG_BUILTIN_F64)) 82 kit_cg_push_float(cg, (double)i + 0.5, ty); 83 else 84 kit_cg_push_int(cg, i + 1u, ty); 85 kit_cg_local_write(cg, locals[i], mem); 86 } 87 for (uint32_t i = 0; i < n; ++i) { 88 kit_cg_local_read(cg, locals[i], mem); 89 ins[i] = it_asm_op(c, constraint, NULL, ty, KIT_CG_ASM_IN); 90 } 91 } 92 93 static void x64_optimized_stage(KitCompiler* c, KitCg* cg, KitCgTypeId i64_ty) { 94 static const char xmm_use_template[] = 95 "ucomisd %0, %0 ; ucomisd %1, %1 ; ucomisd %2, %2 ; " 96 "ucomisd %3, %3 ; ucomisd %4, %4 ; ucomisd %5, %5 ; " 97 "ucomisd %6, %6 ; ucomisd %7, %7 ; ucomisd %8, %8 ; " 98 "ucomisd %9, %9 ; ucomisd %10, %10 ; ucomisd %11, %11 ; " 99 "ucomisd %12, %12 ; ucomisd %13, %13 ; ucomisd %14, %14 ; " 100 "ucomisd %15, %15"; 101 static const char* const cc_clobber[] = {"cc"}; 102 KitCgTypeId f64_ty = kit_cg_type_builtin(c, KIT_CG_BUILTIN_F64); 103 KitCgAsmOperand ins[18]; 104 KitCgAsmOperand outs[6]; 105 106 x64_push_local_inputs(c, cg, i64_ty, "r", ins, 14); 107 it_inline_asm(c, cg, "", NULL, 0, ins, 14, NULL, 0); 108 109 x64_push_local_inputs(c, cg, i64_ty, "q", ins, 14); 110 it_inline_asm(c, cg, "", NULL, 0, ins, 14, NULL, 0); 111 112 /* Sixteen is the architectural XMM register count. The old spill-to-scratch 113 * rewrite silently aliased simultaneous operands. Reference every input in 114 * the emitted template so the byte-level check can prove that each binding 115 * is distinct; an empty template only proved that planning did not panic. */ 116 x64_push_local_inputs(c, cg, f64_ty, "x", ins, 16); 117 it_inline_asm(c, cg, xmm_use_template, NULL, 0, ins, 16, cc_clobber, 1); 118 119 for (uint32_t i = 0; i < 6u; ++i) 120 outs[i] = it_asm_op(c, i & 1u ? "=x" : "=r", NULL, 121 i & 1u ? f64_ty : i64_ty, KIT_CG_ASM_OUT); 122 it_inline_asm(c, cg, "", outs, 6, NULL, 0, NULL, 0); 123 for (uint32_t i = 0; i < 6u; ++i) kit_cg_drop(cg); 124 } 125 126 int main(void) { 127 static const uint8_t nops[] = {0x90u, 0x90u}; 128 static const uint8_t movq_rcx_rax[] = {0x48u, 0x89u, 0xc8u}; 129 static const uint8_t addq_7_rax[] = {0x48u, 0x83u, 0xc0u, 0x07u}; 130 InlineTestEnv env; 131 InlineText text; 132 133 it_env_init(&env); 134 IT_EXPECT( 135 &env, 136 it_emit_text(&env, KIT_ARCH_X86_64, "x64_inline_public", x64_body, &text), 137 "failed to emit x64 inline-asm object"); 138 if (text.data) { 139 IT_EXPECT(&env, it_contains(text.data, text.len, nops, sizeof nops), 140 "missing two-nop inline asm encoding"); 141 IT_EXPECT( 142 &env, 143 it_contains(text.data, text.len, movq_rcx_rax, sizeof movq_rcx_rax), 144 "missing movq %%rcx, %%rax literal-escape encoding"); 145 IT_EXPECT(&env, 146 it_contains(text.data, text.len, addq_7_rax, sizeof addq_7_rax), 147 "missing addq $7, %%rax immediate-substitution encoding"); 148 } 149 it_text_close(&text); 150 151 IT_EXPECT(&env, 152 it_expect_panic(&env, KIT_ARCH_X86_64, "x64_bad_operand", 153 x64_bad_operand, "operand index"), 154 "expected out-of-range x64 asm operand to panic"); 155 156 { 157 static const uint8_t syscall_bytes[] = {0x0fu, 0x05u}; 158 InlineText sc; 159 IT_EXPECT( 160 &env, 161 it_emit_text(&env, KIT_ARCH_X86_64, "x64_rax_pin", x64_rax_pin, &sc), 162 "failed to emit rax-pinned syscall inline asm"); 163 if (sc.data) 164 IT_EXPECT( 165 &env, 166 it_contains(sc.data, sc.len, syscall_bytes, sizeof syscall_bytes), 167 "missing syscall encoding for rax-pinned operand"); 168 it_text_close(&sc); 169 } 170 171 { 172 InlineText opt; 173 uint32_t xmm_compare_count = 0; 174 uint32_t xmm_bindings = 0; 175 IT_EXPECT(&env, 176 it_emit_text_opt(&env, KIT_ARCH_X86_64, "x64_optimized_stage", 177 x64_optimized_stage, 1, &opt), 178 "failed to emit optimized x64 inline-asm staging case"); 179 if (opt.data) 180 xmm_bindings = x64_self_ucomisd_bindings( 181 opt.data, opt.len, &xmm_compare_count); 182 IT_EXPECT(&env, 183 xmm_compare_count == 16u && xmm_bindings == 0xffffu, 184 "sixteen simultaneous XMM inputs were not independently bound " 185 "(compares=%u mask=%#x)", 186 xmm_compare_count, xmm_bindings); 187 it_text_close(&opt); 188 } 189 190 if (env.fails) { 191 fprintf(stderr, "%d failure(s)\n", env.fails); 192 return 1; 193 } 194 printf("x64_inline_test: ok\n"); 195 return 0; 196 }