For the complete documentation index, see llms.txt. This page is also available as Markdown.

Hexagon support

IDA disassembles and analyzes Qualcomm Hexagon (QDSP6) code: the VLIW DSP found in Snapdragon modem, audio, sensor, compute, and NPU subsystems. The processor module covers the scalar ISA from V4 through V79, the HVX vector extension (V60 and later), and the HMX matrix extension. "Hexagon" is a property of the code, not of a file format: it is decoded by the processor module once a loader has placed the bytes in the database. The loading section covers how that happens.

Hexagon support is new and still settling. IDA 9.4 ships the disassembler and the analysis layer: cross-references, stack frames, calling-convention support, and switch recovery. The Hexagon decompiler is not part of IDA 9.4.

Hexagon is unlike most architectures IDA targets, so this page starts with the one idea that the rest depends on: the instruction packet. The later sections cover registers, memory addressing, and the display options that control how all of this appears in the listing.

The packet model

Hexagon does not execute one instruction at a time. It executes a packet: a group of one to four 32-bit instructions that issue together. Every instruction in a packet reads its source registers at the same instant, the instructions execute in parallel, and their results are written at the end of the packet. Branch decisions are also taken at packet end. One instruction's result is not visible to another instruction in the same packet, with one exception covered below (.new values).

This changes what a single line of disassembly means. On a sequential machine, the instruction above you has already run. In a Hexagon packet, the instruction above you runs at the same time as the one you are reading. IDA shows packets explicitly so that this is never ambiguous.

The packet boundary is encoded in two bits of every instruction word, bits 15:14, called the parse bits:

Parse bits
Meaning

01

Not the last instruction; the packet continues

11

Last instruction of the packet

10

A hardware-loop-end marker. It does not end the packet: it sits on the first instruction (inner loop, :endloop0) and/or the second (outer loop, :endloop1), and the packet still ends on a later 11 or 00.

00

A constant extender (when the instruction class is 0) or a duplex (otherwise); a duplex ends the packet

IDA reads these bits during analysis, marks the first and last instruction of each packet, resolves .new references, and records the loop-end markers. A packet is at most 16 bytes, so the boundary is always found within four words.

How packets appear in the listing

A packet is wrapped in braces. With the default options, each brace sits on its own line and the instructions are indented beneath it:

{
    r5 = add(r5, #1)
    r6 = memw(r5 + #0xc)
}

Single-instruction packets are braced the same way, which keeps the packet structure uniform down the whole listing:

Brace placement is configurable. You can put the opening brace inline with the first instruction, drop the braces around single-instruction packets, or prefix continuation lines with a pipe instead of spaces. See display and annotation options.

Parallel execution and .new values

Because a packet's writes land together at the end, an instruction normally reads the old value of any register that another instruction in the same packet is writing. The .new qualifier is the exception: it lets an instruction read a value produced earlier in the same packet, before that value reaches the register file.

Here the compare produces p0 and the conditional move consumes p0.new from the same packet. The same applies to register producers feeding a new-value store, such as memw(r0) = r2.new. IDA resolves the producer during analysis and, by default, marks it. The related qualifiers .cur and .tmp (used by HVX loads) are rendered the same way, as register postfixes.

Duplex instructions

For code density, two sub-instructions can be packed into a single 32-bit word, called a duplex. A duplex is always the last word of its packet. The two halves are drawn from a restricted set of operations (small loads, small stores, and common ALU operations) and use a compressed register encoding that reaches a subset of the general registers.

By default IDA shows the two sub-instructions on separate lines, so a duplex reads like any other two-instruction packet:

If you prefer the assembler's single-line form, turn off the duplex line break and the two halves are joined with a semicolon: r21 = add(r21, #1); jumpr r31.

Constant extenders

Hexagon immediates are narrow. To carry a full 32-bit constant, an instruction is preceded by a constant extender word (immext) that supplies the high bits. The extender is not a real operation; it only widens the immediate of the instruction that follows it.

IDA folds the extender into the instruction it extends and shows the resulting 32-bit constant with a doubled hash:

The single # marks an ordinary immediate; the doubled ## marks one that was extended. The extender word does not get its own line.

Hardware loops

Hexagon has zero-overhead hardware loops, set up with loop0/loop1 and bounded by a packet whose parse bits mark a loop end instead of a branch. There are two loop levels, controlled by the sa0/lc0 and sa1/lc1 control registers. IDA marks the loop-end packet with a suffix on the closing brace:

The marker is :endloop0 for the inner loop, :endloop1 for the outer loop, and :endloop01 when both end on the same packet.

Parallel branches

A packet can contain more than one branch. All of the branch predicates are evaluated at packet start, in parallel. If several would be taken, the branch in the lower slot wins and the others are discarded by the hardware; a single target is selected.

Treating such a packet as a sequence of separate conditional branches misrepresents it. A sequential reading implies that the second branch is reached only when the first one falls through, and it attaches a path predicate to each branch that does not match the hardware. The module instead models the packet as one multiway split. It orders the branches by slot priority and derives a path predicate for each edge that accounts for the higher-priority branches: the edge taken at slot 1 carries !pred(slot0) && pred(slot1), and the fall-through edge is the conjunction of every branch's negation. Code references are attached at the packet's start address, not at the individual branch instructions, so the control-flow graph shows one packet-level set of successors.

By default IDA flags these packets visually. The braces are doubled, each instruction can carry its execution slot, and the resolved path predicates are shown as a comment on the closing brace:

The double braces and slot labels are controlled by separate options. The packet-aware control-flow graph (the part that changes how successors and cross-references are computed, not how the packet is drawn) is a distinct option that is off by default; enable use packet-aware parallel branch analysis to turn it on.

The slot shown in the listing is derived from each instruction's position in the packet, not from a full slot-assignment scheduler, so treat it as an approximation of the architectural slot.

Registers

Hexagon register names are written in lowercase in the listing. The module models the full register file:

Class
Registers
Notes

General purpose

r0r31

sp is r29, fp is r30, lr is r31. 64-bit values use even/odd pairs written r1:0 through r31:30.

Scalar predicate

p0p3

Conditional execution and compare results. The four predicates are also accessible as the predicate-pair control register, shown as p3:0.

Control

c0c31

Hardware-loop registers sa0/lc0/sa1/lc1, addressing modifiers m0/m1, the global pointer gp, the program counter pc, and the user status register usr.

HVX vector

v0v31

128-byte vectors. Pairs are written v1:0, quads v3:0. Element types are postfixes: .b, .h, .w, the unsigned forms, and the float forms .sf/.hf/.bf.

HVX predicate

q0q3

Vector masks produced by vector compares.

System and guest

s0s127, g0g31

Privileged (monitor) and virtualization state.

A leading ! negates a predicate (if (!p0) ...); a trailing ++ marks a post-incremented address register.

Memory addressing modes

Hexagon has a wide set of addressing modes. The access size is part of the mnemonic: memb/memub for signed and unsigned bytes, memh/memuh for half-words, memw for words, memd for double-words, and vmem/vmemu for aligned and unaligned HVX vectors. The addressing form is shown inside the parentheses:

Mode
Example

Absolute

memw(##0x1000)

Absolute with register set

memw(r1 = ##0x1000)

Base + offset

memw(r1 + #8)

Base + scaled index

memw(r1 + r2<<#2)

GP-relative

memw(gp + #0x100)

Post-increment by immediate

memw(r1++ #4)

Post-increment by modifier

memw(r1++ m0)

Circular

memw(r1++ #4:circ(m0)), memw(r1++ I:circ(m0))

Bit-reversed

memw(r1++ m0:brev)

Locked (atomic)

memw_locked(r1), memd_locked(r1, p0)

Circular and bit-reversed modes use the m0/m1 modifier registers, which is why those addressing forms reference a control register. Acquire/release and non-temporal accesses carry an infix or suffix on the mnemonic, such as memw_aq or :nt.

Display and annotation options

The Hexagon module exposes two groups of options in its processor options dialog (Options → General… → Analysis → Processor specific analysis options). The display options control packet layout; the annotation options control the extra information the module derives and shows. The tables below give each option, its default, and what it does.

Display options:

Option
Default
Effect

Open brace on its own line

on

Places the opening packet brace { on its own line instead of inline with the first instruction.

Close brace on its own line

on

Places the closing brace } on its own line.

Braces for single-instruction packets

on

Shows braces even when a packet holds one instruction.

Duplex sub-instructions on separate lines

on

Splits a duplex onto two lines; when off, the halves are joined with ;.

Pipe prefix for continuation lines

off

Prefixes continuation lines with `

Annotation options:

Option
Default
Effect

Mark .new producers

on

Marks the instruction that produces a .new value consumed in the same packet.

Mark extender usage

on

Shows that an immediate was widened by a constant extender.

Show hardware loop markers

on

Shows loop/endloop structure.

Stack argument comments

on

Simplifies redundant stack-variable displacements and annotates stack arguments.

Show slot assignment labels

on

Shows [S0][S3] slot labels on instructions in parallel-branch packets.

Use double braces for parallel-branch packets

on

Draws {{ }} around packets that contain parallel branches.

Show resolved path predicates

on

Shows the per-edge path predicates for parallel-branch packets.

Show :mem_noshuf inference

off

Shows the inferred no-shuffle suffix on vector accesses.

Show predicate .new timing

off

Annotates predicates generated and consumed within one packet.

Show memory ordering

off

Comments acquire, release, locked, and non-temporal semantics.

Resolve PC-relative targets

off

Shows the absolute address of a PC-relative operand.

Validate packet structure

off

Checks packet legality and flags problems.

Explain branch hints

off

Comments the meaning of the :t and :nt branch hints.

Use packet-aware parallel branch analysis

off

Builds packet-level multiway control flow for parallel-branch packets.

Log decompiler callback decisions

off

Writes diagnostic callback decisions to the Output window.

The display defaults follow the layout used by the LLVM Hexagon assembler: braces on their own lines, braces around every packet, and duplex halves split across lines.

Loading Hexagon code

A Hexagon ELF identifies itself with the machine type EM_QDSP6 (164). IDA reads this from the ELF header and selects the QDSP6 processor and the Hexagon relocation handler with no manual choice. The ISA version is taken from the ELF flags and shown at load time, so you can see whether a binary targets, say, V66 or V73.

Hexagon code also arrives embedded in Qualcomm MBN firmware. MBN is a container format, not a Hexagon one: the same loader extracts ARM and AArch64 images as readily as Hexagon ones, and a single container can hold several. For the container format and the loading procedure, see Loading Qualcomm MBN containers.

See also

Last updated

Was this helpful?