* [PATCH v2 00/17] target/i386/tcg: implement APX
@ 2026-09-23 17:30 Paolo Bonzini
2026-09-23 17:30 ` [PATCH 01/17] target/i386/tcg: generalize zero- and sign-extension Paolo Bonzini
` (17 more replies)
0 siblings, 18 replies; 35+ messages in thread
From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw)
To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu
This series implements APX support; the main reason to do this is actually
to have some initial infrastructure for EVEX, without requiring all the
complexity of AVX512 but introducing the required changes in the
emulator. It also forces some changes that (hopefully) make
QEMU's decoder align a bit more with what Intel processors actually do.
This series was tested with Zhao Liu's https://github.com/trueptolemy/apx-test.
The failures were analyzed by AI but the fixes had to be redone anyway.
Other than for a couple typos, the LLM tended to fix the symptom rather
than be general, which is exactly the opposite of what I wanted. For
example, for the MUL/IMUL/DIV/IDIV assertion failure it wanted to modify
the decoding tables instead of weakening
assert(e->s0 == e->s1);
to this:
assert(e->op1 == X86_TYPE_None || e->s0 == e->s1);
Changing the decoding tables probably is a more common failure mode, indeed,
but not the right approach in the context of this series. Other than that,
the AGENTS.md from the AI policy discussion worked fine and the LLM was very
keen on helping me not stray from the righteous path.
The issues detected by apx-test and fixed here are as follows:
- MUL/IMUL/DIV/IDIV caused an assertion failure
- INC/DEC did not allow 0x66 as a prefix
- CMOV/CFCMOV's implicit zero-upper behavior was missing
- IMUL's use of sextT0 conflicted with the zero-upper modifier (see also
patches 1-3)
- MOVBE's register-to-register implementation was missing (see also
patch 4)
- REX2 incorrectly allowed a 0x0F byte following the prefix (patch 8)
On top of this, the fix between APX and MPX was not correctly resolved,
which is now fixed, and PUSH2/POP2 use a single MO_128 access.
I also included a couple recommendations from Chang Seok Bae. Thanks!
Paolo
Paolo Bonzini (17):
target/i386/tcg: generalize zero- and sign-extension
target/i386/tcg: use standard operand order for BSF and BSR
target/i386/tcg: use zextT1 for CMPXCHG
target/i386/tcg: unify MOVBE and BSWAP generators
target/i386/tcg: add APX support to XSAVE/XRSTOR
target/i386/tcg: add definition for REX2 prefix
target/i386/tcg: mark XSAVE* as not allowing REX2
target/i386/tcg: treat 0x0F as a prefix
target/i386/tcg: decode REX2 prefix
target/i386/tcg: implement JMPABS instruction
target/i386/tcg: extend VEX.vvvv parsing for APX
target/i386/tcg: decode EVEX prefix
target/i386/tcg: add decode functionality for APX
target/i386/tcg: implement CCMP/CTEST
target/i386/tcg: decode APX instructions
target/i386: extract computation of no-autoenable features
target/i386/tcg: mark APX as supported
configs/targets/x86_64-bsd-user.mak | 2 +-
configs/targets/x86_64-linux-user.mak | 2 +-
target/i386/cpu.h | 8 +
target/i386/helper.h | 1 +
target/i386/tcg/decode-new.h | 32 +-
target/i386/tcg/tcg-cpu.h | 18 +-
target/i386/tcg/cc_helper_template.h.inc | 11 +
target/i386/cpu.c | 45 +-
target/i386/helper.c | 11 +
target/i386/tcg/cc_helper.c | 14 +
target/i386/tcg/excp_helper.c | 5 +
target/i386/tcg/fpu_helper.c | 59 +-
target/i386/tcg/tcg-cpu.c | 5 +-
target/i386/tcg/translate.c | 112 ++-
target/i386/tcg/decode-new.c.inc | 823 +++++++++++++++++++----
target/i386/tcg/emit.c.inc | 255 ++++++-
16 files changed, 1210 insertions(+), 193 deletions(-)
--
2.55.0
^ permalink raw reply [flat|nested] 35+ messages in thread* [PATCH 01/17] target/i386/tcg: generalize zero- and sign-extension 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini @ 2026-09-23 17:30 ` Paolo Bonzini 2026-09-23 19:34 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 02/17] target/i386/tcg: use standard operand order for BSF and BSR Paolo Bonzini ` (16 subsequent siblings) 17 siblings, 1 reply; 35+ messages in thread From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw) To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu Allow applying sign and zero extension to T1 load and T0 writeback. T1 load is useful for some cases where the tables are hacked to put the sign extended operand in T0; T0 writeback is used for APX ZU instructions. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/decode-new.h | 15 +++++++++++---- target/i386/tcg/decode-new.c.inc | 21 +++++++++++---------- target/i386/tcg/emit.c.inc | 16 ++++++++++------ 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/target/i386/tcg/decode-new.h b/target/i386/tcg/decode-new.h index 25f7d2215ee..a3702b00614 100644 --- a/target/i386/tcg/decode-new.h +++ b/target/i386/tcg/decode-new.h @@ -148,6 +148,12 @@ typedef enum X86OpUnit { X86_OP_MMX, /* address in either s->ptrX or s->A0 depending on has_ea */ } X86OpUnit; +typedef enum X86OpExt { + X86_EXT_None, + X86_EXT_Signed, + X86_EXT_Unsigned, +} X86OpExt; + typedef enum X86InsnCheck { /* Illegal or exclusive to 64-bit mode */ X86_CHECK_i64 = 1, @@ -226,10 +232,6 @@ typedef enum X86InsnSpecial { */ X86_SPECIAL_MMX, - /* When loaded into s->T0, register operand 1 is zero/sign extended. */ - X86_SPECIAL_SExtT0, - X86_SPECIAL_ZExtT0, - /* Memory operand size of MOV from segment register is MO_16 */ X86_SPECIAL_Op0_Mw, } X86InsnSpecial; @@ -300,6 +302,10 @@ struct X86OpEntry { X86OpSize s3:8; X86InsnSpecial special:8; + unsigned ext0:2; + unsigned ext1:2; + unsigned ext2:2; + unsigned :2; X86CPUIDFeature cpuid:8; unsigned vex_class:8; X86VEXSpecial vex_special:8; @@ -314,6 +320,7 @@ typedef struct X86DecodedOp { int8_t n; MemOp ot; /* For b/c/d/p/s/q/v/w/y/z */ X86OpUnit unit; + X86OpExt ext; bool has_ea; int offset; /* For MMX and SSE */ diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index 26f6d6277be..e8b21c77743 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -233,8 +233,12 @@ #define op0_Rd .special = X86_SPECIAL_Op0_Rd, #define op2_Ry .special = X86_SPECIAL_Op2_Ry, #define avx_movx .special = X86_SPECIAL_AVXExtMov, -#define sextT0 .special = X86_SPECIAL_SExtT0, -#define zextT0 .special = X86_SPECIAL_ZExtT0, +#define sextwT0 .ext0 = X86_EXT_Signed, +#define zextwT0 .ext0 = X86_EXT_Unsigned, +#define sextT0 .ext1 = X86_EXT_Signed, +#define zextT0 .ext1 = X86_EXT_Unsigned, +#define sextT1 .ext2 = X86_EXT_Signed, +#define zextT1 .ext2 = X86_EXT_Unsigned, #define op0_Mw .special = X86_SPECIAL_Op0_Mw, #define btEvGv .special = X86_SPECIAL_BitTest, @@ -1614,9 +1618,9 @@ static void decode_group2(DisasContext *s, CPUX86State *env, X86OpEntry *entry, int op = (get_modrm(s, env) >> 3) & 7; entry->gen = group2_gen[op]; if (op == 7) { - entry->special = X86_SPECIAL_SExtT0; + entry->ext1 = X86_EXT_Signed; } else { - entry->special = X86_SPECIAL_ZExtT0; + entry->ext1 = X86_EXT_Unsigned; } } @@ -2491,6 +2495,7 @@ static bool decode_insn(DisasContext *s, CPUX86State *env, X86DecodeFunc decode_ if (e->op0 == X86_TYPE_I) { s->rip_offset += 1 << decode->op[0].ot; } + decode->op[0].ext = e->ext0; } if (e->op1 != X86_TYPE_None) { if (!decode_op_size(s, e, e->s1, &decode->op[1].ot)) { @@ -2499,6 +2504,7 @@ static bool decode_insn(DisasContext *s, CPUX86State *env, X86DecodeFunc decode_ if (e->op1 == X86_TYPE_I) { s->rip_offset += 1 << decode->op[1].ot; } + decode->op[1].ext = e->ext1; } if (e->op2 != X86_TYPE_None) { if (!decode_op_size(s, e, e->s2, &decode->op[2].ot)) { @@ -2507,6 +2513,7 @@ static bool decode_insn(DisasContext *s, CPUX86State *env, X86DecodeFunc decode_ if (e->op2 == X86_TYPE_I) { s->rip_offset += 1 << decode->op[2].ot; } + decode->op[2].ext = e->ext2; } if (e->op3 != X86_TYPE_None) { /* @@ -3079,12 +3086,6 @@ static void disas_insn(DisasContext *s, CPUState *cpu) } break; - case X86_SPECIAL_SExtT0: - case X86_SPECIAL_ZExtT0: - /* Handled in gen_load. */ - assert(decode.op[1].unit == X86_OP_INT); - break; - case X86_SPECIAL_Op0_Mw: assert(decode.op[0].unit == X86_OP_INT); if (decode.op[0].has_ea) { diff --git a/target/i386/tcg/emit.c.inc b/target/i386/tcg/emit.c.inc index c83ab809408..54dc23ca624 100644 --- a/target/i386/tcg/emit.c.inc +++ b/target/i386/tcg/emit.c.inc @@ -266,23 +266,21 @@ static void gen_load(DisasContext *s, X86DecodedInsn *decode, int opn, TCGv v) #endif case X86_OP_INT: if (op->has_ea) { - if (v == s->T0 && decode->e.special == X86_SPECIAL_SExtT0) { + if (op->ext == X86_EXT_Signed) { gen_op_ld_v(s, op->ot | MO_SIGN, v, s->A0); } else { gen_op_ld_v(s, op->ot, v, s->A0); } - } else if (op->ot < MO_TL && v == s->T0 && - (decode->e.special == X86_SPECIAL_SExtT0 || - decode->e.special == X86_SPECIAL_ZExtT0)) { + } else if (op->ot < MO_TL && op->ext != X86_EXT_None) { if (op->ot == MO_8 && byte_reg_is_xH(s, op->n)) { - if (decode->e.special == X86_SPECIAL_SExtT0) { + if (op->ext == X86_EXT_Signed) { tcg_gen_sextract_tl(v, cpu_regs[op->n - 4], 8, 8); } else { tcg_gen_extract_tl(v, cpu_regs[op->n - 4], 8, 8); } } else { - if (decode->e.special == X86_SPECIAL_SExtT0) { + if (op->ext == X86_EXT_Signed) { tcg_gen_ext_tl(v, cpu_regs[op->n], op->ot | MO_SIGN); } else { tcg_gen_ext_tl(v, cpu_regs[op->n], op->ot); @@ -347,6 +345,12 @@ static void gen_writeback(DisasContext *s, X86DecodedInsn *decode, int opn, TCGv case X86_OP_INT: if (op->has_ea) { gen_op_st_v(s, op->ot, v, s->A0); + } else if (op->ot < MO_TL && op->ext != X86_EXT_None) { + if (op->ext == X86_EXT_Signed) { + tcg_gen_ext_tl(cpu_regs[op->n], v, op->ot | MO_SIGN); + } else { + tcg_gen_ext_tl(cpu_regs[op->n], v, op->ot); + } } else { gen_op_mov_reg_v(s, op->ot, op->n, v); } -- 2.55.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH 01/17] target/i386/tcg: generalize zero- and sign-extension 2026-09-23 17:30 ` [PATCH 01/17] target/i386/tcg: generalize zero- and sign-extension Paolo Bonzini @ 2026-09-23 19:34 ` Richard Henderson 0 siblings, 0 replies; 35+ messages in thread From: Richard Henderson @ 2026-09-23 19:34 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel; +Cc: chang.seok.bae, zhao1.liu On 9/23/26 10:30, Paolo Bonzini wrote: > Allow applying sign and zero extension to T1 load and T0 writeback. > T1 load is useful for some cases where the tables are hacked to > put the sign extended operand in T0; T0 writeback is used for APX ZU > instructions. > > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> > --- > target/i386/tcg/decode-new.h | 15 +++++++++++---- > target/i386/tcg/decode-new.c.inc | 21 +++++++++++---------- > target/i386/tcg/emit.c.inc | 16 ++++++++++------ > 3 files changed, 32 insertions(+), 20 deletions(-) > > diff --git a/target/i386/tcg/decode-new.h b/target/i386/tcg/decode-new.h > index 25f7d2215ee..a3702b00614 100644 > --- a/target/i386/tcg/decode-new.h > +++ b/target/i386/tcg/decode-new.h > @@ -148,6 +148,12 @@ typedef enum X86OpUnit { > X86_OP_MMX, /* address in either s->ptrX or s->A0 depending on has_ea */ > } X86OpUnit; > > +typedef enum X86OpExt { > + X86_EXT_None, > + X86_EXT_Signed, > + X86_EXT_Unsigned, > +} X86OpExt; > + > typedef enum X86InsnCheck { > /* Illegal or exclusive to 64-bit mode */ > X86_CHECK_i64 = 1, > @@ -226,10 +232,6 @@ typedef enum X86InsnSpecial { > */ > X86_SPECIAL_MMX, > > - /* When loaded into s->T0, register operand 1 is zero/sign extended. */ > - X86_SPECIAL_SExtT0, > - X86_SPECIAL_ZExtT0, > - > /* Memory operand size of MOV from segment register is MO_16 */ > X86_SPECIAL_Op0_Mw, > } X86InsnSpecial; > @@ -300,6 +302,10 @@ struct X86OpEntry { > X86OpSize s3:8; > > X86InsnSpecial special:8; > + unsigned ext0:2; > + unsigned ext1:2; > + unsigned ext2:2; Use the enum. With that, Reviewed-by: Richard Henderson <richard.henderson@linaro.org> I did wonder about using a bool here, and > + unsigned :2; > X86CPUIDFeature cpuid:8; > unsigned vex_class:8; > X86VEXSpecial vex_special:8; > @@ -314,6 +320,7 @@ typedef struct X86DecodedOp { > int8_t n; > MemOp ot; /* For b/c/d/p/s/q/v/w/y/z */ > X86OpUnit unit; > + X86OpExt ext; ... MemOp sign here ... > diff --git a/target/i386/tcg/emit.c.inc b/target/i386/tcg/emit.c.inc > index c83ab809408..54dc23ca624 100644 > --- a/target/i386/tcg/emit.c.inc > +++ b/target/i386/tcg/emit.c.inc > @@ -266,23 +266,21 @@ static void gen_load(DisasContext *s, X86DecodedInsn *decode, int opn, TCGv v) > #endif > case X86_OP_INT: > if (op->has_ea) { > - if (v == s->T0 && decode->e.special == X86_SPECIAL_SExtT0) { > + if (op->ext == X86_EXT_Signed) { > gen_op_ld_v(s, op->ot | MO_SIGN, v, s->A0); > } else { > gen_op_ld_v(s, op->ot, v, s->A0); > } ... so that the uses here become e.g. gen_op_ld_v(s, op->ot | op->sign, v, s->A0); > @@ -347,6 +345,12 @@ static void gen_writeback(DisasContext *s, X86DecodedInsn *decode, int opn, TCGv > case X86_OP_INT: > if (op->has_ea) { > gen_op_st_v(s, op->ot, v, s->A0); > + } else if (op->ot < MO_TL && op->ext != X86_EXT_None) { > + if (op->ext == X86_EXT_Signed) { > + tcg_gen_ext_tl(cpu_regs[op->n], v, op->ot | MO_SIGN); > + } else { > + tcg_gen_ext_tl(cpu_regs[op->n], v, op->ot); > + } ... but I can't see how to fit this new case into that scheme easily. r~ ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH 02/17] target/i386/tcg: use standard operand order for BSF and BSR 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini 2026-09-23 17:30 ` [PATCH 01/17] target/i386/tcg: generalize zero- and sign-extension Paolo Bonzini @ 2026-09-23 17:30 ` Paolo Bonzini 2026-09-23 19:36 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 03/17] target/i386/tcg: use zextT1 for CMPXCHG Paolo Bonzini ` (15 subsequent siblings) 17 siblings, 1 reply; 35+ messages in thread From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw) To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu BSF and BSR currently pass their source as operand 0 and the old destination as operand 2. This nonstandard order was needed so that the source could use zextT0. Per-operand extensions now let operand 2 use zextT1, so that BSF and BSR can use the normal X86_OP_ENTRY2. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/decode-new.c.inc | 18 ++++++++---------- target/i386/tcg/emit.c.inc | 12 +++++------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index e8b21c77743..0e6175b8dbc 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -674,12 +674,11 @@ static void decode_0FB8(DisasContext *s, CPUX86State *env, X86OpEntry *entry, ui static void decode_0FBC(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint8_t *b) { - /* For BSF, pass 2op as the third operand so that we can use zextT0 */ static const X86OpEntry opcodes_0FBC[4] = { - X86_OP_ENTRY3(BSF, G,v, E,v, 2op,v, zextT0), - X86_OP_ENTRY3(BSF, G,v, E,v, 2op,v, zextT0), /* 0x66 */ - X86_OP_ENTRYwr(TZCNT, G,v, E,v, zextT0), /* 0xf3 */ - X86_OP_ENTRY3(BSF, G,v, E,v, 2op,v, zextT0), /* 0xf2 */ + X86_OP_ENTRY2(BSF, G,v, E,v, zextT1), + X86_OP_ENTRY2(BSF, G,v, E,v, zextT1), /* 0x66 */ + X86_OP_ENTRYwr(TZCNT, G,v, E,v, zextT0), /* 0xf3 */ + X86_OP_ENTRY2(BSF, G,v, E,v, zextT1), /* 0xf2 */ }; if (!(s->cpuid_ext3_features & CPUID_EXT3_ABM)) { *entry = opcodes_0FBC[0]; @@ -690,12 +689,11 @@ static void decode_0FBC(DisasContext *s, CPUX86State *env, X86OpEntry *entry, ui static void decode_0FBD(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint8_t *b) { - /* For BSR, pass 2op as the third operand so that we can use zextT0 */ static const X86OpEntry opcodes_0FBD[4] = { - X86_OP_ENTRY3(BSR, G,v, E,v, 2op,v, zextT0), - X86_OP_ENTRY3(BSR, G,v, E,v, 2op,v, zextT0), /* 0x66 */ - X86_OP_ENTRYwr(LZCNT, G,v, E,v, zextT0), /* 0xf3 */ - X86_OP_ENTRY3(BSR, G,v, E,v, 2op,v, zextT0), /* 0xf2 */ + X86_OP_ENTRY2(BSR, G,v, E,v, zextT1), + X86_OP_ENTRY2(BSR, G,v, E,v, zextT1), /* 0x66 */ + X86_OP_ENTRYwr(LZCNT, G,v, E,v, zextT0), /* 0xf3 */ + X86_OP_ENTRY2(BSR, G,v, E,v, zextT1), /* 0xf2 */ }; if (!(s->cpuid_7_0_ebx_features & CPUID_7_0_EBX_BMI1)) { *entry = opcodes_0FBD[0]; diff --git a/target/i386/tcg/emit.c.inc b/target/i386/tcg/emit.c.inc index 54dc23ca624..abf40123035 100644 --- a/target/i386/tcg/emit.c.inc +++ b/target/i386/tcg/emit.c.inc @@ -1396,7 +1396,6 @@ static void gen_BOUND(DisasContext *s, X86DecodedInsn *decode) } } -/* Non-standard convention - on entry T0 is zero-extended input, T1 is the output. */ static void gen_BSF(DisasContext *s, X86DecodedInsn *decode) { MemOp ot = decode->op[0].ot; @@ -1404,7 +1403,7 @@ static void gen_BSF(DisasContext *s, X86DecodedInsn *decode) /* Only the Z bit is defined and it is related to the input. */ decode->cc_dst = tcg_temp_new(); decode->cc_op = CC_OP_LOGICB + ot; - tcg_gen_mov_tl(decode->cc_dst, s->T0); + tcg_gen_mov_tl(decode->cc_dst, s->T1); /* * The manual says that the output is undefined when the @@ -1412,10 +1411,9 @@ static void gen_BSF(DisasContext *s, X86DecodedInsn *decode) * real programs appear to depend on that. Accomplish this * by passing the output as the value to return upon zero. */ - tcg_gen_ctz_tl(s->T0, s->T0, s->T1); + tcg_gen_ctz_tl(s->T0, s->T1, s->T0); } -/* Non-standard convention - on entry T0 is zero-extended input, T1 is the output. */ static void gen_BSR(DisasContext *s, X86DecodedInsn *decode) { MemOp ot = decode->op[0].ot; @@ -1423,7 +1421,7 @@ static void gen_BSR(DisasContext *s, X86DecodedInsn *decode) /* Only the Z bit is defined and it is related to the input. */ decode->cc_dst = tcg_temp_new(); decode->cc_op = CC_OP_LOGICB + ot; - tcg_gen_mov_tl(decode->cc_dst, s->T0); + tcg_gen_mov_tl(decode->cc_dst, s->T1); /* * The manual says that the output is undefined when the @@ -1432,8 +1430,8 @@ static void gen_BSR(DisasContext *s, X86DecodedInsn *decode) * by passing the output as the value to return upon zero. * Plus, return the bit index of the first 1 bit. */ - tcg_gen_xori_tl(s->T1, s->T1, TARGET_LONG_BITS - 1); - tcg_gen_clz_tl(s->T0, s->T0, s->T1); + tcg_gen_xori_tl(s->T0, s->T0, TARGET_LONG_BITS - 1); + tcg_gen_clz_tl(s->T0, s->T1, s->T0); tcg_gen_xori_tl(s->T0, s->T0, TARGET_LONG_BITS - 1); } -- 2.55.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH 02/17] target/i386/tcg: use standard operand order for BSF and BSR 2026-09-23 17:30 ` [PATCH 02/17] target/i386/tcg: use standard operand order for BSF and BSR Paolo Bonzini @ 2026-09-23 19:36 ` Richard Henderson 0 siblings, 0 replies; 35+ messages in thread From: Richard Henderson @ 2026-09-23 19:36 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel; +Cc: chang.seok.bae, zhao1.liu On 9/23/26 10:30, Paolo Bonzini wrote: > BSF and BSR currently pass their source as operand 0 and the old > destination as operand 2. This nonstandard order was needed so that the > source could use zextT0. > > Per-operand extensions now let operand 2 use zextT1, so that BSF and BSR > can use the normal X86_OP_ENTRY2. > > Signed-off-by: Paolo Bonzini<pbonzini@redhat.com> > --- > target/i386/tcg/decode-new.c.inc | 18 ++++++++---------- > target/i386/tcg/emit.c.inc | 12 +++++------- > 2 files changed, 13 insertions(+), 17 deletions(-) Reviewed-by: Richard Henderson <richard.henderson@linaro.org> r~ ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH 03/17] target/i386/tcg: use zextT1 for CMPXCHG 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini 2026-09-23 17:30 ` [PATCH 01/17] target/i386/tcg: generalize zero- and sign-extension Paolo Bonzini 2026-09-23 17:30 ` [PATCH 02/17] target/i386/tcg: use standard operand order for BSF and BSR Paolo Bonzini @ 2026-09-23 17:30 ` Paolo Bonzini 2026-09-23 19:36 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 04/17] target/i386/tcg: unify MOVBE and BSWAP generators Paolo Bonzini ` (14 subsequent siblings) 17 siblings, 1 reply; 35+ messages in thread From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw) To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/decode-new.c.inc | 4 ++-- target/i386/tcg/emit.c.inc | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index 0e6175b8dbc..d8299a760c3 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -1365,8 +1365,8 @@ static const X86OpEntry opcodes_0F[256] = { [0xa4] = X86_OP_ENTRY4(SHLD, E,v, 2op,v, G,v), [0xa5] = X86_OP_ENTRY3(SHLD, E,v, 2op,v, G,v), - [0xb0] = X86_OP_ENTRY2(CMPXCHG,E,b, G,b, lock), - [0xb1] = X86_OP_ENTRY2(CMPXCHG,E,v, G,v, lock), + [0xb0] = X86_OP_ENTRY2(CMPXCHG,E,b, G,b, lock zextT1), + [0xb1] = X86_OP_ENTRY2(CMPXCHG,E,v, G,v, lock zextT1), [0xb2] = X86_OP_ENTRY3(LSS, G,v, EM,p, None, None), [0xb3] = X86_OP_ENTRY2(BTR, E,v, G,v, btEvGv), [0xb4] = X86_OP_ENTRY3(LFS, G,v, EM,p, None, None), diff --git a/target/i386/tcg/emit.c.inc b/target/i386/tcg/emit.c.inc index abf40123035..e4700473c9f 100644 --- a/target/i386/tcg/emit.c.inc +++ b/target/i386/tcg/emit.c.inc @@ -1760,11 +1760,10 @@ static void gen_CMPXCHG(DisasContext *s, X86DecodedInsn *decode) MemOp ot = decode->op[2].ot; TCGv cmpv = tcg_temp_new(); TCGv oldv = tcg_temp_new(); - TCGv newv = tcg_temp_new(); + TCGv newv = s->T1; TCGv dest; tcg_gen_ext_tl(cmpv, cpu_regs[R_EAX], ot); - tcg_gen_ext_tl(newv, s->T1, ot); if (s->prefix & PREFIX_LOCK) { tcg_gen_atomic_cmpxchg_tl(oldv, s->A0, cmpv, newv, s->mem_index, ot | MO_LE); @@ -1787,7 +1786,7 @@ static void gen_CMPXCHG(DisasContext *s, X86DecodedInsn *decode) * not zero-extending it on 64-bit processors. So, precompute * the result of a successful writeback and perform the movcond * directly on cpu_regs. In case rm is part of RAX, note that this - * movcond and the one below are mutually exclusive is executed. + * movcond and the one below are mutually exclusive. */ dest = gen_op_deposit_reg_v(s, ot, decode->op[0].n, newv, newv); tcg_gen_movcond_tl(TCG_COND_EQ, dest, oldv, cmpv, newv, dest); -- 2.55.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH 03/17] target/i386/tcg: use zextT1 for CMPXCHG 2026-09-23 17:30 ` [PATCH 03/17] target/i386/tcg: use zextT1 for CMPXCHG Paolo Bonzini @ 2026-09-23 19:36 ` Richard Henderson 0 siblings, 0 replies; 35+ messages in thread From: Richard Henderson @ 2026-09-23 19:36 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel; +Cc: chang.seok.bae, zhao1.liu On 9/23/26 10:30, Paolo Bonzini wrote: > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> > --- > target/i386/tcg/decode-new.c.inc | 4 ++-- > target/i386/tcg/emit.c.inc | 5 ++--- > 2 files changed, 4 insertions(+), 5 deletions(-) > Reviewed-by: Richard Henderson <richard.henderson@linaro.org> r~ ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH 04/17] target/i386/tcg: unify MOVBE and BSWAP generators 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini ` (2 preceding siblings ...) 2026-09-23 17:30 ` [PATCH 03/17] target/i386/tcg: use zextT1 for CMPXCHG Paolo Bonzini @ 2026-09-23 17:30 ` Paolo Bonzini 2026-09-23 19:39 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 05/17] target/i386/tcg: add APX support to XSAVE/XRSTOR Paolo Bonzini ` (13 subsequent siblings) 17 siblings, 1 reply; 35+ messages in thread From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw) To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu APX allows register-to-register MOVBE, which is normally done by BSWAP (except 16-bit is also allowed). Modify gen_MOVBE to also handle BSWAP. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/emit.c.inc | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/target/i386/tcg/emit.c.inc b/target/i386/tcg/emit.c.inc index e4700473c9f..5a880b9760e 100644 --- a/target/i386/tcg/emit.c.inc +++ b/target/i386/tcg/emit.c.inc @@ -1435,17 +1435,6 @@ static void gen_BSR(DisasContext *s, X86DecodedInsn *decode) tcg_gen_xori_tl(s->T0, s->T0, TARGET_LONG_BITS - 1); } -static void gen_BSWAP(DisasContext *s, X86DecodedInsn *decode) -{ -#ifdef TARGET_X86_64 - if (s->dflag == MO_64) { - tcg_gen_bswap64_i64(s->T0, s->T0); - return; - } -#endif - tcg_gen_bswap32_tl(s->T0, s->T0, TCG_BSWAP_OZ); -} - static TCGv gen_bt_mask(DisasContext *s, X86DecodedInsn *decode) { MemOp ot = decode->op[1].ot; @@ -2519,10 +2508,19 @@ static void gen_MOVBE(DisasContext *s, X86DecodedInsn *decode) /* M operand type does not load/store */ if (decode->e.op0 == X86_TYPE_M) { tcg_gen_qemu_st_tl(s->T0, s->A0, s->mem_index, ot | MO_BE); - } else { + } else if (decode->e.op1 == X86_TYPE_M) { tcg_gen_qemu_ld_tl(s->T0, s->A0, s->mem_index, ot | MO_BE); +#ifdef TARGET_X86_64 + } else if (ot == MO_64) { + tcg_gen_bswap64_tl(s->T0, s->T0); +#endif + } else if (ot == MO_32) { + tcg_gen_bswap32_tl(s->T0, s->T0, TCG_BSWAP_OZ); + } else { + tcg_gen_bswap16_tl(s->T0, s->T0, TCG_BSWAP_OZ); } } +#define gen_BSWAP gen_MOVBE static void gen_MOVD_from(DisasContext *s, X86DecodedInsn *decode) { -- 2.55.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH 04/17] target/i386/tcg: unify MOVBE and BSWAP generators 2026-09-23 17:30 ` [PATCH 04/17] target/i386/tcg: unify MOVBE and BSWAP generators Paolo Bonzini @ 2026-09-23 19:39 ` Richard Henderson 0 siblings, 0 replies; 35+ messages in thread From: Richard Henderson @ 2026-09-23 19:39 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel; +Cc: chang.seok.bae, zhao1.liu On 9/23/26 10:30, Paolo Bonzini wrote: > APX allows register-to-register MOVBE, which is normally done by BSWAP > (except 16-bit is also allowed). Modify gen_MOVBE to also handle > BSWAP. > > Signed-off-by: Paolo Bonzini<pbonzini@redhat.com> > --- > target/i386/tcg/emit.c.inc | 22 ++++++++++------------ > 1 file changed, 10 insertions(+), 12 deletions(-) Reviewed-by: Richard Henderson <richard.henderson@linaro.org> r~ ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH 05/17] target/i386/tcg: add APX support to XSAVE/XRSTOR 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini ` (3 preceding siblings ...) 2026-09-23 17:30 ` [PATCH 04/17] target/i386/tcg: unify MOVBE and BSWAP generators Paolo Bonzini @ 2026-09-23 17:30 ` Paolo Bonzini 2026-09-23 17:30 ` [PATCH 06/17] target/i386/tcg: add definition for REX2 prefix Paolo Bonzini ` (12 subsequent siblings) 17 siblings, 0 replies; 35+ messages in thread From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw) To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu With it, add a new hidden flag that will be used to enable the REX2 prefix and EVEX map4. Reviewed-by: Chang S. Bae <chang.seok.bae@intel.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/cpu.h | 3 ++ target/i386/tcg/tcg-cpu.h | 18 ++++++++--- target/i386/helper.c | 11 +++++++ target/i386/tcg/fpu_helper.c | 59 +++++++++++++++++++++++++++++++++--- target/i386/tcg/tcg-cpu.c | 5 +-- 5 files changed, 85 insertions(+), 11 deletions(-) diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 3f69b0a066a..c8619666f07 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -191,6 +191,7 @@ typedef enum X86Seg { #define HF_MPX_IU_SHIFT 26 /* BND registers in-use */ #define HF_UMIP_SHIFT 27 /* CR4.UMIP */ #define HF_AVX_EN_SHIFT 28 /* AVX Enabled (CR4+XCR0) */ +#define HF_APX_EN_SHIFT 29 /* APX Enabled (CR4+XCR0) */ #define HF_CPL_MASK (3 << HF_CPL_SHIFT) #define HF_INHIBIT_IRQ_MASK (1 << HF_INHIBIT_IRQ_SHIFT) @@ -218,6 +219,7 @@ typedef enum X86Seg { #define HF_MPX_IU_MASK (1 << HF_MPX_IU_SHIFT) #define HF_UMIP_MASK (1 << HF_UMIP_SHIFT) #define HF_AVX_EN_MASK (1 << HF_AVX_EN_SHIFT) +#define HF_APX_EN_MASK (1 << HF_APX_EN_SHIFT) /* hflags2 */ @@ -2771,6 +2773,7 @@ static inline bool x86_has_cpuid_0x1f(X86CPU *cpu) /* helper.c */ void x86_cpu_set_a20(X86CPU *cpu, int a20_state); void cpu_sync_avx_hflag(CPUX86State *env); +void cpu_sync_apx_hflag(CPUX86State *env); typedef enum X86ASIdx { X86ASIdx_MEM = 0, diff --git a/target/i386/tcg/tcg-cpu.h b/target/i386/tcg/tcg-cpu.h index 85bcd61678f..919c2f3ee3b 100644 --- a/target/i386/tcg/tcg-cpu.h +++ b/target/i386/tcg/tcg-cpu.h @@ -32,6 +32,7 @@ #define XSAVE_AVX_OFFSET 0x240 #define XSAVE_BNDREG_OFFSET 0x3c0 #define XSAVE_BNDCSR_OFFSET 0x400 +#define XSAVE_APX_OFFSET XSAVE_BNDREG_OFFSET #define XSAVE_OPMASK_OFFSET 0x440 #define XSAVE_ZMM_HI256_OFFSET 0x480 #define XSAVE_HI16_ZMM_OFFSET 0x680 @@ -52,9 +53,15 @@ typedef struct X86XSaveArea { - sizeof(X86XSaveHeader) - sizeof(XSaveAVX)]; - /* MPX State: */ - XSaveBNDREG bndreg_state; - XSaveBNDCSR bndcsr_state; + /* Overlapping MPX and APX States: */ + union { + struct { + XSaveBNDREG bndreg; + XSaveBNDCSR bndcsr; + } mpx_state; + XSaveAPX apx_state; + }; + /* AVX-512 State: */ XSaveOpmask opmask_state; XSaveZMM_Hi256 zmm_hi256_state; @@ -71,8 +78,9 @@ QEMU_BUILD_BUG_ON(offsetof(X86XSaveArea, legacy.mxcsr) != XSAVE_MXCSR_OFFSET); QEMU_BUILD_BUG_ON(offsetof(X86XSaveArea, legacy.fpregs) != XSAVE_ST_SPACE_OFFSET); QEMU_BUILD_BUG_ON(offsetof(X86XSaveArea, legacy.xmm_regs) != XSAVE_XMM_SPACE_OFFSET); QEMU_BUILD_BUG_ON(offsetof(X86XSaveArea, avx_state) != XSAVE_AVX_OFFSET); -QEMU_BUILD_BUG_ON(offsetof(X86XSaveArea, bndreg_state) != XSAVE_BNDREG_OFFSET); -QEMU_BUILD_BUG_ON(offsetof(X86XSaveArea, bndcsr_state) != XSAVE_BNDCSR_OFFSET); +QEMU_BUILD_BUG_ON(offsetof(X86XSaveArea, apx_state) != XSAVE_APX_OFFSET); +QEMU_BUILD_BUG_ON(offsetof(X86XSaveArea, mpx_state.bndreg) != XSAVE_BNDREG_OFFSET); +QEMU_BUILD_BUG_ON(offsetof(X86XSaveArea, mpx_state.bndcsr) != XSAVE_BNDCSR_OFFSET); QEMU_BUILD_BUG_ON(offsetof(X86XSaveArea, opmask_state) != XSAVE_OPMASK_OFFSET); QEMU_BUILD_BUG_ON(offsetof(X86XSaveArea, zmm_hi256_state) != XSAVE_ZMM_HI256_OFFSET); QEMU_BUILD_BUG_ON(offsetof(X86XSaveArea, hi16_zmm_state) != XSAVE_HI16_ZMM_OFFSET); diff --git a/target/i386/helper.c b/target/i386/helper.c index 4133b5f244c..cb25018ecbb 100644 --- a/target/i386/helper.c +++ b/target/i386/helper.c @@ -36,6 +36,16 @@ #include "tcg/insn-start-words.h" #endif +void cpu_sync_apx_hflag(CPUX86State *env) +{ + if ((env->cr[4] & CR4_OSXSAVE_MASK) + && (env->xcr0 & XSTATE_APX_MASK) == XSTATE_APX_MASK) { + env->hflags |= HF_APX_EN_MASK; + } else{ + env->hflags &= ~HF_APX_EN_MASK; + } +} + void cpu_sync_avx_hflag(CPUX86State *env) { if ((env->cr[4] & CR4_OSXSAVE_MASK) @@ -250,6 +260,7 @@ void cpu_x86_update_cr4(CPUX86State *env, uint32_t new_cr4) cpu_sync_bndcs_hflags(env); cpu_sync_avx_hflag(env); + cpu_sync_apx_hflag(env); } #if !defined(CONFIG_USER_ONLY) diff --git a/target/i386/tcg/fpu_helper.c b/target/i386/tcg/fpu_helper.c index 56d093da8c9..fb473c28f7b 100644 --- a/target/i386/tcg/fpu_helper.c +++ b/target/i386/tcg/fpu_helper.c @@ -2709,6 +2709,19 @@ static void do_xsave_pkru(X86Access *ac, target_ulong ptr) access_stq(ac, ptr, ac->env->pkru); } +#ifdef TARGET_X86_64 +static void do_xsave_apx(X86Access *ac, target_ulong ptr) +{ + CPUX86State *env = ac->env; + int i; + + for (i = 16; i < 32; i++) { + access_stq(ac, ptr, env->regs[i]); + ptr += 8; + } +} +#endif + static void do_fxsave(X86Access *ac, target_ulong ptr) { CPUX86State *env = ac->env; @@ -2773,14 +2786,19 @@ static void do_xsave_access(X86Access *ac, target_ulong ptr, uint64_t rfbm, do_xsave_ymmh(ac, ptr + XO(avx_state)); } if (opt & XSTATE_BNDREGS_MASK) { - do_xsave_bndregs(ac, ptr + XO(bndreg_state)); + do_xsave_bndregs(ac, ptr + XO(mpx_state.bndreg)); } if (opt & XSTATE_BNDCSR_MASK) { - do_xsave_bndcsr(ac, ptr + XO(bndcsr_state)); + do_xsave_bndcsr(ac, ptr + XO(mpx_state.bndcsr)); } if (opt & XSTATE_PKRU_MASK) { do_xsave_pkru(ac, ptr + XO(pkru_state)); } +#ifdef TARGET_X86_64 + if (opt & XSTATE_APX_MASK) { + do_xsave_apx(ac, ptr + XO(apx_state)); + } +#endif /* Update the XSTATE_BV field. */ old_bv = access_ldq(ac, ptr + XO(header.xstate_bv)); @@ -2974,6 +2992,29 @@ static void do_fxrstor(X86Access *ac, target_ulong ptr) } } +#ifdef TARGET_X86_64 +static void do_xrstor_apx(X86Access *ac, target_ulong ptr) +{ + CPUX86State *env = ac->env; + int i; + + for (i = 16; i < 32; i++) { + env->regs[i] = access_ldq(ac, ptr); + ptr += 8; + } +} + +static void do_clear_apx(X86Access *ac) +{ + CPUX86State *env = ac->env; + int i; + + for (i = 16; i < 32; i++) { + env->regs[i] = 0; + } +} +#endif + void helper_fxrstor(CPUX86State *env, target_ulong ptr) { uintptr_t ra = GETPC(); @@ -3045,7 +3086,7 @@ static void do_xrstor(X86Access *ac, target_ulong ptr, } if (rfbm & XSTATE_BNDREGS_MASK) { if (xstate_bv & XSTATE_BNDREGS_MASK) { - do_xrstor_bndregs(ac, ptr + XO(bndreg_state)); + do_xrstor_bndregs(ac, ptr + XO(mpx_state.bndreg)); env->hflags |= HF_MPX_IU_MASK; } else { memset(env->bnd_regs, 0, sizeof(env->bnd_regs)); @@ -3054,7 +3095,7 @@ static void do_xrstor(X86Access *ac, target_ulong ptr, } if (rfbm & XSTATE_BNDCSR_MASK) { if (xstate_bv & XSTATE_BNDCSR_MASK) { - do_xrstor_bndcsr(ac, ptr + XO(bndcsr_state)); + do_xrstor_bndcsr(ac, ptr + XO(mpx_state.bndcsr)); } else { memset(&env->bndcs_regs, 0, sizeof(env->bndcs_regs)); } @@ -3072,6 +3113,15 @@ static void do_xrstor(X86Access *ac, target_ulong ptr, tlb_flush(cs); } } +#ifdef TARGET_X86_64 + if (rfbm & XSTATE_APX_MASK) { + if (xstate_bv & XSTATE_APX_MASK) { + do_xrstor_apx(ac, ptr + XO(apx_state)); + } else { + do_clear_apx(ac); + } + } +#endif } #undef XO @@ -3253,6 +3303,7 @@ void helper_xsetbv(CPUX86State *env, uint32_t ecx, uint64_t mask) env->xcr0 = mask; cpu_sync_bndcs_hflags(env); cpu_sync_avx_hflag(env); + cpu_sync_apx_hflag(env); return; do_gpf: diff --git a/target/i386/tcg/tcg-cpu.c b/target/i386/tcg/tcg-cpu.c index 2402608fe90..9db9795a3b2 100644 --- a/target/i386/tcg/tcg-cpu.c +++ b/target/i386/tcg/tcg-cpu.c @@ -200,12 +200,13 @@ static void x86_tcg_cpu_xsave_init(void) XO(XSTATE_FP_BIT, legacy); XO(XSTATE_SSE_BIT, legacy); XO(XSTATE_YMM_BIT, avx_state); - XO(XSTATE_BNDREGS_BIT, bndreg_state); - XO(XSTATE_BNDCSR_BIT, bndcsr_state); + XO(XSTATE_BNDREGS_BIT, mpx_state.bndreg); + XO(XSTATE_BNDCSR_BIT, mpx_state.bndcsr); XO(XSTATE_OPMASK_BIT, opmask_state); XO(XSTATE_ZMM_Hi256_BIT, zmm_hi256_state); XO(XSTATE_Hi16_ZMM_BIT, hi16_zmm_state); XO(XSTATE_PKRU_BIT, pkru_state); + XO(XSTATE_APX_BIT, apx_state); #undef XO } -- 2.55.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 06/17] target/i386/tcg: add definition for REX2 prefix 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini ` (4 preceding siblings ...) 2026-09-23 17:30 ` [PATCH 05/17] target/i386/tcg: add APX support to XSAVE/XRSTOR Paolo Bonzini @ 2026-09-23 17:30 ` Paolo Bonzini 2026-09-23 19:41 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 07/17] target/i386/tcg: mark XSAVE* as not allowing REX2 Paolo Bonzini ` (11 subsequent siblings) 17 siblings, 1 reply; 35+ messages in thread From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw) To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/translate.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c index 3cdc458f95b..b8e8991163f 100644 --- a/target/i386/tcg/translate.c +++ b/target/i386/tcg/translate.c @@ -50,6 +50,7 @@ #define PREFIX_ADR 0x10 #define PREFIX_VEX 0x20 #define PREFIX_REX 0x40 +#define PREFIX_REX2 0x80 #ifdef TARGET_X86_64 # define ctztl ctz64 @@ -208,7 +209,7 @@ typedef struct DisasContext { #endif #ifdef TARGET_X86_64 -#define REX_PREFIX(S) (((S)->prefix & (PREFIX_REX | PREFIX_VEX)) != 0) +#define REX_PREFIX(S) (((S)->prefix & (PREFIX_REX | PREFIX_REX2 | PREFIX_VEX)) != 0) #define REX_W(S) ((S)->vex_w) #define REX_R(S) ((S)->rex_r + 0) #define REX_X(S) ((S)->rex_x + 0) -- 2.55.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH 06/17] target/i386/tcg: add definition for REX2 prefix 2026-09-23 17:30 ` [PATCH 06/17] target/i386/tcg: add definition for REX2 prefix Paolo Bonzini @ 2026-09-23 19:41 ` Richard Henderson 0 siblings, 0 replies; 35+ messages in thread From: Richard Henderson @ 2026-09-23 19:41 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel; +Cc: chang.seok.bae, zhao1.liu On 9/23/26 10:30, Paolo Bonzini wrote: > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> > --- > target/i386/tcg/translate.c | 3 ++- > 1 file changed, 2 insertions(+), 1 deletion(-) > > diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c > index 3cdc458f95b..b8e8991163f 100644 > --- a/target/i386/tcg/translate.c > +++ b/target/i386/tcg/translate.c > @@ -50,6 +50,7 @@ > #define PREFIX_ADR 0x10 > #define PREFIX_VEX 0x20 > #define PREFIX_REX 0x40 > +#define PREFIX_REX2 0x80 > > #ifdef TARGET_X86_64 > # define ctztl ctz64 > @@ -208,7 +209,7 @@ typedef struct DisasContext { > #endif > > #ifdef TARGET_X86_64 > -#define REX_PREFIX(S) (((S)->prefix & (PREFIX_REX | PREFIX_VEX)) != 0) > +#define REX_PREFIX(S) (((S)->prefix & (PREFIX_REX | PREFIX_REX2 | PREFIX_VEX)) != 0) > #define REX_W(S) ((S)->vex_w) > #define REX_R(S) ((S)->rex_r + 0) > #define REX_X(S) ((S)->rex_x + 0) Reviewed-by: Richard Henderson <richard.henderson@linaro.org> r~ ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH 07/17] target/i386/tcg: mark XSAVE* as not allowing REX2 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini ` (5 preceding siblings ...) 2026-09-23 17:30 ` [PATCH 06/17] target/i386/tcg: add definition for REX2 prefix Paolo Bonzini @ 2026-09-23 17:30 ` Paolo Bonzini 2026-09-23 19:42 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 08/17] target/i386/tcg: treat 0x0F as a prefix Paolo Bonzini ` (10 subsequent siblings) 17 siblings, 1 reply; 35+ messages in thread From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw) To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu Reviewed-by: Chang S. Bae <chang.seok.bae@intel.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/decode-new.h | 3 +++ target/i386/tcg/decode-new.c.inc | 21 ++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/target/i386/tcg/decode-new.h b/target/i386/tcg/decode-new.h index a3702b00614..b4b0567e7ca 100644 --- a/target/i386/tcg/decode-new.h +++ b/target/i386/tcg/decode-new.h @@ -187,6 +187,9 @@ typedef enum X86InsnCheck { /* Vendor-specific checks for Intel/AMD differences */ X86_CHECK_i64_amd = 2048, X86_CHECK_o64_intel = 4096, + + /* No REX2 prefix allowed */ + X86_CHECK_no_rex2 = 8192, } X86InsnCheck; typedef enum X86InsnSpecial { diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index d8299a760c3..c4f12ba6c13 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -486,9 +486,9 @@ static void decode_group15(DisasContext *s, CPUX86State *env, X86OpEntry *entry, [1] = X86_OP_ENTRYr(FXRSTOR, M,y, cpuid(FXSR) p_00), [2] = X86_OP_ENTRYr(LDMXCSR, E,d, vex5 chk(VEX128) p_00), [3] = X86_OP_ENTRYw(STMXCSR, E,d, vex5 chk(VEX128) p_00), - [4] = X86_OP_ENTRYw(XSAVE, M,y, cpuid(XSAVE) p_00), - [5] = X86_OP_ENTRYr(XRSTOR, M,y, cpuid(XSAVE) p_00), - [6] = X86_OP_ENTRYw(XSAVEOPT, M,b, cpuid(XSAVEOPT) p_00), + [4] = X86_OP_ENTRYw(XSAVE, M,y, cpuid(XSAVE) p_00 chk(no_rex2)), + [5] = X86_OP_ENTRYr(XRSTOR, M,y, cpuid(XSAVE) p_00 chk(no_rex2)), + [6] = X86_OP_ENTRYw(XSAVEOPT, M,b, cpuid(XSAVEOPT) p_00 chk(no_rex2)), [7] = X86_OP_ENTRYw(NOP, M,b, cpuid(CLFLUSH) p_00), }; @@ -3008,15 +3008,14 @@ static void disas_insn(DisasContext *s, CPUState *cpu) goto illegal_op; } } - if (decode.e.check & X86_CHECK_prot_or_vm86) { - if (!PE(s)) { - goto illegal_op; - } + if ((decode.e.check & X86_CHECK_prot_or_vm86) && !PE(s)) { + goto illegal_op; } - if (decode.e.check & X86_CHECK_no_vm86) { - if (VM86(s)) { - goto illegal_op; - } + if ((decode.e.check & X86_CHECK_no_vm86) && VM86(s)) { + goto illegal_op; + } + if ((decode.e.check & X86_CHECK_no_rex2) && (s->prefix & PREFIX_REX2)) { + goto illegal_op; } if ((decode.e.check & X86_CHECK_VEX128) && s->vex_l) { goto illegal_op; -- 2.55.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH 07/17] target/i386/tcg: mark XSAVE* as not allowing REX2 2026-09-23 17:30 ` [PATCH 07/17] target/i386/tcg: mark XSAVE* as not allowing REX2 Paolo Bonzini @ 2026-09-23 19:42 ` Richard Henderson 0 siblings, 0 replies; 35+ messages in thread From: Richard Henderson @ 2026-09-23 19:42 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel; +Cc: chang.seok.bae, zhao1.liu On 9/23/26 10:30, Paolo Bonzini wrote: > Reviewed-by: Chang S. Bae<chang.seok.bae@intel.com> > Signed-off-by: Paolo Bonzini<pbonzini@redhat.com> > --- > target/i386/tcg/decode-new.h | 3 +++ > target/i386/tcg/decode-new.c.inc | 21 ++++++++++----------- > 2 files changed, 13 insertions(+), 11 deletions(-) > > diff --git a/target/i386/tcg/decode-new.h b/target/i386/tcg/decode-new.h > index a3702b00614..b4b0567e7ca 100644 > --- a/target/i386/tcg/decode-new.h > +++ b/target/i386/tcg/decode-new.h > @@ -187,6 +187,9 @@ typedef enum X86InsnCheck { > /* Vendor-specific checks for Intel/AMD differences */ > X86_CHECK_i64_amd = 2048, > X86_CHECK_o64_intel = 4096, > + > + /* No REX2 prefix allowed */ > + X86_CHECK_no_rex2 = 8192, > } X86InsnCheck; > Reviewed-by: Richard Henderson <richard.henderson@linaro.org> But adding the flags as decimal constants should probably end. r~ ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH 08/17] target/i386/tcg: treat 0x0F as a prefix 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini ` (6 preceding siblings ...) 2026-09-23 17:30 ` [PATCH 07/17] target/i386/tcg: mark XSAVE* as not allowing REX2 Paolo Bonzini @ 2026-09-23 17:30 ` Paolo Bonzini 2026-09-23 19:51 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 09/17] target/i386/tcg: decode REX2 prefix Paolo Bonzini ` (9 subsequent siblings) 17 siblings, 1 reply; 35+ messages in thread From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw) To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu Because 0x0F cannot come after REX2, pick the 0F opcode map during prefix decoding instead of doing so via the root opcode map. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/decode-new.c.inc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index c4f12ba6c13..bcb23c6fae1 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -1848,7 +1848,6 @@ static const X86OpEntry opcodes_root[256] = { [0x0C] = X86_OP_ENTRY2(OR, 0,b, I,b, lock), /* AL, Ib */ [0x0D] = X86_OP_ENTRY2(OR, 0,v, I,z, lock), /* rAX, Iz */ [0x0E] = X86_OP_ENTRYr(PUSH, CS, w, chk(i64)), - [0x0F] = X86_OP_GROUP0(0F), [0x18] = X86_OP_ENTRY2(SBB, E,b, G,b, lock), [0x19] = X86_OP_ENTRY2(SBB, E,v, G,v, lock), @@ -2824,6 +2823,9 @@ static void disas_insn(DisasContext *s, CPUState *cpu) /* Collect prefixes. */ switch (b) { + case 0x0f: + decode_func = decode_0F; + break; case 0xf3: s->prefix |= PREFIX_REPZ; s->prefix &= ~PREFIX_REPNZ; -- 2.55.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH 08/17] target/i386/tcg: treat 0x0F as a prefix 2026-09-23 17:30 ` [PATCH 08/17] target/i386/tcg: treat 0x0F as a prefix Paolo Bonzini @ 2026-09-23 19:51 ` Richard Henderson 0 siblings, 0 replies; 35+ messages in thread From: Richard Henderson @ 2026-09-23 19:51 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel; +Cc: chang.seok.bae, zhao1.liu On 9/23/26 10:30, Paolo Bonzini wrote: > Because 0x0F cannot come after REX2, pick the 0F opcode map > during prefix decoding instead of doing so via the root opcode map. > > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> > --- > target/i386/tcg/decode-new.c.inc | 4 +++- > 1 file changed, 3 insertions(+), 1 deletion(-) > > diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc > index c4f12ba6c13..bcb23c6fae1 100644 > --- a/target/i386/tcg/decode-new.c.inc > +++ b/target/i386/tcg/decode-new.c.inc > @@ -1848,7 +1848,6 @@ static const X86OpEntry opcodes_root[256] = { > [0x0C] = X86_OP_ENTRY2(OR, 0,b, I,b, lock), /* AL, Ib */ > [0x0D] = X86_OP_ENTRY2(OR, 0,v, I,z, lock), /* rAX, Iz */ > [0x0E] = X86_OP_ENTRYr(PUSH, CS, w, chk(i64)), > - [0x0F] = X86_OP_GROUP0(0F), > > [0x18] = X86_OP_ENTRY2(SBB, E,b, G,b, lock), > [0x19] = X86_OP_ENTRY2(SBB, E,v, G,v, lock), > @@ -2824,6 +2823,9 @@ static void disas_insn(DisasContext *s, CPUState *cpu) > > /* Collect prefixes. */ > switch (b) { > + case 0x0f: > + decode_func = decode_0F; > + break; > case 0xf3: > s->prefix |= PREFIX_REPZ; > s->prefix &= ~PREFIX_REPNZ; Reviewed-by: Richard Henderson <richard.henderson@linaro.org> r~ ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH 09/17] target/i386/tcg: decode REX2 prefix 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini ` (7 preceding siblings ...) 2026-09-23 17:30 ` [PATCH 08/17] target/i386/tcg: treat 0x0F as a prefix Paolo Bonzini @ 2026-09-23 17:30 ` Paolo Bonzini 2026-09-23 19:57 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 10/17] target/i386/tcg: implement JMPABS instruction Paolo Bonzini ` (8 subsequent siblings) 17 siblings, 1 reply; 35+ messages in thread From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw) To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu The REX2 prefix has two main complications: it does not apply to vector registers, and it disables or mutates some opcodes (thus needing separate decoding functions instead of decode_root and decode_0F). Otherwise, all it does is extend s->rex_r, s->rex_w and s->rex_b to two bits. Since REX2 provides the ability to access r16...r31, extend cpu_regs[] to CPU_NB_EREGS elements. Reviewed-by: Chang S. Bae <chang.seok.bae@intel.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/translate.c | 22 +++++- target/i386/tcg/decode-new.c.inc | 114 +++++++++++++++++++++++++++++-- 2 files changed, 126 insertions(+), 10 deletions(-) diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c index b8e8991163f..edef866a24b 100644 --- a/target/i386/tcg/translate.c +++ b/target/i386/tcg/translate.c @@ -78,7 +78,7 @@ static TCGv cpu_cc_dst, cpu_cc_src, cpu_cc_src2; static TCGv cpu_eip; static TCGv_i32 cpu_cc_op; -static TCGv cpu_regs[CPU_NB_REGS]; +static TCGv cpu_regs[CPU_NB_EREGS]; static TCGv cpu_seg_base[6]; static TCGv_i64 cpu_bndl[4]; static TCGv_i64 cpu_bndu[4]; @@ -3337,7 +3337,7 @@ static void gen_multi0F(DisasContext *s, X86DecodedInsn *decode) void tcg_x86_init(void) { - static const char reg_names[CPU_NB_REGS][4] = { + static const char reg_names[CPU_NB_EREGS][4] = { #ifdef TARGET_X86_64 [R_EAX] = "rax", [R_EBX] = "rbx", @@ -3355,6 +3355,22 @@ void tcg_x86_init(void) [13] = "r13", [14] = "r14", [15] = "r15", + [16] = "r16", + [17] = "r17", + [18] = "r18", + [19] = "r19", + [20] = "r20", + [21] = "r21", + [22] = "r22", + [23] = "r23", + [24] = "r24", + [25] = "r25", + [26] = "r26", + [27] = "r27", + [28] = "r28", + [29] = "r29", + [30] = "r30", + [31] = "r31", #else [R_EAX] = "eax", [R_EBX] = "ebx", @@ -3399,7 +3415,7 @@ void tcg_x86_init(void) "cc_src2"); cpu_eip = tcg_global_mem_new(tcg_env, offsetof(CPUX86State, eip), eip_name); - for (i = 0; i < CPU_NB_REGS; ++i) { + for (i = 0; i < CPU_NB_EREGS; ++i) { cpu_regs[i] = tcg_global_mem_new(tcg_env, offsetof(CPUX86State, regs[i]), reg_names[i]); diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index bcb23c6fae1..c2b252c01be 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -1991,6 +1991,65 @@ static const X86OpEntry opcodes_root[256] = { [0xFF] = X86_OP_GROUP1(group4_5, E,v), }; +#ifdef TARGET_X86_64 +static const X86OpEntry opcodes_rex2_map0_A0toAF[16] = { +}; + +static void decode_REX2(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint8_t *b, + const X86OpEntry **map) +{ + *b = x86_ldub_code(env, s); + const X86OpEntry *group = map[*b >> 4]; + *entry = group ? group[*b & 15] : UNKNOWN_OPCODE; +} + +static void decode_REX2_map0(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint8_t *b) +{ + static const X86OpEntry *opcode_rex2_map0[16] = { + &opcodes_root[0x00], + &opcodes_root[0x10], + &opcodes_root[0x20], + &opcodes_root[0x30], + NULL, + &opcodes_root[0x50], + &opcodes_root[0x60], + NULL, + &opcodes_root[0x80], + &opcodes_root[0x90], + opcodes_rex2_map0_A0toAF, + &opcodes_root[0xB0], + &opcodes_root[0xC0], + &opcodes_root[0xD0], + NULL, + &opcodes_root[0xF0], + }; + decode_REX2(s, env, entry, b, opcode_rex2_map0); +} + +static void decode_REX2_map1(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint8_t *b) +{ + static const X86OpEntry *opcode_rex2_map1[16] = { + &opcodes_0F[0x00], + &opcodes_0F[0x10], + &opcodes_0F[0x20], + NULL, + &opcodes_0F[0x40], + &opcodes_0F[0x50], + &opcodes_0F[0x60], + &opcodes_0F[0x70], + NULL, + &opcodes_0F[0x90], + &opcodes_0F[0xA0], + &opcodes_0F[0xB0], + &opcodes_0F[0xC0], + &opcodes_0F[0xD0], + &opcodes_0F[0xE0], + &opcodes_0F[0xF0], + }; + decode_REX2(s, env, entry, b, opcode_rex2_map1); +} +#endif + #undef mmx #undef vex1 #undef vex2 @@ -2010,6 +2069,20 @@ static void decode_root(DisasContext *s, CPUX86State *env, X86OpEntry *entry, ui *entry = opcodes_root[*b]; } +static int reg_nb_mask(DisasContext *s, int unit) +{ + switch (unit) { + case X86_OP_MMX: + return 7; + case X86_OP_SSE: + return 15; + break; + default: + return 31; + break; + } +} + /* Decode the MODRM and SIB bytes into a register or memory operand. */ static void decode_modrm(DisasContext *s, CPUX86State *env, X86DecodedInsn *decode, X86DecodedOp *op) @@ -2020,10 +2093,7 @@ static void decode_modrm(DisasContext *s, CPUX86State *env, int sib = -1; if (mod == 3) { - op->n = rm; - if (op->unit != X86_OP_MMX) { - op->n |= REX_B(s); - } + op->n = (rm | REX_B(s)) & reg_nb_mask(s, op->unit); return; } @@ -2324,9 +2394,7 @@ static bool decode_op(DisasContext *s, CPUX86State *env, X86DecodedInsn *decode, } get_reg: op->n = ((s->modrm >> 3) & 7); - if (op->unit != X86_OP_MMX) { - op->n |= REX_R(s); - } + op->n |= REX_R(s) & reg_nb_mask(s, op->unit); break; case X86_TYPE_E: /* ALU modrm operand */ @@ -2785,6 +2853,24 @@ static void dump_unknown_opcode(CPUX86State *env, DisasContext *s) } } +/* MASK must have two bits set. Bring the lowest just below the highest; + * for example if MASK == 0x11, bit 1 of value is moved to bit 3. Clear + * every other bit in VALUE. + * + * Generally mask will be a constant, so that all of the first three + * lines disappear. Likewise, if the bits in mask are already adjacent + * this becomes just "return value & mask". + */ +static inline uint8_t collapse_two_bits(uint8_t value, uint8_t mask) +{ + uint8_t high = mask & (mask - 1); + uint8_t low = mask & ~high; + uint8_t tweak = (high >> 1) - low; + + value &= mask; + return (value + tweak) & (mask + tweak); +} + /* * Convert one instruction. s->base.is_jmp is set if the translation must * be stopped. @@ -2880,6 +2966,20 @@ static void disas_insn(DisasContext *s, CPUState *cpu) goto next_byte_rex; } break; + case 0xd5: /* REX2 */ + if (CODE64(s) && (s->flags & HF_APX_EN_MASK)) { + int rex2 = x86_ldub_code(env, s); + if (rex != -1) { + goto illegal_op; + } + s->prefix |= PREFIX_REX2; + s->rex_b = collapse_two_bits(rex2, 0x11); + s->rex_x = collapse_two_bits(rex2, 0x22) >> 1; + s->rex_r = collapse_two_bits(rex2, 0x44) >> 2; + s->vex_w = (rex2 >> 3) & 1; + decode_func = rex2 & 0x80 ? decode_REX2_map1 : decode_REX2_map0; + } + break; #endif case 0xc5: /* 2-byte VEX */ case 0xc4: /* 3-byte VEX */ -- 2.55.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH 09/17] target/i386/tcg: decode REX2 prefix 2026-09-23 17:30 ` [PATCH 09/17] target/i386/tcg: decode REX2 prefix Paolo Bonzini @ 2026-09-23 19:57 ` Richard Henderson 0 siblings, 0 replies; 35+ messages in thread From: Richard Henderson @ 2026-09-23 19:57 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel; +Cc: chang.seok.bae, zhao1.liu On 9/23/26 10:30, Paolo Bonzini wrote: > The REX2 prefix has two main complications: it does not apply > to vector registers, and it disables or mutates some opcodes > (thus needing separate decoding functions instead of decode_root > and decode_0F). Otherwise, all it does is extend s->rex_r, > s->rex_w and s->rex_b to two bits. > > Since REX2 provides the ability to access r16...r31, extend > cpu_regs[] to CPU_NB_EREGS elements. > > Reviewed-by: Chang S. Bae<chang.seok.bae@intel.com> > Signed-off-by: Paolo Bonzini<pbonzini@redhat.com> > --- > target/i386/tcg/translate.c | 22 +++++- > target/i386/tcg/decode-new.c.inc | 114 +++++++++++++++++++++++++++++-- > 2 files changed, 126 insertions(+), 10 deletions(-) Reviewed-by: Richard Henderson <richard.henderson@linaro.org> r~ ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH 10/17] target/i386/tcg: implement JMPABS instruction 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini ` (8 preceding siblings ...) 2026-09-23 17:30 ` [PATCH 09/17] target/i386/tcg: decode REX2 prefix Paolo Bonzini @ 2026-09-23 17:30 ` Paolo Bonzini 2026-09-23 22:30 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 11/17] target/i386/tcg: extend VEX.vvvv parsing for APX Paolo Bonzini ` (7 subsequent siblings) 17 siblings, 1 reply; 35+ messages in thread From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw) To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu This is the same as an indirect jump, just with immediate operand. The requirement of W=0 and 0x67 are already available in the decoder. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/decode-new.h | 3 +++ target/i386/tcg/decode-new.c.inc | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/target/i386/tcg/decode-new.h b/target/i386/tcg/decode-new.h index b4b0567e7ca..cbc85498a1c 100644 --- a/target/i386/tcg/decode-new.h +++ b/target/i386/tcg/decode-new.h @@ -190,6 +190,9 @@ typedef enum X86InsnCheck { /* No REX2 prefix allowed */ X86_CHECK_no_rex2 = 8192, + + /* No 0x67 prefix allowed */ + X86_CHECK_no_adr = 16384, } X86InsnCheck; typedef enum X86InsnSpecial { diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index c2b252c01be..b6d909d5f2b 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -1993,6 +1993,7 @@ static const X86OpEntry opcodes_root[256] = { #ifdef TARGET_X86_64 static const X86OpEntry opcodes_rex2_map0_A0toAF[16] = { + [1] = X86_OP_ENTRYr(JMP_m, I,q, p_00 chk2(W0, no_adr)), /* JMPABS */ }; static void decode_REX2(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint8_t *b, @@ -3119,6 +3120,9 @@ static void disas_insn(DisasContext *s, CPUState *cpu) if ((decode.e.check & X86_CHECK_no_rex2) && (s->prefix & PREFIX_REX2)) { goto illegal_op; } + if ((decode.e.check & X86_CHECK_no_adr) && (s->prefix & PREFIX_ADR)) { + goto illegal_op; + } if ((decode.e.check & X86_CHECK_VEX128) && s->vex_l) { goto illegal_op; } -- 2.55.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH 10/17] target/i386/tcg: implement JMPABS instruction 2026-09-23 17:30 ` [PATCH 10/17] target/i386/tcg: implement JMPABS instruction Paolo Bonzini @ 2026-09-23 22:30 ` Richard Henderson 0 siblings, 0 replies; 35+ messages in thread From: Richard Henderson @ 2026-09-23 22:30 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel; +Cc: chang.seok.bae, zhao1.liu On 9/23/26 10:30, Paolo Bonzini wrote: > This is the same as an indirect jump, just with immediate operand. > The requirement of W=0 and 0x67 are already available in the decoder. > > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> > --- > target/i386/tcg/decode-new.h | 3 +++ > target/i386/tcg/decode-new.c.inc | 4 ++++ > 2 files changed, 7 insertions(+) > > diff --git a/target/i386/tcg/decode-new.h b/target/i386/tcg/decode-new.h > index b4b0567e7ca..cbc85498a1c 100644 > --- a/target/i386/tcg/decode-new.h > +++ b/target/i386/tcg/decode-new.h > @@ -190,6 +190,9 @@ typedef enum X86InsnCheck { > > /* No REX2 prefix allowed */ > X86_CHECK_no_rex2 = 8192, > + > + /* No 0x67 prefix allowed */ > + X86_CHECK_no_adr = 16384, > } X86InsnCheck; > > typedef enum X86InsnSpecial { > diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc > index c2b252c01be..b6d909d5f2b 100644 > --- a/target/i386/tcg/decode-new.c.inc > +++ b/target/i386/tcg/decode-new.c.inc > @@ -1993,6 +1993,7 @@ static const X86OpEntry opcodes_root[256] = { > > #ifdef TARGET_X86_64 > static const X86OpEntry opcodes_rex2_map0_A0toAF[16] = { > + [1] = X86_OP_ENTRYr(JMP_m, I,q, p_00 chk2(W0, no_adr)), /* JMPABS */ > }; > > static void decode_REX2(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint8_t *b, > @@ -3119,6 +3120,9 @@ static void disas_insn(DisasContext *s, CPUState *cpu) > if ((decode.e.check & X86_CHECK_no_rex2) && (s->prefix & PREFIX_REX2)) { > goto illegal_op; > } > + if ((decode.e.check & X86_CHECK_no_adr) && (s->prefix & PREFIX_ADR)) { > + goto illegal_op; > + } > if ((decode.e.check & X86_CHECK_VEX128) && s->vex_l) { > goto illegal_op; > } Reviewed-by: Richard Henderson <richard.henderson@linaro.org> r~ ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH 11/17] target/i386/tcg: extend VEX.vvvv parsing for APX 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini ` (9 preceding siblings ...) 2026-09-23 17:30 ` [PATCH 10/17] target/i386/tcg: implement JMPABS instruction Paolo Bonzini @ 2026-09-23 17:30 ` Paolo Bonzini 2026-09-23 22:39 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 12/17] target/i386/tcg: decode EVEX prefix Paolo Bonzini ` (6 subsequent siblings) 17 siblings, 1 reply; 35+ messages in thread From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw) To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu BMI uses operand type B when VEX.vvvv provides one of the register indices of three-operand instructions. However, APX needs to use it even for two-operand instructions, similar to how SSE and AVX share destination but use the modrm operand when there is no VEX prefix. To do this , operand type B can mostly reuse the code for operand type H but there are two extra possibilities: - instructions with VEX/EVEX prefix but no new data destination. To handle this, use a new field s->vex_ndd to retrieve whether the B (or H) operand comes from VEX.vvvv or from modrm. For now, this uses the same existing logic from validate_vex(), but later it will be extended to read EVEX.NDD. - instructions with operands "m,r" that NDD extends to "r,m,r". In this case, in the non-NDD format "m,r" the B operand corresponds to a memory location; when this happens, it is incorrect to parse again op1 as a memory location, because that would read the displacement again from the instruction stream. Instead, transform op1 into X86_TYPE_2op if VEX.vvvv is the destination operand. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/translate.c | 1 + target/i386/tcg/decode-new.c.inc | 38 +++++++++++++++++++++----------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c index edef866a24b..1901d9372bb 100644 --- a/target/i386/tcg/translate.c +++ b/target/i386/tcg/translate.c @@ -105,6 +105,7 @@ typedef struct DisasContext { #endif uint8_t vex_l; /* vex vector length */ uint8_t vex_v; /* vex vvvv register, without 1's complement. */ + bool vex_ndd; /* is this a 3-operand instruction? */ uint8_t popl_esp_hack; /* for correct popl with esp base handling */ uint8_t rip_offset; /* only used in x86_64, but left for simplicity */ diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index b6d909d5f2b..b3873043b16 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -2333,10 +2333,9 @@ static bool decode_op(DisasContext *s, CPUX86State *env, X86DecodedInsn *decode, case X86_TYPE_Y: /* string destination */ break; - case X86_TYPE_B: /* VEX.vvvv selects a GPR */ + case X86_TYPE_B: /* VEX.vvvv selects a GPR, else use modrm */ op->unit = X86_OP_INT; - op->n = s->vex_v; - break; + goto get_vex_v; case X86_TYPE_C: /* REG in the modrm byte selects a control register */ op->unit = X86_OP_CR; @@ -2466,18 +2465,25 @@ static bool decode_op(DisasContext *s, CPUX86State *env, X86DecodedInsn *decode, }; break; - case X86_TYPE_H: /* For AVX, VEX.vvvv selects an XMM/YMM register */ - if ((s->prefix & PREFIX_VEX)) { - op->unit = X86_OP_SSE; + case X86_TYPE_H: /* VEX.vvvv selects an XMM/YMM register, else use modrm */ + op->unit = X86_OP_SSE; + get_vex_v: + if (s->vex_ndd) { + assert(s->prefix & PREFIX_VEX); op->n = s->vex_v; break; } + /* Two-operand form: the other one of op0 and op1 is a modrm operand, use it. */ + assert(decode->e.s1 == decode->e.s0); if (op == &decode->op[0]) { - /* shifts place the destination in VEX.vvvv, use modrm */ - return decode_op(s, env, decode, op, decode->e.op1, b); + if (!decode_op(s, env, decode, op, decode->e.op1, b)) { + return false; + } + decode->e.op1 = X86_TYPE_2op; } else { - return decode_op(s, env, decode, op, decode->e.op0, b); + *op = decode->op[0]; } + break; case X86_TYPE_I: /* Immediate */ case X86_TYPE_J: /* Relative offset for a jump */ @@ -2723,6 +2729,14 @@ static bool validate_vex(DisasContext *s, X86DecodedInsn *decode) { X86OpEntry *e = &decode->e; + if (s->prefix & PREFIX_VEX) { + if (e->op0 == X86_TYPE_H || e->op0 == X86_TYPE_B || + e->op1 == X86_TYPE_H || e->op1 == X86_TYPE_B || + e->op2 == X86_TYPE_H || e->op2 == X86_TYPE_B) { + s->vex_ndd = true; + } + } + switch (e->vex_special) { case X86_VEX_None: break; @@ -2814,10 +2828,7 @@ static bool validate_vex(DisasContext *s, X86DecodedInsn *decode) return true; } - if (s->vex_v != 0 && - e->op0 != X86_TYPE_H && e->op0 != X86_TYPE_B && - e->op1 != X86_TYPE_H && e->op1 != X86_TYPE_B && - e->op2 != X86_TYPE_H && e->op2 != X86_TYPE_B) { + if (s->vex_v != 0 && !s->vex_ndd) { goto illegal; } @@ -2896,6 +2907,7 @@ static void disas_insn(DisasContext *s, CPUState *cpu) s->vex_l = 0; s->vex_v = 0; s->vex_w = false; + s->vex_ndd = false; s->has_modrm = false; s->prefix = 0; -- 2.55.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH 11/17] target/i386/tcg: extend VEX.vvvv parsing for APX 2026-09-23 17:30 ` [PATCH 11/17] target/i386/tcg: extend VEX.vvvv parsing for APX Paolo Bonzini @ 2026-09-23 22:39 ` Richard Henderson 0 siblings, 0 replies; 35+ messages in thread From: Richard Henderson @ 2026-09-23 22:39 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel; +Cc: chang.seok.bae, zhao1.liu On 9/23/26 10:30, Paolo Bonzini wrote: > BMI uses operand type B when VEX.vvvv provides one of the register indices > of three-operand instructions. However, APX needs to use it even for > two-operand instructions, similar to how SSE and AVX share destination > but use the modrm operand when there is no VEX prefix. > > To do this , operand type B can mostly reuse the code for operand type H > but there are two extra possibilities: > > - instructions with VEX/EVEX prefix but no new data destination. To handle > this, use a new field s->vex_ndd to retrieve whether the B (or H) operand > comes from VEX.vvvv or from modrm. For now, this uses the same existing > logic from validate_vex(), but later it will be extended to read EVEX.NDD. > > - instructions with operands "m,r" that NDD extends to "r,m,r". > In this case, in the non-NDD format "m,r" the B operand corresponds to > a memory location; when this happens, it is incorrect to parse again > op1 as a memory location, because that would read the displacement again > from the instruction stream. Instead, transform op1 into X86_TYPE_2op > if VEX.vvvv is the destination operand. > > Signed-off-by: Paolo Bonzini<pbonzini@redhat.com> > --- > target/i386/tcg/translate.c | 1 + > target/i386/tcg/decode-new.c.inc | 38 +++++++++++++++++++++----------- > 2 files changed, 26 insertions(+), 13 deletions(-) Reviewed-by: Richard Henderson <richard.henderson@linaro.org> r~ ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH 12/17] target/i386/tcg: decode EVEX prefix 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini ` (10 preceding siblings ...) 2026-09-23 17:30 ` [PATCH 11/17] target/i386/tcg: extend VEX.vvvv parsing for APX Paolo Bonzini @ 2026-09-23 17:30 ` Paolo Bonzini 2026-09-23 23:28 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 13/17] target/i386/tcg: add decode functionality for APX Paolo Bonzini ` (5 subsequent siblings) 17 siblings, 1 reply; 35+ messages in thread From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw) To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu EVEX is really messy and the exact position of the fields is spread all over the place. For now store the three data bytes of the prefix in DisasContext, later the EVEX instruction classes for APX will be added to extract_evex_params and validate_vex. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/translate.c | 8 ++- target/i386/tcg/decode-new.c.inc | 89 +++++++++++++++++++++++++++++--- 2 files changed, 87 insertions(+), 10 deletions(-) diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c index 1901d9372bb..c28cd9809e9 100644 --- a/target/i386/tcg/translate.c +++ b/target/i386/tcg/translate.c @@ -51,6 +51,7 @@ #define PREFIX_VEX 0x20 #define PREFIX_REX 0x40 #define PREFIX_REX2 0x80 +#define PREFIX_EVEX 0x100 #ifdef TARGET_X86_64 # define ctztl ctz64 @@ -93,8 +94,8 @@ typedef struct DisasContext { MemOp aflag; MemOp dflag; + uint16_t prefix; int8_t override; /* -1 if no override, else R_CS, R_DS, etc */ - uint8_t prefix; bool has_modrm; uint8_t modrm; @@ -114,6 +115,9 @@ typedef struct DisasContext { uint8_t rex_x; uint8_t rex_b; #endif + uint8_t evex2; + uint8_t evex3; + uint8_t evex4; bool vex_w; /* used by AVX even on 32-bit processors */ bool jmp_opt; /* use direct block chaining for direct jumps */ bool cc_op_dirty; @@ -210,7 +214,7 @@ typedef struct DisasContext { #endif #ifdef TARGET_X86_64 -#define REX_PREFIX(S) (((S)->prefix & (PREFIX_REX | PREFIX_REX2 | PREFIX_VEX)) != 0) +#define REX_PREFIX(S) (((S)->prefix & (PREFIX_REX | PREFIX_REX2 | PREFIX_VEX | PREFIX_EVEX)) != 0) #define REX_W(S) ((S)->vex_w) #define REX_R(S) ((S)->rex_r + 0) #define REX_X(S) ((S)->rex_x + 0) diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index b3873043b16..10c6833bb3c 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -2049,6 +2049,11 @@ static void decode_REX2_map1(DisasContext *s, CPUX86State *env, X86OpEntry *entr }; decode_REX2(s, env, entry, b, opcode_rex2_map1); } + +static void decode_EVEX_map4(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint8_t *b) +{ + *entry = UNKNOWN_OPCODE; +} #endif #undef mmx @@ -2076,8 +2081,7 @@ static int reg_nb_mask(DisasContext *s, int unit) case X86_OP_MMX: return 7; case X86_OP_SSE: - return 15; - break; + return s->prefix & PREFIX_EVEX ? 31 : 15; default: return 31; break; @@ -2469,7 +2473,7 @@ static bool decode_op(DisasContext *s, CPUX86State *env, X86DecodedInsn *decode, op->unit = X86_OP_SSE; get_vex_v: if (s->vex_ndd) { - assert(s->prefix & PREFIX_VEX); + assert(s->prefix & (PREFIX_VEX | PREFIX_EVEX)); op->n = s->vex_v; break; } @@ -2598,6 +2602,15 @@ static bool decode_insn(DisasContext *s, CPUX86State *env, X86DecodeFunc decode_ return true; } +static bool extract_evex_params(DisasContext *s, X86DecodedInsn *decode) +{ + /* + * Here, the position of RXB and (for AVX512) displacement multiplier + * should be known. + */ + return false; +} + static bool decode_ops(DisasContext *s, CPUX86State *env, X86DecodeFunc decode_func, X86DecodedInsn *decode) { @@ -2729,13 +2742,16 @@ static bool validate_vex(DisasContext *s, X86DecodedInsn *decode) { X86OpEntry *e = &decode->e; - if (s->prefix & PREFIX_VEX) { + if (s->prefix & (PREFIX_VEX | PREFIX_EVEX)) { if (e->op0 == X86_TYPE_H || e->op0 == X86_TYPE_B || e->op1 == X86_TYPE_H || e->op1 == X86_TYPE_B || e->op2 == X86_TYPE_H || e->op2 == X86_TYPE_B) { s->vex_ndd = true; } } + if ((s->prefix & PREFIX_EVEX) && !extract_evex_params(s, decode)) { + goto illegal; + } switch (e->vex_special) { case X86_VEX_None: @@ -2883,6 +2899,10 @@ static inline uint8_t collapse_two_bits(uint8_t value, uint8_t mask) return (value + tweak) & (mask + tweak); } +static const int pp_prefix[4] = { + 0, PREFIX_DATA, PREFIX_REPZ, PREFIX_REPNZ +}; + /* * Convert one instruction. s->base.is_jmp is set if the translation must * be stopped. @@ -2908,6 +2928,9 @@ static void disas_insn(DisasContext *s, CPUState *cpu) s->vex_v = 0; s->vex_w = false; s->vex_ndd = false; + s->evex2 = 0; + s->evex3 = 0; + s->evex4 = 0; s->has_modrm = false; s->prefix = 0; @@ -2994,6 +3017,59 @@ static void disas_insn(DisasContext *s, CPUState *cpu) } break; #endif + case 0x62: /* EVEX */ + if (CODE32(s) && !VM86(s)) { + int evex2 = x86_ldub_code(env, s); + + if (!CODE64(s) && (evex2 & 0xc0) != 0xc0) { + s->pc--; /* rewind the advance_pc() x86_ldub_code() did */ + break; + } +#ifdef TARGET_X86_64 + if (rex != -1) { + goto illegal_op; + } +#endif + if (s->prefix & (PREFIX_REPZ | PREFIX_REPNZ + | PREFIX_LOCK | PREFIX_DATA)) { + goto illegal_op; + } + /* + * Store them because the exact correspondence between EVEX + * and RXB bits is only known later. It's different for APX, + * AVX512 register operands, and AVX512 VSIB index operands. + */ + s->evex2 = evex2; + s->evex3 = x86_ldub_code(env, s); + s->evex4 = x86_ldub_code(env, s); + s->vex_w = (s->evex3 >> 7) & 1; + s->vex_l = (s->evex4 >> 5) & 3; + s->prefix |= pp_prefix[s->evex3 & 3] | PREFIX_EVEX; + switch (evex2 & 7) { + case 0x01: + /* + * Note: actually uses VEX map1, to which AVX512 adds extra + * instructions encoded with VEX.L=1. Of these, KMOV* + * has an APX extension too. + */ + decode_func = decode_0F; + break; + case 0x02: + decode_func = decode_0F38; + break; + case 0x03: + decode_func = decode_0F3A; + break; +#ifdef TARGET_X86_64 + case 0x04: + decode_func = decode_EVEX_map4; + break; +#endif + default: /* Reserved for future use. */ + goto unknown_op; + } + } + break; case 0xc5: /* 2-byte VEX */ case 0xc4: /* 3-byte VEX */ /* @@ -3001,9 +3077,6 @@ static void disas_insn(DisasContext *s, CPUState *cpu) * Otherwise the instruction is LES or LDS. Not allowed in real mode. */ if (PE(s) && !VM86(s)) { - static const int pp_prefix[4] = { - 0, PREFIX_DATA, PREFIX_REPZ, PREFIX_REPNZ - }; int vex3, vex2 = x86_ldub_code(env, s); if (!CODE64(s) && (vex2 & 0xc0) != 0xc0) { @@ -3152,7 +3225,7 @@ static void disas_insn(DisasContext *s, CPUState *cpu) } if (decode.e.vex_class == 0) { - if (s->prefix & PREFIX_VEX) { + if (s->prefix & (PREFIX_VEX | PREFIX_EVEX)) { goto illegal_op; } } else { -- 2.55.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH 12/17] target/i386/tcg: decode EVEX prefix 2026-09-23 17:30 ` [PATCH 12/17] target/i386/tcg: decode EVEX prefix Paolo Bonzini @ 2026-09-23 23:28 ` Richard Henderson 0 siblings, 0 replies; 35+ messages in thread From: Richard Henderson @ 2026-09-23 23:28 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel; +Cc: chang.seok.bae, zhao1.liu On 9/23/26 10:30, Paolo Bonzini wrote: > EVEX is really messy and the exact position of the fields is spread all > over the place. For now store the three data bytes of the prefix in > DisasContext, later the EVEX instruction classes for APX will be added > to extract_evex_params and validate_vex. > > Signed-off-by: Paolo Bonzini<pbonzini@redhat.com> > --- > target/i386/tcg/translate.c | 8 ++- > target/i386/tcg/decode-new.c.inc | 89 +++++++++++++++++++++++++++++--- > 2 files changed, 87 insertions(+), 10 deletions(-) Reviewed-by: Richard Henderson <richard.henderson@linaro.org> r~ ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH 13/17] target/i386/tcg: add decode functionality for APX 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini ` (11 preceding siblings ...) 2026-09-23 17:30 ` [PATCH 12/17] target/i386/tcg: decode EVEX prefix Paolo Bonzini @ 2026-09-23 17:30 ` Paolo Bonzini 2026-09-24 15:17 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 14/17] target/i386/tcg: implement CCMP/CTEST Paolo Bonzini ` (4 subsequent siblings) 17 siblings, 1 reply; 35+ messages in thread From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw) To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu Handle the EVEX formats for APX (including the extension of BMI and CMPccXADD instructions that already supported VEX) and add their validation to validate_vex(). Exit disas_insn() early if the NF field is set. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/decode-new.h | 10 ++ target/i386/tcg/translate.c | 11 +- target/i386/tcg/decode-new.c.inc | 168 ++++++++++++++++++++++++++++++- 3 files changed, 181 insertions(+), 8 deletions(-) diff --git a/target/i386/tcg/decode-new.h b/target/i386/tcg/decode-new.h index cbc85498a1c..ec65ae5d75b 100644 --- a/target/i386/tcg/decode-new.h +++ b/target/i386/tcg/decode-new.h @@ -193,6 +193,9 @@ typedef enum X86InsnCheck { /* No 0x67 prefix allowed */ X86_CHECK_no_adr = 16384, + + /* EVEX.NF bit not allowed */ + X86_CHECK_nf0 = 32768, } X86InsnCheck; typedef enum X86InsnSpecial { @@ -279,6 +282,13 @@ typedef enum X86VEXSpecial { X86_VEX_LIG, } X86VEXSpecial; +typedef enum X86EVEXClass { + /* Non-vector instruction that can use APX EGPRs. */ + X86_EVEX_APX = 128, + X86_EVEX_APX_cmp = 129, + X86_EVEX_APX_pp2 = 130, + X86_EVEX_APX_zu = 131, +} X86EVEXClass; typedef struct X86OpEntry X86OpEntry; typedef struct X86DecodedInsn X86DecodedInsn; diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c index c28cd9809e9..c35869ab500 100644 --- a/target/i386/tcg/translate.c +++ b/target/i386/tcg/translate.c @@ -107,18 +107,17 @@ typedef struct DisasContext { uint8_t vex_l; /* vex vector length */ uint8_t vex_v; /* vex vvvv register, without 1's complement. */ bool vex_ndd; /* is this a 3-operand instruction? */ + bool vex_w; /* used by AVX even on 32-bit processors */ uint8_t popl_esp_hack; /* for correct popl with esp base handling */ uint8_t rip_offset; /* only used in x86_64, but left for simplicity */ -#ifdef TARGET_X86_64 - uint8_t rex_r; + uint8_t rex_r; /* 0 for i386, but left for simplicity */ uint8_t rex_x; uint8_t rex_b; -#endif + uint8_t evex2; uint8_t evex3; uint8_t evex4; - bool vex_w; /* used by AVX even on 32-bit processors */ bool jmp_opt; /* use direct block chaining for direct jumps */ bool cc_op_dirty; @@ -219,12 +218,16 @@ typedef struct DisasContext { #define REX_R(S) ((S)->rex_r + 0) #define REX_X(S) ((S)->rex_x + 0) #define REX_B(S) ((S)->rex_b + 0) +#define EVEX_APX_ND(S) (((S)->evex4 & 0x10) != 0) +#define EVEX_APX_NF(S) (((S)->evex4 & 0x04) != 0) #else #define REX_PREFIX(S) false #define REX_W(S) false #define REX_R(S) 0 #define REX_X(S) 0 #define REX_B(S) 0 +#define EVEX_APX_ND(S) 0 +#define EVEX_APX_NF(S) 0 #endif /* diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index 10c6833bb3c..b2e6dd3a488 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -257,6 +257,10 @@ #define vex11 .vex_class = 11, #define vex12 .vex_class = 12, #define vex13 .vex_class = 13, +#define evex_apx .vex_class = X86_EVEX_APX, +#define evex_apx_cmp .vex_class = X86_EVEX_APX_cmp, +#define evex_apx_pp2 .vex_class = X86_EVEX_APX_pp2, +#define evex_apx_zu .vex_class = X86_EVEX_APX_zu, #define chk(a) .check = X86_CHECK_##a, #define chk2(a, b) .check = X86_CHECK_##a | X86_CHECK_##b, @@ -2531,9 +2535,23 @@ static bool validate_sse_prefix(DisasContext *s, X86OpEntry *e) { uint16_t sse_prefixes; - if (!e->valid_prefix) { - return true; + switch (e->vex_class) { + case X86_EVEX_APX: + case X86_EVEX_APX_cmp: + case X86_EVEX_APX_pp2: + case X86_EVEX_APX_zu: + /* For APX-extended instructions, only EVEX versions check the prefix. */ + if (!(s->prefix & PREFIX_EVEX)) { + return true; + } + + /* fallthrough */ + default: + if (!e->valid_prefix) { + return true; + } } + if (s->prefix & (PREFIX_REPZ | PREFIX_REPNZ)) { /* In SSE instructions, 0xF3 and 0xF2 cancel 0x66. */ s->prefix &= ~PREFIX_DATA; @@ -2608,7 +2626,35 @@ static bool extract_evex_params(DisasContext *s, X86DecodedInsn *decode) * Here, the position of RXB and (for AVX512) displacement multiplier * should be known. */ - return false; + X86OpEntry *e = &decode->e; + uint8_t rex_r, rex_x, rex_b; + + switch (e->vex_class) { + case 13: + case X86_EVEX_APX: + case X86_EVEX_APX_cmp: + case X86_EVEX_APX_pp2: + case X86_EVEX_APX_zu: + /* Only reached through EVEX map 4. */ + rex_r = (~s->evex2 & 0x10) | ((~s->evex2 >> 4) & 8); + rex_x = ((~s->evex3 << 2) & 0x10) | ((~s->evex2 >> 3) & 8); + rex_b = ((s->evex2 << 1) & 0x10) | ((~s->evex2 >> 2) & 8); + s->vex_v = ((~s->evex4 << 1) & 0x10) | ((~s->evex3 >> 3) & 15); + break; + default: + return false; + } + +#ifdef TARGET_X86_64 + s->rex_r = rex_r; + s->rex_x = rex_x; + s->rex_b = rex_b; +#else + (void)rex_r; + (void)rex_x; + (void)rex_b; +#endif + return true; } static bool decode_ops(DisasContext *s, CPUX86State *env, X86DecodeFunc decode_func, @@ -2738,6 +2784,87 @@ static bool has_cpuid_feature(DisasContext *s, X86CPUIDFeature cpuid) g_assert_not_reached(); } +static bool validate_evex_apx(DisasContext *s, X86DecodedInsn *decode) +{ + X86OpEntry *e = &decode->e; + uint8_t evex4_mask = 0; + + assert(s->has_modrm); + switch (e->vex_class) { + case 13: + /* + * APX-EVEX-BMI and APX-EVEX-CMPCCXADD do not define EVEX.ND. + * They only differ for checks on NF, which happen elsewhere. + */ + if (!CODE64(s) || !(s->flags & HF_APX_EN_MASK)) { + goto illegal; + } + evex4_mask = 0x0C; + break; + + case X86_EVEX_APX: + /* APX-EVEX-INT. Checks on NF happen elsewhere. */ + assert(e->op0 == X86_TYPE_None || e->s0 == e->s1); + if (!CODE64(s) || !(s->flags & HF_APX_EN_MASK)) { + goto illegal; + } + if (!EVEX_APX_ND(s)) { + s->vex_ndd = false; + } else if (!s->vex_ndd) { + goto illegal; + } + evex4_mask = 0x1C; + break; + + case X86_EVEX_APX_zu: + /* APX-EVEX-INT, with ZU bit defined. Checks on NF happen elsewhere. */ + assert(decode->e.ext0 == X86_EXT_None && !s->vex_ndd); + if (!CODE64(s) || !(s->flags & HF_APX_EN_MASK)) { + goto illegal; + } + if (s->vex_v != 0) { + goto illegal; + } + if (EVEX_APX_ND(s)) { + decode->op[0].ext = X86_EXT_Unsigned; + } + evex4_mask = 0x1C; + break; + + case X86_EVEX_APX_pp2: + if (!CODE64(s) || !(s->flags & HF_APX_EN_MASK) || + !EVEX_APX_ND(s) || + (e->op0 != X86_TYPE_None && s->vex_v == ((s->modrm & 7) | REX_B(s))) || + ((s->modrm & 7) | REX_B(s)) == 4 || + s->vex_v == 4) { + goto illegal; + } + evex4_mask = 0x18; + break; + + case X86_EVEX_APX_cmp: + if (!CODE64(s) || !(s->flags & HF_APX_EN_MASK)) { + goto illegal; + } + evex4_mask = 0x0F; + break; + } + + if (s->evex4 & ~evex4_mask) { + goto illegal; + } + + /* r/m == 11b requires X4=0 (that is, EVEX.U is 1) */ + if ((s->modrm >> 6) == 3 && (s->rex_x & 16)) { + goto illegal; + } + return true; + +illegal: + gen_illegal_opcode(s); + return false; +} + static bool validate_vex(DisasContext *s, X86DecodedInsn *decode) { X86OpEntry *e = &decode->e; @@ -2834,14 +2961,32 @@ static bool validate_vex(DisasContext *s, X86DecodedInsn *decode) } break; case 13: - if (!(s->prefix & PREFIX_VEX)) { + if (!(s->prefix & (PREFIX_VEX | PREFIX_EVEX))) { goto illegal; } if (s->vex_l) { goto illegal; } + if (s->prefix & PREFIX_EVEX) { + if (!validate_evex_apx(s, decode)) { + return false; + } + } /* All integer instructions use VEX.vvvv, so exit. */ return true; + case X86_EVEX_APX: + case X86_EVEX_APX_cmp: + case X86_EVEX_APX_pp2: + case X86_EVEX_APX_zu: + /* Only reached through EVEX map 4. */ + assert(!(s->prefix & PREFIX_VEX)); + if (s->vex_l) { + goto illegal; + } + if ((s->prefix & PREFIX_EVEX) && !validate_evex_apx(s, decode)) { + return false; + } + return true; } if (s->vex_v != 0 && !s->vex_ndd) { @@ -3217,6 +3362,9 @@ static void disas_insn(DisasContext *s, CPUState *cpu) if ((decode.e.check & X86_CHECK_W1) && !s->vex_w) { goto illegal_op; } + if ((decode.e.check & X86_CHECK_nf0) && EVEX_APX_NF(s)) { + goto illegal_op; + } } if (op_has_modrm(decode.e.op0) || op_has_modrm(decode.e.op1) || @@ -3352,6 +3500,18 @@ static void disas_insn(DisasContext *s, CPUState *cpu) * cause incorrect tracking of CC_OP for instructions that write to both memory * and flags. */ + switch (decode.e.vex_class) { + case 13: + case X86_EVEX_APX: + case X86_EVEX_APX_zu: + if (EVEX_APX_NF(s)) { + return; + } + break; + default: + break; + } + if (decode.cc_op != -1) { if (decode.cc_dst) { tcg_gen_mov_tl(cpu_cc_dst, decode.cc_dst); -- 2.55.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH 13/17] target/i386/tcg: add decode functionality for APX 2026-09-23 17:30 ` [PATCH 13/17] target/i386/tcg: add decode functionality for APX Paolo Bonzini @ 2026-09-24 15:17 ` Richard Henderson 2026-09-28 10:34 ` Zhao Liu 2026-09-28 12:16 ` Paolo Bonzini 0 siblings, 2 replies; 35+ messages in thread From: Richard Henderson @ 2026-09-24 15:17 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel; +Cc: chang.seok.bae, zhao1.liu On 9/23/26 10:30, Paolo Bonzini wrote: > + case X86_EVEX_APX: > + case X86_EVEX_APX_cmp: > + case X86_EVEX_APX_pp2: > + case X86_EVEX_APX_zu: > + /* Only reached through EVEX map 4. */ > + assert(!(s->prefix & PREFIX_VEX)); > + if (s->vex_l) { > + goto illegal; > + } > + if ((s->prefix & PREFIX_EVEX) && !validate_evex_apx(s, decode)) { > + return false; > + } They're only reached by map4, so surely assert(s->prefix & PREFIX_EVEX); is more appropriate, and the subsequent test for evex moot. Otherwise, Reviewed-by: Richard Henderson <richard.henderson@linaro.org> r~ ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 13/17] target/i386/tcg: add decode functionality for APX 2026-09-24 15:17 ` Richard Henderson @ 2026-09-28 10:34 ` Zhao Liu 2026-09-28 12:16 ` Paolo Bonzini 1 sibling, 0 replies; 35+ messages in thread From: Zhao Liu @ 2026-09-28 10:34 UTC (permalink / raw) To: Richard Henderson; +Cc: Paolo Bonzini, qemu-devel, chang.seok.bae, Zhao Liu On Thu, Sep 24, 2026 at 08:17:17AM -0700, Richard Henderson wrote: > Date: Thu, 24 Sep 2026 08:17:17 -0700 > From: Richard Henderson <richard.henderson@linaro.org> > Subject: Re: [PATCH 13/17] target/i386/tcg: add decode functionality for APX > > On 9/23/26 10:30, Paolo Bonzini wrote: > > + case X86_EVEX_APX: > > + case X86_EVEX_APX_cmp: > > + case X86_EVEX_APX_pp2: > > + case X86_EVEX_APX_zu: > > + /* Only reached through EVEX map 4. */ > > + assert(!(s->prefix & PREFIX_VEX)); > > + if (s->vex_l) { > > + goto illegal; > > + } > > + if ((s->prefix & PREFIX_EVEX) && !validate_evex_apx(s, decode)) { > > + return false; > > + } > > They're only reached by map4, so surely > > assert(s->prefix & PREFIX_EVEX); > > is more appropriate, and the subsequent test for evex moot. I think the code comment is misleading (and assert seems right). Not only evex map4, but also some legacy decoding reaches here, and those legacy decoding share the decode helpers with evex map4, e.g. decode_group1/decode_group3/decode_group4_5. And REX2 could also reach here since it reuses legacy decoding table in decode_REX2_map0. Maybe we can refine the commnet like: /* EVEX map4/REX2/legacy all may reach here. Only VEX won't */ or we can drop that comment directly? :-) Regards, Zhao ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 13/17] target/i386/tcg: add decode functionality for APX 2026-09-24 15:17 ` Richard Henderson 2026-09-28 10:34 ` Zhao Liu @ 2026-09-28 12:16 ` Paolo Bonzini 2026-09-29 7:50 ` Richard Henderson 1 sibling, 1 reply; 35+ messages in thread From: Paolo Bonzini @ 2026-09-28 12:16 UTC (permalink / raw) To: Richard Henderson; +Cc: qemu-devel, chang.seok.bae, zhao1.liu On Thu, Sep 24, 2026 at 5:17 PM Richard Henderson <richard.henderson@linaro.org> wrote: > > On 9/23/26 10:30, Paolo Bonzini wrote: > > + case X86_EVEX_APX: > > + case X86_EVEX_APX_cmp: > > + case X86_EVEX_APX_pp2: > > + case X86_EVEX_APX_zu: > > + /* Only reached through EVEX map 4. */ > > + assert(!(s->prefix & PREFIX_VEX)); > > + if (s->vex_l) { > > + goto illegal; > > + } > > + if ((s->prefix & PREFIX_EVEX) && !validate_evex_apx(s, decode)) { > > + return false; > > + } > > They're only reached by map4, so surely > > assert(s->prefix & PREFIX_EVEX); > > is more appropriate, and the subsequent test for evex moot. No, validate_vex() is also invoked for non-VEX/EVEX instructions. In that case, for example, it will raise #UD for opcodes that are only the VEX/EVEX maps and not in the 0F map. See for example: case 13: if (!(s->prefix & (PREFIX_VEX | PREFIX_EVEX))) { goto illegal; } which will reject an ANDN that is encoded as 0F 38 F2 instead of using VEX/EVEX. For this patch, in particular, group1, group2, group3 and group4_5 are shared between the root map and EVEX map 4. Some of the instructions starting with F6/F7/FE/FF will call validate_vpx even with no prefix and have to be accepted. I'll add a comment /* Not reached through any VEX map. */ for clarity. Paolo ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 13/17] target/i386/tcg: add decode functionality for APX 2026-09-28 12:16 ` Paolo Bonzini @ 2026-09-29 7:50 ` Richard Henderson 2026-09-29 8:08 ` Paolo Bonzini 0 siblings, 1 reply; 35+ messages in thread From: Richard Henderson @ 2026-09-29 7:50 UTC (permalink / raw) To: Paolo Bonzini; +Cc: qemu-devel, chang.seok.bae, zhao1.liu On 9/28/26 13:16, Paolo Bonzini wrote: > On Thu, Sep 24, 2026 at 5:17 PM Richard Henderson > <richard.henderson@linaro.org> wrote: >> >> On 9/23/26 10:30, Paolo Bonzini wrote: >>> + case X86_EVEX_APX: >>> + case X86_EVEX_APX_cmp: >>> + case X86_EVEX_APX_pp2: >>> + case X86_EVEX_APX_zu: >>> + /* Only reached through EVEX map 4. */ >>> + assert(!(s->prefix & PREFIX_VEX)); >>> + if (s->vex_l) { >>> + goto illegal; >>> + } >>> + if ((s->prefix & PREFIX_EVEX) && !validate_evex_apx(s, decode)) { >>> + return false; >>> + } >> >> They're only reached by map4, so surely >> >> assert(s->prefix & PREFIX_EVEX); >> >> is more appropriate, and the subsequent test for evex moot. > > No, validate_vex() is also invoked for non-VEX/EVEX instructions. Of course. But how do you get X86_EVEX_APX* from non EVEX paths? r~ ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 13/17] target/i386/tcg: add decode functionality for APX 2026-09-29 7:50 ` Richard Henderson @ 2026-09-29 8:08 ` Paolo Bonzini 0 siblings, 0 replies; 35+ messages in thread From: Paolo Bonzini @ 2026-09-29 8:08 UTC (permalink / raw) To: Richard Henderson; +Cc: qemu-devel, chang.seok.bae, zhao1.liu On 9/29/26 09:50, Richard Henderson wrote: > On 9/28/26 13:16, Paolo Bonzini wrote: >> On Thu, Sep 24, 2026 at 5:17 PM Richard Henderson >> <richard.henderson@linaro.org> wrote: >>> >>> On 9/23/26 10:30, Paolo Bonzini wrote: >>>> + case X86_EVEX_APX: >>>> + case X86_EVEX_APX_cmp: >>>> + case X86_EVEX_APX_pp2: >>>> + case X86_EVEX_APX_zu: >>>> + /* Only reached through EVEX map 4. */ >>>> + assert(!(s->prefix & PREFIX_VEX)); >>>> + if (s->vex_l) { >>>> + goto illegal; >>>> + } >>>> + if ((s->prefix & PREFIX_EVEX) && !validate_evex_apx(s, >>>> decode)) { >>>> + return false; >>>> + } >>> >>> They're only reached by map4, so surely >>> >>> assert(s->prefix & PREFIX_EVEX); >>> >>> is more appropriate, and the subsequent test for evex moot. >> >> No, validate_vex() is also invoked for non-VEX/EVEX instructions. > > Of course. But how do you get X86_EVEX_APX* from non EVEX paths? From decode_group* where it is shared between normal and EVEX paths. For example decode_group1 (ALU operation with a memory and an immediate operand) and can set X86_EVEX_APX_cmp, and decode_group{3,4_5} can set X86_EVEX_APX. Paolo ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH 14/17] target/i386/tcg: implement CCMP/CTEST 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini ` (12 preceding siblings ...) 2026-09-23 17:30 ` [PATCH 13/17] target/i386/tcg: add decode functionality for APX Paolo Bonzini @ 2026-09-23 17:30 ` Paolo Bonzini 2026-09-23 17:30 ` [PATCH 15/17] target/i386/tcg: decode APX instructions Paolo Bonzini ` (3 subsequent siblings) 17 siblings, 0 replies; 35+ messages in thread From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw) To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu CCMP/CTEST are new instructions that will either perform a CMP/TEST or set EFLAGS to a predetermined value. To support this double functionality without using CC_OP_DYNAMIC, introduce a new CCOp that is inspired by x86_flags.h. By shifting the carry computation part of compute_aco_sub* to TCG ops, it provides enough flexibility that the result of a subtraction, the result of an AND, and any value of EFLAGS can be encoded with a single CCOp. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/cpu.h | 5 + target/i386/tcg/cc_helper_template.h.inc | 11 ++ target/i386/tcg/cc_helper.c | 14 +++ target/i386/tcg/translate.c | 69 ++++++++++++ target/i386/tcg/decode-new.c.inc | 30 ++--- target/i386/tcg/emit.c.inc | 138 ++++++++++++++++++++++- 6 files changed, 251 insertions(+), 16 deletions(-) diff --git a/target/i386/cpu.h b/target/i386/cpu.h index c8619666f07..e7a88074c07 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -1606,6 +1606,11 @@ typedef enum { CC_OP_BLSIL, CC_OP_BLSIQ, + CC_OP_CCMPB, /* Z via CC_DST, P,S via CC_SRC2, carry-out in CC_SRC */ + CC_OP_CCMPW, + CC_OP_CCMPL, + CC_OP_CCMPQ, + /* * Note that only CC_OP_POPCNT (i.e. the one with MO_TL size) * is used or implemented, because the translation needs diff --git a/target/i386/tcg/cc_helper_template.h.inc b/target/i386/tcg/cc_helper_template.h.inc index af58c2409f7..8ec449fd3ff 100644 --- a/target/i386/tcg/cc_helper_template.h.inc +++ b/target/i386/tcg/cc_helper_template.h.inc @@ -60,6 +60,17 @@ static uint32_t glue(compute_aco_cout, SUFFIX)(DATA_TYPE carries) return af_cf + of; } +static uint32_t glue(compute_all_ccmp, SUFFIX)(DATA_TYPE dst, DATA_TYPE src1, DATA_TYPE src2) +{ + uint32_t pf, zf, sf; + + /* Compute all bits here because PF and SF do not come from DST. */ + zf = dst == 0 ? CC_Z : 0; + pf = compute_pf(src2); + sf = lshift(src2, 8 - DATA_BITS) & CC_S; + return pf + zf + sf + glue(compute_aco_cout, SUFFIX)(src1); +} + static uint32_t glue(compute_aco_add, SUFFIX)(DATA_TYPE dst, DATA_TYPE src1) { DATA_TYPE src2 = dst - src1; diff --git a/target/i386/tcg/cc_helper.c b/target/i386/tcg/cc_helper.c index 6ddd64fcb07..b868ef2949f 100644 --- a/target/i386/tcg/cc_helper.c +++ b/target/i386/tcg/cc_helper.c @@ -111,6 +111,13 @@ target_ulong helper_cc_compute_all(target_ulong dst, target_ulong src1, case CC_OP_ADCOX: return compute_all_adcox(dst, src1, src2); + case CC_OP_CCMPB: + return compute_all_ccmpb(dst, src1, src2); + case CC_OP_CCMPW: + return compute_all_ccmpw(dst, src1, src2); + case CC_OP_CCMPL: + return compute_all_ccmpl(dst, src1, src2); + case CC_OP_MULB: flags = compute_aco_mul(src1); goto psz_b; @@ -232,6 +239,9 @@ target_ulong helper_cc_compute_all(target_ulong dst, target_ulong src1, goto psz_l; #ifdef TARGET_X86_64 + case CC_OP_CCMPQ: + return compute_all_ccmpq(dst, src1, src2); + case CC_OP_MULQ: flags = compute_aco_mul(src1); goto psz_q; @@ -365,10 +375,13 @@ target_ulong helper_cc_compute_c(target_ulong dst, target_ulong src1, return compute_c_sbbl(dst, src1, src2); case CC_OP_SHLB: + case CC_OP_CCMPB: return compute_c_shlb(dst, src1); case CC_OP_SHLW: + case CC_OP_CCMPW: return compute_c_shlw(dst, src1); case CC_OP_SHLL: + case CC_OP_CCMPL: return compute_c_shll(dst, src1); case CC_OP_BMILGB: @@ -395,6 +408,7 @@ target_ulong helper_cc_compute_c(target_ulong dst, target_ulong src1, case CC_OP_SBBQ: return compute_c_sbbq(dst, src1, src2); case CC_OP_SHLQ: + case CC_OP_CCMPQ: return compute_c_shlq(dst, src1); case CC_OP_BMILGQ: return compute_c_bmilgq(dst, src1); diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c index c35869ab500..e25e2abad75 100644 --- a/target/i386/tcg/translate.c +++ b/target/i386/tcg/translate.c @@ -286,6 +286,11 @@ enum { JCC_LE, }; +enum { + CCMP_T = JCC_P << 1, + CCMP_F, +}; + enum { USES_CC_DST = 1, USES_CC_SRC = 2, @@ -309,6 +314,7 @@ static const uint8_t cc_op_live_[] = { [CC_OP_SARB ... CC_OP_SARQ] = USES_CC_DST | USES_CC_SRC, [CC_OP_BMILGB ... CC_OP_BMILGQ] = USES_CC_DST | USES_CC_SRC, [CC_OP_BLSIB ... CC_OP_BLSIQ] = USES_CC_DST | USES_CC_SRC, + [CC_OP_CCMPB ... CC_OP_CCMPQ] = USES_CC_DST | USES_CC_SRC | USES_CC_SRC2, [CC_OP_ADCX] = USES_CC_DST | USES_CC_SRC, [CC_OP_ADOX] = USES_CC_SRC | USES_CC_SRC2, [CC_OP_ADCOX] = USES_CC_DST | USES_CC_SRC | USES_CC_SRC2, @@ -908,6 +914,7 @@ static CCPrepare gen_prepare_eflags_c(DisasContext *s, TCGv reg) .rhs_type = CC_PREPARE_DIRECT }; case CC_OP_SHLB ... CC_OP_SHLQ: + case CC_OP_CCMPB ... CC_OP_CCMPQ: /* (CC_SRC >> (DATA_BITS - 1)) & 1 */ size = cc_op_size(s->cc_op); return gen_prepare_sign_nz(cpu_cc_src, size); @@ -976,6 +983,8 @@ static CCPrepare gen_prepare_eflags_s(DisasContext *s, TCGv reg) .imm = CC_S }; case CC_OP_POPCNT: return (CCPrepare) { .cond = TCG_COND_NEVER }; + case CC_OP_CCMPB ... CC_OP_CCMPQ: + return gen_prepare_sign_nz(cpu_cc_src2, cc_op_size(s->cc_op)); default: return gen_prepare_sign_nz(cpu_cc_dst, cc_op_size(s->cc_op)); } @@ -995,6 +1004,20 @@ static CCPrepare gen_prepare_eflags_o(DisasContext *s, TCGv reg) return (CCPrepare) { .cond = TCG_COND_NEVER }; case CC_OP_MULB ... CC_OP_MULQ: return (CCPrepare) { .cond = TCG_COND_NE, .reg = cpu_cc_src }; + + case CC_OP_CCMPB ... CC_OP_CCMPQ: + if (!reg) { + reg = tcg_temp_new(); + } + /* + * Sum the carry-out vector and the value of the bit below the MSB; + * the XOR of the top two carry bits ends up in the sign bit. + */ + int size = cc_op_size(s->cc_op); + target_ulong adj = 1ull << ((8 << size) - 2); + tcg_gen_add_tl(reg, cpu_cc_src, tcg_constant_tl(adj)); + return gen_prepare_sign_nz(reg, size); + default: gen_compute_eflags(s); return (CCPrepare) { .cond = TCG_COND_TSTNE, .reg = cpu_cc_src, @@ -1077,6 +1100,52 @@ static CCPrepare gen_prepare_cc(DisasContext *s, int b, TCGv reg) } goto slow_jcc; + case CC_OP_CCMPB ... CC_OP_CCMPQ: + size = cc_op_size(s->cc_op); + switch (jcc_op) { + CCPrepare zf; + + case JCC_L: + case JCC_LE: + if (!reg) { + reg = tcg_temp_new(); + } + /* + * Sum the carry-out vector and the value of the bit below the MSB; + * the XOR of the top two carry bits ends up in the sign bit. + */ + size = s->cc_op - CC_OP_CCMPB; + target_ulong adj = 1ull << ((8 << size) - 2); + tcg_gen_add_tl(reg, cpu_cc_src, tcg_constant_tl(adj)); + /* Now XOR in SF too. */ + tcg_gen_xor_tl(reg, reg, cpu_cc_src2); + /* And possibly OR the zero flag... */ + if (jcc_op == JCC_LE) { + goto ccmp_or_zf; + } + cc = gen_prepare_sign_nz(reg, size); + break; + + case JCC_BE: + if (!reg) { + reg = tcg_temp_new(); + } + tcg_gen_mov_tl(reg, cpu_cc_src); + ccmp_or_zf: + /* OR ZF into CF: if CPU_CC_DST is zero, set reg to all ones. */ + zf = gen_prepare_val_nz(cpu_cc_dst, size, true); + if (zf.rhs_type != CC_PREPARE_REG) { + zf.reg2 = tcg_constant_tl(zf.imm); + } + tcg_gen_movcond_tl(zf.cond, reg, zf.reg, zf.reg2, tcg_constant_tl(-1), reg); + cc = gen_prepare_sign_nz(reg, size); + break; + + default: + goto slow_jcc; + } + break; + case CC_OP_LOGICB ... CC_OP_LOGICQ: /* Mostly used for test+jump */ size = s->cc_op - CC_OP_LOGICB; diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index b2e6dd3a488..e8fbf31e963 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -1583,7 +1583,7 @@ static void decode_63(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint static void decode_group1(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint8_t *b) { static const X86GenFunc group1_gen[8] = { - gen_ADD, gen_OR, gen_ADC, gen_SBB, gen_AND, gen_SUB, gen_XOR, gen_SUB, + gen_ADD, gen_OR, gen_ADC, gen_SBB, gen_AND, gen_SUB, gen_XOR, gen_CMP, }; int op = (get_modrm(s, env) >> 3) & 7; entry->gen = group1_gen[op]; @@ -1630,8 +1630,8 @@ static void decode_group3(DisasContext *s, CPUX86State *env, X86OpEntry *entry, { static const X86OpEntry opcodes_grp3[16] = { /* 0xf6 */ - [0x00] = X86_OP_ENTRYrr(AND, E,b, I,b), - [0x01] = X86_OP_ENTRYrr(AND, E,b, I,b), + [0x00] = X86_OP_ENTRYrr(TEST, E,b, I,b), + [0x01] = X86_OP_ENTRYrr(TEST, E,b, I,b), [0x02] = X86_OP_ENTRY1(NOT, E,b, lock), [0x03] = X86_OP_ENTRY1(NEG, E,b, lock), [0x04] = X86_OP_ENTRYrr(MUL, E,b, 0,b, zextT0), @@ -1640,8 +1640,8 @@ static void decode_group3(DisasContext *s, CPUX86State *env, X86OpEntry *entry, [0x07] = X86_OP_ENTRYr(IDIV, E,b), /* 0xf7 */ - [0x08] = X86_OP_ENTRYrr(AND, E,v, I,z), - [0x09] = X86_OP_ENTRYrr(AND, E,v, I,z), + [0x08] = X86_OP_ENTRYrr(TEST, E,v, I,z), + [0x09] = X86_OP_ENTRYrr(TEST, E,v, I,z), [0x0a] = X86_OP_ENTRY1(NOT, E,v, lock), [0x0b] = X86_OP_ENTRY1(NEG, E,v, lock), [0x0c] = X86_OP_ENTRYrr(MUL, E,v, 0,v, zextT0), @@ -1780,8 +1780,8 @@ static const X86OpEntry opcodes_root[256] = { [0x81] = X86_OP_GROUP2(group1, E,v, I,z), [0x82] = X86_OP_GROUP2(group1, E,b, I,b, chk(i64)), [0x83] = X86_OP_GROUP2(group1, E,v, I,b), - [0x84] = X86_OP_ENTRYrr(AND, E,b, G,b), - [0x85] = X86_OP_ENTRYrr(AND, E,v, G,v), + [0x84] = X86_OP_ENTRYrr(TEST, E,b, G,b), + [0x85] = X86_OP_ENTRYrr(TEST, E,v, G,v), [0x86] = X86_OP_ENTRY2(XCHG, E,b, G,b, xchg), [0x87] = X86_OP_ENTRY2(XCHG, E,v, G,v, xchg), @@ -1871,12 +1871,12 @@ static const X86OpEntry opcodes_root[256] = { [0x2E] = {}, [0x2F] = X86_OP_ENTRY0(DAS, chk(i64)), - [0x38] = X86_OP_ENTRYrr(SUB, E,b, G,b), - [0x39] = X86_OP_ENTRYrr(SUB, E,v, G,v), - [0x3A] = X86_OP_ENTRYrr(SUB, G,b, E,b), - [0x3B] = X86_OP_ENTRYrr(SUB, G,v, E,v), - [0x3C] = X86_OP_ENTRYrr(SUB, 0,b, I,b), /* AL, Ib */ - [0x3D] = X86_OP_ENTRYrr(SUB, 0,v, I,z), /* rAX, Iz */ + [0x38] = X86_OP_ENTRYrr(CMP, E,b, G,b), + [0x39] = X86_OP_ENTRYrr(CMP, E,v, G,v), + [0x3A] = X86_OP_ENTRYrr(CMP, G,b, E,b), + [0x3B] = X86_OP_ENTRYrr(CMP, G,v, E,v), + [0x3C] = X86_OP_ENTRYrr(CMP, 0,b, I,b), /* AL, Ib */ + [0x3D] = X86_OP_ENTRYrr(CMP, 0,v, I,z), /* rAX, Iz */ [0x3E] = {}, [0x3F] = X86_OP_ENTRY0(AAS, chk(i64)), @@ -1935,8 +1935,8 @@ static const X86OpEntry opcodes_root[256] = { [0x9E] = X86_OP_ENTRY0(SAHF), [0x9F] = X86_OP_ENTRY0(LAHF), - [0xA8] = X86_OP_ENTRYrr(AND, 0,b, I,b), /* AL, Ib */ - [0xA9] = X86_OP_ENTRYrr(AND, 0,v, I,z), /* rAX, Iz */ + [0xA8] = X86_OP_ENTRYrr(TEST, 0,b, I,b), /* AL, Ib */ + [0xA9] = X86_OP_ENTRYrr(TEST, 0,v, I,z), /* rAX, Iz */ [0xAA] = X86_OP_ENTRYwr(STOS, Y,b, 0,b), [0xAB] = X86_OP_ENTRYwr(STOS, Y,v, 0,v), /* Manual writeback because REP LODS (!) has to write EAX/RAX after every LODS. */ diff --git a/target/i386/tcg/emit.c.inc b/target/i386/tcg/emit.c.inc index 5a880b9760e..ba347c4e6b7 100644 --- a/target/i386/tcg/emit.c.inc +++ b/target/i386/tcg/emit.c.inc @@ -1638,6 +1638,117 @@ static void gen_CMOVcc(DisasContext *s, X86DecodedInsn *decode) gen_cmovcc(s, decode->b & 0xf, s->T0, s->T1); } +/* Convert the repurposed V bits of CCMP or CTEST instructions to + * an EFLAGS value. + */ +static inline uint32_t evex_to_eflags(DisasContext *s) +{ + uint16_t eflags = 0; + eflags |= (s->evex3 & 0x08 ? CC_C | CC_P : 0); + eflags |= (s->evex3 & 0x10 ? CC_Z : 0); + eflags |= (s->evex3 & 0x20 ? CC_S : 0); + eflags |= (s->evex3 & 0x40 ? CC_O : 0); + return eflags; +} + +/* If the condition COND is false, fill CC_DST/SRC/SRC2 with values that + * produce the flag values in DFV for CC_OP_CCMP*. If instead COND is true, + * set up the flag values from CC_DST and COUT. + * + * On input, CC_DST contains the result of the subtraction or AND, which + * ends up in CC_DST and CC_SRC2 as well. COUT is non-NULL for CCMP, and will + * be used for the output CC_SRC if COND is true; it is NULL for CTEST and + * will result in CC_SRC==0 (resulting in CF=AF=OF=0) is COND is true. + */ +static void gen_dfv_movcond(DisasContext *s, X86DecodedInsn *decode, int cond, uint32_t dfv, + MemOp ot, TCGv cout) +{ + target_ulong dst, src, src2; + CCPrepare cc = gen_prepare_cc(s, cond, NULL); + + /* ZF from dst */ + dst = (dfv & CC_Z) ? 0 : -1; + + /* CF is the high bit of SRC, OF the XOR of the high two bits */ + src = deposit64(0, (8 << ot) - 2, 1, !!(dfv & CC_O)); + src ^= (dfv & CC_C) ? -1 : 0; + + /* + * SF is the high bit of SRC2, PF the parity of the low byte. But also + * ensure the value is nonzero if dfv requests zf=0. This triggers the + * optimization below in some extra cases (e.g. dfv == CC_P | CC_C). + * + * Note dst is either 0 or -1 so it has even parity. + */ + src2 = dst & 3; + src2 ^= (dfv & CC_P) ? 0 : 1; + src2 ^= (dfv & CC_S) ? -1 : 0; + + if (cout) { + decode->cc_src = cout; + tcg_gen_movcond_tl(cc.cond, decode->cc_src, cc.reg, cc.reg2, + decode->cc_src, tcg_constant_tl(src)); + } else if (src) { + decode->cc_src = tcg_temp_new(); + tcg_gen_movcond_tl(cc.cond, decode->cc_src, cc.reg, cc.reg2, + tcg_constant_tl(0), tcg_constant_tl(src)); + } else { + decode->cc_src = tcg_constant_tl(0); + } + + if (!!dst == !!src2) { + /* + * When an actual CMP or TEST result is stored, DST will be + * equal to SRC2, and when the default flag value is stored, + * only zero/nonzero matters for DST. If DST and SRC2 + * are equally zero/non-zero, use the same value for both. + */ + decode->cc_src2 = decode->cc_dst; + dst = src2; + } else { + decode->cc_src2 = tcg_temp_new(); + tcg_gen_movcond_tl(cc.cond, decode->cc_src2, cc.reg, cc.reg2, + decode->cc_dst, tcg_constant_tl(src2)); + } + tcg_gen_movcond_tl(cc.cond, decode->cc_dst, cc.reg, cc.reg2, + decode->cc_dst, tcg_constant_tl(dst)); +} + +static void gen_SUB(DisasContext *s, X86DecodedInsn *decode); +static void gen_CMP(DisasContext *s, X86DecodedInsn *decode) +{ + int cond = (s->prefix & PREFIX_EVEX) ? s->evex4 & 0x0f : CCMP_T; + uint32_t dfv = evex_to_eflags(s); + MemOp ot = decode->op[1].ot; + TCGv cout; + + switch (cond) { + case CCMP_T: + gen_SUB(s, decode); + return; + case CCMP_F: + decode->cc_op = CC_OP_EFLAGS; + decode->cc_src = tcg_constant_tl(dfv); + return; + default: + break; + } + + decode->cc_op = CC_OP_CCMPB + ot; + decode->cc_dst = tcg_temp_new(); + cout = tcg_temp_new(); + tcg_gen_sub_tl(decode->cc_dst, s->T0, s->T1); + + /* Compute carry-out vector of subtraction. */ + tcg_gen_xor_tl(cout, s->T1, decode->cc_dst); + tcg_gen_xor_tl(s->T0, s->T0, s->T1); + tcg_gen_and_tl(cout, cout, s->T0); + tcg_gen_xor_tl(cout, cout, decode->cc_dst); + + /* src2 == dst if cond is true. */ + gen_dfv_movcond(s, decode, cond, dfv, ot, cout); +} + static void gen_CMPccXADD(DisasContext *s, X86DecodedInsn *decode) { TCGLabel *label_top = gen_new_label(); @@ -3854,7 +3965,6 @@ static void gen_SARX(DisasContext *s, X86DecodedInsn *decode) tcg_gen_sar_tl(s->T0, s->T0, s->T1); } -static void gen_SUB(DisasContext *s, X86DecodedInsn *decode); static void gen_SBB(DisasContext *s, X86DecodedInsn *decode) { MemOp ot = decode->op[0].ot; @@ -4169,6 +4279,32 @@ static void gen_SYSRET(DisasContext *s, X86DecodedInsn *decode) s->base.is_jmp = DISAS_EOB_RECHECK_TF; } +static void gen_TEST(DisasContext *s, X86DecodedInsn *decode) +{ + int cond = (s->prefix & PREFIX_EVEX) ? s->evex4 & 0x0f : CCMP_T; + uint32_t dfv = evex_to_eflags(s); + MemOp ot = decode->op[1].ot; + + switch (cond) { + case CCMP_T: + gen_AND(s, decode); + return; + case CCMP_F: + decode->cc_op = CC_OP_EFLAGS; + decode->cc_src = tcg_constant_tl(dfv); + return; + default: + break; + } + + decode->cc_op = CC_OP_CCMPB + ot; + decode->cc_dst = tcg_temp_new(); + tcg_gen_and_tl(decode->cc_dst, s->T0, s->T1); + + /* src=0, src2 == dst if cond is true. */ + gen_dfv_movcond(s, decode, cond, dfv, ot, NULL); +} + static void gen_TZCNT(DisasContext *s, X86DecodedInsn *decode) { MemOp ot = decode->op[0].ot; -- 2.55.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 15/17] target/i386/tcg: decode APX instructions 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini ` (13 preceding siblings ...) 2026-09-23 17:30 ` [PATCH 14/17] target/i386/tcg: implement CCMP/CTEST Paolo Bonzini @ 2026-09-23 17:30 ` Paolo Bonzini 2026-09-23 17:30 ` [PATCH 16/17] target/i386: extract computation of no-autoenable features Paolo Bonzini ` (2 subsequent siblings) 17 siblings, 0 replies; 35+ messages in thread From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw) To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu The bulk of the APX implementation, comprising new map4-specific encodings, extensions to legacy root and 0F tables, and the implementation of new instructions CFCMOV, PUSH2 and POP2. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/helper.h | 1 + target/i386/tcg/decode-new.h | 1 + target/i386/tcg/excp_helper.c | 5 + target/i386/tcg/decode-new.c.inc | 332 +++++++++++++++++++++++++------ target/i386/tcg/emit.c.inc | 62 ++++++ 5 files changed, 344 insertions(+), 57 deletions(-) diff --git a/target/i386/helper.h b/target/i386/helper.h index 3f67098f11f..99cbbacadfc 100644 --- a/target/i386/helper.h +++ b/target/i386/helper.h @@ -57,6 +57,7 @@ DEF_HELPER_2(sysret, void, env, int) DEF_HELPER_FLAGS_1(pause, TCG_CALL_NO_WG, noreturn, env) DEF_HELPER_FLAGS_3(raise_interrupt, TCG_CALL_NO_WG, noreturn, env, int, int) DEF_HELPER_FLAGS_2(raise_exception, TCG_CALL_NO_WG, noreturn, env, int) +DEF_HELPER_FLAGS_1(raise_gpf, TCG_CALL_NO_WG, noreturn, env) DEF_HELPER_FLAGS_1(icebp, TCG_CALL_NO_WG, noreturn, env) DEF_HELPER_3(boundw, void, env, tl, int) DEF_HELPER_3(boundl, void, env, tl, int) diff --git a/target/i386/tcg/decode-new.h b/target/i386/tcg/decode-new.h index ec65ae5d75b..c7237393e1f 100644 --- a/target/i386/tcg/decode-new.h +++ b/target/i386/tcg/decode-new.h @@ -53,6 +53,7 @@ typedef enum X86OpType { X86_TYPE_nop, /* modrm operand decoded but not loaded into s->T{0,1} */ X86_TYPE_2op, /* 2-operand RMW instruction */ X86_TYPE_LoBits, /* encoded in bits 0-2 of the operand + REX.B */ + X86_TYPE_ZERO, /* Constant zero, for CFCMOV */ X86_TYPE_0, /* Hard-coded GPRs (RAX..RDI) */ X86_TYPE_1, X86_TYPE_2, diff --git a/target/i386/tcg/excp_helper.c b/target/i386/tcg/excp_helper.c index 1eaa602c91d..d6410d6fc11 100644 --- a/target/i386/tcg/excp_helper.c +++ b/target/i386/tcg/excp_helper.c @@ -37,6 +37,11 @@ G_NORETURN void helper_raise_exception(CPUX86State *env, int exception_index) raise_exception(env, exception_index); } +G_NORETURN void helper_raise_gpf(CPUX86State *env) +{ + raise_exception_err_ra(env, EXCP0D_GPF, 0, GETPC()); +} + /* * Check nested exceptions and change to double or triple fault if * needed. It should only be called, if this is not an interrupt. diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index e8fbf31e963..453242b4a3b 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -184,6 +184,8 @@ X86_OP_GROUP3(op, op0, s0, None, None, None, None, ## __VA_ARGS__) #define X86_OP_GROUPwr(op, op0, s0, op1, s1, ...) \ X86_OP_GROUP3(op, op0, s0, op1, s1, None, None, ## __VA_ARGS__) +#define X86_OP_GROUPrr(op, op0, s0, op1, s1, ...) \ + X86_OP_GROUP3(op, None, None, op0, s0, op1, s1, ## __VA_ARGS__) #define X86_OP_GROUP0(op, ...) \ X86_OP_GROUP3(op, None, None, None, None, None, None, ## __VA_ARGS__) @@ -280,8 +282,10 @@ #define p_f3 .valid_prefix = P_F3, #define p_f2 .valid_prefix = P_F2, #define p_00_66 .valid_prefix = P_00 | P_66, +#define p_00_f2 .valid_prefix = P_00 | P_F2, #define p_00_f3 .valid_prefix = P_00 | P_F3, #define p_66_f2 .valid_prefix = P_66 | P_F2, +#define p_66_f3 .valid_prefix = P_66 | P_F3, #define p_00_66_f3 .valid_prefix = P_00 | P_66 | P_F3, #define p_66_f3_f2 .valid_prefix = P_66 | P_F3 | P_F2, #define p_00_66_f3_f2 .valid_prefix = P_00 | P_66 | P_F3 | P_F2, @@ -869,28 +873,33 @@ static const X86OpEntry opcodes_0F38_00toEF[240] = { /* * REG selects srcdest2 operand, VEX.vvvv selects src3. VEX class not found - * in manual, assumed to be 13 from the VEX.L0 constraint. + * in manual, assumed to be 13 from the VEX.L0 constraint; EVEX-APX-BMI and + * EVEX-APX-CMPccXADD are also pretty much the same; the difference are + * reflected in chk(nf0) and in the M operand type. */ - [0xe0] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk(o64) cpuid(CMPCCXADD) p_66), - [0xe1] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk(o64) cpuid(CMPCCXADD) p_66), - [0xe2] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk(o64) cpuid(CMPCCXADD) p_66), - [0xe3] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk(o64) cpuid(CMPCCXADD) p_66), - [0xe4] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk(o64) cpuid(CMPCCXADD) p_66), - [0xe5] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk(o64) cpuid(CMPCCXADD) p_66), - [0xe6] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk(o64) cpuid(CMPCCXADD) p_66), - [0xe7] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk(o64) cpuid(CMPCCXADD) p_66), + [0xe0] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk2(nf0, o64) cpuid(CMPCCXADD) p_66), + [0xe1] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk2(nf0, o64) cpuid(CMPCCXADD) p_66), + [0xe2] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk2(nf0, o64) cpuid(CMPCCXADD) p_66), + [0xe3] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk2(nf0, o64) cpuid(CMPCCXADD) p_66), + [0xe4] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk2(nf0, o64) cpuid(CMPCCXADD) p_66), + [0xe5] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk2(nf0, o64) cpuid(CMPCCXADD) p_66), + [0xe6] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk2(nf0, o64) cpuid(CMPCCXADD) p_66), + [0xe7] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk2(nf0, o64) cpuid(CMPCCXADD) p_66), - [0xe8] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk(o64) cpuid(CMPCCXADD) p_66), - [0xe9] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk(o64) cpuid(CMPCCXADD) p_66), - [0xea] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk(o64) cpuid(CMPCCXADD) p_66), - [0xeb] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk(o64) cpuid(CMPCCXADD) p_66), - [0xec] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk(o64) cpuid(CMPCCXADD) p_66), - [0xed] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk(o64) cpuid(CMPCCXADD) p_66), - [0xee] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk(o64) cpuid(CMPCCXADD) p_66), - [0xef] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk(o64) cpuid(CMPCCXADD) p_66), + [0xe8] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk2(nf0, o64) cpuid(CMPCCXADD) p_66), + [0xe9] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk2(nf0, o64) cpuid(CMPCCXADD) p_66), + [0xea] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk2(nf0, o64) cpuid(CMPCCXADD) p_66), + [0xeb] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk2(nf0, o64) cpuid(CMPCCXADD) p_66), + [0xec] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk2(nf0, o64) cpuid(CMPCCXADD) p_66), + [0xed] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk2(nf0, o64) cpuid(CMPCCXADD) p_66), + [0xee] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk2(nf0, o64) cpuid(CMPCCXADD) p_66), + [0xef] = X86_OP_ENTRY3(CMPccXADD, M,y, G,y, B,y, vex13 xchg chk2(nf0, o64) cpuid(CMPCCXADD) p_66), }; -/* four rows for no prefix, 66, F3, F2 (including 66+F2 operand size override) */ +/* + * four rows for no prefix, 66, F3, F2 (including 66+F2 operand size override). + * all VEX13 instructions extend to APX, while MOVBE/CRC32 move to EVEX map4 + */ static const X86OpEntry opcodes_0F38_F0toFF[16][4] = { /* * MOVBE and CRC32 are incorrectly listed as always doing 32-bit operation @@ -923,20 +932,20 @@ static const X86OpEntry opcodes_0F38_F0toFF[16][4] = { [5] = { X86_OP_ENTRY3(BZHI, G,y, E,y, B,y, vex13 cpuid(BMI1)), {}, - X86_OP_ENTRY3(PEXT, G,y, B,y, E,y, vex13 zextT0 cpuid(BMI2)), - X86_OP_ENTRY3(PDEP, G,y, B,y, E,y, vex13 zextT0 cpuid(BMI2)), + X86_OP_ENTRY3(PEXT, G,y, B,y, E,y, vex13 zextT0 chk(nf0) cpuid(BMI2)), + X86_OP_ENTRY3(PDEP, G,y, B,y, E,y, vex13 zextT0 chk(nf0) cpuid(BMI2)), }, [6] = { {}, X86_OP_ENTRY2(ADCX, G,y, E,y, cpuid(ADX)), X86_OP_ENTRY2(ADOX, G,y, E,y, cpuid(ADX)), - X86_OP_ENTRY3(MULX, /* B,y, */ G,y, E,y, 2,y, vex13 cpuid(BMI2)), + X86_OP_ENTRY3(MULX, /* B,y, */ G,y, E,y, 2,y, vex13 chk(nf0) cpuid(BMI2)), }, [7] = { X86_OP_ENTRY3(BEXTR, G,y, E,y, B,y, vex13 zextT0 cpuid(BMI1)), - X86_OP_ENTRY3(SHLX, G,y, E,y, B,y, vex13 cpuid(BMI1)), - X86_OP_ENTRY3(SARX, G,y, E,y, B,y, vex13 sextT0 cpuid(BMI1)), - X86_OP_ENTRY3(SHRX, G,y, E,y, B,y, vex13 zextT0 cpuid(BMI1)), + X86_OP_ENTRY3(SHLX, G,y, E,y, B,y, vex13 chk(nf0) cpuid(BMI1)), + X86_OP_ENTRY3(SARX, G,y, E,y, B,y, vex13 chk(nf0) sextT0 cpuid(BMI1)), + X86_OP_ENTRY3(SHRX, G,y, E,y, B,y, vex13 chk(nf0) zextT0 cpuid(BMI1)), }, }; @@ -1025,7 +1034,7 @@ static const X86OpEntry opcodes_0F3A[256] = { [0xdf] = X86_OP_ENTRY3(VAESKEYGEN, V,dq, W,dq, I,b, vex4 cpuid(AES) p_66), - [0xF0] = X86_OP_ENTRY3(RORX, G,y, E,y, I,b, vex13 cpuid(BMI2) p_f2), + [0xF0] = X86_OP_ENTRY3(RORX, G,y, E,y, I,b, vex13 chk(nf0) cpuid(BMI2) p_f2), }; static void decode_0F3A(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint8_t *b) @@ -1590,9 +1599,9 @@ static void decode_group1(DisasContext *s, CPUX86State *env, X86OpEntry *entry, if (op == 7) { /* prevent writeback for CMP */ - entry->op1 = entry->op0; entry->op0 = X86_TYPE_None; entry->s0 = X86_SIZE_None; + entry->vex_class = X86_EVEX_APX_cmp; } else { entry->special = X86_SPECIAL_HasLock; } @@ -1619,6 +1628,9 @@ static void decode_group2(DisasContext *s, CPUX86State *env, X86OpEntry *entry, }; int op = (get_modrm(s, env) >> 3) & 7; entry->gen = group2_gen[op]; + if (op == 2 || op == 3) { + entry->check |= X86_CHECK_nf0; + } if (op == 7) { entry->ext1 = X86_EXT_Signed; } else { @@ -1630,24 +1642,24 @@ static void decode_group3(DisasContext *s, CPUX86State *env, X86OpEntry *entry, { static const X86OpEntry opcodes_grp3[16] = { /* 0xf6 */ - [0x00] = X86_OP_ENTRYrr(TEST, E,b, I,b), - [0x01] = X86_OP_ENTRYrr(TEST, E,b, I,b), - [0x02] = X86_OP_ENTRY1(NOT, E,b, lock), - [0x03] = X86_OP_ENTRY1(NEG, E,b, lock), - [0x04] = X86_OP_ENTRYrr(MUL, E,b, 0,b, zextT0), - [0x05] = X86_OP_ENTRYrr(IMUL,E,b, 0,b, sextT0), - [0x06] = X86_OP_ENTRYr(DIV, E,b), - [0x07] = X86_OP_ENTRYr(IDIV, E,b), + [0x00] = X86_OP_ENTRYrr(TEST, E,b, I,b, evex_apx_cmp p_00), + [0x01] = X86_OP_ENTRYrr(TEST, E,b, I,b, evex_apx_cmp p_00), + [0x02] = X86_OP_ENTRYwr(NOT, B,b, E,b, evex_apx p_00 chk(nf0) lock), + [0x03] = X86_OP_ENTRYwr(NEG, B,b, E,b, evex_apx p_00 lock), + [0x04] = X86_OP_ENTRYrr(MUL, E,b, 0,b, evex_apx p_00 zextT0), + [0x05] = X86_OP_ENTRYrr(IMUL, E,b, 0,b, evex_apx p_00 sextT0), + [0x06] = X86_OP_ENTRYr(DIV, E,b, evex_apx p_00), + [0x07] = X86_OP_ENTRYr(IDIV, E,b, evex_apx p_00), /* 0xf7 */ - [0x08] = X86_OP_ENTRYrr(TEST, E,v, I,z), - [0x09] = X86_OP_ENTRYrr(TEST, E,v, I,z), - [0x0a] = X86_OP_ENTRY1(NOT, E,v, lock), - [0x0b] = X86_OP_ENTRY1(NEG, E,v, lock), - [0x0c] = X86_OP_ENTRYrr(MUL, E,v, 0,v, zextT0), - [0x0d] = X86_OP_ENTRYrr(IMUL,E,v, 0,v, sextT0), - [0x0e] = X86_OP_ENTRYr(DIV, E,v), - [0x0f] = X86_OP_ENTRYr(IDIV, E,v), + [0x08] = X86_OP_ENTRYrr(TEST, E,v, I,z, evex_apx_cmp p_00_66), + [0x09] = X86_OP_ENTRYrr(TEST, E,v, I,z, evex_apx_cmp p_00_66), + [0x0a] = X86_OP_ENTRYwr(NOT, B,v, E,v, evex_apx p_00_66 chk(nf0) lock), + [0x0b] = X86_OP_ENTRYwr(NEG, B,v, E,v, evex_apx p_00_66 lock), + [0x0c] = X86_OP_ENTRYrr(MUL, E,v, 0,v, evex_apx p_00_66 zextT0), + [0x0d] = X86_OP_ENTRYrr(IMUL, E,v, 0,v, evex_apx p_00_66 sextT0), + [0x0e] = X86_OP_ENTRYr(DIV, E,v, evex_apx p_00_66), + [0x0f] = X86_OP_ENTRYr(IDIV, E,v, evex_apx p_00_66), }; int w = (*b & 1); @@ -1660,12 +1672,12 @@ static void decode_group4_5(DisasContext *s, CPUX86State *env, X86OpEntry *entry { static const X86OpEntry opcodes_grp4_5[16] = { /* 0xfe */ - [0x00] = X86_OP_ENTRY1(INC, E,b, lock), - [0x01] = X86_OP_ENTRY1(DEC, E,b, lock), + [0x00] = X86_OP_ENTRYwr(INC, B,b, E,b, evex_apx p_00 lock), + [0x01] = X86_OP_ENTRYwr(DEC, B,b, E,b, evex_apx p_00 lock), /* 0xff */ - [0x08] = X86_OP_ENTRY1(INC, E,v, lock), - [0x09] = X86_OP_ENTRY1(DEC, E,v, lock), + [0x08] = X86_OP_ENTRYwr(INC, B,v, E,v, evex_apx p_00_66 lock), + [0x09] = X86_OP_ENTRYwr(DEC, B,v, E,v, evex_apx p_00_66 lock), [0x0a] = X86_OP_ENTRYr(CALL_m, E,f64, zextT0), [0x0b] = X86_OP_ENTRYr(CALLF_m, M,p), [0x0c] = X86_OP_ENTRYr(JMP_m, E,f64, zextT0), @@ -1776,10 +1788,11 @@ static const X86OpEntry opcodes_root[256] = { [0x76] = X86_OP_ENTRYr(Jcc, J,b), [0x77] = X86_OP_ENTRYr(Jcc, J,b), - [0x80] = X86_OP_GROUP2(group1, E,b, I,b), - [0x81] = X86_OP_GROUP2(group1, E,v, I,z), - [0x82] = X86_OP_GROUP2(group1, E,b, I,b, chk(i64)), - [0x83] = X86_OP_GROUP2(group1, E,v, I,b), + /* decode_group1 is shared with EVEX map4. */ + [0x80] = X86_OP_GROUP3(group1, B,b, E,b, I,b), + [0x81] = X86_OP_GROUP3(group1, B,v, E,v, I,z), + [0x82] = X86_OP_GROUP3(group1, B,b, E,b, I,b, chk(i64)), + [0x83] = X86_OP_GROUP3(group1, B,v, E,v, I,b), [0x84] = X86_OP_ENTRYrr(TEST, E,b, G,b), [0x85] = X86_OP_ENTRYrr(TEST, E,v, G,v), [0x86] = X86_OP_ENTRY2(XCHG, E,b, G,b, xchg), @@ -1812,8 +1825,9 @@ static const X86OpEntry opcodes_root[256] = { [0xB6] = X86_OP_ENTRY3(MOV, LoBits,b, I,b, None, None), [0xB7] = X86_OP_ENTRY3(MOV, LoBits,b, I,b, None, None), - [0xC0] = X86_OP_GROUP2(group2, E,b, I,b), - [0xC1] = X86_OP_GROUP2(group2, E,v, I,b), + /* decode_group2 is shared with EVEX map4. */ + [0xC0] = X86_OP_GROUP3(group2, B,b, E,b, I,b), + [0xC1] = X86_OP_GROUP3(group2, B,v, E,v, I,b), [0xC2] = X86_OP_ENTRYr(RET, I,w), [0xC3] = X86_OP_ENTRY0(RET), [0xC4] = X86_OP_ENTRY3(LES, G,z, EM,p, None, None, chk(i64)), @@ -1821,10 +1835,11 @@ static const X86OpEntry opcodes_root[256] = { [0xC6] = X86_OP_GROUP3(group11, E,b, I,b, None, None), /* reg=000b */ [0xC7] = X86_OP_GROUP3(group11, E,v, I,z, None, None), /* reg=000b */ - [0xD0] = X86_OP_GROUP1(group2, E,b), - [0xD1] = X86_OP_GROUP1(group2, E,v), - [0xD2] = X86_OP_GROUP2(group2, E,b, 1,b), /* CL */ - [0xD3] = X86_OP_GROUP2(group2, E,v, 1,b), /* CL */ + /* decode_group2 is shared with EVEX map4. */ + [0xD0] = X86_OP_GROUPwr(group2, B,b, E,b), + [0xD1] = X86_OP_GROUPwr(group2, B,v, E,v), + [0xD2] = X86_OP_GROUP3(group2, B,b, E,b, 1,b), /* CL */ + [0xD3] = X86_OP_GROUP3(group2, B,v, E,v, 1,b), /* CL */ [0xD4] = X86_OP_ENTRY2(AAM, 0,w, I,b, chk(i64)), [0xD5] = X86_OP_ENTRY2(AAD, 0,w, I,b, chk(i64)), [0xD6] = X86_OP_ENTRYw(SALC, 0,b, chk(i64)), @@ -2054,9 +2069,203 @@ static void decode_REX2_map1(DisasContext *s, CPUX86State *env, X86OpEntry *entr decode_REX2(s, env, entry, b, opcode_rex2_map1); } +static void decode_EVEX4cc(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint8_t *b) +{ + uint8_t modrm = get_modrm(s, env); + int mod = (modrm >> 6) & 3; + + static const X86OpEntry setcc = + X86_OP_ENTRYw(SETcc, E,b, evex_apx_zu chk(nf0) p_f2); + + /* + * All EVEX-promoted register-destination conditional moves (all CFCMOV + * cases except "CFCMOV rm, r", and three-operand CMOV) have zero-upper + * Note that this is specific to CMOV/CFCMOV, and not ZU! + * + * For NDD=0, NF represents the direction. For NDD=1, NF represents fault + * suppression. + */ + static const X86OpEntry cfcmov_nd0[2][2] = { + { /* NF=0 */ + X86_OP_ENTRY3(CFCMOVcc_ld, G,v, ZERO,v, M,v, p_00_66 evex_apx zextwT0), + X86_OP_ENTRY3(CMOVcc, G,v, ZERO,v, E,v, p_00_66 evex_apx zextwT0), + }, + { /* NF=1 */ + X86_OP_ENTRYwr(CFCMOVcc_st, M,v, G,v, p_00_66 evex_apx), + X86_OP_ENTRY3(CMOVcc, E,v, ZERO,v, G,v, p_00_66 evex_apx zextwT0), + }, + }; + static const X86OpEntry cfcmov_nd1[2][2] = { + { /* NF=0 */ + X86_OP_ENTRY3(CMOVcc, B,v, G,v, E,v, p_00_66 evex_apx zextwT0), + X86_OP_ENTRY3(CMOVcc, B,v, G,v, E,v, p_00_66 evex_apx zextwT0), + }, + { /* NF=1 */ + X86_OP_ENTRY3(CFCMOVcc_ld, B,v, G,v, M,v, p_00_66 evex_apx zextwT0), + X86_OP_ENTRY3(CMOVcc, B,v, G,v, E,v, p_00_66 evex_apx zextwT0), + }, + }; + + if (s->prefix & PREFIX_REPNZ) { + *entry = setcc; + if (EVEX_APX_ND(s)) { + entry->s1 = X86_SIZE_q; /* optimization for zu */ + } + } else { + *entry = (EVEX_APX_ND(s) ? cfcmov_nd1 : cfcmov_nd0)[EVEX_APX_NF(s)][mod == 3]; + } +} + +static void decode_EVEX4_MOVBE(DisasContext *s, CPUX86State *env, + X86OpEntry *entry, uint8_t *b) +{ + entry->gen = gen_MOVBE; + if ((get_modrm(s, env) >> 6) != 3) { + /* skip the load/store to be able to use MO_BE in code generation. */ + if (entry->op0 == X86_TYPE_E) { + entry->op0 = X86_TYPE_M; + } else { + entry->op1 = X86_TYPE_M; + } + } +} + +static void decode_EVEX4_66(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint8_t *b) +{ + entry->gen = (s->prefix & PREFIX_DATA) ? gen_ADCX : gen_ADOX; +} + +static void decode_EVEX4_8F(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint8_t *b) +{ + int op = (get_modrm(s, env) >> 3) & 7; + if (op == 0) { + entry->gen = gen_POP2; + } else { + *entry = UNKNOWN_OPCODE; + } +} + +static void decode_EVEX4_FF(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint8_t *b) +{ + static const X86OpEntry push2 = + X86_OP_ENTRYrr(PUSH2, B,d64, R,d64, evex_apx_pp2 p_00); + + int op = (get_modrm(s, env) >> 3) & 7; + if (op == 6) { + *entry = push2; + } else { + decode_group4_5(s, env, entry, b); + } +} + +/* + * Missing: ENQCMD, ENQCMDS, INVEPT, INVPCID, INVVPID, + * KMOVD, KMOVW, KMOVD, KMOVQ, MOVDIR64B, MOVDIRI, URDMSR, + * UWRMSR, WRSSD, WRSSQ, WRUSSD, WRUSSQ, LDTILECFG, STTILECFG, + * TILELOADD, TILELOADDDT1, TILESTORED. + */ +static const X86OpEntry opcodes_EVEX_map4[256] = { + [0x00] = X86_OP_ENTRY3(ADD, B,b, E,b, G,b, evex_apx p_00 lock), + [0x01] = X86_OP_ENTRY3(ADD, B,v, E,v, G,v, evex_apx p_00_66 lock), + [0x02] = X86_OP_ENTRY3(ADD, B,b, G,b, E,b, evex_apx p_00 lock), + [0x03] = X86_OP_ENTRY3(ADD, B,v, G,v, E,v, evex_apx p_00_66 lock), + [0x08] = X86_OP_ENTRY3(OR, B,b, E,b, G,b, evex_apx p_00 lock), + [0x09] = X86_OP_ENTRY3(OR, B,v, E,v, G,v, evex_apx p_00_66 lock), + [0x0A] = X86_OP_ENTRY3(OR, B,b, G,b, E,b, evex_apx p_00 lock), + [0x0B] = X86_OP_ENTRY3(OR, B,v, G,v, E,v, evex_apx p_00_66 lock), + + [0x10] = X86_OP_ENTRY3(ADC, B,b, E,b, G,b, evex_apx p_00 chk(nf0) lock), + [0x11] = X86_OP_ENTRY3(ADC, B,v, E,v, G,v, evex_apx p_00_66 chk(nf0) lock), + [0x12] = X86_OP_ENTRY3(ADC, B,b, G,b, E,b, evex_apx p_00 chk(nf0) lock), + [0x13] = X86_OP_ENTRY3(ADC, B,v, G,v, E,v, evex_apx p_00_66 chk(nf0) lock), + [0x18] = X86_OP_ENTRY3(SBB, B,b, E,b, G,b, evex_apx p_00 chk(nf0) lock), + [0x19] = X86_OP_ENTRY3(SBB, B,v, E,v, G,v, evex_apx p_00_66 chk(nf0) lock), + [0x1A] = X86_OP_ENTRY3(SBB, B,b, G,b, E,b, evex_apx p_00 chk(nf0) lock), + [0x1B] = X86_OP_ENTRY3(SBB, B,v, G,v, E,v, evex_apx p_00_66 chk(nf0) lock), + + [0x20] = X86_OP_ENTRY3(AND, B,b, E,b, G,b, evex_apx p_00 lock), + [0x21] = X86_OP_ENTRY3(AND, B,v, E,v, G,v, evex_apx p_00_66 lock), + [0x22] = X86_OP_ENTRY3(AND, B,b, G,b, E,b, evex_apx p_00 lock), + [0x23] = X86_OP_ENTRY3(AND, B,v, G,v, E,v, evex_apx p_00_66 lock), + [0x24] = X86_OP_ENTRY4(SHLD, B,v, E,v, G,v, evex_apx p_00_66), + [0x28] = X86_OP_ENTRY3(SUB, B,b, E,b, G,b, evex_apx p_00 lock), + [0x29] = X86_OP_ENTRY3(SUB, B,v, E,v, G,v, evex_apx p_00_66 lock), + [0x2A] = X86_OP_ENTRY3(SUB, B,b, G,b, E,b, evex_apx p_00 lock), + [0x2B] = X86_OP_ENTRY3(SUB, B,v, G,v, E,v, evex_apx p_00_66 lock), + [0x2C] = X86_OP_ENTRY4(SHRD, B,v, E,v, G,v, evex_apx p_00_66), + + [0x30] = X86_OP_ENTRY3(XOR, B,b, E,b, G,b, evex_apx p_00 lock), + [0x31] = X86_OP_ENTRY3(XOR, B,v, E,v, G,v, evex_apx p_00_66 lock), + [0x32] = X86_OP_ENTRY3(XOR, B,b, G,b, E,b, evex_apx p_00 lock), + [0x33] = X86_OP_ENTRY3(XOR, B,v, G,v, E,v, evex_apx p_00_66 lock), + [0x38] = X86_OP_ENTRYrr(CMP, E,b, G,b, evex_apx_cmp p_00), + [0x39] = X86_OP_ENTRYrr(CMP, E,v, G,v, evex_apx_cmp p_00_66), + [0x3A] = X86_OP_ENTRYrr(CMP, G,b, E,b, evex_apx_cmp p_00), + [0x3B] = X86_OP_ENTRYrr(CMP, G,v, E,v, evex_apx_cmp p_00_66), + + [0x40] = X86_OP_GROUP0(EVEX4cc), + [0x41] = X86_OP_GROUP0(EVEX4cc), + [0x42] = X86_OP_GROUP0(EVEX4cc), + [0x43] = X86_OP_GROUP0(EVEX4cc), + [0x44] = X86_OP_GROUP0(EVEX4cc), + [0x45] = X86_OP_GROUP0(EVEX4cc), + [0x46] = X86_OP_GROUP0(EVEX4cc), + [0x47] = X86_OP_GROUP0(EVEX4cc), + [0x48] = X86_OP_GROUP0(EVEX4cc), + [0x49] = X86_OP_GROUP0(EVEX4cc), + [0x4A] = X86_OP_GROUP0(EVEX4cc), + [0x4B] = X86_OP_GROUP0(EVEX4cc), + [0x4C] = X86_OP_GROUP0(EVEX4cc), + [0x4D] = X86_OP_GROUP0(EVEX4cc), + [0x4E] = X86_OP_GROUP0(EVEX4cc), + [0x4F] = X86_OP_GROUP0(EVEX4cc), + + [0x60] = X86_OP_GROUPwr(EVEX4_MOVBE, G,v, E,v, evex_apx cpuid(MOVBE) chk(nf0) p_00_66), + [0x61] = X86_OP_GROUPwr(EVEX4_MOVBE, E,v, G,v, evex_apx cpuid(MOVBE) chk(nf0) p_00_66), + [0x66] = X86_OP_GROUP3(EVEX4_66, B,y, G,y, E,y, evex_apx cpuid(ADX) chk(nf0) p_66_f3), + [0x69] = X86_OP_ENTRY3(IMUL3, G,v, E,v, I,z, evex_apx_zu sextT0 p_00_66), + [0x6B] = X86_OP_ENTRY3(IMUL3, G,v, E,v, I,b, evex_apx_zu sextT0 p_00_66), + + [0x80] = X86_OP_GROUP3(group1, B,b, E,b, I,b, evex_apx p_00), + [0x81] = X86_OP_GROUP3(group1, B,v, E,v, I,z, evex_apx p_00_66), + [0x83] = X86_OP_GROUP3(group1, B,v, E,v, I,b, evex_apx p_00_66), + [0x84] = X86_OP_ENTRYrr(TEST, E,b, G,b, evex_apx_cmp p_00_66), + [0x85] = X86_OP_ENTRYrr(TEST, E,v, G,v, evex_apx_cmp p_00_66), + [0x88] = X86_OP_ENTRYwr(POPCNT, G,v, E,v, evex_apx cpuid(POPCNT) zextT0 p_00_66), + [0x8F] = X86_OP_GROUPw(EVEX4_8F, R,d64, /* B,d64 */ evex_apx_pp2 p_00), + + [0xA4] = X86_OP_ENTRY4(SHLD, B,v, E,v, G,v, evex_apx p_00_66), + [0xA5] = X86_OP_ENTRY3(SHLD, B,v, E,v, G,v, evex_apx p_00_66), + [0xAC] = X86_OP_ENTRY4(SHRD, B,v, E,v, G,v, evex_apx p_00_66), + [0xAD] = X86_OP_ENTRY3(SHRD, B,v, E,v, G,v, evex_apx p_00_66), + /* + * Note that unlike the non-APX version this cannot place the Ev + * operand in T0, because Bv rewrites op1 to OP_TYPE_2op if NDD=0. + */ + [0xAF] = X86_OP_ENTRY3(IMUL3, B,v, G,v, E,v, evex_apx sextT0 p_00_66), + + [0xC0] = X86_OP_GROUP3(group2, B,b, E,b, I,b, evex_apx p_00), + [0xC1] = X86_OP_GROUP3(group2, B,v, E,v, I,b, evex_apx p_00_66), + + [0xD0] = X86_OP_GROUPwr(group2, B,b, E,b, evex_apx p_00), + [0xD1] = X86_OP_GROUPwr(group2, B,v, E,v, evex_apx p_00_66), + [0xD2] = X86_OP_GROUP3(group2, B,b, E,b, 1,b, evex_apx p_00), /* CL */ + [0xD3] = X86_OP_GROUP3(group2, B,v, E,v, 1,b, evex_apx p_00_66), /* CL */ + + [0xF0] = X86_OP_ENTRY2(CRC32, G,d, E,b, evex_apx cpuid(SSE42) chk(nf0) p_00), + [0xF1] = X86_OP_ENTRY2(CRC32, G,d, E,v, evex_apx cpuid(SSE42) chk(nf0) p_00_66), + [0xF4] = X86_OP_ENTRYwr(TZCNT, G,v, E,v, evex_apx zextT0 p_00_66), + [0xF5] = X86_OP_ENTRYwr(LZCNT, G,v, E,v, evex_apx zextT0 p_00_66), + [0xF6] = X86_OP_GROUP1(group3, E,b), + [0xF7] = X86_OP_GROUP1(group3, E,v), + [0xFE] = X86_OP_GROUP1(group4_5, E,b), + [0xFF] = X86_OP_GROUP1(EVEX4_FF, E,v), +}; + static void decode_EVEX_map4(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint8_t *b) { - *entry = UNKNOWN_OPCODE; + *b = x86_ldub_code(env, s); + *entry = opcodes_EVEX_map4[*b]; } #endif @@ -2073,6 +2282,10 @@ static void decode_EVEX_map4(DisasContext *s, CPUX86State *env, X86OpEntry *entr #undef vex11 #undef vex12 #undef vex13 +#undef evex_apx +#undef evex_apx_cmp +#undef evex_apx_pp2 +#undef evex_apx_zu static void decode_root(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint8_t *b) { @@ -2526,6 +2739,11 @@ static bool decode_op(DisasContext *s, CPUX86State *env, X86DecodedInsn *decode, op->n = type - X86_TYPE_ES; op->unit = X86_OP_SEG; break; + + case X86_TYPE_ZERO: + op->unit = X86_OP_IMM; + decode->immediate = op->imm = 0; + break; } return true; diff --git a/target/i386/tcg/emit.c.inc b/target/i386/tcg/emit.c.inc index ba347c4e6b7..c353d97ee74 100644 --- a/target/i386/tcg/emit.c.inc +++ b/target/i386/tcg/emit.c.inc @@ -1633,6 +1633,30 @@ static void gen_CMC(DisasContext *s, X86DecodedInsn *decode) tcg_gen_xori_tl(cpu_cc_src, cpu_cc_src, CC_C); } +#ifdef TARGET_X86_64 +static void gen_CFCMOVcc_ld(DisasContext *s, X86DecodedInsn *decode) +{ + TCGLabel *label_false = gen_new_label(); + int cond = decode->b & 0xf; + MemOp ot = decode->op[2].ot; + + gen_jcc_noeob(s, cond ^ 1, label_false); + gen_op_ld_v(s, ot, s->T0, s->A0); + gen_set_label(label_false); +} + +static void gen_CFCMOVcc_st(DisasContext *s, X86DecodedInsn *decode) +{ + TCGLabel *label_false = gen_new_label(); + int cond = decode->b & 0xf; + MemOp ot = decode->op[0].ot; + + gen_jcc_noeob(s, cond ^ 1, label_false); + gen_op_st_v(s, ot, s->T0, s->A0); + gen_set_label(label_false); +} +#endif + static void gen_CMOVcc(DisasContext *s, X86DecodedInsn *decode) { gen_cmovcc(s, decode->b & 0xf, s->T0, s->T1); @@ -3137,6 +3161,24 @@ static void gen_PMOVMSKB(DisasContext *s, X86DecodedInsn *decode) } } +#ifdef TARGET_X86_64 +static void gen_POP2(DisasContext *s, X86DecodedInsn *decode) +{ + TCGLabel *aligned = gen_new_label(); + TCGv_i128 t = tcg_temp_new_i128(); + + tcg_gen_brcondi_tl(TCG_COND_TSTEQ, cpu_regs[R_ESP], 15, aligned); + gen_helper_raise_gpf(tcg_env); + gen_set_label(aligned); + + gen_lea_ss_ofs(s, s->A0, cpu_regs[R_ESP], 0); + tcg_gen_qemu_ld_i128(t, s->A0, s->mem_index, + MO_LE | MO_128 | MO_ALIGN | MO_ATOM_IFALIGN_PAIR); + tcg_gen_extr_i128_i64(cpu_regs[s->vex_v], s->T0, t); + gen_pop_update(s, MO_128); +} +#endif + static void gen_POP(DisasContext *s, X86DecodedInsn *decode) { X86DecodedOp *op = &decode->op[0]; @@ -3334,6 +3376,26 @@ static void gen_PSLLDQ_i(DisasContext *s, X86DecodedInsn *decode) } } +#ifdef TARGET_X86_64 +static void gen_PUSH2(DisasContext *s, X86DecodedInsn *decode) +{ + TCGLabel *aligned = gen_new_label(); + TCGv_i128 t = tcg_temp_new_i128(); + TCGv new_esp = tcg_temp_new(); + + tcg_gen_brcondi_tl(TCG_COND_TSTEQ, cpu_regs[R_ESP], 15, aligned); + gen_helper_raise_gpf(tcg_env); + gen_set_label(aligned); + + tcg_gen_subi_tl(new_esp, cpu_regs[R_ESP], 16); + gen_lea_ss_ofs(s, s->A0, new_esp, 0); + tcg_gen_concat_i64_i128(t, s->T1, s->T0); + tcg_gen_qemu_st_i128(t, s->A0, s->mem_index, + MO_LE | MO_128 | MO_ALIGN | MO_ATOM_IFALIGN_PAIR); + gen_op_mov_reg_v(s, mo_stacksize(s), R_ESP, new_esp); +} +#endif + static void gen_PUSH(DisasContext *s, X86DecodedInsn *decode) { gen_push_v(s, s->T0); -- 2.55.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 16/17] target/i386: extract computation of no-autoenable features 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini ` (14 preceding siblings ...) 2026-09-23 17:30 ` [PATCH 15/17] target/i386/tcg: decode APX instructions Paolo Bonzini @ 2026-09-23 17:30 ` Paolo Bonzini 2026-09-23 17:30 ` [PATCH 17/17] target/i386/tcg: mark APX as supported Paolo Bonzini 2026-09-28 10:57 ` [PATCH v2 00/17] target/i386/tcg: implement APX Zhao Liu 17 siblings, 0 replies; 35+ messages in thread From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw) To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/cpu.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 2a69ffeda40..b6ad69d5047 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -9723,13 +9723,10 @@ void x86_cpu_expand_features(X86CPU *cpu, Error **errp) */ if (xcc->max_features) { for (w = 0; w < FEATURE_WORDS; w++) { - /* Override only features that weren't set explicitly - * by the user. - */ - env->features[w] |= - x86_cpu_get_supported_feature_word(cpu, w) & - ~env->user_features[w] & - ~feature_word_info[w].no_autoenable_flags; + /* Override only features that weren't set explicitly by the user. */ + uint64_t no_autoenable_flags = env->user_features[w] | feature_word_info[w].no_autoenable_flags; + + env->features[w] |= x86_cpu_get_supported_feature_word(cpu, w) & ~no_autoenable_flags; } if ((env->features[FEAT_7_1_EDX] & CPUID_7_1_EDX_AVX10) && !env->avx10_version) { -- 2.55.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 17/17] target/i386/tcg: mark APX as supported 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini ` (15 preceding siblings ...) 2026-09-23 17:30 ` [PATCH 16/17] target/i386: extract computation of no-autoenable features Paolo Bonzini @ 2026-09-23 17:30 ` Paolo Bonzini 2026-09-28 10:57 ` [PATCH v2 00/17] target/i386/tcg: implement APX Zhao Liu 17 siblings, 0 replies; 35+ messages in thread From: Paolo Bonzini @ 2026-09-23 17:30 UTC (permalink / raw) To: qemu-devel; +Cc: chang.seok.bae, zhao1.liu Finally allow TCG to set the CPUID bit for the APXF feature. The effect of APX is about 0.5% larger binaries and about 1% less TCG ops. QEMU seems to produce about 1% *more* assembly instructions, because the optimizer could already produce roughly the same ops as NDD or NF instructions, and the new PUSH2/POP2 instructions include a stack alignment check that isn't there in non-APX code. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- configs/targets/x86_64-bsd-user.mak | 2 +- configs/targets/x86_64-linux-user.mak | 2 +- target/i386/cpu.c | 34 ++++++++++++++++++++++++--- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/configs/targets/x86_64-bsd-user.mak b/configs/targets/x86_64-bsd-user.mak index d754186347f..26b7cfad4e2 100644 --- a/configs/targets/x86_64-bsd-user.mak +++ b/configs/targets/x86_64-bsd-user.mak @@ -1,4 +1,4 @@ TARGET_ARCH=x86_64 TARGET_BASE_ARCH=i386 -TARGET_XML_FILES= i386-64bit.xml +TARGET_XML_FILES= i386-64bit.xml i386-64bit-apx.xml TARGET_LONG_BITS=64 diff --git a/configs/targets/x86_64-linux-user.mak b/configs/targets/x86_64-linux-user.mak index 7f9527fd1de..cad68746bdb 100644 --- a/configs/targets/x86_64-linux-user.mak +++ b/configs/targets/x86_64-linux-user.mak @@ -2,5 +2,5 @@ TARGET_ARCH=x86_64 TARGET_BASE_ARCH=i386 TARGET_SYSTBL_ABI=common,64 TARGET_SYSTBL=syscall_64.tbl -TARGET_XML_FILES= i386-64bit.xml i386-64bit-linux.xml +TARGET_XML_FILES= i386-64bit.xml i386-64bit-linux.xml i386-64bit-apx.xml TARGET_LONG_BITS=64 diff --git a/target/i386/cpu.c b/target/i386/cpu.c index b6ad69d5047..62cce7d3105 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -1005,7 +1005,7 @@ void x86_cpu_vendor_words2str(char *dst, uint32_t vendor1, #define TCG_7_1_EAX_FEATURES (CPUID_7_1_EAX_FZRM | CPUID_7_1_EAX_FSRS | \ CPUID_7_1_EAX_FSRC | CPUID_7_1_EAX_CMPCCXADD) #define TCG_7_1_ECX_FEATURES 0 -#define TCG_7_1_EDX_FEATURES 0 +#define TCG_7_1_EDX_FEATURES CPUID_7_1_EDX_APXF #define TCG_7_2_EDX_FEATURES 0 #define TCG_APM_FEATURES 0 #define TCG_6_EAX_FEATURES CPUID_6_EAX_ARAT @@ -1017,7 +1017,7 @@ void x86_cpu_vendor_words2str(char *dst, uint32_t vendor1, #define TCG_SGX_12_0_EBX_FEATURES 0 #define TCG_SGX_12_1_EAX_FEATURES 0 #define TCG_24_0_EBX_FEATURES 0 -#define TCG_29_0_EBX_FEATURES 0 +#define TCG_29_0_EBX_FEATURES CPUID_29_0_EBX_APX_NCI_NDD_NF #define TCG_1E_1_EAX_FEATURES 0 #define TCG_24_1_ECX_FEATURES 0 @@ -1554,7 +1554,7 @@ FeatureWordInfo feature_word_info[FEATURE_WORDS] = { }, .tcg_features = XSTATE_FP_MASK | XSTATE_SSE_MASK | XSTATE_YMM_MASK | XSTATE_BNDREGS_MASK | XSTATE_BNDCSR_MASK | - XSTATE_PKRU_MASK, + XSTATE_PKRU_MASK | XSTATE_APX_MASK, .migratable_flags = XSTATE_FP_MASK | XSTATE_SSE_MASK | XSTATE_YMM_MASK | XSTATE_BNDREGS_MASK | XSTATE_BNDCSR_MASK | XSTATE_OPMASK_MASK | XSTATE_ZMM_Hi256_MASK | XSTATE_Hi16_ZMM_MASK | @@ -9722,9 +9722,37 @@ void x86_cpu_expand_features(X86CPU *cpu, Error **errp) * inside x86_cpu_parse_featurestr() too. */ if (xcc->max_features) { + /* + * TCG supports both MPX and APX. Since they they cannot be enabled together, + * disable one---prefer APX if none was chosen explicitly. + */ + uint64_t feat7_1_edx = env->user_features[FEAT_7_1_EDX] & CPUID_7_1_EDX_APXF + ? env->features[FEAT_7_1_EDX] + : x86_cpu_get_supported_feature_word(cpu, FEAT_7_1_EDX); + uint64_t feat7_0_ebx = env->user_features[FEAT_7_0_EBX] & CPUID_7_0_EBX_MPX + ? env->features[FEAT_7_0_EBX] + : x86_cpu_get_supported_feature_word(cpu, FEAT_7_0_EBX); + + bool apx = feat7_1_edx & CPUID_7_1_EDX_APXF; + bool mpx = feat7_0_ebx & CPUID_7_0_EBX_MPX; + if (apx && mpx) { + if (env->user_features[FEAT_7_1_EDX] & CPUID_7_1_EDX_APXF + || !(env->user_features[FEAT_7_0_EBX] & CPUID_7_0_EBX_MPX)) { + mpx = false; + } else { + apx = false; + } + } + for (w = 0; w < FEATURE_WORDS; w++) { /* Override only features that weren't set explicitly by the user. */ uint64_t no_autoenable_flags = env->user_features[w] | feature_word_info[w].no_autoenable_flags; + if (w == FEAT_7_1_EDX && mpx) { + no_autoenable_flags |= CPUID_7_1_EDX_APXF; + } + if (w == FEAT_7_0_EBX && apx) { + no_autoenable_flags |= CPUID_7_0_EBX_MPX; + } env->features[w] |= x86_cpu_get_supported_feature_word(cpu, w) & ~no_autoenable_flags; } -- 2.55.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH v2 00/17] target/i386/tcg: implement APX 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini ` (16 preceding siblings ...) 2026-09-23 17:30 ` [PATCH 17/17] target/i386/tcg: mark APX as supported Paolo Bonzini @ 2026-09-28 10:57 ` Zhao Liu 17 siblings, 0 replies; 35+ messages in thread From: Zhao Liu @ 2026-09-28 10:57 UTC (permalink / raw) To: Paolo Bonzini; +Cc: qemu-devel, chang.seok.bae, Zhao Liu On Wed, Sep 23, 2026 at 07:30:04PM +0200, Paolo Bonzini wrote: > Date: Wed, 23 Sep 2026 19:30:04 +0200 > From: Paolo Bonzini <pbonzini@redhat.com> > Subject: [PATCH v2 00/17] target/i386/tcg: implement APX > X-Mailer: git-send-email 2.55.0 > > This series implements APX support; the main reason to do this is actually > to have some initial infrastructure for EVEX, without requiring all the > complexity of AVX512 but introducing the required changes in the > emulator. It also forces some changes that (hopefully) make > QEMU's decoder align a bit more with what Intel processors actually do. > > This series was tested with Zhao Liu's https://github.com/trueptolemy/apx-test. About the APX testing, I'd also like to acknowledge Chang’s help :-). He discussed many things of the APX TCG and offered a lot of insightful advice. > The failures were analyzed by AI but the fixes had to be redone anyway. > Other than for a couple typos, the LLM tended to fix the symptom rather > than be general, which is exactly the opposite of what I wanted. For > example, for the MUL/IMUL/DIV/IDIV assertion failure it wanted to modify > the decoding tables instead of weakening > > assert(e->s0 == e->s1); > > to this: > > assert(e->op1 == X86_TYPE_None || e->s0 == e->s1); > > Changing the decoding tables probably is a more common failure mode, indeed, > but not the right approach in the context of this series. Other than that, > the AGENTS.md from the AI policy discussion worked fine and the LLM was very > keen on helping me not stray from the righteous path. Maybe off-topic, for AI usage, I have a qemu prompt for AI use of my own: https://github.com/trueptolemy/review-prompts/tree/qemu-v0.1-06-08-2026/qemu (Such prompts and skills also respect the policies in AGENTS.md.) From my personal experience, at least it is effective to catch annoying compatibility issues. I'm also considering "upstreaming" it (which I think would require some refinement) - i.e., contributing it to the masoncl/review-prompts project, which should be helpful for any future qemu-sashiko work. > The issues detected by apx-test and fixed here are as follows: > > - MUL/IMUL/DIV/IDIV caused an assertion failure > > - INC/DEC did not allow 0x66 as a prefix > > - CMOV/CFCMOV's implicit zero-upper behavior was missing > > - IMUL's use of sextT0 conflicted with the zero-upper modifier (see also > patches 1-3) > > - MOVBE's register-to-register implementation was missing (see also > patch 4) > > - REX2 incorrectly allowed a 0x0F byte following the prefix (patch 8) > > On top of this, the fix between APX and MPX was not correctly resolved, > which is now fixed, and PUSH2/POP2 use a single MO_128 access. > > I also included a couple recommendations from Chang Seok Bae. Thanks! Tested-by: Zhao Liu <zhao1.liu@intel.com> Thanks, Zhao ^ permalink raw reply [flat|nested] 35+ messages in thread
end of thread, other threads:[~2026-09-29 8:09 UTC | newest] Thread overview: 35+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-23 17:30 [PATCH v2 00/17] target/i386/tcg: implement APX Paolo Bonzini 2026-09-23 17:30 ` [PATCH 01/17] target/i386/tcg: generalize zero- and sign-extension Paolo Bonzini 2026-09-23 19:34 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 02/17] target/i386/tcg: use standard operand order for BSF and BSR Paolo Bonzini 2026-09-23 19:36 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 03/17] target/i386/tcg: use zextT1 for CMPXCHG Paolo Bonzini 2026-09-23 19:36 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 04/17] target/i386/tcg: unify MOVBE and BSWAP generators Paolo Bonzini 2026-09-23 19:39 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 05/17] target/i386/tcg: add APX support to XSAVE/XRSTOR Paolo Bonzini 2026-09-23 17:30 ` [PATCH 06/17] target/i386/tcg: add definition for REX2 prefix Paolo Bonzini 2026-09-23 19:41 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 07/17] target/i386/tcg: mark XSAVE* as not allowing REX2 Paolo Bonzini 2026-09-23 19:42 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 08/17] target/i386/tcg: treat 0x0F as a prefix Paolo Bonzini 2026-09-23 19:51 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 09/17] target/i386/tcg: decode REX2 prefix Paolo Bonzini 2026-09-23 19:57 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 10/17] target/i386/tcg: implement JMPABS instruction Paolo Bonzini 2026-09-23 22:30 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 11/17] target/i386/tcg: extend VEX.vvvv parsing for APX Paolo Bonzini 2026-09-23 22:39 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 12/17] target/i386/tcg: decode EVEX prefix Paolo Bonzini 2026-09-23 23:28 ` Richard Henderson 2026-09-23 17:30 ` [PATCH 13/17] target/i386/tcg: add decode functionality for APX Paolo Bonzini 2026-09-24 15:17 ` Richard Henderson 2026-09-28 10:34 ` Zhao Liu 2026-09-28 12:16 ` Paolo Bonzini 2026-09-29 7:50 ` Richard Henderson 2026-09-29 8:08 ` Paolo Bonzini 2026-09-23 17:30 ` [PATCH 14/17] target/i386/tcg: implement CCMP/CTEST Paolo Bonzini 2026-09-23 17:30 ` [PATCH 15/17] target/i386/tcg: decode APX instructions Paolo Bonzini 2026-09-23 17:30 ` [PATCH 16/17] target/i386: extract computation of no-autoenable features Paolo Bonzini 2026-09-23 17:30 ` [PATCH 17/17] target/i386/tcg: mark APX as supported Paolo Bonzini 2026-09-28 10:57 ` [PATCH v2 00/17] target/i386/tcg: implement APX Zhao Liu
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.