Xtensa: gate ESP32-S3 and HIFI3 ops behind CS_MODE_XTENSA_ESP32S3 (#2979)

The Xtensa disassembler enabled the ESP32-S3 SIMD/AI (ee.*) and HiFi3 ops for
every target because the subtarget feature gates were stubbed to return true
(Xtensa_getFeatureBits and hasDensity/hasESP32S3Ops/hasHIFI3). As a result, on a
non-ESP32-S3 Xtensa config any instruction with op0=0xE/0xF was matched as a
4-byte ee.* op, so a base-ISA byte stream desynced.

Make Xtensa_getFeatureBits map the mode to a feature set (mirroring the SystemZ
precedent), thread MI->csh->mode into the three has*Ops gates, and add an opt-in
CS_MODE_XTENSA_ESP32S3. Base/esp32/esp32s2/esp8266 no longer emit ESP32-S3 ops;
CS_MODE_XTENSA_ESP32S3 preserves them. Concrete instance of issue #1992.

The saved auto-sync patch hashes are updated so the fix survives the next LLVM
re-sync. Adds tests/MC/Xtensa/esp32s3.s.yaml.

Co-authored-by: Denys Melnyk <denys@com2cloud.com>
This commit is contained in:
Denys Melnyk 2026-07-22 09:13:28 -07:00 committed by GitHub
parent a0b10fee51
commit 4eb90672f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 135 additions and 18 deletions

View file

@ -235,8 +235,23 @@ static DecodeStatus DecodeMR23RegisterClass(MCInst *Inst, uint64_t RegNo,
bool Xtensa_getFeatureBits(unsigned int mode, unsigned int feature)
{
// we support everything
return true;
switch (feature) {
case Xtensa_FeatureESP32S3Ops:
// SIMD/AI "ee.*" ops only exist on the ESP32-S3.
return (mode & CS_MODE_XTENSA_ESP32S3) != 0;
case Xtensa_FeatureHIFI3:
// HiFi3 DSP ops are gated behind the ESP32-S3 in this tree.
return (mode & CS_MODE_XTENSA_ESP32S3) != 0;
case Xtensa_FeatureDensity:
// Code Density is a base Tensilica default option.
return true;
default:
// Default case is the "allow all features", which is normal
// Capstone behavior until
// https://github.com/capstone-engine/capstone/issues/1992
// is implemented.
return true;
}
}
// Verify SR and UR
@ -1036,17 +1051,17 @@ DecodeToMCInst(decodeToMCInst_6, fieldFromInstruction_6, uint64_t);
DecodeInstruction(decodeInstruction_6, fieldFromInstruction_6, decodeToMCInst_6,
uint64_t);
static bool hasDensity()
static bool hasDensity(MCInst *MI)
{
return true;
return Xtensa_getFeatureBits(MI->csh->mode, Xtensa_FeatureDensity);
}
static bool hasESP32S3Ops()
static bool hasESP32S3Ops(MCInst *MI)
{
return true;
return Xtensa_getFeatureBits(MI->csh->mode, Xtensa_FeatureESP32S3Ops);
}
static bool hasHIFI3()
static bool hasHIFI3(MCInst *MI)
{
return true;
return Xtensa_getFeatureBits(MI->csh->mode, Xtensa_FeatureHIFI3);
}
static DecodeStatus getInstruction(MCInst *MI, uint64_t *Size,
@ -1058,7 +1073,7 @@ static DecodeStatus getInstruction(MCInst *MI, uint64_t *Size,
bool IsLittleEndian = MI->csh->mode & CS_MODE_LITTLE_ENDIAN;
// Parse 16-bit instructions
if (hasDensity()) {
if (hasDensity(MI)) {
Result = readInstruction16(MI, Bytes, BytesLen, Address, Size,
&Insn, IsLittleEndian);
if (Result == MCDisassembler_Fail)
@ -1084,7 +1099,7 @@ static DecodeStatus getInstruction(MCInst *MI, uint64_t *Size,
return Result;
}
if (hasESP32S3Ops()) {
if (hasESP32S3Ops(MI)) {
// Parse ESP32S3 24-bit instructions
Result = readInstruction24(MI, Bytes, BytesLen, Address, Size,
&Insn, IsLittleEndian, true);
@ -1111,7 +1126,7 @@ static DecodeStatus getInstruction(MCInst *MI, uint64_t *Size,
}
}
if (hasHIFI3()) {
if (hasHIFI3(MI)) {
Result = decodeInstruction_3(DecoderTableHIFI324, MI, Insn,
Address, NULL);
if (Result != MCDisassembler_Fail)

View file

@ -217,6 +217,10 @@ __all__ = [
"CS_MODE_SYSTEMZ_Z15",
"CS_MODE_SYSTEMZ_Z16",
"CS_MODE_SYSTEMZ_GENERIC",
"CS_MODE_XTENSA_ESP32",
"CS_MODE_XTENSA_ESP32S2",
"CS_MODE_XTENSA_ESP8266",
"CS_MODE_XTENSA_ESP32S3",
"CS_OPT_SYNTAX",
"CS_OPT_SYNTAX_DEFAULT",
"CS_OPT_SYNTAX_INTEL",
@ -568,6 +572,10 @@ CS_MODE_SYSTEMZ_Z14 = 1 << 12
CS_MODE_SYSTEMZ_Z15 = 1 << 13
CS_MODE_SYSTEMZ_Z16 = 1 << 14
CS_MODE_SYSTEMZ_GENERIC = 1 << 15
CS_MODE_XTENSA_ESP32 = 1 << 1 # Xtensa ESP32
CS_MODE_XTENSA_ESP32S2 = 1 << 2 # Xtensa ESP32S2
CS_MODE_XTENSA_ESP8266 = 1 << 3 # Xtensa ESP8266
CS_MODE_XTENSA_ESP32S3 = 1 << 4 # Xtensa ESP32-S3 (SIMD/AI "ee.*" ops)
# Capstone option type
CS_OPT_INVALID = 0 # No option specified

2
cs.c
View file

@ -262,7 +262,7 @@ typedef struct cs_arch_config {
Xtensa_global_init, \
Xtensa_option, \
~(CS_MODE_XTENSA_ESP32 | CS_MODE_XTENSA_ESP32S2 | \
CS_MODE_XTENSA_ESP8266), \
CS_MODE_XTENSA_ESP8266 | CS_MODE_XTENSA_ESP32S3), \
}
#define CS_ARCH_CONFIG_ARC \

View file

@ -540,6 +540,7 @@ static struct {
CS_MODE_LOONGARCH64 },
{ "esp32", "Xtensa ESP32", CS_ARCH_XTENSA, CS_MODE_XTENSA_ESP32 },
{ "esp32s2", "Xtensa ESP32S2", CS_ARCH_XTENSA, CS_MODE_XTENSA_ESP32S2 },
{ "esp32s3", "Xtensa ESP32S3", CS_ARCH_XTENSA, CS_MODE_XTENSA_ESP32S3 },
{ "esp8266", "Xtensa ESP8266", CS_ARCH_XTENSA, CS_MODE_XTENSA_ESP8266 },
{ "arc", "ARC Little-Endian", CS_ARCH_ARC, CS_MODE_LITTLE_ENDIAN },

View file

@ -341,6 +341,7 @@ typedef enum cs_mode {
CS_MODE_XTENSA_ESP32 = 1 << 1, ///< Xtensa ESP32
CS_MODE_XTENSA_ESP32S2 = 1 << 2, ///< Xtensa ESP32S2
CS_MODE_XTENSA_ESP8266 = 1 << 3, ///< Xtensa ESP328266
CS_MODE_XTENSA_ESP32S3 = 1 << 4, ///< Xtensa ESP32S3
} cs_mode;
typedef void *(CAPSTONE_API *cs_malloc_t)(size_t size);

View file

@ -2372,7 +2372,7 @@
},
"Xtensa_getFeatureBits": {
"apply_type": "OLD",
"old_hash": "126cff581e1a79a655c62e3489e238f7bf00647749f619230ab77061d0423295",
"old_hash": "54f9c8bb5264b1b92f2ebc7ba4bf70aaa7f203d537ca9e806ab6b4d69c397474",
"new_hash": "",
"edit": ""
},
@ -2390,19 +2390,19 @@
},
"hasDensity": {
"apply_type": "OLD",
"old_hash": "da63483f075aba3c9f8937fb1900c646cb15f01b6b2e25568e6dcaa940b4798b",
"old_hash": "8bf85ed5fd8d904a43c0504ae5dedebaa8a4838be3c3d761f1c24e6625473d7e",
"new_hash": "",
"edit": ""
},
"hasESP32S3Ops": {
"apply_type": "OLD",
"old_hash": "464131365b24dec185e04d43154a85d9765a50a18214888c26014d8d85165a99",
"old_hash": "87849edd4b0f36b2f057f384624847b1f2dccd52f4070f57795bcb3a46bd98cb",
"new_hash": "",
"edit": ""
},
"hasHIFI3": {
"apply_type": "OLD",
"old_hash": "e1fcb3c8f47f38e571d1a81e7e522efb5c67e0aea05fd7b3980aa1eb3e71c9ff",
"old_hash": "7780479933da49a6e4b4c59d1cebbd6ba0c6cfa8877950d8b03e2b101f6d94dd",
"new_hash": "",
"edit": ""
},

View file

@ -255,6 +255,7 @@ static const cs_enum_id_map test_mode_map[] = {
{ .str = "CS_MODE_V9", .val = CS_MODE_V9 },
{ .str = "CS_MODE_XTENSA_ESP32", .val = CS_MODE_XTENSA_ESP32 },
{ .str = "CS_MODE_XTENSA_ESP32S2", .val = CS_MODE_XTENSA_ESP32S2 },
{ .str = "CS_MODE_XTENSA_ESP32S3", .val = CS_MODE_XTENSA_ESP32S3 },
{ .str = "CS_MODE_XTENSA_ESP8266", .val = CS_MODE_XTENSA_ESP8266 },
};

View file

@ -0,0 +1,58 @@
test_cases:
# Positive: the ESP32-S3 SIMD/AI "ee.*" ops only decode when the
# CS_MODE_XTENSA_ESP32S3 feature is selected. Vector taken from real
# MT7961 firmware (it happens to also be a valid ESP32-S3 ee.vmulas).
-
input:
name: "ee.vmulas decodes on ESP32-S3"
bytes: [ 0x9e, 0x3e, 0x09, 0x40 ]
arch: "CS_ARCH_XTENSA"
options: [ "CS_MODE_XTENSA_ESP32S3" ]
address: 0x0
expected:
insns:
-
asm_text: "ee.vmulas.u16.accx.ld.ip.qup q1, a9, 0xe0, q0, q6, q0, q1"
size: 4
# Negative (ESP32): without the ESP32-S3 feature the 4-byte ee.* encoding
# must NOT be produced. These bytes do not form a valid base/ESP32
# instruction, so decoding fails (zero instructions).
-
input:
name: "ee.vmulas must NOT decode on ESP32 (#1)"
bytes: [ 0x9e, 0x3e, 0x09, 0x40 ]
arch: "CS_ARCH_XTENSA"
options: [ "CS_MODE_XTENSA_ESP32" ]
address: 0x0
expected:
insns: []
-
input:
name: "ee.vmulas must NOT decode on ESP32 (#2)"
bytes: [ 0x3e, 0x19, 0x45, 0x48 ]
arch: "CS_ARCH_XTENSA"
options: [ "CS_MODE_XTENSA_ESP32" ]
address: 0x0
expected:
insns: []
# Negative (base Tensilica, no vendor feature bits): same expectation.
-
input:
name: "ee.vmulas must NOT decode on base Xtensa (#1)"
bytes: [ 0x9e, 0x3e, 0x09, 0x40 ]
arch: "CS_ARCH_XTENSA"
options: [ "CS_MODE_LITTLE_ENDIAN" ]
address: 0x0
expected:
insns: []
-
input:
name: "ee.vmulas must NOT decode on base Xtensa (#2)"
bytes: [ 0x3e, 0x19, 0x45, 0x48 ]
arch: "CS_ARCH_XTENSA"
options: [ "CS_MODE_LITTLE_ENDIAN" ]
address: 0x0
expected:
insns: []

View file

@ -65,3 +65,35 @@ test_cases:
- type: XTENSA_OP_IMM
imm: -1
access: CS_AC_READ
# ee.* op, gated behind CS_MODE_XTENSA_ESP32S3 (MT7961 firmware bytes).
- input:
bytes: [ 0x9e, 0x3e, 0x09, 0x40 ]
arch: "CS_ARCH_XTENSA"
options: [ CS_MODE_XTENSA_ESP32S3, CS_OPT_DETAIL ]
expected:
insns:
- asm_text: "ee.vmulas.u16.accx.ld.ip.qup q1, a9, 0xe0, q0, q6, q0, q1"
details:
xtensa:
operands:
- type: XTENSA_OP_REG
reg: q1
access: CS_AC_WRITE
- type: XTENSA_OP_REG
reg: a9
access: CS_AC_READ
- type: XTENSA_OP_IMM
imm: 0xe0
access: CS_AC_READ
- type: XTENSA_OP_REG
reg: q0
access: CS_AC_READ
- type: XTENSA_OP_REG
reg: q6
access: CS_AC_READ
- type: XTENSA_OP_REG
reg: q0
access: CS_AC_READ
- type: XTENSA_OP_REG
reg: q1
access: CS_AC_READ

View file

@ -197,7 +197,8 @@ static void test_ub_isintn_xtensa_offset(void)
static const uint8_t code[] = { 0x2f, 0x70, 0x44, 0x84 };
csh handle;
if (cs_open(CS_ARCH_XTENSA, CS_MODE_XTENSA_ESP32, &handle) != CS_ERR_OK)
if (cs_open(CS_ARCH_XTENSA, CS_MODE_XTENSA_ESP32S3, &handle) !=
CS_ERR_OK)
return;
cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON);

View file

@ -6239,7 +6239,7 @@ test_cases:
name: "#2986 Xtensa decodeOffset_16_16Operand swapped isIntN arguments"
bytes: [ 0x2f, 0x70, 0x44, 0x84 ]
arch: "CS_ARCH_XTENSA"
options: [ CS_MODE_XTENSA_ESP32, CS_OPT_DETAIL ]
options: [ CS_MODE_XTENSA_ESP32S3, CS_OPT_DETAIL ]
address: 0x0
expected:
insns: