* [PATCH 00/20] target/i386/tcg: implement APX
@ 2026-08-25 12:29 Paolo Bonzini
2026-08-25 12:29 ` [PATCH 01/20] target/i386/tcg: do not reuse cc_srcT Paolo Bonzini
` (20 more replies)
0 siblings, 21 replies; 38+ messages in thread
From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw)
To: qemu-devel
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.
In fact, most of the new code is for EVEX decoding. The extensions to
existing instruction can be implemented almost entirely by tweaking the
decode tables: the new data destination feature comes almost for free
thanks to the existing support for BMI instructions. Variants such as no
flags update and zero-upper are quite easy as well. The new instructions
(CCMP/CTEST, CFCMOV, PUSH2/POP2) are trivial except for CCMP/CTEST,
which try to be reasonably efficient. A lot of the recent changes to
AF/CF/OF computation make the changes to helpers very small, while
efficient implementation of JL/JLE/JB/JBE takes a bit of effort.
The main design decision was whether to treat VEX and EVEX maps as an
extension of the regular maps, or just bite the bullet, copy them over
to a new array and define them from scratch. Here I went the copying
route due to some annoying differences in accepted prefixes and due to
opcodes that were moved to a different spot. But in some cases APX's
three-operand variants end up being visible in the regular table,
due to shared code in decode_group*.
Don't expect any performance gains. APX binaries do produce about 1%
fewer TCG ops, but they map to about 1% *more* assembly instructions,
at least for x86-on-x86: that's because while the optimizer could
already produce roughly the same ops as NDD or NF instructions, the new
PUSH2/POP2 instructions include a stack alignment check that isn't there
in non-APX code. I don't think it's worth wasting a precious HF_ bit
for it, at least not for the next few years.
Other than the first three patches, and other than not reusing decode_root,
this is mostly the same as the RFC[1] since I didn't have much more
time to work on it. System mode emulation was tested very little,
basically nothing more than a simple initrd that runs a few instructions
and does xsave/xrstor.
Paolo
[1] https://lore.kernel.org/qemu-devel/20260301144218.458140-1-pbonzini@redhat.com/
Paolo Bonzini (20):
target/i386/tcg: do not reuse cc_srcT
target/i386/tcg: inline gen_ext_tl
target/i386/tcg: simplify return value of gen_prepare_cc
target/i386/tcg: move check bits out of validate_vex
target/i386/tcg: add APX support to XSAVE/XRSTOR
target/i386/tcg: treat VEX as disabling high-byte registers
target/i386/tcg: add definition for REX2 prefix
target/i386/tcg: mark XSAVE* as not allowing REX2
target/i386/tcg: decode REX2 prefix
target/i386/tcg: implement JMPABS instruction
target/i386/tcg: fetch modrm early
target/i386/tcg: move VEX validation early
target/i386/tcg: extend VEX.vvvv parsing for APX
target/i386/tcg: decode EVEX prefix
target/i386/tcg: add ZU writeback
target/i386/tcg: add decode functionality for APX
target/i386/tcg: implement CCMP/CTEST
target/i386/tcg: decode APX instructions
target/i386/tcg: mark APX as supported
target/i386/tcg: optimize CCMP
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 | 20 +
target/i386/tcg/tcg-cpu.h | 16 +-
target/i386/tcg/cc_helper_template.h.inc | 11 +
target/i386/cpu.c | 15 +-
target/i386/helper.c | 11 +
target/i386/tcg/cc_helper.c | 10 +
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 | 203 +++--
target/i386/tcg/decode-new.c.inc | 896 +++++++++++++++++++----
target/i386/tcg/emit.c.inc | 256 ++++++-
16 files changed, 1274 insertions(+), 246 deletions(-)
--
2.55.0
^ permalink raw reply [flat|nested] 38+ messages in thread* [PATCH 01/20] target/i386/tcg: do not reuse cc_srcT 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-08-25 23:09 ` Richard Henderson 2026-08-25 12:29 ` [PATCH 02/20] target/i386/tcg: inline gen_ext_tl Paolo Bonzini ` (19 subsequent siblings) 20 siblings, 1 reply; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel Use a different temporary for every instruction that uses it in the translation block. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/translate.c | 6 ++++-- target/i386/tcg/emit.c.inc | 11 +++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c index 2115c5cd24b..d1791550772 100644 --- a/target/i386/tcg/translate.c +++ b/target/i386/tcg/translate.c @@ -342,7 +342,7 @@ static void set_cc_op_1(DisasContext *s, CCOp op, bool dirty) tcg_gen_discard_tl(cpu_cc_src2); } if (dead & USES_CC_SRCT) { - tcg_gen_discard_tl(s->cc_srcT); + s->cc_srcT = NULL; } if (dirty && s->cc_op == CC_OP_DYNAMIC) { @@ -1236,6 +1236,7 @@ static void gen_lods(DisasContext *s, MemOp ot, TCGv dshift) static void gen_scas(DisasContext *s, MemOp ot, TCGv dshift) { + s->cc_srcT = tcg_temp_new(); gen_string_movl_A0_EDI(s); gen_op_ld_v(s, ot, s->T1, s->A0); tcg_gen_mov_tl(cpu_cc_src, s->T1); @@ -1248,6 +1249,7 @@ static void gen_scas(DisasContext *s, MemOp ot, TCGv dshift) static void gen_cmps(DisasContext *s, MemOp ot, TCGv dshift) { + s->cc_srcT = tcg_temp_new(); gen_string_movl_A0_EDI(s); gen_op_ld_v(s, ot, s->T1, s->A0); gen_string_movl_A0_ESI(s); @@ -3484,7 +3486,7 @@ static void i386_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cpu) dc->T1 = tcg_temp_new(); dc->A0 = tcg_temp_new(); - dc->cc_srcT = tcg_temp_new(); + dc->cc_srcT = NULL; } static void i386_tr_tb_start(DisasContextBase *db, CPUState *cpu) diff --git a/target/i386/tcg/emit.c.inc b/target/i386/tcg/emit.c.inc index 473f415766f..968abd47a73 100644 --- a/target/i386/tcg/emit.c.inc +++ b/target/i386/tcg/emit.c.inc @@ -1699,6 +1699,7 @@ static void gen_CMPccXADD(DisasContext *s, X86DecodedInsn *decode) * - s->cc_srcT: memory operand (lhs for comparison) * - cmpv: rhs for comparison */ + s->cc_srcT = tcg_temp_new(); gen_set_label(label_top); gen_op_ld_v(s, ot_full, s->cc_srcT, s->A0); tcg_gen_sub_tl(s->T0, s->cc_srcT, cmpv); @@ -1800,11 +1801,12 @@ static void gen_CMPXCHG(DisasContext *s, X86DecodedInsn *decode) dest = gen_op_deposit_reg_v(s, ot, R_EAX, s->T0, oldv); tcg_gen_movcond_tl(TCG_COND_NE, dest, oldv, cmpv, s->T0, dest); - tcg_gen_mov_tl(s->cc_srcT, cmpv); - tcg_gen_sub_tl(cmpv, cmpv, oldv); - decode->cc_dst = cmpv; + s->cc_srcT = cmpv; + decode->cc_dst = tcg_temp_new(); decode->cc_src = oldv; decode->cc_op = CC_OP_SUBB + ot; + + tcg_gen_sub_tl(decode->cc_dst, cmpv, oldv); } static void gen_CMPXCHG16B(DisasContext *s, X86DecodedInsn *decode) @@ -2722,7 +2724,7 @@ static void gen_NEG(DisasContext *s, X86DecodedInsn *decode) decode->cc_dst = s->T0; decode->cc_src = oldv; - tcg_gen_movi_tl(s->cc_srcT, 0); + s->cc_srcT = tcg_constant_tl(0); decode->cc_op = CC_OP_SUBB + ot; } @@ -4114,6 +4116,7 @@ static void gen_SUB(DisasContext *s, X86DecodedInsn *decode) { MemOp ot = decode->op[1].ot; + s->cc_srcT = tcg_temp_new(); if (s->prefix & PREFIX_LOCK) { tcg_gen_neg_tl(s->T0, s->T1); tcg_gen_atomic_fetch_add_tl(s->cc_srcT, s->A0, s->T0, -- 2.55.0 ^ permalink raw reply related [flat|nested] 38+ messages in thread
* Re: [PATCH 01/20] target/i386/tcg: do not reuse cc_srcT 2026-08-25 12:29 ` [PATCH 01/20] target/i386/tcg: do not reuse cc_srcT Paolo Bonzini @ 2026-08-25 23:09 ` Richard Henderson 0 siblings, 0 replies; 38+ messages in thread From: Richard Henderson @ 2026-08-25 23:09 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel On 8/25/26 05:29, Paolo Bonzini wrote: > Use a different temporary for every instruction that uses it in the > translation block. > > Signed-off-by: Paolo Bonzini<pbonzini@redhat.com> > --- > target/i386/tcg/translate.c | 6 ++++-- > target/i386/tcg/emit.c.inc | 11 +++++++---- > 2 files changed, 11 insertions(+), 6 deletions(-) Reviewed-by: Richard Henderson <richard.henderson@linaro.org> r~ ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH 02/20] target/i386/tcg: inline gen_ext_tl 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini 2026-08-25 12:29 ` [PATCH 01/20] target/i386/tcg: do not reuse cc_srcT Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-08-25 23:09 ` Richard Henderson 2026-08-25 12:29 ` [PATCH 03/20] target/i386/tcg: simplify return value of gen_prepare_cc Paolo Bonzini ` (18 subsequent siblings) 20 siblings, 1 reply; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel It has been mostly replaced by tcg_gen_ext_tl(), other than for a single appearance in gen_op_j_ecx(). No need to have a separate function. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/translate.c | 22 ++++++++-------------- target/i386/tcg/emit.c.inc | 4 ---- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c index d1791550772..70f9bb42209 100644 --- a/target/i386/tcg/translate.c +++ b/target/i386/tcg/translate.c @@ -640,23 +640,17 @@ static inline void gen_string_movl_A0_EDI(DisasContext *s) gen_lea_v_seg(s, cpu_regs[R_EDI], R_ES, -1); } -static TCGv gen_ext_tl(TCGv dst, TCGv src, MemOp size, bool sign) -{ - if (size == MO_TL) { - return src; - } - if (!dst) { - dst = tcg_temp_new(); - } - tcg_gen_ext_tl(dst, src, size | (sign ? MO_SIGN : 0)); - return dst; -} - static void gen_op_j_ecx(DisasContext *s, TCGCond cond, TCGLabel *label1) { - TCGv tmp = gen_ext_tl(NULL, cpu_regs[R_ECX], s->aflag, false); + TCGv cmpval; + if (s->aflag == MO_TL) { + cmpval = cpu_regs[R_ECX]; + } else { + cmpval = tcg_temp_new(); + tcg_gen_ext_tl(cmpval, cpu_regs[R_ECX], s->aflag); + } - tcg_gen_brcondi_tl(cond, tmp, 0, label1); + tcg_gen_brcondi_tl(cond, cmpval, 0, label1); } static inline void gen_op_jz_ecx(DisasContext *s, TCGLabel *label1) diff --git a/target/i386/tcg/emit.c.inc b/target/i386/tcg/emit.c.inc index 968abd47a73..c83ab809408 100644 --- a/target/i386/tcg/emit.c.inc +++ b/target/i386/tcg/emit.c.inc @@ -1686,10 +1686,6 @@ static void gen_CMPccXADD(DisasContext *s, X86DecodedInsn *decode) ot_full |= MO_SIGN; } - /* - * cmpv will be moved to cc_src *after* cpu_regs[] is written back, so use - * tcg_gen_ext_tl instead of gen_ext_tl. - */ tcg_gen_ext_tl(cmpv, cpu_regs[decode->op[1].n], ot_full); /* -- 2.55.0 ^ permalink raw reply related [flat|nested] 38+ messages in thread
* Re: [PATCH 02/20] target/i386/tcg: inline gen_ext_tl 2026-08-25 12:29 ` [PATCH 02/20] target/i386/tcg: inline gen_ext_tl Paolo Bonzini @ 2026-08-25 23:09 ` Richard Henderson 0 siblings, 0 replies; 38+ messages in thread From: Richard Henderson @ 2026-08-25 23:09 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel On 8/25/26 05:29, Paolo Bonzini wrote: > It has been mostly replaced by tcg_gen_ext_tl(), other than for a single > appearance in gen_op_j_ecx(). No need to have a separate function. > > Signed-off-by: Paolo Bonzini<pbonzini@redhat.com> > --- > target/i386/tcg/translate.c | 22 ++++++++-------------- > target/i386/tcg/emit.c.inc | 4 ---- > 2 files changed, 8 insertions(+), 18 deletions(-) Reviewed-by: Richard Henderson <richard.henderson@linaro.org> r~ ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH 03/20] target/i386/tcg: simplify return value of gen_prepare_cc 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini 2026-08-25 12:29 ` [PATCH 01/20] target/i386/tcg: do not reuse cc_srcT Paolo Bonzini 2026-08-25 12:29 ` [PATCH 02/20] target/i386/tcg: inline gen_ext_tl Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-08-25 12:29 ` [PATCH 04/20] target/i386/tcg: move check bits out of validate_vex Paolo Bonzini ` (17 subsequent siblings) 20 siblings, 0 replies; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel Always make reg2 valid, unify .use_reg2 and .no_setcond now that .use_reg2 is a misnomer. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/translate.c | 63 ++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 36 deletions(-) diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c index 70f9bb42209..bbd1c3b7893 100644 --- a/target/i386/tcg/translate.c +++ b/target/i386/tcg/translate.c @@ -828,13 +828,21 @@ static void gen_compute_eflags(DisasContext *s) set_cc_op(s, CC_OP_EFLAGS); } +typedef enum { + /* imm is valid and reg2 is a constant */ + CC_PREPARE_IMM, + /* imm is invalid */ + CC_PREPARE_REG, + /* imm and reg2 are 0, and reg is known to be 0/1 */ + CC_PREPARE_DIRECT, +} CCPrepareRHS; + typedef struct CCPrepare { TCGCond cond; TCGv reg; TCGv reg2; target_ulong imm; - bool use_reg2; - bool no_setcond; + CCPrepareRHS rhs_type; } CCPrepare; static CCPrepare gen_prepare_sign_nz(TCGv src, MemOp size) @@ -871,7 +879,7 @@ static CCPrepare gen_prepare_eflags_c(DisasContext *s, TCGv reg) tcg_gen_ext_tl(s->cc_srcT, s->cc_srcT, size); tcg_gen_ext_tl(cpu_cc_src, cpu_cc_src, size); return (CCPrepare) { .cond = TCG_COND_LTU, .reg = s->cc_srcT, - .reg2 = cpu_cc_src, .use_reg2 = true }; + .reg2 = cpu_cc_src, .rhs_type = CC_PREPARE_REG }; case CC_OP_ADDB ... CC_OP_ADDQ: /* (DATA_TYPE)CC_DST < (DATA_TYPE)CC_SRC */ @@ -879,7 +887,7 @@ static CCPrepare gen_prepare_eflags_c(DisasContext *s, TCGv reg) tcg_gen_ext_tl(cpu_cc_dst, cpu_cc_dst, size); tcg_gen_ext_tl(cpu_cc_src, cpu_cc_src, size); return (CCPrepare) { .cond = TCG_COND_LTU, .reg = cpu_cc_dst, - .reg2 = cpu_cc_src, .use_reg2 = true }; + .reg2 = cpu_cc_src, .rhs_type = CC_PREPARE_REG }; case CC_OP_LOGICB ... CC_OP_LOGICQ: case CC_OP_POPCNT: @@ -888,7 +896,7 @@ static CCPrepare gen_prepare_eflags_c(DisasContext *s, TCGv reg) case CC_OP_INCB ... CC_OP_INCQ: case CC_OP_DECB ... CC_OP_DECQ: return (CCPrepare) { .cond = TCG_COND_NE, .reg = cpu_cc_src, - .no_setcond = true }; + .rhs_type = CC_PREPARE_DIRECT }; case CC_OP_SHLB ... CC_OP_SHLQ: /* (CC_SRC >> (DATA_BITS - 1)) & 1 */ @@ -913,7 +921,7 @@ static CCPrepare gen_prepare_eflags_c(DisasContext *s, TCGv reg) case CC_OP_ADCX: case CC_OP_ADCOX: return (CCPrepare) { .cond = TCG_COND_NE, .reg = cpu_cc_dst, - .no_setcond = true }; + .rhs_type = CC_PREPARE_DIRECT }; case CC_OP_EFLAGS: case CC_OP_SARB ... CC_OP_SARQ: @@ -931,7 +939,7 @@ static CCPrepare gen_prepare_eflags_c(DisasContext *s, TCGv reg) gen_helper_cc_compute_c(reg, cpu_cc_dst, cpu_cc_src, cpu_cc_src2, cpu_cc_op); return (CCPrepare) { .cond = TCG_COND_NE, .reg = reg, - .no_setcond = true }; + .rhs_type = CC_PREPARE_DIRECT }; } } @@ -970,7 +978,7 @@ static CCPrepare gen_prepare_eflags_o(DisasContext *s, TCGv reg) case CC_OP_ADOX: case CC_OP_ADCOX: return (CCPrepare) { .cond = TCG_COND_NE, .reg = cpu_cc_src2, - .no_setcond = true }; + .rhs_type = CC_PREPARE_DIRECT }; case CC_OP_SBB_SELF: case CC_OP_LOGICB ... CC_OP_LOGICQ: case CC_OP_POPCNT: @@ -1032,7 +1040,7 @@ static CCPrepare gen_prepare_cc(DisasContext *s, int b, TCGv reg) tcg_gen_ext_tl(s->cc_srcT, s->cc_srcT, size); tcg_gen_ext_tl(cpu_cc_src, cpu_cc_src, size); cc = (CCPrepare) { .cond = TCG_COND_LEU, .reg = s->cc_srcT, - .reg2 = cpu_cc_src, .use_reg2 = true }; + .reg2 = cpu_cc_src, .rhs_type = CC_PREPARE_REG }; break; case JCC_L: cond = TCG_COND_LT; @@ -1043,7 +1051,7 @@ static CCPrepare gen_prepare_cc(DisasContext *s, int b, TCGv reg) tcg_gen_ext_tl(s->cc_srcT, s->cc_srcT, size | MO_SIGN); tcg_gen_ext_tl(cpu_cc_src, cpu_cc_src, size | MO_SIGN); cc = (CCPrepare) { .cond = cond, .reg = s->cc_srcT, - .reg2 = cpu_cc_src, .use_reg2 = true }; + .reg2 = cpu_cc_src, .rhs_type = CC_PREPARE_REG }; break; default: @@ -1128,6 +1136,9 @@ static CCPrepare gen_prepare_cc(DisasContext *s, int b, TCGv reg) break; } + if (cc.rhs_type != CC_PREPARE_REG) { + cc.reg2 = tcg_constant_tl(cc.imm); + } if (inv) { cc.cond = tcg_invert_cond(cc.cond); } @@ -1138,7 +1149,7 @@ static void gen_neg_setcc(DisasContext *s, int b, TCGv reg) { CCPrepare cc = gen_prepare_cc(s, b, reg); - if (cc.no_setcond) { + if (cc.rhs_type == CC_PREPARE_DIRECT) { if (cc.cond == TCG_COND_EQ) { tcg_gen_addi_tl(reg, cc.reg, -1); } else { @@ -1147,18 +1158,14 @@ static void gen_neg_setcc(DisasContext *s, int b, TCGv reg) return; } - if (cc.use_reg2) { - tcg_gen_negsetcond_tl(cc.cond, reg, cc.reg, cc.reg2); - } else { - tcg_gen_negsetcondi_tl(cc.cond, reg, cc.reg, cc.imm); - } + tcg_gen_negsetcond_tl(cc.cond, reg, cc.reg, cc.reg2); } static void gen_setcc(DisasContext *s, int b, TCGv reg) { CCPrepare cc = gen_prepare_cc(s, b, reg); - if (cc.no_setcond) { + if (cc.rhs_type == CC_PREPARE_DIRECT) { if (cc.cond == TCG_COND_EQ) { tcg_gen_xori_tl(reg, cc.reg, 1); } else { @@ -1167,11 +1174,7 @@ static void gen_setcc(DisasContext *s, int b, TCGv reg) return; } - if (cc.use_reg2) { - tcg_gen_setcond_tl(cc.cond, reg, cc.reg, cc.reg2); - } else { - tcg_gen_setcondi_tl(cc.cond, reg, cc.reg, cc.imm); - } + tcg_gen_setcond_tl(cc.cond, reg, cc.reg, cc.reg2); } static inline void gen_compute_eflags_c(DisasContext *s, TCGv reg) @@ -1185,11 +1188,7 @@ static inline void gen_jcc_noeob(DisasContext *s, int b, TCGLabel *l1) { CCPrepare cc = gen_prepare_cc(s, b, NULL); - if (cc.use_reg2) { - tcg_gen_brcond_tl(cc.cond, cc.reg, cc.reg2, l1); - } else { - tcg_gen_brcondi_tl(cc.cond, cc.reg, cc.imm, l1); - } + tcg_gen_brcond_tl(cc.cond, cc.reg, cc.reg2, l1); } /* Generate a conditional jump to label 'l1' according to jump opcode @@ -1206,11 +1205,7 @@ static inline void gen_jcc(DisasContext *s, int b, TCGLabel *l1) * it's cheaper to just compute the flags)! */ gen_update_cc_op(s); - if (cc.use_reg2) { - tcg_gen_brcond_tl(cc.cond, cc.reg, cc.reg2, l1); - } else { - tcg_gen_brcondi_tl(cc.cond, cc.reg, cc.imm, l1); - } + tcg_gen_brcond_tl(cc.cond, cc.reg, cc.reg2, l1); } static void gen_stos(DisasContext *s, MemOp ot, TCGv dshift) @@ -1717,10 +1712,6 @@ static void gen_cmovcc(DisasContext *s, int b, TCGv dest, TCGv src) { CCPrepare cc = gen_prepare_cc(s, b, NULL); - if (!cc.use_reg2) { - cc.reg2 = tcg_constant_tl(cc.imm); - } - tcg_gen_movcond_tl(cc.cond, dest, cc.reg, cc.reg2, src, dest); } -- 2.55.0 ^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 04/20] target/i386/tcg: move check bits out of validate_vex 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (2 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 03/20] target/i386/tcg: simplify return value of gen_prepare_cc Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-08-25 12:29 ` [PATCH 05/20] target/i386/tcg: add APX support to XSAVE/XRSTOR Paolo Bonzini ` (16 subsequent siblings) 20 siblings, 0 replies; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel JMPABS will need the W0 check; move all of these checks together with the others in disas_insn. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/decode-new.c.inc | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index 9c429e113de..47bc30a22fc 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -2723,24 +2723,6 @@ static bool validate_vex(DisasContext *s, X86DecodedInsn *decode) if (s->flags & HF_EM_MASK) { goto illegal; } - - if (e->check) { - if (e->check & X86_CHECK_VEX128) { - if (s->vex_l) { - goto illegal; - } - } - if (e->check & X86_CHECK_W0) { - if (s->vex_w) { - goto illegal; - } - } - if (e->check & X86_CHECK_W1) { - if (!s->vex_w) { - goto illegal; - } - } - } return true; nm_exception: @@ -3000,6 +2982,15 @@ static void disas_insn(DisasContext *s, CPUState *cpu) goto illegal_op; } } + if ((decode.e.check & X86_CHECK_VEX128) && s->vex_l) { + goto illegal_op; + } + if ((decode.e.check & X86_CHECK_W0) && s->vex_w) { + goto illegal_op; + } + if ((decode.e.check & X86_CHECK_W1) && !s->vex_w) { + goto illegal_op; + } } switch (decode.e.special) { -- 2.55.0 ^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 05/20] target/i386/tcg: add APX support to XSAVE/XRSTOR 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (3 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 04/20] target/i386/tcg: move check bits out of validate_vex Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-09-08 2:10 ` Chang S. Bae 2026-08-25 12:29 ` [PATCH 06/20] target/i386/tcg: treat VEX as disabling high-byte registers Paolo Bonzini ` (15 subsequent siblings) 20 siblings, 1 reply; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel With it, add a new hidden flag that will be used to enable the REX2 prefix and EVEX map4. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/cpu.h | 3 ++ target/i386/tcg/tcg-cpu.h | 16 +++++++--- target/i386/helper.c | 11 +++++++ target/i386/tcg/fpu_helper.c | 59 +++++++++++++++++++++++++++++++++--- target/i386/tcg/tcg-cpu.c | 5 +-- 5 files changed, 83 insertions(+), 11 deletions(-) diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 641f3ee5c2f..dc5d48aceea 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 */ @@ -2759,6 +2761,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..451f61e2043 100644 --- a/target/i386/tcg/tcg-cpu.h +++ b/target/i386/tcg/tcg-cpu.h @@ -52,9 +52,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 +77,8 @@ 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, 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 68362141621..22d205bed66 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 b812125efa1..e341aefb9f0 100644 --- a/target/i386/tcg/fpu_helper.c +++ b/target/i386/tcg/fpu_helper.c @@ -2690,6 +2690,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; @@ -2754,14 +2767,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)); @@ -2955,6 +2973,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(); @@ -3026,7 +3067,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)); @@ -3035,7 +3076,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)); } @@ -3053,6 +3094,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 @@ -3228,6 +3278,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 6f5dc06b3b9..2965c304e57 100644 --- a/target/i386/tcg/tcg-cpu.c +++ b/target/i386/tcg/tcg-cpu.c @@ -198,12 +198,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] 38+ messages in thread
* Re: [PATCH 05/20] target/i386/tcg: add APX support to XSAVE/XRSTOR 2026-08-25 12:29 ` [PATCH 05/20] target/i386/tcg: add APX support to XSAVE/XRSTOR Paolo Bonzini @ 2026-09-08 2:10 ` Chang S. Bae 0 siblings, 0 replies; 38+ messages in thread From: Chang S. Bae @ 2026-09-08 2:10 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel On 8/25/2026 5:29 AM, Paolo Bonzini wrote: > With it, add a new hidden flag that will be used to enable the REX2 prefix > and EVEX map4. > > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> > --- > target/i386/cpu.h | 3 ++ > target/i386/tcg/tcg-cpu.h | 16 +++++++--- > target/i386/helper.c | 11 +++++++ > target/i386/tcg/fpu_helper.c | 59 +++++++++++++++++++++++++++++++++--- > target/i386/tcg/tcg-cpu.c | 5 +-- > 5 files changed, 83 insertions(+), 11 deletions(-) ... > > - /* MPX State: */ > - XSaveBNDREG bndreg_state; > - XSaveBNDCSR bndcsr_state; > + /* Overlapping MPX and APX States: */ > + union { > + struct { > + XSaveBNDREG bndreg; > + XSaveBNDCSR bndcsr; > + } mpx_state; > + XSaveAPX apx_state; > + }; > + Nit: Along with this, I was curios if something like this would be worthwhile, or too much: #define XSAVE_APX_OFFSET XSAVE_BNDREG_OFFSET > > +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; > + } > +} This check could be simply (xcr0 & XSTATE_APX_MASK) since testing one bit here unlike the AVX check below. > + > 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) Overall, the change looks fine from my perspective. If allowed, Reviewed-by: Chang S. Bae <chang.seok.bae@intel.com> ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH 06/20] target/i386/tcg: treat VEX as disabling high-byte registers 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (4 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 05/20] target/i386/tcg: add APX support to XSAVE/XRSTOR Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-08-25 12:29 ` [PATCH 07/20] target/i386/tcg: add definition for REX2 prefix Paolo Bonzini ` (14 subsequent siblings) 20 siblings, 0 replies; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel This does not have any functional effect because the only instruction where it would make a difference is VPINSRB/VPINSRW, and they disable high-byte registers even for the non-AVX encoding. However, with APX it becomes a general property of the EVEX prefix and it makes sense to treat VEX the same way since it *does* include REX bits. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/translate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c index bbd1c3b7893..d8de290acb6 100644 --- a/target/i386/tcg/translate.c +++ b/target/i386/tcg/translate.c @@ -208,7 +208,7 @@ typedef struct DisasContext { #endif #ifdef TARGET_X86_64 -#define REX_PREFIX(S) (((S)->prefix & PREFIX_REX) != 0) +#define REX_PREFIX(S) (((S)->prefix & (PREFIX_REX | 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] 38+ messages in thread
* [PATCH 07/20] target/i386/tcg: add definition for REX2 prefix 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (5 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 06/20] target/i386/tcg: treat VEX as disabling high-byte registers Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-09-08 2:10 ` Chang S. Bae 2026-08-25 12:29 ` [PATCH 08/20] target/i386/tcg: mark XSAVE* as not allowing REX2 Paolo Bonzini ` (13 subsequent siblings) 20 siblings, 1 reply; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel 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 d8de290acb6..2b94f4163d9 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] 38+ messages in thread
* Re: [PATCH 07/20] target/i386/tcg: add definition for REX2 prefix 2026-08-25 12:29 ` [PATCH 07/20] target/i386/tcg: add definition for REX2 prefix Paolo Bonzini @ 2026-09-08 2:10 ` Chang S. Bae 0 siblings, 0 replies; 38+ messages in thread From: Chang S. Bae @ 2026-09-08 2:10 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel On 8/25/2026 5:29 AM, Paolo Bonzini wrote: > > #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) Nit: REX_PREFIX() being true looks to matter for bye_reg_is_xH(). Just wondered this macro naming. Perhaps, could be REX_FAMILY_PREFIX() or something due to the addition of other prefix kinds. ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH 08/20] target/i386/tcg: mark XSAVE* as not allowing REX2 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (6 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 07/20] target/i386/tcg: add definition for REX2 prefix Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-09-08 2:10 ` Chang S. Bae 2026-08-25 12:29 ` [PATCH 09/20] target/i386/tcg: decode REX2 prefix Paolo Bonzini ` (12 subsequent siblings) 20 siblings, 1 reply; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel 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 92822969003..c974ca9d7c5 100644 --- a/target/i386/tcg/decode-new.h +++ b/target/i386/tcg/decode-new.h @@ -181,6 +181,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 47bc30a22fc..a7e3ecec8a2 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -481,9 +481,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), }; @@ -2972,15 +2972,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] 38+ messages in thread
* Re: [PATCH 08/20] target/i386/tcg: mark XSAVE* as not allowing REX2 2026-08-25 12:29 ` [PATCH 08/20] target/i386/tcg: mark XSAVE* as not allowing REX2 Paolo Bonzini @ 2026-09-08 2:10 ` Chang S. Bae 0 siblings, 0 replies; 38+ messages in thread From: Chang S. Bae @ 2026-09-08 2:10 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel On 8/25/2026 5:29 AM, Paolo Bonzini wrote: > 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(-) ... > > @@ -2972,15 +2972,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; > + } These folded conditions don't seem directly related to the main point of this patch. Just saying... > + 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; Overall, the change looks quite straightforward and sane to me: Reviewed-by: Chang S. Bae <chang.seok.bae@intel.com> ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH 09/20] target/i386/tcg: decode REX2 prefix 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (7 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 08/20] target/i386/tcg: mark XSAVE* as not allowing REX2 Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-09-08 2:10 ` Chang S. Bae 2026-08-25 12:29 ` [PATCH 10/20] target/i386/tcg: implement JMPABS instruction Paolo Bonzini ` (11 subsequent siblings) 20 siblings, 1 reply; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel 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. 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 2b94f4163d9..ea70e9a5568 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]; @@ -3336,7 +3336,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", @@ -3354,6 +3354,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", @@ -3398,7 +3414,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 a7e3ecec8a2..1f59fd665f7 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -1989,6 +1989,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 @@ -2008,6 +2067,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) @@ -2019,10 +2092,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; } @@ -2301,9 +2371,7 @@ static bool decode_op(DisasContext *s, CPUX86State *env, X86DecodedInsn *decode, } get_reg: op->n = ((get_modrm(s, env) >> 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 */ @@ -2750,6 +2818,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. @@ -2842,6 +2928,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] 38+ messages in thread
* Re: [PATCH 09/20] target/i386/tcg: decode REX2 prefix 2026-08-25 12:29 ` [PATCH 09/20] target/i386/tcg: decode REX2 prefix Paolo Bonzini @ 2026-09-08 2:10 ` Chang S. Bae 0 siblings, 0 replies; 38+ messages in thread From: Chang S. Bae @ 2026-09-08 2:10 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel On 8/25/2026 5:29 AM, 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. The code looks like it matches the definition quite well to me: Reviewed-by: Chang S. Bae <chang.seok.bae@intel.com> > +/* 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); > +} This logic looks quite interesting. The following changes look pretty much usage sites -- for the possible combinations for the input. Then just quickly ran the calculation. The math seems to work as expected: value, mask, return 0x0 0x11 0x0 0x1 0x11 0x8 0x10 0x11 0x10 0x11 0x11 0x18 0x0 0x22 0x0 0x2 0x22 0x10 0x20 0x22 0x20 0x22 0x22 0x30 0x0 0x44 0x0 0x4 0x44 0x20 0x40 0x44 0x40 0x44 0x44 0x60 Alternatively, while with more lines, enum rex { REX_B, REX_X, REX_R }; static inline uint8_t get_reg_bits(uint8_t value, enum rex rex) { bool bit4 = false, bit3 = false; uint8_t bits = 0; switch (rex) { case REX_B: bit4 = 0x10 & value; bit3 = 0x01 & value; break; case REX_X: bit4 = 0x20 & value; bit3 = 0x02 & value; break; case REX_R: bit4 = 0x40 & value; bit3 = 0x04 & value; break; default: } bits += bit4 ? 0x10 : 0; bits += bit3 ? 0x08 : 0; return bits; } ...later s->rex_b = get_reg_bits(rex2, REX_B); s->rex_x = get_reg_bits(rex2, REX_X); s->rex_r = get_reg_bits(rex2, REX_R); ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH 10/20] target/i386/tcg: implement JMPABS instruction 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (8 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 09/20] target/i386/tcg: decode REX2 prefix Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-08-26 14:30 ` Zhao Liu 2026-08-25 12:29 ` [PATCH 11/20] target/i386/tcg: fetch modrm early Paolo Bonzini ` (10 subsequent siblings) 20 siblings, 1 reply; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel 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 c974ca9d7c5..4998712e86d 100644 --- a/target/i386/tcg/decode-new.h +++ b/target/i386/tcg/decode-new.h @@ -184,6 +184,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 1f59fd665f7..d215d28cec5 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -1991,6 +1991,7 @@ static const X86OpEntry opcodes_root[256] = { #ifdef TARGET_X86_64 static const X86OpEntry opcodes_rex2_map0_A0toAF[16] = { + [1] = X86_OP_ENTRY1(JMP_m, I,q, p_00 chk2(W0, no_adr)), }; static void decode_REX2(DisasContext *s, CPUX86State *env, X86OpEntry *entry, uint8_t *b, @@ -3081,6 +3082,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] 38+ messages in thread
* Re: [PATCH 10/20] target/i386/tcg: implement JMPABS instruction 2026-08-25 12:29 ` [PATCH 10/20] target/i386/tcg: implement JMPABS instruction Paolo Bonzini @ 2026-08-26 14:30 ` Zhao Liu 2026-08-27 6:33 ` Paolo Bonzini 0 siblings, 1 reply; 38+ messages in thread From: Zhao Liu @ 2026-08-26 14:30 UTC (permalink / raw) To: Paolo Bonzini; +Cc: qemu-devel, Zhao Liu On Tue, Aug 25, 2026 at 02:29:11PM +0200, Paolo Bonzini wrote: > Date: Tue, 25 Aug 2026 14:29:11 +0200 > From: Paolo Bonzini <pbonzini@redhat.com> > Subject: [PATCH 10/20] target/i386/tcg: implement JMPABS instruction > X-Mailer: git-send-email 2.55.0 > > 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 c974ca9d7c5..4998712e86d 100644 > --- a/target/i386/tcg/decode-new.h > +++ b/target/i386/tcg/decode-new.h > @@ -184,6 +184,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 1f59fd665f7..d215d28cec5 100644 > --- a/target/i386/tcg/decode-new.c.inc > +++ b/target/i386/tcg/decode-new.c.inc > @@ -1991,6 +1991,7 @@ static const X86OpEntry opcodes_root[256] = { > > #ifdef TARGET_X86_64 > static const X86OpEntry opcodes_rex2_map0_A0toAF[16] = { > + [1] = X86_OP_ENTRY1(JMP_m, I,q, p_00 chk2(W0, no_adr)), > }; Maybe it should use X86_OP_ENTRYr? since JMPABS doesn't need to write back. Just a simple test: #include <stdio.h> int main(void) { __asm__ volatile ( /* JMPABS: REX2(M0=0,W=0) = D5 00, opcode A1, then imm64 target */ ".byte 0xd5, 0x00, 0xa1\n\t" ".quad 1f\n\t" "ud2\n\t" /* this should be skipped */ "1:\n\t"); puts("JMPABS -- OK"); return 0; } And this hits an abort(): $ ./build/qemu-x86_64 /tmp/jmpabs ** ERROR:../target/i386/tcg/emit.c.inc:380:gen_writeback: code should not be reached Bail out! ERROR:../target/i386/tcg/emit.c.inc:380:gen_writeback: code should not be reached Aborted (core dumped) more GDB backtrace: Thread 1 "qemu-x86_64" received signal SIGABRT, Aborted. __pthread_kill_implementation (no_tid=0, signo=6, threadid=140737352460800) at ./nptl/pthread_kill.c:44 44 ./nptl/pthread_kill.c: No such file or directory. #0 __pthread_kill_implementation (no_tid=0, signo=6, threadid=140737352460800) at ./nptl/pthread_kill.c:44 #1 __pthread_kill_internal (signo=6, threadid=140737352460800) at ./nptl/pthread_kill.c:78 #2 __GI___pthread_kill (threadid=140737352460800, signo=signo@entry=6) at ./nptl/pthread_kill.c:89 #3 0x00007ffff7242476 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26 #4 0x00007ffff72287f3 in __GI_abort () at ./stdlib/abort.c:79 #5 0x00007ffff76e3b57 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0 #6 0x00007ffff773d70f in g_assertion_message_expr () from /lib/x86_64-linux-gnu/libglib-2.0.so.0 #7 0x0000555555664f37 in gen_writeback (s=s@entry=0x7fffffffd240, decode=decode@entry=0x7fffffffd0b0, opn=opn@entry=0, v=<optimized out>) at ../target/i386/tcg/emit.c.inc:380 #8 0x0000555555677778 in disas_insn (cpu=0x55555593e780, s=0x7fffffffd240) at ../target/i386/tcg/decode-new.c.inc:3682 #9 i386_tr_translate_insn (dcbase=0x7fffffffd240, cpu=0x55555593e780) at ../target/i386/tcg/translate.c:3608 #10 0x00005555555d7962 in translator_loop (cpu=0x55555593e780, tb=0x7fffe80d0700 <code_gen_buffer+853715>, max_insns=0x7fffffffd4cc, pc=<optimized out>, host_pc=<optimized out>, ops=ops@entry=0x5555557dc5a0 <i386_tr_ops>, db=0x7fffffffd240, addr_type=TCG_TYPE_I64) at ../accel/tcg/translator.c:178 #11 0x000055555567b212 in x86_translate_code (cpu=<optimized out>, tb=<optimized out>, max_insns=<optimized out>, pc=<optimized out>, host_pc=<optimized out>) at ../target/i386/tcg/translate.c:3699 #12 0x00005555555d6644 in setjmp_gen_code (env=env@entry=0x5555559427f0, tb=tb@entry=0x7fffe80d0700 <code_gen_buffer+853715>, pc=pc@entry=4198710, host_pc=0x401136, max_insns=max_insns@entry=0x7fffffffd4cc, ti=<optimized out>) at ../accel/tcg/translate-all.c:251 #13 0x00005555555d6b0e in tb_gen_code (cpu=cpu@entry=0x55555593e780, s=...) at ../accel/tcg/translate-all.c:324 #14 0x00005555555cec1a in cpu_exec_loop (cpu=cpu@entry=0x55555593e780, sc=<optimized out>) at ../accel/tcg/cpu-exec.c:973 #15 0x00005555555ced99 in cpu_exec_setjmp (cpu=cpu@entry=0x55555593e780, sc=<optimized out>) at ../accel/tcg/cpu-exec.c:1019 #16 0x00005555555cf381 in cpu_exec (cpu=cpu@entry=0x55555593e780) at ../accel/tcg/cpu-exec.c:1045 #17 0x0000555555622c80 in cpu_loop (env=env@entry=0x5555559427f0) at ../linux-user/x86_64/../i386/cpu_loop.c:217 #18 0x00005555555a1d17 in main (argc=<optimized out>, argv=<optimized out>, envp=<optimized out>) at ../linux-user/main.c:1010 Regards, Zhao ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH 10/20] target/i386/tcg: implement JMPABS instruction 2026-08-26 14:30 ` Zhao Liu @ 2026-08-27 6:33 ` Paolo Bonzini 0 siblings, 0 replies; 38+ messages in thread From: Paolo Bonzini @ 2026-08-27 6:33 UTC (permalink / raw) To: Zhao Liu; +Cc: qemu-devel [-- Attachment #1: Type: text/plain, Size: 5210 bytes --] Il mer 26 ago 2026, 16:31 Zhao Liu <zhao1.liu@intel.com> ha scritto: > On Tue, Aug 25, 2026 at 02:29:11PM +0200, Paolo Bonzini wrote: > > Date: Tue, 25 Aug 2026 14:29:11 +0200 > > From: Paolo Bonzini <pbonzini@redhat.com> > > Subject: [PATCH 10/20] target/i386/tcg: implement JMPABS instruction > > X-Mailer: git-send-email 2.55.0 > > > > 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 c974ca9d7c5..4998712e86d 100644 > > --- a/target/i386/tcg/decode-new.h > > +++ b/target/i386/tcg/decode-new.h > > @@ -184,6 +184,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 1f59fd665f7..d215d28cec5 100644 > > --- a/target/i386/tcg/decode-new.c.inc > > +++ b/target/i386/tcg/decode-new.c.inc > > @@ -1991,6 +1991,7 @@ static const X86OpEntry opcodes_root[256] = { > > > > #ifdef TARGET_X86_64 > > static const X86OpEntry opcodes_rex2_map0_A0toAF[16] = { > > + [1] = X86_OP_ENTRY1(JMP_m, I,q, p_00 chk2(W0, no_adr)), > > }; > > Maybe it should use X86_OP_ENTRYr? since JMPABS doesn't need to write > back. > Absolutely, thanks Zhao! Paolo > Just a simple test: > > #include <stdio.h> > > int main(void) > { > __asm__ volatile ( > /* JMPABS: REX2(M0=0,W=0) = D5 00, opcode A1, then imm64 target */ > ".byte 0xd5, 0x00, 0xa1\n\t" > ".quad 1f\n\t" > "ud2\n\t" /* this should be skipped */ > "1:\n\t"); > puts("JMPABS -- OK"); > return 0; > } > > And this hits an abort(): > > $ ./build/qemu-x86_64 /tmp/jmpabs > ** > ERROR:../target/i386/tcg/emit.c.inc:380:gen_writeback: code should not be > reached > Bail out! ERROR:../target/i386/tcg/emit.c.inc:380:gen_writeback: code > should not be reached > Aborted (core dumped) > > more GDB backtrace: > > Thread 1 "qemu-x86_64" received signal SIGABRT, Aborted. > __pthread_kill_implementation (no_tid=0, signo=6, > threadid=140737352460800) at ./nptl/pthread_kill.c:44 > 44 ./nptl/pthread_kill.c: No such file or directory. > #0 __pthread_kill_implementation (no_tid=0, signo=6, > threadid=140737352460800) at ./nptl/pthread_kill.c:44 > #1 __pthread_kill_internal (signo=6, threadid=140737352460800) at > ./nptl/pthread_kill.c:78 > #2 __GI___pthread_kill (threadid=140737352460800, signo=signo@entry=6) > at ./nptl/pthread_kill.c:89 > #3 0x00007ffff7242476 in __GI_raise (sig=sig@entry=6) at > ../sysdeps/posix/raise.c:26 > #4 0x00007ffff72287f3 in __GI_abort () at ./stdlib/abort.c:79 > #5 0x00007ffff76e3b57 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0 > #6 0x00007ffff773d70f in g_assertion_message_expr () from > /lib/x86_64-linux-gnu/libglib-2.0.so.0 > #7 0x0000555555664f37 in gen_writeback (s=s@entry=0x7fffffffd240, > decode=decode@entry=0x7fffffffd0b0, opn=opn@entry=0, v=<optimized out>) > at ../target/i386/tcg/emit.c.inc:380 > #8 0x0000555555677778 in disas_insn (cpu=0x55555593e780, > s=0x7fffffffd240) at ../target/i386/tcg/decode-new.c.inc:3682 > #9 i386_tr_translate_insn (dcbase=0x7fffffffd240, cpu=0x55555593e780) at > ../target/i386/tcg/translate.c:3608 > #10 0x00005555555d7962 in translator_loop (cpu=0x55555593e780, > tb=0x7fffe80d0700 <code_gen_buffer+853715>, max_insns=0x7fffffffd4cc, > pc=<optimized out>, host_pc=<optimized out>, ops=ops@entry=0x5555557dc5a0 > <i386_tr_ops>, db=0x7fffffffd240, addr_type=TCG_TYPE_I64) at > ../accel/tcg/translator.c:178 > #11 0x000055555567b212 in x86_translate_code (cpu=<optimized out>, > tb=<optimized out>, max_insns=<optimized out>, pc=<optimized out>, > host_pc=<optimized out>) at ../target/i386/tcg/translate.c:3699 > #12 0x00005555555d6644 in setjmp_gen_code (env=env@entry=0x5555559427f0, > tb=tb@entry=0x7fffe80d0700 <code_gen_buffer+853715>, pc=pc@entry=4198710, > host_pc=0x401136, max_insns=max_insns@entry=0x7fffffffd4cc, ti=<optimized > out>) at ../accel/tcg/translate-all.c:251 > #13 0x00005555555d6b0e in tb_gen_code (cpu=cpu@entry=0x55555593e780, > s=...) at ../accel/tcg/translate-all.c:324 > #14 0x00005555555cec1a in cpu_exec_loop (cpu=cpu@entry=0x55555593e780, > sc=<optimized out>) at ../accel/tcg/cpu-exec.c:973 > #15 0x00005555555ced99 in cpu_exec_setjmp (cpu=cpu@entry=0x55555593e780, > sc=<optimized out>) at ../accel/tcg/cpu-exec.c:1019 > #16 0x00005555555cf381 in cpu_exec (cpu=cpu@entry=0x55555593e780) at > ../accel/tcg/cpu-exec.c:1045 > #17 0x0000555555622c80 in cpu_loop (env=env@entry=0x5555559427f0) at > ../linux-user/x86_64/../i386/cpu_loop.c:217 > #18 0x00005555555a1d17 in main (argc=<optimized out>, argv=<optimized > out>, envp=<optimized out>) at ../linux-user/main.c:1010 > > Regards, > Zhao > > [-- Attachment #2: Type: text/html, Size: 6619 bytes --] ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH 11/20] target/i386/tcg: fetch modrm early 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (9 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 10/20] target/i386/tcg: implement JMPABS instruction Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-08-25 12:29 ` [PATCH 12/20] target/i386/tcg: move VEX validation early Paolo Bonzini ` (9 subsequent siblings) 20 siblings, 0 replies; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel As a first step towards handling EVEX prefixes, fetch the modrm byte before decode_ops(). This way, EVEX validation can use the mod bits: for example, APX instructions require X4=0 if the r/m operand is a register. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/decode-new.c.inc | 65 ++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index d215d28cec5..2033776ce1c 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -2086,9 +2086,8 @@ static int reg_nb_mask(DisasContext *s, int unit) static void decode_modrm(DisasContext *s, CPUX86State *env, X86DecodedInsn *decode, X86DecodedOp *op) { - int modrm = get_modrm(s, env); - int mod = (modrm >> 6) & 3; - int rm = modrm & 7; + int mod = (s->modrm >> 6) & 3; + int rm = s->modrm & 7; bool is_vsib = decode->e.vex_class == 12; int sib = -1; @@ -2297,11 +2296,33 @@ static bool decode_op_size(DisasContext *s, X86OpEntry *e, X86OpSize size, MemOp } } +static bool op_has_modrm(X86OpType type) +{ + switch (type) { + case X86_TYPE_C: /* REG in the modrm byte selects a control register */ + case X86_TYPE_D: /* REG in the modrm byte selects a debug register */ + case X86_TYPE_E: /* ALU modrm operand */ + case X86_TYPE_EM: /* modrm byte selects an ALU memory operand */ + case X86_TYPE_G: /* REG in the modrm byte selects a GPR */ + case X86_TYPE_M: /* modrm byte selects a memory operand */ + case X86_TYPE_nop: /* modrm operand decoded but not fetched */ + case X86_TYPE_N: /* R/M in the modrm byte selects an MMX register */ + case X86_TYPE_P: /* REG in the modrm byte selects an MMX register */ + case X86_TYPE_Q: /* MMX modrm operand */ + case X86_TYPE_R: /* R/M in the modrm byte selects a register */ + case X86_TYPE_U: /* R/M in the modrm byte selects an XMM/YMM register */ + case X86_TYPE_V: /* reg in the modrm byte selects an XMM/YMM register */ + case X86_TYPE_WM: /* modrm byte selects an XMM/YMM memory operand */ + case X86_TYPE_W: /* XMM/YMM modrm operand */ + return true; + default: + return false; + } +} + static bool decode_op(DisasContext *s, CPUX86State *env, X86DecodedInsn *decode, X86DecodedOp *op, X86OpType type, int b) { - int modrm; - switch (type) { case X86_TYPE_None: /* Implicit or absent */ case X86_TYPE_A: /* Implicit */ @@ -2317,7 +2338,7 @@ static bool decode_op(DisasContext *s, CPUX86State *env, X86DecodedInsn *decode, case X86_TYPE_C: /* REG in the modrm byte selects a control register */ op->unit = X86_OP_CR; - op->n = ((get_modrm(s, env) >> 3) & 7) | REX_R(s); + op->n = ((s->modrm >> 3) & 7) | REX_R(s); if (op->n == 0 && (s->prefix & PREFIX_LOCK) && (s->cpuid_ext3_features & CPUID_EXT3_CR8LEG)) { op->n = 8; @@ -2333,7 +2354,7 @@ static bool decode_op(DisasContext *s, CPUX86State *env, X86DecodedInsn *decode, case X86_TYPE_D: /* REG in the modrm byte selects a debug register */ op->unit = X86_OP_DR; - op->n = ((get_modrm(s, env) >> 3) & 7) | REX_R(s); + op->n = ((s->modrm >> 3) & 7) | REX_R(s); if (op->n >= 8) { /* * illegal opcode. The DR4 and DR5 case is checked in the generated @@ -2352,14 +2373,14 @@ static bool decode_op(DisasContext *s, CPUX86State *env, X86DecodedInsn *decode, case X86_TYPE_S: /* reg selects a segment register */ op->unit = X86_OP_SEG; - op->n = (get_modrm(s, env) >> 3) & 7; + op->n = (s->modrm >> 3) & 7; /* Values outside [CDEFGS]S, as well as storing to CS, are invalid. */ if (op->n >= 6 || (op->n == R_CS && op == &decode->op[0])) { return false; } break; - case X86_TYPE_P: + case X86_TYPE_P: /* REG in the modrm byte selects an MMX register */ op->unit = X86_OP_MMX; goto get_reg; @@ -2371,7 +2392,7 @@ static bool decode_op(DisasContext *s, CPUX86State *env, X86DecodedInsn *decode, op->unit = X86_OP_SSE; } get_reg: - op->n = ((get_modrm(s, env) >> 3) & 7); + op->n = ((s->modrm >> 3) & 7); op->n |= REX_R(s) & reg_nb_mask(s, op->unit); break; @@ -2408,8 +2429,7 @@ static bool decode_op(DisasContext *s, CPUX86State *env, X86DecodedInsn *decode, case X86_TYPE_R: /* R/M in the modrm byte selects a register */ op->unit = X86_OP_INT; get_modrm_reg: - modrm = get_modrm(s, env); - if ((modrm >> 6) != 3) { + if ((s->modrm >> 6) != 3) { return false; } goto get_modrm; @@ -2423,8 +2443,7 @@ static bool decode_op(DisasContext *s, CPUX86State *env, X86DecodedInsn *decode, /* fall through */ case X86_TYPE_M: /* modrm byte selects a memory operand */ get_modrm_mem: - modrm = get_modrm(s, env); - if ((modrm >> 6) == 3) { + if ((s->modrm >> 6) == 3) { return false; } /* fall through */ @@ -2532,7 +2551,7 @@ static bool decode_insn(DisasContext *s, CPUX86State *env, X86DecodeFunc decode_ return false; } - /* First compute size of operands in order to initialize s->rip_offset. */ + /* Compute size of operands here in order to initialize s->rip_offset. */ if (e->op0 != X86_TYPE_None) { if (!decode_op_size(s, e, e->s0, &decode->op[0].ot)) { return false; @@ -2565,6 +2584,13 @@ static bool decode_insn(DisasContext *s, CPUX86State *env, X86DecodeFunc decode_ assert(e->op3 == X86_TYPE_I && e->s3 == X86_SIZE_b); s->rip_offset += 1; } + return true; +} + +static bool decode_ops(DisasContext *s, CPUX86State *env, X86DecodeFunc decode_func, + X86DecodedInsn *decode) +{ + X86OpEntry *e = &decode->e; if (e->op0 != X86_TYPE_None && !decode_op(s, env, decode, &decode->op[0], e->op0, decode->b)) { @@ -3096,6 +3122,15 @@ static void disas_insn(DisasContext *s, CPUState *cpu) } } + if (op_has_modrm(decode.e.op0) || op_has_modrm(decode.e.op1) || + op_has_modrm(decode.e.op2)) { + get_modrm(s, env); + } + + if (!decode_ops(s, env, decode_func, &decode)) { + goto illegal_op; + } + switch (decode.e.special) { case X86_SPECIAL_None: break; -- 2.55.0 ^ permalink raw reply related [flat|nested] 38+ messages in thread
* [PATCH 12/20] target/i386/tcg: move VEX validation early 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (10 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 11/20] target/i386/tcg: fetch modrm early Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-08-28 8:32 ` Zhao Liu 2026-08-25 12:29 ` [PATCH 13/20] target/i386/tcg: extend VEX.vvvv parsing for APX Paolo Bonzini ` (8 subsequent siblings) 20 siblings, 1 reply; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel Perform the X86_VEX_REPScalar transformation on X86OpEntry instead of X86DecodedOp; everything else can be checked equally well before immediates are fetched etc. This is especially important for APX, which may decode operands differently depending on the NDD bit: if EVEX.NDD=0, VEX.vvvv is ignored and must be zero. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/decode-new.c.inc | 54 ++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index 2033776ce1c..d49f467cc84 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -2611,6 +2611,22 @@ static bool decode_ops(DisasContext *s, CPUX86State *env, X86DecodeFunc decode_f decode->immediate = insn_get_signed(env, s, MO_8); } + if (e->vex_class == 12) { + /* Check no overlap between registers. */ + if (!decode->op[0].has_ea && + (decode->op[0].n == decode->mem.index || decode->op[0].n == decode->op[1].n)) { + return false; + } + assert(!decode->op[1].has_ea); + if (decode->op[1].n == decode->mem.index) { + return false; + } + if (!decode->op[2].has_ea && + (decode->op[2].n == decode->mem.index || decode->op[2].n == decode->op[1].n)) { + return false; + } + } + return true; } @@ -2717,9 +2733,9 @@ static bool validate_vex(DisasContext *s, X86DecodedInsn *decode) if (s->vex_l) { goto illegal; } - assert(decode->e.s2 == X86_SIZE_x); - if (decode->op[2].has_ea) { - decode->op[2].ot = s->prefix & PREFIX_REPZ ? MO_32 : MO_64; + assert(e->op2 == X86_TYPE_W && e->s2 == X86_SIZE_x); + if ((s->modrm >> 6) != 3) { + e->s2 = s->prefix & PREFIX_REPNZ ? X86_SIZE_sd : X86_SIZE_ss; } } break; @@ -2736,10 +2752,7 @@ static bool validate_vex(DisasContext *s, X86DecodedInsn *decode) switch (e->vex_class) { case 0: - if (s->prefix & PREFIX_VEX) { - goto illegal; - } - return true; + g_assert_not_reached(); case 1: case 2: case 3: @@ -2764,19 +2777,6 @@ static bool validate_vex(DisasContext *s, X86DecodedInsn *decode) goto illegal; } - /* Check no overlap between registers. */ - if (!decode->op[0].has_ea && - (decode->op[0].n == decode->mem.index || decode->op[0].n == decode->op[1].n)) { - goto illegal; - } - assert(!decode->op[1].has_ea); - if (decode->op[1].n == decode->mem.index) { - goto illegal; - } - if (!decode->op[2].has_ea && - (decode->op[2].n == decode->mem.index || decode->op[2].n == decode->op[1].n)) { - goto illegal; - } /* fall through */ case 6: case 11: @@ -3127,6 +3127,16 @@ static void disas_insn(DisasContext *s, CPUState *cpu) get_modrm(s, env); } + if (decode.e.vex_class == 0) { + if (s->prefix & PREFIX_VEX) { + goto illegal_op; + } + } else { + if (!validate_vex(s, &decode)) { + return; + } + } + if (!decode_ops(s, env, decode_func, &decode)) { goto illegal_op; } @@ -3188,10 +3198,6 @@ static void disas_insn(DisasContext *s, CPUState *cpu) goto illegal_op; } - if (!validate_vex(s, &decode)) { - return; - } - /* * Checks that result in #GP or VMEXIT come second. Intercepts are * generally checked after non-memory exceptions (i.e. after all -- 2.55.0 ^ permalink raw reply related [flat|nested] 38+ messages in thread
* Re: [PATCH 12/20] target/i386/tcg: move VEX validation early 2026-08-25 12:29 ` [PATCH 12/20] target/i386/tcg: move VEX validation early Paolo Bonzini @ 2026-08-28 8:32 ` Zhao Liu 0 siblings, 0 replies; 38+ messages in thread From: Zhao Liu @ 2026-08-28 8:32 UTC (permalink / raw) To: Paolo Bonzini; +Cc: qemu-devel, Zhao Liu > @@ -2717,9 +2733,9 @@ static bool validate_vex(DisasContext *s, X86DecodedInsn *decode) > if (s->vex_l) { > goto illegal; > } > - assert(decode->e.s2 == X86_SIZE_x); > - if (decode->op[2].has_ea) { > - decode->op[2].ot = s->prefix & PREFIX_REPZ ? MO_32 : MO_64; > + assert(e->op2 == X86_TYPE_W && e->s2 == X86_SIZE_x); > + if ((s->modrm >> 6) != 3) { > + e->s2 = s->prefix & PREFIX_REPNZ ? X86_SIZE_sd : X86_SIZE_ss; Missing the decode->op[2].ot adjustment? I think here we still need: decode->op[2].ot = s->prefix & PREFIX_REPNZ ? MO_64 : MO_32; otherwise ot is still MO_128 (MO_256 is filtered by the s->vex_l check), which is set by X86_SIZE_x in decode_insn() before validate_vex(), and we don't have the opportunity to adjust the size later. Thanks, Zhao ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH 13/20] target/i386/tcg: extend VEX.vvvv parsing for APX 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (11 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 12/20] target/i386/tcg: move VEX validation early Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-08-25 12:29 ` [PATCH 14/20] target/i386/tcg: decode EVEX prefix Paolo Bonzini ` (7 subsequent siblings) 20 siblings, 0 replies; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel 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 ea70e9a5568..7e50cf2355f 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 d49f467cc84..08f1fdddf16 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -2331,10 +2331,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; @@ -2464,18 +2463,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 */ @@ -2718,6 +2724,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; @@ -2805,10 +2819,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; } @@ -2887,6 +2898,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] 38+ messages in thread
* [PATCH 14/20] target/i386/tcg: decode EVEX prefix 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (12 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 13/20] target/i386/tcg: extend VEX.vvvv parsing for APX Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-08-28 9:10 ` Zhao Liu 2026-08-25 12:29 ` [PATCH 15/20] target/i386/tcg: add ZU writeback Paolo Bonzini ` (6 subsequent siblings) 20 siblings, 1 reply; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel 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 | 86 +++++++++++++++++++++++++++++--- 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c index 7e50cf2355f..1d6714f3c6c 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 08f1fdddf16..37245570751 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -2047,6 +2047,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 @@ -2074,8 +2079,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; @@ -2467,7 +2471,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; } @@ -2593,6 +2597,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. + */ + g_assert_not_reached(); +} + static bool decode_ops(DisasContext *s, CPUX86State *env, X86DecodeFunc decode_func, X86DecodedInsn *decode) { @@ -2724,13 +2737,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: @@ -2874,6 +2890,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. @@ -2899,6 +2919,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; @@ -2982,6 +3005,56 @@ 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; + } + 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; + case 0x04: +#ifdef TARGET_X86_64 + decode_func = decode_EVEX_map4; + break; +#else + goto illegal_op; +#endif + default: /* Reserved for future use. */ + goto unknown_op; + } + } + break; case 0xc5: /* 2-byte VEX */ case 0xc4: /* 3-byte VEX */ /* @@ -2989,9 +3062,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) { @@ -3140,7 +3210,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] 38+ messages in thread
* Re: [PATCH 14/20] target/i386/tcg: decode EVEX prefix 2026-08-25 12:29 ` [PATCH 14/20] target/i386/tcg: decode EVEX prefix Paolo Bonzini @ 2026-08-28 9:10 ` Zhao Liu 0 siblings, 0 replies; 38+ messages in thread From: Zhao Liu @ 2026-08-28 9:10 UTC (permalink / raw) To: Paolo Bonzini; +Cc: qemu-devel, Zhao Liu > + 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; > + } > + if (s->prefix & (PREFIX_REPZ | PREFIX_REPNZ > + | PREFIX_LOCK | PREFIX_DATA)) { > + goto illegal_op; > + } Just a nit, per APX spec (section 3.1.2.3): The prefix rules for the extended EVEX prefix are the same as for the current EVEX prefix. The extended EVEX prefix must be the last prefix preceding the main opcode byte. The only prefixes which may precede the extended EVEX prefix are ASIZE override (0x67) and segment overrides. The presence of any other prefix triggers #UD. So we can also reject REX prefix like VEX did: #ifdef TARGET_X86_64 if (rex != -1) { goto illegal_op; } #endif Thanks, Zhao ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH 15/20] target/i386/tcg: add ZU writeback 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (13 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 14/20] target/i386/tcg: decode EVEX prefix Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-08-25 12:29 ` [PATCH 16/20] target/i386/tcg: add decode functionality for APX Paolo Bonzini ` (5 subsequent siblings) 20 siblings, 0 replies; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/decode-new.h | 3 +++ target/i386/tcg/decode-new.c.inc | 4 ++++ target/i386/tcg/emit.c.inc | 2 ++ 3 files changed, 9 insertions(+) diff --git a/target/i386/tcg/decode-new.h b/target/i386/tcg/decode-new.h index 4998712e86d..8650b5ae7a0 100644 --- a/target/i386/tcg/decode-new.h +++ b/target/i386/tcg/decode-new.h @@ -212,6 +212,9 @@ typedef enum X86InsnSpecial { */ X86_SPECIAL_Op0_Rd, + /* ZU-modified APX instruction. */ + X86_SPECIAL_Op0_ZU, + /* * Ry/Mb in the manual (PINSRB). However, the high bits are never used by * the instruction in either the register or memory cases; the *real* effect diff --git a/target/i386/tcg/decode-new.c.inc b/target/i386/tcg/decode-new.c.inc index 37245570751..76266b829cb 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -3237,6 +3237,10 @@ static void disas_insn(DisasContext *s, CPUState *cpu) accept_lock = decode.op[0].has_ea; break; + case X86_SPECIAL_Op0_ZU: + assert(decode.op[0].unit == X86_OP_INT); + break; + case X86_SPECIAL_Op0_Rd: 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..8c8adcf3255 100644 --- a/target/i386/tcg/emit.c.inc +++ b/target/i386/tcg/emit.c.inc @@ -347,6 +347,8 @@ 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 (decode->e.special == X86_SPECIAL_Op0_ZU) { + 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] 38+ messages in thread
* [PATCH 16/20] target/i386/tcg: add decode functionality for APX 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (14 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 15/20] target/i386/tcg: add ZU writeback Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-09-01 14:56 ` Zhao Liu 2026-09-02 6:55 ` Zhao Liu 2026-08-25 12:29 ` [PATCH 17/20] target/i386/tcg: implement CCMP/CTEST Paolo Bonzini ` (4 subsequent siblings) 20 siblings, 2 replies; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel 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 8650b5ae7a0..1c7ed73c437 100644 --- a/target/i386/tcg/decode-new.h +++ b/target/i386/tcg/decode-new.h @@ -187,6 +187,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 { @@ -274,6 +277,13 @@ typedef enum X86VEXSpecial { X86_VEX_AVX2_256, } 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 1d6714f3c6c..770a05f7d8d 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 76266b829cb..ebbfa4f6592 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -253,6 +253,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, @@ -2529,9 +2533,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; @@ -2603,7 +2621,35 @@ static bool extract_evex_params(DisasContext *s, X86DecodedInsn *decode) * Here, the position of RXB and (for AVX512) displacement multiplier * should be known. */ - g_assert_not_reached(); + 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, @@ -2733,6 +2779,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->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.special == 0 && !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->e.special = X86_SPECIAL_Op0_ZU; + } + 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 == ((decode->b & 7) | REX_B(s))) || + ((decode->b & 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; @@ -2825,14 +2952,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) { @@ -3202,6 +3347,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) || @@ -3347,6 +3495,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] 38+ messages in thread
* Re: [PATCH 16/20] target/i386/tcg: add decode functionality for APX 2026-08-25 12:29 ` [PATCH 16/20] target/i386/tcg: add decode functionality for APX Paolo Bonzini @ 2026-09-01 14:56 ` Zhao Liu 2026-09-02 6:55 ` Zhao Liu 1 sibling, 0 replies; 38+ messages in thread From: Zhao Liu @ 2026-09-01 14:56 UTC (permalink / raw) To: Paolo Bonzini; +Cc: qemu-devel, Zhao Liu > + 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 == ((decode->b & 7) | REX_B(s))) || ^^^^^^^^^^^^^^^^^^^^^^^^^^ > + ((decode->b & 7) | REX_B(s)) == 4 || ^^^^^^^^^^^^^^^^^^^^^^^^^^ Typo? For B register, here I think it should check ModRM: (s->modrm & 7) | REX_B(s) > + s->vex_v == 4) { > + goto illegal; > + } > + evex4_mask = 0x18; > + break; > + ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH 16/20] target/i386/tcg: add decode functionality for APX 2026-08-25 12:29 ` [PATCH 16/20] target/i386/tcg: add decode functionality for APX Paolo Bonzini 2026-09-01 14:56 ` Zhao Liu @ 2026-09-02 6:55 ` Zhao Liu 1 sibling, 0 replies; 38+ messages in thread From: Zhao Liu @ 2026-09-02 6:55 UTC (permalink / raw) To: Paolo Bonzini; +Cc: qemu-devel, Zhao Liu > +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->s0 == e->s1); This assertion seems too strong, should we only check it for X86_TYPE_B? Thanks, Zhao > + 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; ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH 17/20] target/i386/tcg: implement CCMP/CTEST 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (15 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 16/20] target/i386/tcg: add decode functionality for APX Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-09-02 6:06 ` Zhao Liu 2026-08-25 12:29 ` [PATCH 18/20] target/i386/tcg: decode APX instructions Paolo Bonzini ` (3 subsequent siblings) 20 siblings, 1 reply; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel 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 | 10 ++ target/i386/tcg/translate.c | 69 ++++++++++++ target/i386/tcg/decode-new.c.inc | 30 ++--- target/i386/tcg/emit.c.inc | 138 ++++++++++++++++++++++- 6 files changed, 247 insertions(+), 16 deletions(-) diff --git a/target/i386/cpu.h b/target/i386/cpu.h index dc5d48aceea..87f857f9bcb 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..a43b42badc0 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; diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c index 770a05f7d8d..42213e38962 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); @@ -975,6 +982,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)); } @@ -994,6 +1003,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, @@ -1076,6 +1099,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 ebbfa4f6592..33d366769e7 100644 --- a/target/i386/tcg/decode-new.c.inc +++ b/target/i386/tcg/decode-new.c.inc @@ -1580,7 +1580,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]; @@ -1627,8 +1627,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), @@ -1637,8 +1637,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), @@ -1777,8 +1777,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), @@ -1869,12 +1869,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)), @@ -1933,8 +1933,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 8c8adcf3255..48181182aa7 100644 --- a/target/i386/tcg/emit.c.inc +++ b/target/i386/tcg/emit.c.inc @@ -1649,6 +1649,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). + * + * 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(); @@ -3857,7 +3968,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; @@ -4172,6 +4282,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] 38+ messages in thread
* Re: [PATCH 17/20] target/i386/tcg: implement CCMP/CTEST 2026-08-25 12:29 ` [PATCH 17/20] target/i386/tcg: implement CCMP/CTEST Paolo Bonzini @ 2026-09-02 6:06 ` Zhao Liu 0 siblings, 0 replies; 38+ messages in thread From: Zhao Liu @ 2026-09-02 6:06 UTC (permalink / raw) To: Paolo Bonzini; +Cc: qemu-devel, Zhao Liu > 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..a43b42badc0 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; IIUC, it seems helper_cc_compute_c() missed CCMP ops, afterall it said "should never happen" for default case: target_ulong helper_cc_compute_c(target_ulong dst, target_ulong src1, target_ulong src2, int op) { switch (op) { default: /* should never happen */ ... I think we would need a compute_c_ccmp helper. Thanks, Zhao ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH 18/20] target/i386/tcg: decode APX instructions 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (16 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 17/20] target/i386/tcg: implement CCMP/CTEST Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-09-02 7:27 ` Zhao Liu 2026-08-25 12:29 ` [PATCH 19/20] target/i386/tcg: mark APX as supported Paolo Bonzini ` (2 subsequent siblings) 20 siblings, 1 reply; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel 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 | 307 +++++++++++++++++++++++++------ target/i386/tcg/emit.c.inc | 55 ++++++ 5 files changed, 312 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 1c7ed73c437..de35fb44a37 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 33d366769e7..a92bc1faeff 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__) @@ -275,8 +277,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, @@ -866,28 +870,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 @@ -920,20 +929,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)), }, }; @@ -1022,7 +1031,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) @@ -1587,9 +1596,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; } @@ -1616,6 +1625,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->special = X86_SPECIAL_SExtT0; } else { @@ -1627,24 +1639,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); @@ -1657,12 +1669,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 lock), + [0x09] = X86_OP_ENTRYwr(DEC, B,v, E,v, evex_apx p_00 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), @@ -1773,10 +1785,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), @@ -1809,8 +1822,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)), @@ -1818,10 +1832,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)), @@ -2052,9 +2067,178 @@ 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); + 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), + X86_OP_ENTRY3(CMOVcc, G,v, ZERO,v, E,v, p_00_66 evex_apx), + }, + { /* 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), + }, + }; + static const X86OpEntry cfcmov_nd1[2][2] = { + { /* NF=0 */ + X86_OP_ENTRY3(CMOVcc, B,v, G,v, E,v, p_00_66 evex_apx), + X86_OP_ENTRY3(CMOVcc, B,v, G,v, E,v, p_00_66 evex_apx), + }, + { /* NF=1 */ + X86_OP_ENTRY3(CFCMOVcc_ld, B,v, G,v, M,v, p_00_66 evex_apx), + X86_OP_ENTRY3(CMOVcc, B,v, G,v, E,v, p_00_66 evex_apx), + }, + }; + + 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_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_ENTRYwr(MOVBE, G,v, M,v, evex_apx cpuid(MOVBE) chk(nf0) p_00_66), + [0x61] = X86_OP_ENTRYwr(MOVBE, M,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), + + [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 @@ -2071,6 +2255,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) { @@ -2524,6 +2712,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 48181182aa7..1144953abb1 100644 --- a/target/i386/tcg/emit.c.inc +++ b/target/i386/tcg/emit.c.inc @@ -1644,6 +1644,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); @@ -3140,6 +3164,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(); + + 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); + gen_op_ld_v(s, MO_64, cpu_regs[s->vex_v], s->A0); + + tcg_gen_addi_tl(s->A0, s->A0, 8); + gen_op_ld_v(s, MO_64, s->T0, s->A0); + gen_pop_update(s, MO_128); +} +#endif + static void gen_POP(DisasContext *s, X86DecodedInsn *decode) { X86DecodedOp *op = &decode->op[0]; @@ -3337,6 +3379,19 @@ 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(); + + tcg_gen_brcondi_tl(TCG_COND_TSTEQ, cpu_regs[R_ESP], 15, aligned); + gen_helper_raise_gpf(tcg_env); + gen_set_label(aligned); + gen_push_v(s, s->T0); + gen_push_v(s, s->T1); +} +#endif + static void gen_PUSH(DisasContext *s, X86DecodedInsn *decode) { gen_push_v(s, s->T0); -- 2.55.0 ^ permalink raw reply related [flat|nested] 38+ messages in thread
* Re: [PATCH 18/20] target/i386/tcg: decode APX instructions 2026-08-25 12:29 ` [PATCH 18/20] target/i386/tcg: decode APX instructions Paolo Bonzini @ 2026-09-02 7:27 ` Zhao Liu 0 siblings, 0 replies; 38+ messages in thread From: Zhao Liu @ 2026-09-02 7:27 UTC (permalink / raw) To: Paolo Bonzini; +Cc: qemu-devel, Zhao Liu > +#ifdef TARGET_X86_64 > +static void gen_PUSH2(DisasContext *s, X86DecodedInsn *decode) > +{ > + TCGLabel *aligned = gen_new_label(); > + > + tcg_gen_brcondi_tl(TCG_COND_TSTEQ, cpu_regs[R_ESP], 15, aligned); > + gen_helper_raise_gpf(tcg_env); > + gen_set_label(aligned); > + gen_push_v(s, s->T0); > + gen_push_v(s, s->T1); > +} > +#endif Or maybe we can refer gen_pusha() to update RSX in one goes. But I'm still not sure, because there are still two memory writes. What if the 1st memory write succeeds, but 2nd fails due to #PF? I'm just not sure if this case actually exists - I don't think that's very likely since POP2 requires 16B-aligned. Though APX spec said "For PUSH2, the two register values being pushed are either both written to memory or neither one is written", however, it seems difficult to fully comply with this rule. Thanks, Zhao ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH 19/20] target/i386/tcg: mark APX as supported 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (17 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 18/20] target/i386/tcg: decode APX instructions Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-09-02 9:15 ` Zhao Liu 2026-08-25 12:29 ` [PATCH 20/20] target/i386/tcg: optimize CCMP Paolo Bonzini 2026-09-08 2:10 ` [PATCH 00/20] target/i386/tcg: implement APX Chang S. Bae 20 siblings, 1 reply; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel 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 | 15 +++++++++++++-- 3 files changed, 15 insertions(+), 4 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 e5ffb10d156..e966bd8049b 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 @@ -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,6 +9722,17 @@ 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. + */ + if ((x86_cpu_get_supported_feature_word(cpu, FEAT_7_1_EDX) & CPUID_7_1_EDX_APXF) && + env->user_features[FEAT_7_0_EBX] & CPUID_7_0_EBX_MPX) { + feature_word_info[FEAT_7_1_EDX].no_autoenable_flags |= CPUID_7_1_EDX_APXF; + } else { + feature_word_info[FEAT_7_0_EBX].no_autoenable_flags |= CPUID_7_0_EBX_MPX; + } + for (w = 0; w < FEATURE_WORDS; w++) { /* Override only features that weren't set explicitly * by the user. -- 2.55.0 ^ permalink raw reply related [flat|nested] 38+ messages in thread
* Re: [PATCH 19/20] target/i386/tcg: mark APX as supported 2026-08-25 12:29 ` [PATCH 19/20] target/i386/tcg: mark APX as supported Paolo Bonzini @ 2026-09-02 9:15 ` Zhao Liu 0 siblings, 0 replies; 38+ messages in thread From: Zhao Liu @ 2026-09-02 9:15 UTC (permalink / raw) To: Paolo Bonzini; +Cc: qemu-devel, Zhao Liu > diff --git a/target/i386/cpu.c b/target/i386/cpu.c > index e5ffb10d156..e966bd8049b 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 And NCI/NDD/NF is supported :-) +#define TCG_29_0_EBX_FEATURES CPUID_29_0_EBX_APX_NCI_NDD_NF > #define TCG_7_2_EDX_FEATURES 0 > #define TCG_APM_FEATURES 0 > #define TCG_6_EAX_FEATURES CPUID_6_EAX_ARAT > @@ -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,6 +9722,17 @@ 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. > + */ > + if ((x86_cpu_get_supported_feature_word(cpu, FEAT_7_1_EDX) & CPUID_7_1_EDX_APXF) && > + env->user_features[FEAT_7_0_EBX] & CPUID_7_0_EBX_MPX) { > + feature_word_info[FEAT_7_1_EDX].no_autoenable_flags |= CPUID_7_1_EDX_APXF; > + } else { > + feature_word_info[FEAT_7_0_EBX].no_autoenable_flags |= CPUID_7_0_EBX_MPX; > + } I have some thoughts here: 1. we should check env->features[] instead of env->user_features[] since the latter just indicates whether user touches this feature (maybe on, or maybe off), in x86_cpu_set_bit_prop(). 2. if accel doesn't supports APXF, then it goes to "else" and disable MPX by default - I think this would hurt KVM. 3. changing feature_word_info seems not good - it is a global variable and has longer life time than CPU object - so that changes to it will affect multiple CPU instances (QMP's dummy instance). For example, (QMP) query-cpu-model-expansion type=full model={"name":"max"} [mpx=off, apxf=on] (QMP) query-cpu-model-expansion type=full model={"name":"max","props":{"mpx":true}} [mpx=on, apxf=off] (QMP) query-cpu-model-expansion type=full model={"name":"max"} [mpx=off, apxf=off, NOTE: this gets the different result with 1st try!] So, I think that if we're going to change a feature, we should change the feature list that corresponds to the CPU lifetime, such as env->features[]. But I find it's still tricky to keep this check before FEATURE_WORDS loop. So how about the following instead? - /* - * TCG supports both MPX and APX. Since they they cannot be enabled together, - * disable one---prefer APX if none was chosen explicitly. - */ - if ((x86_cpu_get_supported_feature_word(cpu, FEAT_7_1_EDX) & CPUID_7_1_EDX_APXF) && - env->user_features[FEAT_7_0_EBX] & CPUID_7_0_EBX_MPX) { - feature_word_info[FEAT_7_1_EDX].no_autoenable_flags |= CPUID_7_1_EDX_APXF; - } else { - feature_word_info[FEAT_7_0_EBX].no_autoenable_flags |= CPUID_7_0_EBX_MPX; - } - 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; } + /* + * TCG supports both MPX and APX. Since they they cannot be enabled together, + * disable one---prefer APX if none was chosen explicitly. If both were + * requested explicitly, leave them alone and let the check below report the + * conflict. + */ + if ((env->features[FEAT_7_0_EBX] & CPUID_7_0_EBX_MPX) && + (env->features[FEAT_7_1_EDX] & CPUID_7_1_EDX_APXF)) { + if (!(env->user_features[FEAT_7_0_EBX] & CPUID_7_0_EBX_MPX)) { + env->features[FEAT_7_0_EBX] &= ~CPUID_7_0_EBX_MPX; + } else if (!(env->user_features[FEAT_7_1_EDX] & CPUID_7_1_EDX_APXF)) { + env->features[FEAT_7_1_EDX] &= ~CPUID_7_1_EDX_APXF; + } + } Thanks, Zhao ^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH 20/20] target/i386/tcg: optimize CCMP 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (18 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 19/20] target/i386/tcg: mark APX as supported Paolo Bonzini @ 2026-08-25 12:29 ` Paolo Bonzini 2026-09-02 9:29 ` Zhao Liu 2026-09-08 2:10 ` [PATCH 00/20] target/i386/tcg: implement APX Chang S. Bae 20 siblings, 1 reply; 38+ messages in thread From: Paolo Bonzini @ 2026-08-25 12:29 UTC (permalink / raw) To: qemu-devel If possible, modify conditionally the input arguments to generate the flags requested by dfv; this way CCMP can use CC_OP_SUB* instead of having to compute the carry-out vector by hand. This happens relatively often for dfv=0, and also for dfv=sf for code produced by clang. Of the combinations that cannot be optimized, both GCC and clang generate dfv=zf. GCC also generates OS and OSZ. Do not bother doing this for CTEST; the savings are modest because it does not need complex code to compute the carry-out vector (CC_SRC is always 0). In addition, trivially replacing the arguments to the AND would only support dfv=0 (produced by -2 & 2) or dfv=S (produced by -2 & -2), because TEST cannot produce any value where CF or OF are not zero. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> --- target/i386/tcg/emit.c.inc | 56 ++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/target/i386/tcg/emit.c.inc b/target/i386/tcg/emit.c.inc index 1144953abb1..7641b2fc104 100644 --- a/target/i386/tcg/emit.c.inc +++ b/target/i386/tcg/emit.c.inc @@ -1749,6 +1749,53 @@ static void gen_dfv_movcond(DisasContext *s, X86DecodedInsn *decode, int cond, u decode->cc_dst, tcg_constant_tl(dst)); } +static bool gen_ccmp_movcond(DisasContext *s, int cond, uint32_t dfv, MemOp ot) +{ + target_ulong op0, op1, max_int; + if (cond == CCMP_T) { + return true; + } + + /* + * For some values of dfv, it's possible to overwrite operands + * instead of using CC_OP_CCMP. The weird "16"s are to set AF==0. + */ + max_int = MAKE_64BIT_MASK(0, (8 << ot) - 1); + switch(dfv) { + case 0: + op0 = 1, op1 = 0; + break; + case CC_P|CC_C: + op0 = 1, op1 = -16; + break; + case CC_S: + op0 = -3, op1 = 0; + break; + case CC_S|CC_P|CC_C: + op0 = 0, op1 = 16; + break; + case CC_O: + /* + * While for >8-bit results -max_int has 1 in the lower byte, for 8-bit + * results -max_int is 0x81, so it flips the parity flag. Compensate + * in op1. + */ + op0 = -max_int, op1 = 16 + (ot == MO_8); + break; + case CC_O|CC_S|CC_P|CC_C: + /* Same here. */ + op0 = max_int, op1 = -1 - (ot == MO_8); + break; + default: + return false; + } + + CCPrepare cc = gen_prepare_cc(s, cond, NULL); + tcg_gen_movcond_tl(cc.cond, s->T0, cc.reg, cc.reg2, s->T0, tcg_constant_tl(op0)); + tcg_gen_movcond_tl(cc.cond, s->T1, cc.reg, cc.reg2, s->T1, tcg_constant_tl(op1)); + return true; +} + static void gen_SUB(DisasContext *s, X86DecodedInsn *decode); static void gen_CMP(DisasContext *s, X86DecodedInsn *decode) { @@ -1757,16 +1804,15 @@ static void gen_CMP(DisasContext *s, X86DecodedInsn *decode) MemOp ot = decode->op[1].ot; TCGv cout; - switch (cond) { - case CCMP_T: + if (gen_ccmp_movcond(s, cond, dfv, ot)) { + /* Including CCMP_T, aka "normal" CMP. */ gen_SUB(s, decode); return; - case CCMP_F: + } + if (cond == 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; -- 2.55.0 ^ permalink raw reply related [flat|nested] 38+ messages in thread
* Re: [PATCH 20/20] target/i386/tcg: optimize CCMP 2026-08-25 12:29 ` [PATCH 20/20] target/i386/tcg: optimize CCMP Paolo Bonzini @ 2026-09-02 9:29 ` Zhao Liu 0 siblings, 0 replies; 38+ messages in thread From: Zhao Liu @ 2026-09-02 9:29 UTC (permalink / raw) To: Paolo Bonzini; +Cc: qemu-devel, Zhao Liu On Tue, Aug 25, 2026 at 02:29:21PM +0200, Paolo Bonzini wrote: > Date: Tue, 25 Aug 2026 14:29:21 +0200 > From: Paolo Bonzini <pbonzini@redhat.com> > Subject: [PATCH 20/20] target/i386/tcg: optimize CCMP > X-Mailer: git-send-email 2.55.0 > > If possible, modify conditionally the input arguments to generate the > flags requested by dfv; this way CCMP can use CC_OP_SUB* instead of having > to compute the carry-out vector by hand. This happens relatively often > for dfv=0, and also for dfv=sf for code produced by clang. > > Of the combinations that cannot be optimized, both GCC and clang generate > dfv=zf. GCC also generates OS and OSZ. > > Do not bother doing this for CTEST; the savings are modest because it > does not need complex code to compute the carry-out vector (CC_SRC > is always 0). In addition, trivially replacing the arguments to the AND > would only support dfv=0 (produced by -2 & 2) or dfv=S (produced by -2 & > -2), because TEST cannot produce any value where CF or OF are not zero. > > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> > --- > target/i386/tcg/emit.c.inc | 56 ++++++++++++++++++++++++++++++++++---- > 1 file changed, 51 insertions(+), 5 deletions(-) > > diff --git a/target/i386/tcg/emit.c.inc b/target/i386/tcg/emit.c.inc > index 1144953abb1..7641b2fc104 100644 > --- a/target/i386/tcg/emit.c.inc > +++ b/target/i386/tcg/emit.c.inc > @@ -1749,6 +1749,53 @@ static void gen_dfv_movcond(DisasContext *s, X86DecodedInsn *decode, int cond, u > decode->cc_dst, tcg_constant_tl(dst)); > } > > +static bool gen_ccmp_movcond(DisasContext *s, int cond, uint32_t dfv, MemOp ot) > +{ > + target_ulong op0, op1, max_int; > + if (cond == CCMP_T) { > + return true; > + } should we return false for CCMP_F? Or... > +} > + > static void gen_SUB(DisasContext *s, X86DecodedInsn *decode); > static void gen_CMP(DisasContext *s, X86DecodedInsn *decode) > { > @@ -1757,16 +1804,15 @@ static void gen_CMP(DisasContext *s, X86DecodedInsn *decode) > MemOp ot = decode->op[1].ot; > TCGv cout; > > - switch (cond) { > - case CCMP_T: > + if (gen_ccmp_movcond(s, cond, dfv, ot)) { > + /* Including CCMP_T, aka "normal" CMP. */ > gen_SUB(s, decode); > return; > - case CCMP_F: > + } > + if (cond == CCMP_F) { > decode->cc_op = CC_OP_EFLAGS; > decode->cc_src = tcg_constant_tl(dfv); > return; > - default: > - break; > } ...check the CCMP_F and return first: if (cond == CCMP_F) { ... return; } if (gen_ccmp_movcond(s, cond, dfv, ot)) { ... return; } Thanks, Zhao ^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH 00/20] target/i386/tcg: implement APX 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini ` (19 preceding siblings ...) 2026-08-25 12:29 ` [PATCH 20/20] target/i386/tcg: optimize CCMP Paolo Bonzini @ 2026-09-08 2:10 ` Chang S. Bae 20 siblings, 0 replies; 38+ messages in thread From: Chang S. Bae @ 2026-09-08 2:10 UTC (permalink / raw) To: Paolo Bonzini, qemu-devel; +Cc: Zhao Liu On 8/25/2026 5:29 AM, Paolo Bonzini wrote: > > Other than the first three patches, and other than not reusing decode_root, > this is mostly the same as the RFC[1] since I didn't have much more > time to work on it. System mode emulation was tested very little, > basically nothing more than a simple initrd that runs a few instructions > and does xsave/xrstor. I really respect your taking this on while being busy with so many other things! Otherwise, this could have been in hindsight. Indeed, we Intel folks should help testing effort here at least. I'm sure Zhao did some test as he continues commenting. I just tried to look through what I could digest (if that's helpful), although I'm not familiar with the Qemu side of things. Thanks, Chang ^ permalink raw reply [flat|nested] 38+ messages in thread
end of thread, other threads:[~2026-09-08 2:11 UTC | newest] Thread overview: 38+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-25 12:29 [PATCH 00/20] target/i386/tcg: implement APX Paolo Bonzini 2026-08-25 12:29 ` [PATCH 01/20] target/i386/tcg: do not reuse cc_srcT Paolo Bonzini 2026-08-25 23:09 ` Richard Henderson 2026-08-25 12:29 ` [PATCH 02/20] target/i386/tcg: inline gen_ext_tl Paolo Bonzini 2026-08-25 23:09 ` Richard Henderson 2026-08-25 12:29 ` [PATCH 03/20] target/i386/tcg: simplify return value of gen_prepare_cc Paolo Bonzini 2026-08-25 12:29 ` [PATCH 04/20] target/i386/tcg: move check bits out of validate_vex Paolo Bonzini 2026-08-25 12:29 ` [PATCH 05/20] target/i386/tcg: add APX support to XSAVE/XRSTOR Paolo Bonzini 2026-09-08 2:10 ` Chang S. Bae 2026-08-25 12:29 ` [PATCH 06/20] target/i386/tcg: treat VEX as disabling high-byte registers Paolo Bonzini 2026-08-25 12:29 ` [PATCH 07/20] target/i386/tcg: add definition for REX2 prefix Paolo Bonzini 2026-09-08 2:10 ` Chang S. Bae 2026-08-25 12:29 ` [PATCH 08/20] target/i386/tcg: mark XSAVE* as not allowing REX2 Paolo Bonzini 2026-09-08 2:10 ` Chang S. Bae 2026-08-25 12:29 ` [PATCH 09/20] target/i386/tcg: decode REX2 prefix Paolo Bonzini 2026-09-08 2:10 ` Chang S. Bae 2026-08-25 12:29 ` [PATCH 10/20] target/i386/tcg: implement JMPABS instruction Paolo Bonzini 2026-08-26 14:30 ` Zhao Liu 2026-08-27 6:33 ` Paolo Bonzini 2026-08-25 12:29 ` [PATCH 11/20] target/i386/tcg: fetch modrm early Paolo Bonzini 2026-08-25 12:29 ` [PATCH 12/20] target/i386/tcg: move VEX validation early Paolo Bonzini 2026-08-28 8:32 ` Zhao Liu 2026-08-25 12:29 ` [PATCH 13/20] target/i386/tcg: extend VEX.vvvv parsing for APX Paolo Bonzini 2026-08-25 12:29 ` [PATCH 14/20] target/i386/tcg: decode EVEX prefix Paolo Bonzini 2026-08-28 9:10 ` Zhao Liu 2026-08-25 12:29 ` [PATCH 15/20] target/i386/tcg: add ZU writeback Paolo Bonzini 2026-08-25 12:29 ` [PATCH 16/20] target/i386/tcg: add decode functionality for APX Paolo Bonzini 2026-09-01 14:56 ` Zhao Liu 2026-09-02 6:55 ` Zhao Liu 2026-08-25 12:29 ` [PATCH 17/20] target/i386/tcg: implement CCMP/CTEST Paolo Bonzini 2026-09-02 6:06 ` Zhao Liu 2026-08-25 12:29 ` [PATCH 18/20] target/i386/tcg: decode APX instructions Paolo Bonzini 2026-09-02 7:27 ` Zhao Liu 2026-08-25 12:29 ` [PATCH 19/20] target/i386/tcg: mark APX as supported Paolo Bonzini 2026-09-02 9:15 ` Zhao Liu 2026-08-25 12:29 ` [PATCH 20/20] target/i386/tcg: optimize CCMP Paolo Bonzini 2026-09-02 9:29 ` Zhao Liu 2026-09-08 2:10 ` [PATCH 00/20] target/i386/tcg: implement APX Chang S. Bae
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.