[PATCH v1 2/2] Support x86 ACE ISA

Jan Beulich jbeulich@suse.com
Mon Jun 29 09:52:44 GMT 2026


On 27.06.2026 13:53, Ganesh Gopalasubramanian wrote:
> @@ -4253,6 +4257,7 @@ install_template (const insn_template *t)
>  	}
>  
>        if ((maybe_cpu (t, CpuCMPCCXADD) || maybe_cpu (t, CpuAMX_TILE)
> +	   || maybe_cpu (t, CpuACE)
>  	   || maybe_cpu (t, CpuAVX512F) || maybe_cpu (t, CpuAVX512DQ)
>  	   || maybe_cpu (t, CpuAVX512BW) || maybe_cpu (t, CpuBMI)
>  	   || maybe_cpu (t, CpuBMI2) || maybe_cpu (t, CpuUSER_MSR)

Please can you append to such conditionals, or at least insert based on
how many templates there are, and hence (very vaguely) how often one of
them would make it here? (The ordering here is far from great, but I
think ACE wants to at least go after AVX512*.)

> @@ -9323,6 +9331,29 @@ check_APX_operands (const insn_template *t)
>    return 0;
>  }
>  
> +/* Check if ACE operands are valid for the instruction.  */
> +static bool
> +check_ACE_operands (const insn_template *t)
> +{
> +  switch (t->mnem_off)
> +    {
> +    case MN_tilemovrow:
> +    case MN_tilemovcol:
> +      if (is_cpu (t, CpuACE)
> +	  && i.op[0].imms && i.op[0].imms->X_op == O_constant)

i.op[].imms is a member of a union. You can't check or deref it without first
checking that the corresponding operand actually is an immediate one.

That said, ...

> +	{
> +	  offsetT val = i.op[0].imms->X_add_number;
> +	  if (val < 0 || val > 15)
> +	    {
> +	      i.error = unsupported_tile_index;
> +	      return 1;
> +	    }
> +	}
> +      break;
> +    }
> +  return 0;
> +}

... do we need such a check, seeing that the respective AMX-AVX512 insn doesn't
have one? And if so, should it perhaps better be a warning?

And then function having a return type "bool" want to return "true" / "false",
not 0 / 1. The fact that many pre-existing ones don't (yet) has historical
reasons.

> @@ -16943,13 +16982,18 @@ static bool check_register (const reg_entry *r)
>      return false;
>  
>    if (r->reg_type.bitfield.tmmword
> -      && (!cpu_arch_flags.bitfield.cpuamx_tile
> -          || flag_code != CODE_64BIT))
> +      && ((!cpu_arch_flags.bitfield.cpuamx_tile
> +	   && !cpu_arch_flags.bitfield.cpuace)
> +	  || flag_code != CODE_64BIT))
>      return false;
>  
>    if (r->reg_type.bitfield.class == RegBND && !cpu_arch_flags.bitfield.cpumpx)
>      return false;
>  
> +  if (r->reg_type.bitfield.class == RegBS
> +      && !cpu_arch_flags.bitfield.cpuace)
> +    return false;

Juding from the earlier if(), this one also will fit on a single line, and
hence would better do so for uniformity.

> --- /dev/null
> +++ b/gas/testsuite/gas/i386/x86-64-ace.s
> @@ -0,0 +1,45 @@
> +# Check 64-bit ACE instructions
> +
> +	.text
> +start:
> +	bsrinit %bsr0
> +	bsrmovf %zmm3, %zmm1, %bsr0
> +
> +	bsrmovh %zmm10, %bsr0
> +	bsrmovl %zmm10, %bsr0
> +	bsrmovh (%eax), %bsr0
> +	bsrmovl (%eax), %bsr0
> +
> +	bsrmovh %bsr0, %zmm4
> +	bsrmovl %bsr0, %zmm4
> +	bsrmovh %bsr0, (%eax)
> +	bsrmovl %bsr0, (%eax)
> +
> +	tilemovcol $8, %zmm2, %tmm1
> +	tilemovrow $8, %zmm2, %tmm1
> +
> +	tilemovcol %ebx, %zmm2, %tmm1
> +	tilemovrow %ebx, %zmm2, %tmm1
> +
> +	top2bf16ps %zmm2, %zmm1, %tmm0
> +	top4bssd   %zmm2, %zmm1, %tmm0
> +	top4bsud   %zmm2, %zmm1, %tmm0
> +	top4busd   %zmm2, %zmm1, %tmm0
> +	top4buud   %zmm2, %zmm1, %tmm0
> +
> +	top4mxbf8ps  $7, %zmm2, %zmm1, %tmm1
> +	top4mxbhf8ps $7, %zmm2, %zmm1, %tmm1
> +	top4mxhbf8ps $7, %zmm2, %zmm1, %tmm1
> +	top4mxhf8ps  $7, %zmm2, %zmm1, %tmm1
> +	top4mxbssps  $7, %zmm2, %zmm1, %tmm1
> +
> +	tcvtrowd2ps     %edx, %tmm5, %zmm30
> +	tcvtrowd2ps     $0x7b, %tmm5, %zmm30
> +	tcvtrowps2bf16h %edx, %tmm5, %zmm30
> +	tcvtrowps2bf16h $0x7b, %tmm5, %zmm30
> +	tcvtrowps2bf16l %edx, %tmm5, %zmm30
> +	tcvtrowps2bf16l $0x7b, %tmm5, %zmm30
> +	tcvtrowps2phh   %edx, %tmm5, %zmm30
> +	tcvtrowps2phh   $0x7b, %tmm5, %zmm30
> +	tcvtrowps2phl   %edx, %tmm5, %zmm30
> +	tcvtrowps2phl   $0x7b, %tmm5, %zmm30

If you cover pre-existing, re-used insns in the testcase, please cover them
all. For this to be really useful you will want to have another variant of
the test with the same output expectations, but with the ISA limited to ACE
(i.e. in particular no AMX). Iirc you can achieve this by having two #as:
lines (specifying different options) in the respective .d file(s).

> --- a/opcodes/i386-dis-evex-len.h
> +++ b/opcodes/i386-dis-evex-len.h
> @@ -51,6 +51,13 @@ static const struct dis386 evex_len_table[][3] = {
>      { PREFIX_TABLE (PREFIX_EVEX_0F384A_X86_64_W_0_L_2) },
>    },
>  
> +  /* EVEX_LEN_0F384B */
> +  {
> +    { X86_64_EVEX_W_TABLE (VEX_W_0F384B_X86_64_L_0) },
> +    { Bad_Opcode },
> +    { PREFIX_TABLE (PREFIX_EVEX_0F384B) },

The referenced enumerator needs renaming, to carry in its name that L_2
was already decoded.

> --- a/opcodes/i386-dis-evex-prefix.h
> +++ b/opcodes/i386-dis-evex-prefix.h
> @@ -254,6 +254,18 @@
>      { "tcvtrowd2ps",	{ XM, Rtmm, VexGdq }, 0 },
>      { "tilemovrow",	{ XM, Rtmm, VexGdq }, 0 },
>    },
> +  /* PREFIX_EVEX_0F384A */
> +  {
> +    { Bad_Opcode },
> +    { Bad_Opcode },
> +    { "tilemovrow",	{ TMM, Rzmm, VexGd }, 0 },
> +  },
> +  /* PREFIX_EVEX_0F384B */
> +  {
> +    { Bad_Opcode },
> +    { Bad_Opcode },
> +    { "tilemovcol",	{ TMM, Rzmm, VexGd }, 0 },

As per above, the enumerators in the comments need to specify what was
already decoded. As it stands, this gives the impression that neither
L'L nor W were. (Also applies elsewhere.)

> --- a/opcodes/i386-dis.c
> +++ b/opcodes/i386-dis.c
> @@ -70,6 +70,7 @@ static bool OP_D (instr_info *, int, int);
>  static bool OP_T (instr_info *, int, int);
>  static bool OP_MMX (instr_info *, int, int);
>  static bool OP_XMM (instr_info *, int, int);
> +static bool OP_BSR (instr_info *, int, int);
>  static bool OP_EM (instr_info *, int, int);
>  static bool OP_EX (instr_info *, int, int);
>  static bool OP_EMC (instr_info *, int,int);
> @@ -513,6 +514,7 @@ fetch_error (const instr_info *ins)
>  #define RMBH { OP_REG, bh_reg }
>  #define RMAX { OP_REG, ax_reg }
>  #define RMDX { OP_REG, dx_reg }
> +#define BSR { OP_BSR, bsr }
>  
>  #define eAX { OP_IMREG, eAX_reg }
>  #define AL { OP_IMREG, al_reg }
> @@ -566,6 +568,7 @@ fetch_error (const instr_info *ins)
>  #define EXxS { OP_EX, x_swap_mode }
>  #define EXxmm { OP_EX, xmm_mode }
>  #define EXymm { OP_EX, ymm_mode }
> +#define EXzmm { OP_EX, zmm_mode }

Why would this be needed? You use it in places where ordinary EVEX.L'L-
controlled reg/mem operands (without broadcast) are used, i.e. EXEvexXNoBcst
should do there.

> @@ -583,6 +586,7 @@ fetch_error (const instr_info *ins)
>  #define Uxmm { OP_R, xmm_mode }
>  #define Rxmmq { OP_R, xmmq_mode }
>  #define Rymm { OP_R, ymm_mode }
> +#define Rzmm { OP_R, zmm_mode }
The R-prefixed constants here aren't quite correct. As you can see newer
ones use U instead, and so should you. We try to match the SDM nomenclature.
And we already have Uxmm, which (like EXx / EXEvexXNoBcst above) should fit
here.

> @@ -600,6 +604,7 @@ fetch_error (const instr_info *ins)
>  #define VexGatherQ { OP_VEX, vex_vsib_q_w_dq_mode }
>  #define VexGdq { OP_VEX, dq_mode }
>  #define VexGb { OP_VEX, b_mode }
> +#define VexGd { OP_VEX, d_mode }

This doesn't match the existing TILEMOVROW and TCVTROW*, which use VexGdq.
With EVEX.W now gaining a different meaning, that wants sorting first (and
preferably separately).

> @@ -821,6 +828,8 @@ enum
>    rSI_reg,
>    rDI_reg,
>  
> +  bsr,

Do we really need this when OP_BSR() is a separate routine anyway?

> --- a/opcodes/i386-gen.c
> +++ b/opcodes/i386-gen.c
> @@ -263,6 +263,8 @@ static const dependency isa_dependencies[] =
>      "RTM|HLE" },
>    { "TSXLDTRK",
>      "RTM" },
> +  { "ACE",
> +    "AVX512F|64" },

Isn't AVX512BW|AVX512_BF16 the minimally sensible prereq, seeing the element
types of the outer-product insns?

> --- a/opcodes/i386-opc.h
> +++ b/opcodes/i386-opc.h
> @@ -359,6 +359,8 @@ enum i386_cpu
>    CpuAVX10_2,
>    /* Intel AMX-TRANSPOSE Instructions support required.  */
>    CpuAMX_TRANSPOSE,
> +  /* ACE instructions support required.  */
> +  CpuACE,
>    /* Not supported in the 64bit mode  */
>    CpuNo64,
>  
> @@ -397,6 +399,7 @@ enum i386_cpu
>  		   cpuapx_f:1, \
>  		   cpuavx10_2:1, \
>  		   cpuamx_transpose:1, \
> +		   cpuace:1, \
>        /* NOTE: This field needs to remain last. */ \
>  		   cpuno64:1
>  
> @@ -889,6 +892,7 @@ enum operand_class
>    RegSIMD, /* XMM/YMM/ZMM registers, distinguished by operand size */
>    RegMask, /* Vector Mask register */
>    RegBND, /* Bound register */
> +  RegBS, /* Block scale register */

I think RegBSR would be a better name here.

> --- a/opcodes/i386-opc.tbl
> +++ b/opcodes/i386-opc.tbl
> @@ -62,6 +62,8 @@
>  
>  #define RegBND Class=RegBND
>  
> +#define RegBS Class=RegBS
> +
>  #define Mmword Qword
>  #define Oword  Xmmword
>  
> @@ -118,6 +120,7 @@
>  #define SpaceXOP0A OpcodeSpace=SPACE_XOP0A
>  
>  #define EVexMap4 OpcodeSpace=SPACE_MAP4|EVex128
> +#define EVexMap6 OpcodeSpace=SPACE_MAP6

Why, when this isn't any different from ...

>  #define Map5 OpcodeSpace=SPACE_MAP5
>  #define Map6 OpcodeSpace=SPACE_MAP6

... this?

> @@ -3288,22 +3297,51 @@ tdpbhf8ps, 0xf2fd, AMX_FP8, Modrm|Vex128|Map5|Src2VVVV|VexW0|NoSuf, { RegTMM, Re
>  tdphbf8ps, 0xf3fd, AMX_FP8, Modrm|Vex128|Map5|Src2VVVV|VexW0|NoSuf, { RegTMM, RegTMM, RegTMM }
>  tdphf8ps, 0x66fd, AMX_FP8, Modrm|Vex128|Map5|Src2VVVV|VexW0|NoSuf, { RegTMM, RegTMM, RegTMM }
>  
> -tcvtrowd2ps, 0xf34a, AMX_AVX512, Modrm|EVex512|Space0F38|Src2VVVV|VexW0|NoSuf, { Reg32, RegTMM, RegZMM }
> -tcvtrowd2ps, 0xf307, AMX_AVX512, Modrm|EVex512|Space0F3A|VexW0|NoSuf, { Imm8, RegTMM, RegZMM }
> +tcvtrowd2ps, 0xf34a, AMX_AVX512|ACE, Modrm|EVex512|Space0F38|Src2VVVV|VexW0|NoSuf, { Reg32, RegTMM, RegZMM }
> +tcvtrowd2ps, 0xf307, AMX_AVX512|ACE, Modrm|EVex512|Space0F3A|VexW0|NoSuf, { Imm8, RegTMM, RegZMM }
> +
> +tcvtrowps2bf16h, 0xf26d, AMX_AVX512|ACE, Modrm|EVex512|Space0F38|Src2VVVV|VexW0|NoSuf, { Reg32, RegTMM, RegZMM }
> +tcvtrowps2bf16h, 0xf207, AMX_AVX512|ACE, Modrm|EVex512|Space0F3A|VexW0|NoSuf, { Imm8, RegTMM, RegZMM }
> +tcvtrowps2bf16l, 0xf36d, AMX_AVX512|ACE, Modrm|EVex512|Space0F38|Src2VVVV|VexW0|NoSuf, { Reg32, RegTMM, RegZMM }
> +tcvtrowps2bf16l, 0xf377, AMX_AVX512|ACE, Modrm|EVex512|Space0F3A|VexW0|NoSuf, { Imm8, RegTMM, RegZMM }
> +tcvtrowps2phh, 0x6d, AMX_AVX512|ACE, Modrm|EVex512|Space0F38|Src2VVVV|VexW0|NoSuf, { Reg32, RegTMM, RegZMM }
> +tcvtrowps2phh, 0x07, AMX_AVX512|ACE, Modrm|EVex512|Space0F3A|VexW0|NoSuf, { Imm8, RegTMM, RegZMM }
> +tcvtrowps2phl, 0x666d, AMX_AVX512|ACE, Modrm|EVex512|Space0F38|Src2VVVV|VexW0|NoSuf, { Reg32, RegTMM, RegZMM }
> +tcvtrowps2phl, 0xf277, AMX_AVX512|ACE, Modrm|EVex512|Space0F3A|VexW0|NoSuf, { Imm8, RegTMM, RegZMM }
> +
> +tilemovrow, 0x664a, AMX_AVX512|ACE, Modrm|EVex512|Space0F38|Src2VVVV|VexW0|NoSuf, { Reg32, RegTMM, RegZMM }
> +tilemovrow, 0x6607, AMX_AVX512|ACE, Modrm|EVex512|Space0F3A|VexW0|NoSuf, { Imm8, RegTMM, RegZMM }
> +
> +// AMX & ACE instructions end.
> +
> +// ACE instructions.
> +
> +bsrinit, 0xf249, ACE, Modrm|Vex128|Space0F38|VexW1|NoSuf, { RegBS }
> +bsrmovf, 0x95, ACE, Modrm|EVex512|EVexMap6|VexW1|Src1VVVV|NoSuf, { RegZMM|Unspecified|BaseIndex, RegZMM, RegBS }
> +bsrmovh, 0xf295, ACE, Modrm|EVex512|EVexMap6|VexW1|NoSuf, { RegZMM|Unspecified|BaseIndex, RegBS }
> +bsrmovh, 0xf295, ACE, RegMem|Modrm|EVex512|EVexMap6|VexW0|NoSuf, { RegBS, RegZMM|Unspecified|BaseIndex }
> +bsrmovl, 0xf395, ACE, Modrm|EVex512|EVexMap6|VexW1|NoSuf, { RegZMM|Unspecified|BaseIndex, RegBS }
> +bsrmovl, 0xf395, ACE, RegMem|Modrm|EVex512|EVexMap6|VexW0|NoSuf, { RegBS, RegZMM|Unspecified|BaseIndex }

These two pairs may want to use D, just like the FMA4 and XOP insns do (where
VEX.W has a similar purpose as EVEX.W here).

> +tilemovcol, 0x662f, ACE, Modrm|EVex512|Space0F3A|VexW1|NoSuf, { Imm8, RegZMM, RegTMM }
> +tilemovcol, 0x664b, ACE, Modrm|EVex512|Space0F38|VexW1|Src2VVVV|NoSuf, { Reg32, RegZMM, RegTMM }
> +tilemovrow, 0x6607, ACE, Modrm|EVex512|Space0F3A|VexW1|NoSuf, { Imm8, RegZMM, RegTMM }
> +tilemovrow, 0x664a, ACE, Modrm|EVex512|Space0F38|VexW1|Src2VVVV|NoSuf, { Reg32, RegZMM, RegTMM }
> +
> +top2bf16ps, 0xf35c, ACE, Modrm|EVex512|Space0F38|VexW0|Src2VVVV|NoSuf, { RegZMM, RegZMM, RegTMM }
>  
> -tcvtrowps2bf16h, 0xf26d, AMX_AVX512, Modrm|EVex512|Space0F38|Src2VVVV|VexW0|NoSuf, { Reg32, RegTMM, RegZMM }
> -tcvtrowps2bf16h, 0xf207, AMX_AVX512, Modrm|EVex512|Space0F3A|VexW0|NoSuf, { Imm8, RegTMM, RegZMM }
> -tcvtrowps2bf16l, 0xf36d, AMX_AVX512, Modrm|EVex512|Space0F38|Src2VVVV|VexW0|NoSuf, { Reg32, RegTMM, RegZMM }
> -tcvtrowps2bf16l, 0xf377, AMX_AVX512, Modrm|EVex512|Space0F3A|VexW0|NoSuf, { Imm8, RegTMM, RegZMM }
> -tcvtrowps2phh, 0x6d, AMX_AVX512, Modrm|EVex512|Space0F38|Src2VVVV|VexW0|NoSuf, { Reg32, RegTMM, RegZMM }
> -tcvtrowps2phh, 0x07, AMX_AVX512, Modrm|EVex512|Space0F3A|VexW0|NoSuf, { Imm8, RegTMM, RegZMM }
> -tcvtrowps2phl, 0x666d, AMX_AVX512, Modrm|EVex512|Space0F38|Src2VVVV|VexW0|NoSuf, { Reg32, RegTMM, RegZMM }
> -tcvtrowps2phl, 0xf277, AMX_AVX512, Modrm|EVex512|Space0F3A|VexW0|NoSuf, { Imm8, RegTMM, RegZMM }
> +top4bssd, 0xf25e, ACE, Modrm|EVex512|Space0F38|VexW0|Src2VVVV|NoSuf, { RegZMM, RegZMM, RegTMM }
> +top4bsud, 0xf35e, ACE, Modrm|EVex512|Space0F38|VexW0|Src2VVVV|NoSuf, { RegZMM, RegZMM, RegTMM }
> +top4busd, 0x665e, ACE, Modrm|EVex512|Space0F38|VexW0|Src2VVVV|NoSuf, { RegZMM, RegZMM, RegTMM }
> +top4buud, 0x5e, ACE, Modrm|EVex512|Space0F38|VexW0|Src2VVVV|NoSuf, { RegZMM, RegZMM, RegTMM }

Along the lines of the existing <dpb> for AVX-VNNI-INT, the tile dot product
insns will want templatizing, for that template then to be re-use here.

> -tilemovrow, 0x664a, AMX_AVX512, Modrm|EVex512|Space0F38|Src2VVVV|VexW0|NoSuf, { Reg32, RegTMM, RegZMM }
> -tilemovrow, 0x6607, AMX_AVX512, Modrm|EVex512|Space0F3A|VexW0|NoSuf, { Imm8, RegTMM, RegZMM }
> +top4mxbf8ps, 0x8d, ACE, Modrm|EVex512|Space0F3A|VexW0|Src2VVVV|NoSuf, { Imm8, RegZMM, RegZMM, RegTMM }
> +top4mxbhf8ps, 0xf28d, ACE, Modrm|EVex512|Space0F3A|VexW0|Src2VVVV|NoSuf, { Imm8, RegZMM, RegZMM, RegTMM }
> +top4mxhbf8ps, 0xf38d, ACE, Modrm|EVex512|Space0F3A|VexW0|Src2VVVV|NoSuf, { Imm8, RegZMM, RegZMM, RegTMM }
> +top4mxhf8ps, 0x668d, ACE, Modrm|EVex512|Space0F3A|VexW0|Src2VVVV|NoSuf, { Imm8, RegZMM, RegZMM, RegTMM }
> +top4mxbssps, 0xf28f, ACE, Modrm|EVex512|Space0F3A|VexW0|Src2VVVV|NoSuf, { Imm8, RegZMM, RegZMM, RegTMM }
>  
> -// AMX instructions end.
> +// ACE instructions end.

Overall: Might be nice to split things - 1st patch to re-use pre-existing insns,
2nd one to add new ones.

> --- a/opcodes/i386-reg.tbl
> +++ b/opcodes/i386-reg.tbl
> @@ -344,7 +344,7 @@ zmm28, Class=RegSIMD|Zmmword, RegVRex|RegRex, 4, Dw2Inval, 79
>  zmm29, Class=RegSIMD|Zmmword, RegVRex|RegRex, 5, Dw2Inval, 80
>  zmm30, Class=RegSIMD|Zmmword, RegVRex|RegRex, 6, Dw2Inval, 81
>  zmm31, Class=RegSIMD|Zmmword, RegVRex|RegRex, 7, Dw2Inval, 82
> -// TMM registers for AMX
> +// TMM registers for AMX/ACE
>  tmm0, Class=RegSIMD|Tmmword, 0, 0, Dw2Inval, Dw2Inval
>  tmm1, Class=RegSIMD|Tmmword, 0, 1, Dw2Inval, Dw2Inval
>  tmm2, Class=RegSIMD|Tmmword, 0, 2, Dw2Inval, Dw2Inval
> @@ -353,6 +353,8 @@ tmm4, Class=RegSIMD|Tmmword, 0, 4, Dw2Inval, Dw2Inval
>  tmm5, Class=RegSIMD|Tmmword, 0, 5, Dw2Inval, Dw2Inval
>  tmm6, Class=RegSIMD|Tmmword, 0, 6, Dw2Inval, Dw2Inval
>  tmm7, Class=RegSIMD|Tmmword, 0, 7, Dw2Inval, Dw2Inval
> +// Block scale register for ACE
> +bsr0, Class=RegBS, 0, 0, Dw2Inval, Dw2Inval

Again a new register without the ability to represent it in Dwarf / EH-frame?

Jan


More information about the Binutils mailing list