]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb/record: add support to vinsert and vextract instructions
authorGuinevere Larsen <guinevere@redhat.com>
Tue, 10 Jun 2025 14:28:25 +0000 (11:28 -0300)
committerGuinevere Larsen <guinevere@redhat.com>
Fri, 11 Jul 2025 14:55:34 +0000 (11:55 -0300)
This patch adds support for the following instructions:
* VEXTRACT[F128|I128|PS]
* VINSERT[F128|I128|PS]
* VPEXTR[B|W|D|Q]

And associated test. For some reason, it seems that the extract
instructions deal with the output register as though it was the first
source register, so they use ModRM.r/m and VEX.B, instead of the usual
ModRM.reg and VEX.R. This meant that the opcode collision with
vbroadcastsd wasn't trivial. It can be easily solved by checking the
VEX.map_select field, so soslving it was very easy.

The VPEXTR instructions had several complicated collisions, and notably,
vpextrw to a register works completely different to any other
instruction in the family, so the code is messy, but it should be
correct.

gdb/i386-tdep.c
gdb/testsuite/gdb.reverse/i386-avx-reverse.c
gdb/testsuite/gdb.reverse/i386-avx-reverse.exp

index 31a113a729a31f4dbee333a7903e3c91adb4a732..45ae8b65e3d65cfe47d193999f155024ceec630c 100644 (file)
@@ -4859,12 +4859,6 @@ i386_record_vex (struct i386_record_s *ir, uint8_t vex_w, uint8_t vex_r,
          i386_record_lea_modrm (ir);
        }
       break;
-    case 0x14: /* VUNPCKL[PS|PD].  */
-    case 0x15: /* VUNPCKH [PS|PD].  */
-      i386_record_modrm (ir);
-      record_full_arch_list_add_reg (ir->regcache,
-                                    tdep->ymm0_regnum + ir->reg + vex_r * 8);
-      break;
     case 0x6e: /* VMOVD XMM, reg/mem  */
       /* This is moving from a regular register or memory region into an
         XMM register. */
@@ -4994,14 +4988,34 @@ i386_record_vex (struct i386_record_s *ir, uint8_t vex_w, uint8_t vex_r,
        }
       break;
 
-    case 0x19: /* VBROADCASTSD.  */
+    case 0x17: /* VEXTRACTPS.  */
       i386_record_modrm (ir);
       record_full_arch_list_add_reg (ir->regcache,
-                                    tdep->ymm0_regnum + ir->reg
-                                    + 8 * vex_r);
+                                    ir->regmap[X86_RECORD_REAX_REGNUM
+                                               + ir->rm]);
+      break;
+
+    case 0x19: /* VBROADCASTSD and VEXTRACTF128.  */
+    case 0x39: /* VEXTRACTI128.  */
+      i386_record_modrm (ir);
+      /* vextract instructions use ModRM.R/M and VEX.B to address the
+        output register, while vbroadcast use ModRM.Reg and VEX.R.
+        They are differentiated through map_select.  */
+      if (ir->map_select == 2)
+       record_full_arch_list_add_reg (ir->regcache,
+                                      tdep->ymm0_regnum + ir->reg
+                                      + 8 * vex_r);
+      else
+       record_full_arch_list_add_reg (ir->regcache,
+                                      tdep->ymm0_regnum + ir->rm
+                                      + 8 * ir->rex_b);
       break;
 
-    case 0x18: /* VBROADCASTSS.  */
+    case 0x18: /* VBROADCASTSS and VINSERTI128.  */
+    case 0x20: /* VPINSRB.  */
+    case 0x21: /* VINSERTPS.  */
+    case 0x22: /* VINSR[D|Q].  */
+    case 0x38: /* VINSERTF128.  */
     case 0x60: /* VPUNPCKLBW  */
     case 0x61: /* VPUNPCKLWD  */
     case 0x62: /* VPUNPCKLDQ  */
@@ -5010,6 +5024,7 @@ i386_record_vex (struct i386_record_s *ir, uint8_t vex_w, uint8_t vex_r,
     case 0x69: /* VPUNPCKHWD  */
     case 0x6a: /* VPUNPCKHDQ  */
     case 0x6d: /* VPUNPCKHQDQ */
+    case 0xc4: /* VPINSRW.  */
       {
        i386_record_modrm (ir);
        int reg_offset = ir->reg + vex_r * 8;
@@ -5018,6 +5033,61 @@ i386_record_vex (struct i386_record_s *ir, uint8_t vex_w, uint8_t vex_r,
       }
       break;
 
+    case 0x14: /* VPEXTRB and VUNPCKL[PS|PD].  */
+    case 0x15: /* VPEXTRW (to memory) and VUNPCKH [PS|PD].  */
+    case 0x16: /* VPEXTR[D|Q] and VPERMPS.  */
+      {
+       i386_record_modrm (ir);
+       /* All vpextr instructions in this case use map_select == 3,
+          while vpermps and vunpck have map_select equal to 2 and 1,
+          respectively.  The opcode 0xc5 is for vpextr, but uses
+          map_select == 1, but due to other inconsistencies with
+          the other vpextr instructions, it is in a separate case to
+          avoid making this even more of a mess.  */
+       if (ir->map_select == 3)
+         {
+           if (ir->mod == 3)
+             {
+               /* ModRM.Mod being equal to 3 means this ModRM encodes
+                  a register.  */
+               record_full_arch_list_add_reg (ir->regcache,
+                                         ir->regmap[X86_RECORD_REAX_REGNUM
+                                                    + ir->rm]);
+             }
+           else
+             {
+               /* Even though the test only generated ModRM.Mod == 0,
+                  in theory all values != 3 are viable to encode a memory
+                  address, so all of them are passed along.  */
+               /* Size is mostly based on the opcode, except for
+                  double/quadword difference.  */
+               ir->ot = opcode - 0x14;
+               if (opcode == 0x16 && vex_w == 1)
+                 ir->ot ++;
+               /* I'm not sure if this is the original use, but in here
+                  rip_offset is used to indicate that the RIP pointer will
+                  be 1 byte away from where the instruction expects it to
+                  be, because the immediate will not have been read by the
+                  time the address changed is calculated.  */
+               ir->rip_offset = 1;
+               i386_record_lea_modrm (ir);
+             }
+         }
+       else
+         {
+           record_full_arch_list_add_reg (ir->regcache,
+                                          tdep->ymm0_regnum + ir->reg
+                                          + vex_r * 8);
+         }
+       break;
+      }
+    case 0xc5: /* VPEXTRW to register.  */
+      i386_record_modrm (ir);
+      record_full_arch_list_add_reg (ir->regcache,
+                               ir->regmap[X86_RECORD_REAX_REGNUM
+                                          + ir->reg]);
+      break;
+
     case 0x74: /* VPCMPEQB  */
     case 0x75: /* VPCMPEQB  */
     case 0x76: /* VPCMPEQB  */
@@ -5045,7 +5115,6 @@ i386_record_vex (struct i386_record_s *ir, uint8_t vex_w, uint8_t vex_r,
     case 0x06: /* VMPERM2F128.  */
     case 0x0c: /* VPERMILPS with register.  */
     case 0x0d: /* VPERMILPD with register.  */
-    case 0x16: /* VPERMPS.  */
     case 0x1a: /* VBROADCASTF128.  */
     case 0x36: /* VPERMD.  */
     case 0x40: /* VPMULLD  */
index 8b3f7077e457f16b30d64842bf8b968937a62e4c..ef7d0d86f50c6d06dcd5e9349785124b87fa6ffc 100644 (file)
@@ -589,6 +589,45 @@ permute_test ()
   return 0; /* end permute_test  */
 }
 
+int
+extract_insert_test ()
+{
+  /* start extract_insert_test.  */
+  /* Using GDB, load these values onto registers for testing.
+     ymm0.v2_int128 = {0, 0}
+     ymm1.v16_int16 = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}
+     xmm2.uint128 = 0xcafe
+     ymm15.v2_int128 = {0x0, 0x0}
+     eax = 0
+     this way it's easy to confirm we're undoing things correctly.  */
+
+  asm volatile ("vinserti128 $1, %xmm2, %ymm1, %ymm0");
+  asm volatile ("vinsertf128 $0, %xmm2, %ymm1, %ymm15");
+  asm volatile ("vextracti128 $1, %ymm1, %xmm0");
+  asm volatile ("vextractf128 $0, %ymm1, %xmm15");
+  asm volatile ("vinsertps $16, %xmm2, %xmm1, %xmm0");
+  asm volatile ("vextractps $0, %xmm2, %rax");
+
+  asm volatile ("vpextrb $5, %xmm1, %rax");
+  asm volatile ("vpextrb $4, %%xmm1, %0" : "=m" (global_buf1));
+  asm volatile ("vpextrd $3, %xmm1, %eax");
+  asm volatile ("vpextrd $2, %%xmm1, %0" : "=m" (global_buf1));
+  asm volatile ("vpextrq $1, %xmm1, %rax");
+  asm volatile ("vpextrq $0, %%xmm1, %0" : "=m" (global_buf1));
+
+  asm volatile ("vpinsrb $3, %rax, %xmm2, %xmm0");
+  asm volatile ("vpinsrw $2, %eax, %xmm2, %xmm15");
+  asm volatile ("vpinsrd $1, %eax, %xmm2, %xmm0");
+  asm volatile ("vpinsrq $0, %rax, %xmm2, %xmm15");
+
+  /* vpextrw has completely different mechanics to other vpextr
+     instructions, so separate them for ease of testing later.  */
+  asm volatile ("vpextrw $1, %xmm1, %eax");
+  asm volatile ("vpextrw $1, %%xmm1, %0" : "=m" (global_buf1));
+
+  return 0; /* end extract_insert_test  */
+}
+
 /* This include is used to allocate the dynamic buffer and have
    the pointers aligned to a 32-bit boundary, so we can test instructions
    that require aligned memory.  */
@@ -624,5 +663,6 @@ main ()
   shift_test ();
   shuffle_test ();
   permute_test ();
+  extract_insert_test ();
   return 0;    /* end of main */
 }
index ae13a6e86e64aea807676c5bae5662871c3e1e2a..36e611fe0ddd55cc3f0e990cbdc4bdaf0a7006ed 100644 (file)
@@ -959,3 +959,58 @@ if {[record_full_function "permute"] == true} {
 }
 gdb_test "finish" "Run till exit from.*permute_test.*" \
     "leaving permute"
+
+# Preparation and testing extract_insert instructions.
+gdb_test_no_output \
+    "set \$ymm0.v2_int128 = {0, 0}" "set ymm0 for extract_insert"
+gdb_test_no_output \
+    "set \$ymm1.v16_int16 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}" \
+    "set ymm1 for extract_insert"
+gdb_test_no_output "set \$xmm2.uint128 = 0xcafe" \
+    "set ymm2 for extract_insert"
+gdb_test_no_output "set \$ymm15.v2_int128 = {0,0}" "set ymm15 for extract_insert"
+gdb_test_no_output "set \$rax = 0" "set eax for extract_insert"
+
+if {[record_full_function "extract_insert"] == true} {
+    test_one_memory "vpextrw" "global_buf1" \
+       "\\\{0x1, 0x0, 0x2, 0x0, 0x3, 0x0, 0x4, 0x0, 0x18"
+    test_one_general_register "vpextrw" "rax" "0x8000700060005"
+
+    test_one_register "vpinsrq" "ymm15" \
+       "0x50000cafe, 0x0"
+    test_one_register "vpinsrd" "ymm0" \
+       "0x500cafe, 0x0"
+    test_one_register "vpinsrw" "ymm15" \
+       "0x80007000600050004000300020001, 0x0"
+    test_one_register "vpinsrb" "ymm0" \
+       "0x80007000600050000cafe00020001, 0x0"
+
+    test_one_memory "vpextrq" "global_buf1" \
+       "\\\{0x5, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18"
+    test_one_general_register "vpextrq" "rax" "0x80007"
+    test_one_memory "vpextrd" "global_buf1" \
+       "\\\{0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18"
+    test_one_general_register "vpextrd" "rax" "0x0"
+    test_one_memory "vpextrb" "global_buf1" \
+       "\\\{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x18"
+    test_one_general_register "vpextrb" "rax" "0xcafe"
+
+    test_one_general_register "vextractps" "eax" "0x0"
+    test_one_register "vinsertps" "ymm0" \
+       "0x10000f000e000d000c000b000a0009, 0x0"
+    test_one_register "vextractf128" "ymm15" \
+       "0xcafe, 0x10000f000e000d000c000b000a0009"
+    test_one_register "vextracti128" "ymm0" \
+       "0x80007000600050004000300020001, 0xcafe"
+    test_one_register "vinsertf128" "ymm15" \
+       "0x0, 0x0"
+    test_one_register "vinserti128" "ymm0" \
+       "0x0, 0x0"
+
+    gdb_test "record stop" "Process record is stopped.*" \
+       "delete history for extract_insert_test"
+} else {
+    untested "couldn't run extract_insert tests"
+}
+gdb_test "finish" "Run till exit from.*extract_insert_test.*" \
+    "leaving extract_insert"