Skip to content

Encode the remaining arm64 instructions with full wildcard support - #12

Merged
scizzydo merged 2 commits into
masterfrom
arm-instructions
Oct 12, 2026
Merged

scizzydo merged 2 commits into
masterfrom
arm-instructions

Conversation

@scizzydo

Copy link
Copy Markdown
Owner

Closes #5, closes #6, closes #7, closes #8, closes #9, closes #10, closes #11

Summary

 a64_encode_instruction
   B.cond                         -> parse_bcond (now via parse_cond)
   parseTable lookup
+    TBZ/TBNZ                     -> parse_tbz
+    SVC/HVC/SMC/BRK/HLT/UDF      -> parse_imm16<shift>
+    BTI                          -> parse_bti
+    LDRAA/LDRAB                  -> parse_ldraa
+    BRAA, PACIA, MADD, LDXR, ... -> parse_regs<width_bit, width_regs, shifts...>
+    LDR/STR/LDUR/STUR B/H/S/D/Q  -> parse_load_store (V, size, opc from Rt)
+    LDRSB/LDRSH                  -> parse_load_store<..., sign_extend>
+    LDP/STP S/D/Q, LDPSW, LDNP   -> parse_ldp<opc, no_allocate>
+    FMOV                         -> parse_fmov
+    CSEL family, CSET, CINC, ... -> parse_csel<regs>
+    CCMP/CCMN                    -> parse_ccmp
+    LSL/LSR/ASR/ROR              -> parse_shift (bitfield or LSLV form)
+    UBFX/BFI/SXTB/EXTR/...       -> parse_bitfield<kind>
+    BIC/ORN/EON, MVN, NEG(S)     -> parse_orr (N from table), parse_zr<parse, pos>
+  not in table: CAS/SWP/LD<op>/ST<op> -> parse_atomic (from the mnemonic)

/d (get_result):

 a64_decode_branch_imm   (was a64_decode_imm19)
   B.cond, CBZ/CBNZ, LDR literal
+  LDR S/D/Q literal, TBZ/TBNZ (imm14)
 a64_decode_load_store
+  FP/SIMD loads and stores (V = 1, Q scale 16), so ADRP + LDR D0, [X8,#lo] resolves

Wildcard rules (in docs/arm64.md): a ? opens exactly the bits its operand decides. Fields computed from a ? are open too. A bare ? register leaves the width open. With unknown width, both 32 and 64-bit encodings are computed and only the bits they share stay pinned. An operand that can't be encoded makes the line mask 0, never a pattern that can't match. A bare ? load/store register means W or X; FP registers use D?/Q?.

Evidence

  • Before: test_arm: 151 exact-encoding rows, 127 wildcard rows. Every new form was a mask-0 wildcard.
    After: 409 exact-encoding rows, 291 wildcard rows (each wildcard field has one match and one near miss), 33 unencodable forms that must give mask 0, plus new /d cases. All encodings were generated with llvm-mc. On Apple arm64 (mac-mini), assembler_agrees re-assembles every row: tables match the assembler ok.
  • Regression snapshot: pattern/mask for 1229 existing lines (each test line plus versions with each operand swapped for ?), before vs after. Only 13 differ, all ? used as a shift type (ORR X0, X1, X2, ?#3): it used to pin LSL and now leaves the type open.
  • Linux gcc and clang: ctest 19/19. macOS arm64: test_arm, test_patterns (with and without SIMD), test_cpp17 pass. The IDA plugin builds and its test passes.

Merge Danger

Door: two-way

Header-only; reverting the commit restores the old encoder.

Blast Radius: encoder

  • A pattern using ? as a shift or extend type now matches any shift type instead of only LSL.
  • Lines that used to be silent mask-0 wildcards (FMOV, CSEL, LSL #n, FP loads, ...) now encode, so existing patterns containing them match more strictly. That's intended, but a pattern built against a wrong line would stop matching. The IDA plugin warns about fewer lines.
  • MSVC wasn't built locally; the extra compile-time patterns in test_arm are the thing to watch in Windows CI.

🤖 Generated with Claude Code

TBZ/TBNZ and exceptions, BTI and pointer authentication, FP/SIMD loads,
stores and FMOV, signed/exclusive/acquire-release loads and LSE atomics,
bitfield/shift/extend aliases, conditional select/compare, multiply/divide
and the inverted-operand logical forms. /d now resolves TBZ/TBNZ, FP
literal loads and FP loads after ADRP.

A "?" shift or extend type now leaves the type open instead of pinning LSL.
The mnemonic buffer grows to 15 chars (LDADDALB). Wildcard rules are
documented in docs/arm64.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… loads

- A "?" extend in a register-offset load/store ([X1, X2, ?], ? #3) pinned
  LSL with the wrong S bit, so the pattern could never match. It now leaves
  the extend (and the amount unless one is given) open.
- A bare "?" Rt with a known offset picked the scaled form from neither
  width, missing the LDUR the assembler emits (LDR ?, [X1,#0x81]) or the
  only width that fits (#0x4000 is X only). W and X are now both encoded
  and merged, like the bitfield aliases (shared either() helper).
- /d: a load into an FP register or a store between ADRP and its use no
  longer ends the chain as if it overwrote the ADRP register.
- docs: #? in LDR is the scaled form, FMOV #? doesn't cover #0.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@scizzydo
scizzydo merged commit 7d813d4 into master Oct 12, 2026
3 checks passed
@scizzydo
scizzydo deleted the arm-instructions branch October 12, 2026 21:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment