kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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 }