Skip to content

PowerPC: lift more instructions, model reservations and barriers, fix decoding and lifting bugs - #8379

Open
Marvisak wants to merge 8 commits into
Vector35:devfrom
Marvisak:powerpc-improvements
Open

PowerPC: lift more instructions, model reservations and barriers, fix decoding and lifting bugs#8379
Marvisak wants to merge 8 commits into
Vector35:devfrom
Marvisak:powerpc-improvements

Conversation

@Marvisak

@Marvisak Marvisak commented Aug 1, 2026

Copy link
Copy Markdown

Summary

A batch of PowerPC improvements: fills in lifting for a large number of instructions the decoder already recognized but that were dropped on the floor at the IL layer, models the load-and-reserve / store-conditional pair properly, adds intrinsic modeling for barriers and system instructions, and fixes a series of decoding and lifting bugs found along the way — including several that corrupted address dataflow on ppc64.

Decoder fixes

mtmsr / mtmsrd — the optional L bit sits in the low bit of the RA field slot, but the decoder required the entire field to be zero, so mtmsrd rS, 1 decoded as invalid. The check now masks off the L bit (a & 0x1e), and L is emitted as a UIMM operand when set, so both mtmsrd rS and mtmsrd rS, 1 disassemble.

mtocrf — decoded its operands as rS, FXM, reversed from mtcrf, the assembler's own grammar (mtocrf NUM, GPR), and the ISA syntax, so round-tripping assembly through the disassembler was broken. It now decodes as FXM, rS.

Reservation / atomics modeling

stwcx. was previously lifted as a plain unconditional store and never touched CR0, and lwarx / ldarx / stdcx. had no lifting at all. Two new intrinsics — __set_reservation and __check_reservation — model the reservation:

  • lwarx / ldarx / lbarx / lharx emit __set_reservation(ea) after the load.
  • stwcx. / stdcx. / stbcx. / sthcx. branch on __check_reservation(ea): on success the store is performed and CR0 is set to 0b00 || 1 || XER[SO]; on failure no store happens and CR0 reports failure.

This mirrors how the existing MIPS (ll/sc) and ARMv7/AArch64 (ldrex/strex) lifters model store-conditional, so the resulting IL should look familiar.

Newly lifted instructions

Integer

  • Count leading/trailing zeroscntlzw[.] was lifted through a __builtin_clz intrinsic; it now uses the native CountLeadingZeros IL op, and cntlzd[.], cnttzw[.], cnttzd[.] are lifted the same way. All four honor the Rc bit and write CR0.
  • Load word algebraiclwa, lwax, lwaux (load 4 bytes, sign-extend into the destination register; lwaux updates rA).
  • 64-bit multiply/dividemulld[.], mulhd[.], mulhdu[.], divd[.], divdu[.].
  • Extended dividesdivwe[.]/divweu[.] as widened 64-bit divides of rA << 32, and divde[.]/divdeu[.] via double-precision division of a 128-bit dividend.
  • Modulomodsw, moduw, modsd, modud.
  • extswsli[.], setb (computed branchlessly as GT - LT of the CR field's flags), and cmpeqb / cmprb (P9 byte compares, setting the target CR field's EQ from the byte match / range check).
  • PC materializationlnia / addpcis lift to the actual next-instruction-address constant, which unlocks downstream analysis of position-independent code.
  • Byte-reversed doublewordldbrx / stdbrx (the byte-reverse helper is generalized to 8-byte operations).
  • String opslswi / stswi are unrolled at lift time: the byte count is an immediate, so full words become word loads/stores (with register wraparound past r31) and the final partial register becomes explicit byte accesses, left-justified per the ISA.
  • CR field movesmfocrf / mtocrf (single-field variants of the already-lifted mfcr / mtcrf).

Traps

Previously only twu was lifted (as an unconditional trap). Now the full TO field is decoded for td/tw/tdi/twi and all of their extended mnemonics (tdeq, twlgt, twllei, …), producing an If on the corresponding comparison guarding the Trap. Arbitrary TO values on the generic forms fall back to OR-ing together the enabled comparisons; TO == 0 lifts to a nop and TO == 0x1f to an unconditional trap. The canonical trap encoding (tw 31,0,0) — what compilers emit for __builtin_trap and assertion failures — gets its own decoder ID and is now covered too.

Floating point

  • Conversionsfctiw[u][z.], fctid[u][z.]FloatToInt; fcfid[u][s.]IntToFloat. (LLIL has no unsigned or rounding-mode-aware conversion ops, so the u/z variants map to the signed conversion.)
  • Roundingfrin/friz/frip/frimRoundToInt/FloatTrunc/Ceil/Floor.
  • fsqrt[s.], fsel (compare-against-zero select), fcpsgn (sign/magnitude bit ops).
  • Integer-word FPR accesseslfiwax, lfiwzx, stfiwx.
  • Missing load/store formslfdx/lfdu/lfdux and stfsu/stfsux/stfdx/stfdu/stfdux, matching the existing lfs/stfs conventions.

Control flow / misc

  • rfid, hrfid, rfci, rfdi, rfmci, rfebb lift as returns like rfi, so blocks terminate properly in kernel/firmware code.
  • xnop lifts as a nop.

System and barrier instructions as intrinsics

Following the __set_reservation pattern:

  • Memory barrierssync, lwsync, ptesync, eieio, isync, mbar__sync, __lwsync, ….
  • Cache maintenance with visible effectsdcbz/dcbzl (block zeroing), dcbf, dcbst, dcbi, icbi, each taking the computed effective address. Pure placement hints (dcbt, dcbtt, dcbtst, dcbtstt, dcba) lift to nops.
  • Bit operations without LLIL equivalentspopcntb/popcntw/popcntd, cmpb, bpermd, darn.
  • Time basemftb / mftbu.
  • MSR/SPR accessmfmsr and mfspr upgraded from rD = unimplemented placeholders to __mfmsr() / __mfspr(spr); mtmsr/mtmsrd and mtspr newly lifted as their write counterparts.
  • FPSCR accessmffs, mtfsf, mtfsb0, mtfsb1.
  • mfxer / mtxer are lifted in pure IL instead: since SO/OV/CA are already modeled as flags, they're assembled into / split out of their XER bit positions directly.

Lifting bug fixes

  • stdu never updated rA — the update branch tested for PPC_ID_STWU, which can't reach that case. Since stdu r1, -N(r1) is the standard ppc64 prologue, this broke stack-pointer tracking for essentially every 64-bit function. ldu had the identical bug (tested PPC_ID_LWZU).
  • ldux never updated rA — same dead-guard pattern (tested PPC_ID_LWZUX), and it also computed the wrong value, writing back rA instead of rA + rB.
  • lbzux / lhzux / lhaux / lwzux updates were no-ops — for these X-form loads the "update" emitted rA = rA; they now write rA = rA + rB.
  • lbzu passed the address size into operToIL's options parameter instead of the register-size parameter, computing the update at 4 bytes on ppc64.
  • 64-bit truncation in the ld/std familyld/ldu/ldx/ldux/ldarx/stdx/stdux read their base/index registers at 4 bytes (the operToIL default), computing every address in 32-bit math on ppc64; stdx also read the stored value at 4 bytes. stfs/stfsx/stfd had the same 4-byte address math.
  • load_float hardcoded 32-bit address math and update writes (wrong on ppc64), compared the base register against 0 instead of PPC_REG_GPR0 (so lfs/lfd with rA=0 read r0 instead of using literal zero), and produced invalid IL for X-form loads with rA=0.
  • fctiwz had a broken lifting: it "converted" with FloatTrunc (a float→float rounding, so no integer ever materialized), read the double at 4 bytes, and left the result in the wrong half of the register due to an IBM bit-numbering mix-up. It now goes through the same conversion path as the rest of the fcti* family.

Notes

The PPCIntrinsic enum has been renumbered (the CNTLZW intrinsic was removed and the reservation/barrier/system intrinsics added). Databases saved with the old numbering may display stale intrinsic names for the paired-single intrinsics; fresh analysis is unaffected.

@CLAassistant

CLAassistant commented Aug 1, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

- stdu/ldu never updated rA (guards checked the wrong instruction ID)
- lbzux/lhzux/lhaux/lwzux updates were no-ops (rA = rA instead of rA + rB)
- lbzu passed the address size into operToIL's options parameter
- ld/ldu/ldx/ldux/ldarx/stdx/stdux read registers at 4 bytes, truncating
  addresses (and stdx's stored value) to 32 bits on ppc64
- load_float used hardcoded 32-bit address math and update writes
- trap lifting now emits a nop for the TO=0 never-trap form and guards
  operand counts
- trap: the canonical unconditional trap encoding (tw 31,0,0) now lifts
  to a trap instead of unimplemented
- lnia/addpcis: PC materialization lifts to the actual address constant
- lbarx/lharx and stbcx./sthcx.: byte/halfword reservation forms wired
  into the existing load-reserve/store-conditional modeling
- lfdx/lfdu/lfdux and stfsu/stfsux/stfdx/stfdu/stfdux: double/update FP
  load and store forms, matching the existing lfs/stfs conventions
- ldbrx/stdbrx: byte-reversed doubleword accesses (ByteReverseRegister
  generalized to 8-byte operations)
- rfid/hrfid/rfci/rfdi/rfmci/rfebb: lifted as returns like rfi so blocks
  terminate properly in kernel/firmware code
- xnop lifts to a nop

Also fixes in this area: load_float compared the base register against 0
instead of PPC_REG_GPR0 (lfs/lfd with rA=0 read r0 instead of using 0),
its X-form path produced invalid IL for rA=0, and stfs/stfsx/stfd
computed addresses with 4-byte math on ppc64.
- fsqrt/fsqrts -> FloatSqrt; frin/friz/frip/frim -> RoundToInt/
  FloatTrunc/Ceil/Floor
- fctiw[u][z]/fctid[u][z] -> FloatToInt and fcfid[u][s] -> IntToFloat
  (LLIL has no unsigned or rounding-mode-aware conversions, so u/z
  variants map to the signed conversion)
- fsel lifted as a compare-and-select, fcpsgn as sign/magnitude bit ops
- lfiwax/lfiwzx/stfiwx integer-word FPR accesses
- extswsli, divwe/divweu (widened 64-bit divide) and divde/divdeu
  (double-precision divide)
- lswi/stswi unrolled into word/byte loads and stores at lift time
- setb from the CR field's LT/GT flags

Replaces the previous fctiwz lifting, which never converted to an
integer (FloatTrunc is a float->float rounding), read the double at
4 bytes, and left the result in the wrong half of the register due to
an IBM bit-numbering mix-up.
- cmpeqb: EQ of the target CR field set from an OR of the 8 byte
  compares; cmprb: EQ from the range check(s) of rA's low byte against
  the bounds in rB, honoring the L operand
- mfocrf: builds the selected CR field's bits in their CR positions;
  mtocrf: shares the mtcrf lifting (same per-field flag writes)
- mtocrf's decoded operand order was rS, FXM - reversed from mtcrf, the
  assembler's grammar (mtocrf NUM, GPR), and the ISA syntax; now decodes
  as FXM, rS
Follows the __set_reservation/__check_reservation pattern:

- memory barriers: sync/lwsync/ptesync/eieio/isync/mbar
- cache maintenance with visible effects: dcbz/dcbzl (block zeroing),
  dcbf/dcbst/dcbi/icbi, all taking the effective address; pure hints
  (dcbt/dcbtt/dcbtst/dcbtstt/dcba) lift to nops
- bit operations without LLIL equivalents: popcntb/popcntw/popcntd,
  cmpb, bpermd, darn
- time base reads: mftb/mftbu
- MSR/SPR access: mfmsr and mfspr upgraded from "rD = unimplemented"
  placeholders to intrinsics; mtmsr/mtmsrd and mtspr newly lifted
- FPSCR access: mffs/mtfsf/mtfsb0/mtfsb1

mfxer/mtxer are lifted in pure IL instead, assembling and splitting the
modeled SO/OV/CA flags at their XER bit positions.
@Marvisak Marvisak changed the title PowerPC: lift more instructions, model reservations, and fix mtmsr/ldux decoding PowerPC: lift more instructions, model reservations and barriers, fix decoding and lifting bugs Aug 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants