From 8d4ab6efaf7e55d53b3a0dd782a223c55c142eab Mon Sep 17 00:00:00 2001 From: Denys Melnyk Date: Sun, 28 Jun 2026 01:27:30 +0200 Subject: [PATCH] Xtensa: gate ESP32-S3 and HIFI3 ops behind CS_MODE_XTENSA_ESP32S3 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. --- arch/Xtensa/XtensaDisassembler.c | 37 ++++++++---- bindings/python/capstone/__init__.py | 8 +++ cs.c | 2 +- cstool/cstool.c | 1 + include/capstone/capstone.h | 1 + .../autosync/cpptranslator/saved_patches.json | 8 +-- suite/cstest/include/test_mapping.h | 1 + tests/MC/Xtensa/esp32s3.s.yaml | 58 +++++++++++++++++++ 8 files changed, 100 insertions(+), 16 deletions(-) create mode 100644 tests/MC/Xtensa/esp32s3.s.yaml diff --git a/arch/Xtensa/XtensaDisassembler.c b/arch/Xtensa/XtensaDisassembler.c index 2c1e117f53..a721a26f71 100644 --- a/arch/Xtensa/XtensaDisassembler.c +++ b/arch/Xtensa/XtensaDisassembler.c @@ -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) diff --git a/bindings/python/capstone/__init__.py b/bindings/python/capstone/__init__.py index b1c8df3ed9..149d437290 100755 --- a/bindings/python/capstone/__init__.py +++ b/bindings/python/capstone/__init__.py @@ -216,6 +216,10 @@ "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", @@ -565,6 +569,10 @@ 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 diff --git a/cs.c b/cs.c index 257e70f6c7..7b7aef59d6 100644 --- a/cs.c +++ b/cs.c @@ -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 \ diff --git a/cstool/cstool.c b/cstool/cstool.c index 9d8c44334e..b18d39dc80 100644 --- a/cstool/cstool.c +++ b/cstool/cstool.c @@ -535,6 +535,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 }, diff --git a/include/capstone/capstone.h b/include/capstone/capstone.h index f4e127c058..3fedadd874 100644 --- a/include/capstone/capstone.h +++ b/include/capstone/capstone.h @@ -340,6 +340,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); diff --git a/suite/auto-sync/src/autosync/cpptranslator/saved_patches.json b/suite/auto-sync/src/autosync/cpptranslator/saved_patches.json index eb8fb34de0..9e2d8b6386 100644 --- a/suite/auto-sync/src/autosync/cpptranslator/saved_patches.json +++ b/suite/auto-sync/src/autosync/cpptranslator/saved_patches.json @@ -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": "" }, diff --git a/suite/cstest/include/test_mapping.h b/suite/cstest/include/test_mapping.h index 81843f6694..9da5d81d39 100644 --- a/suite/cstest/include/test_mapping.h +++ b/suite/cstest/include/test_mapping.h @@ -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 }, }; diff --git a/tests/MC/Xtensa/esp32s3.s.yaml b/tests/MC/Xtensa/esp32s3.s.yaml new file mode 100644 index 0000000000..d3bf9cdd0e --- /dev/null +++ b/tests/MC/Xtensa/esp32s3.s.yaml @@ -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: []