Skip to content

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

Open
germiBest wants to merge 1 commit into
capstone-engine:nextfrom
germiBest:xtensa-gate-esp32s3-ops
Open

Xtensa: gate ESP32-S3 and HIFI3 ops behind CS_MODE_XTENSA_ESP32S3#2979
germiBest wants to merge 1 commit into
capstone-engine:nextfrom
germiBest:xtensa-gate-esp32s3-ops

Conversation

@germiBest

Copy link
Copy Markdown

Your checklist for this pull request

  • I've documented or updated the documentation of every API function and struct this PR changes.
  • I've added tests that prove my fix is effective or that my feature works (if possible)

Detailed description

The Xtensa disassembler enabled the ESP32-S3 SIMD/AI (ee.*) and HiFi3 ops for every target,
because the subtarget feature gates in arch/Xtensa/XtensaDisassembler.c were stubbed to always
return true: Xtensa_getFeatureBits() was // we support everything; return true;, and
hasDensity() / hasESP32S3Ops() / hasHIFI3() were hardcoded return true;.

Because op0 0xE/0xF always has bit 3 set, on a non-ESP32-S3 Xtensa config any such byte was
matched as a 4-byte ee.* op via the ESP32-S3 32-bit table, so a base-ISA byte stream over-read by
one byte and desynced everything after it. LLVM itself gates these ops behind a real subtarget
feature and only decodes them for an esp32s3 target; capstone's auto-translated port dropped that
gating.

Found on real MediaTek MT7961 Wi-Fi firmware (Tensilica Xtensa LX with a vendor TIE coprocessor;
those op0 0xE/0xF ops are 3-byte vendor ops, not ESP32-S3 ee.*).

This PR:

  • Makes Xtensa_getFeatureBits(mode, feature) map the mode to a feature set, mirroring the SystemZ
    precedent in arch/SystemZ/SystemZDisassemblerExtension.c. ESP32S3Ops and HIFI3 are enabled
    only when the new CS_MODE_XTENSA_ESP32S3 is set; Density stays on (base Tensilica default);
    anything else keeps the allow-all fallback.
  • Threads MI->csh->mode into hasDensity() / hasESP32S3Ops() / hasHIFI3() and their call
    sites in getInstruction(). The generated checkDecoderPredicate already reads the mode, so this
    single change gates both the outer probe and the inner table predicate, and readInstruction32 is
    no longer entered for op0 0xE/0xF in non-S3 modes.
  • Adds an opt-in CS_MODE_XTENSA_ESP32S3, wired through cs.c (valid-mode mask), cstool, and the
    python binding.
  • Updates the suite/auto-sync saved patch hashes for the four functions so the gated bodies survive
    the next LLVM re-sync. The stubs had been re-introduced by auto-sync, which is why this regressed
    silently; without this the fix would be reverted on the next sync.

This is a concrete instance of #1992 (features enabled by default produce wrong disassembly on
targets that do not have them).

Behavior change worth calling out: ESP32-S3 SIMD/AI and HiFi3 ops become opt-in via
CS_MODE_XTENSA_ESP32S3. Callers that previously received ee.* without passing a mode will need to
pass the new mode. That previous behavior was the bug, since it mis-decoded every non-ESP32-S3 Xtensa
config.

Files changed: arch/Xtensa/XtensaDisassembler.c, cs.c, include/capstone/capstone.h,
cstool/cstool.c, bindings/python/capstone/__init__.py, suite/cstest/include/test_mapping.h,
suite/auto-sync/src/autosync/cpptranslator/saved_patches.json, and new
tests/MC/Xtensa/esp32s3.s.yaml.

Test plan

Before/after with cstool on real MT7961 bytes 9e3e0940 and 3e194548:

# before
cstool esp32   9e3e0940  ->  ee.vmulas.u16.accx.ld.ip.qup q1, a9, 0xe0, ...  (size 4)   wrong

# after
cstool esp32   9e3e0940  ->  invalid assembly code                                       base config, correct
cstool esp32   3e194548  ->  invalid assembly code
cstool esp32s3 9e3e0940  ->  ee.vmulas.u16.accx.ld.ip.qup q1, a9, 0xe0, ...  (size 4)   preserved
cstool esp32s3 3e194548  ->  ee.vmulas.u16.accx.ld.ip.qup q0, a3, ...        (size 4)   preserved

Base and density ops still decode in every mode (no regression):

cstool esp32   002000  ->  isync           cstool esp32s3 002000  ->  isync
cstool esp32   0020c0  ->  sub a2, a0, a0   cstool esp32s3 0df0    ->  ret.n
cstool esp32   0df0    ->  ret.n

Added tests/MC/Xtensa/esp32s3.s.yaml: 2 positive cases (both vectors decode
ee.vmulas.u16.accx.ld.ip.qup, size 4, under CS_MODE_XTENSA_ESP32S3) and 4 negative cases (both
vectors expect a decode failure under CS_MODE_XTENSA_ESP32 and base CS_MODE_LITTLE_ENDIAN). These
follow the existing Xtensa test convention under tests/MC/Xtensa/ (this tree has no
suite/cstest/issues.cs; all Xtensa disassembly tests are MC YAML files there).

Full run: tests/MC/Xtensa/ 105/105 pass (99 pre-existing + 6 new), tests/details/xtensa.yaml
3/3 pass.

Closing issues

No existing issue tracks this specific Xtensa decode bug. It is a concrete instance of the umbrella
issue #1992, which this PR does not fully close.

@github-actions github-actions Bot added Python Bindings CS-core-files auto-sync Auto-Sync-files Auto-Sync Xtensa Arch labels Jun 28, 2026
@germiBest germiBest force-pushed the xtensa-gate-esp32s3-ops branch from fd7af34 to 84545f4 Compare June 28, 2026 09:59
Comment thread suite/auto-sync/src/autosync/cpptranslator/saved_patches.json
@Rot127

Rot127 commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

@b1llow Please take a look.

@Rot127

Rot127 commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

@germiBest Please fix the existing tests

@germiBest

Copy link
Copy Markdown
Author

working on it, my bad, lost some changes on local

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 capstone-engine#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.
@germiBest germiBest force-pushed the xtensa-gate-esp32s3-ops branch from 84545f4 to 8d4ab6e Compare June 28, 2026 14:49
@germiBest

Copy link
Copy Markdown
Author

tests are resolved

@wargio wargio left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a quite reasonable patch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants