From 9d4c191a46076d2bc0d3631ad39bf763a6a2a2dc Mon Sep 17 00:00:00 2001 From: Shawn Hoffman Date: Thu, 7 May 2026 16:04:06 -0700 Subject: [PATCH] Hexagon: add stub- and packet-coverage regression-pin tests Two new JUnit tests pin the disassembly text emitted for representative encodings of the recently added decoder stubs (HexagonStubCoverageTest) and packet-context decoding (HexagonPacketCoverageTest), so a future edit that accidentally drops or shadows a constructor will surface as a deliberate review point rather than a silent regression. --- .../sleigh/HexagonPacketCoverageTest.java | 419 ++++++++++++++++++ .../sleigh/HexagonStubCoverageTest.java | 246 ++++++++++ 2 files changed, 665 insertions(+) create mode 100644 Ghidra/Processors/Hexagon/src/test/java/ghidra/app/plugin/assembler/sleigh/HexagonPacketCoverageTest.java create mode 100644 Ghidra/Processors/Hexagon/src/test/java/ghidra/app/plugin/assembler/sleigh/HexagonStubCoverageTest.java diff --git a/Ghidra/Processors/Hexagon/src/test/java/ghidra/app/plugin/assembler/sleigh/HexagonPacketCoverageTest.java b/Ghidra/Processors/Hexagon/src/test/java/ghidra/app/plugin/assembler/sleigh/HexagonPacketCoverageTest.java new file mode 100644 index 0000000000..ad83b4ab6e --- /dev/null +++ b/Ghidra/Processors/Hexagon/src/test/java/ghidra/app/plugin/assembler/sleigh/HexagonPacketCoverageTest.java @@ -0,0 +1,419 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.assembler.sleigh; + +import static org.junit.Assert.assertEquals; + +import java.math.BigInteger; + +import org.junit.Test; + +import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock; +import ghidra.app.util.PseudoInstruction; +import ghidra.program.model.lang.LanguageID; +import ghidra.program.model.lang.RegisterValue; + +/** + * Packet-context regression-pin coverage test for the Hexagon SLEIGH spec. + * + *

Many Hexagon instruction families (NV-cmp-jumps, NV-stores, HVX + * V6_vS32b_new_*, dealloc_return:new, ...) only decode in the context of a + * non-zero packet position with prior-slot register tracking. The mainline + * canonical-byte oracle test (compare_3way.json) decodes each instruction + * standalone and so reports these families as DECODE_FAILED -- but that is a + * methodology artifact, not a real spec gap. + * + *

This class constructs the required {@code packetOffset} / + * {@code packetBits} context by hand and disassembles the second-or-later + * instruction in the packet, asserting the resulting mnemonic and operand + * text. The pattern follows + * {@code HexagonAssemblyTest#testAssemble_memw_mSP_n0x4_R0new}, which already + * exercises packet-context for {@code R0.new} stores. + * + *

Each assertion pins the current spec output for one representative + * encoding of one packet-context-dependent family; if the constructor + * disappears or its rendering changes the assertion fires. + * + *

The byte encodings below are LLVM canonical primaries (one per + * {@code def : HInst<>}) modified only to: + *

+ * + *

The 3-bit new-value field lives at: + *

+ */ +public class HexagonPacketCoverageTest extends AbstractAssemblyTest { + + @Override + protected LanguageID getLanguageID() { + return new LanguageID("Hexagon:LE:32:default"); + } + + /** + * Build a context byte string with the given {@code packetOffset} and + * {@code packetBits} sub-register values. Mirrors the helper in + * {@link HexagonAssemblyTest}. + * + * @param packetOffset slot index within the packet (0..3) + * @param packetBits raw value of the {@code packetBits} sub-register; the + * high 2 bits are {@code parse1} (slot-0 parse), the + * next 2 are {@code parse2} (slot-1 parse), then 5-bit + * {@code nreg0}, {@code nreg1}, {@code nreg2}, etc. + */ + private String makeCtx(int packetOffset, long packetBits) { + RegisterValue ctxVal = new RegisterValue(lang.getContextBaseRegister()); + ctxVal = ctxVal.assign(lang.getRegister("packetOffset"), + BigInteger.valueOf(packetOffset)); + ctxVal = ctxVal.assign(lang.getRegister("packetBits"), + BigInteger.valueOf(packetBits)); + return AssemblyPatternBlock.fromRegisterValue(ctxVal).fillMask().toString(); + } + + /** + * Decode a single 4-byte little-endian Hexagon word at packet position + * {@code packetOffset} with the given prior-slot {@code packetBits} and + * assert the disassembly text. + * + * @param hexBytes 8 hex chars representing the 4 bytes in + * little-endian order as they appear in memory + * @param packetOffset slot index within the packet (0..3) + * @param packetBits raw {@code packetBits} sub-register value + * @param expected expected disassembly text, trimmed + */ + protected void assertPacketDecode(String hexBytes, int packetOffset, long packetBits, + String expected) { + if (hexBytes.length() != 8) { + throw new IllegalArgumentException( + "expected 4-byte hex word (8 chars), got: " + hexBytes); + } + byte[] bytes = new byte[4]; + for (int i = 0; i < 4; i++) { + bytes[i] = (byte) Integer.parseInt(hexBytes.substring(i * 2, i * 2 + 2), 16); + } + String ctxStr = makeCtx(packetOffset, packetBits); + byte[] ctx = AssemblyPatternBlock.fromString(ctxStr).fillMask().getVals(); + PseudoInstruction pi; + try { + pi = disassemble(DEFAULT_ADDR, bytes, ctx); + } + catch (Exception e) { + throw new AssertionError( + "disassembly threw for bytes " + hexBytes + " ctx=" + ctxStr + + " (expected: " + expected + ")", + e); + } + String actual = pi.toString().trim(); + assertEquals("bytes " + hexBytes + " offset=" + packetOffset + " packetBits=" + + Long.toHexString(packetBits), expected, actual); + } + + /** + * Common context for "slot 1 reads slot 0's writer of R0": + * + * packetBits = parse1 in high 2 bits = 0b01_00_00000_00000_00000_00000_00000_000 + * = 0x40000000. + */ + private static final long PB_S1_NREG0_R0 = 0x40000000L; + + // --------------------------------------------------------------------- + // NV cmp-jump families (J4_cmp{eq,gt,gtu}{,i,n1}_{t,f}_jumpnv_{nt,t}) + // --------------------------------------------------------------------- + // + // Encoding layout: + // 31..28 : iclass = 0010 + // 27..22 : opcode bits selecting cmp.{eq,gt,gtu} and t/f + // 18..16 : Ns8 (3-bit new-value source) + // 15..14 : parse bits + // 13 : taken hint (1=jump:t, 0=jump:nt) + // 12..8 : Rt32 (or U5 immediate) + // 7..1 : i7 immediate + // + // Canonical sets Ns8 = 000; we set bit 17 -> Ns8 = 010. In LE memory + // order byte2 (bits 16..23) gets bit 1 set: byte2 |= 0x02. + // + // Mainline display order: + // "jump.if: [!]cmp.(R0.new,)," + // Jump target = inst_start - 4*packetOffset + sext(imm)*4. With + // DEFAULT_ADDR = 0x40000000, packetOffset=1, imm=0: 0x40000000 - 4 = 0x3ffffffc. + + /** Pin the J4_cmpeq*_*_jumpnv_* families. */ + @Test + public void testPacketCoverage_NVCmpJump_eq() { + // J4_cmpeq_t_jumpnv_t (canonical 00e00020) + assertPacketDecode("00e00220", 1, PB_S1_NREG0_R0, + "jump.if:t cmp.eq(R0.new,R0),0x3ffffffc"); + // J4_cmpeq_t_jumpnv_nt (canonical 00c00020) + assertPacketDecode("00c00220", 1, PB_S1_NREG0_R0, + "jump.if:nt cmp.eq(R0.new,R0),0x3ffffffc"); + // J4_cmpeq_f_jumpnv_t (canonical 00e04020) + assertPacketDecode("00e04220", 1, PB_S1_NREG0_R0, + "jump.if:t !cmp.eq(R0.new,R0),0x3ffffffc"); + // J4_cmpeqi_t_jumpnv_t (canonical 00e00024) + assertPacketDecode("00e00224", 1, PB_S1_NREG0_R0, + "jump.if:t cmp.eq(R0.new,#0x0),0x3ffffffc"); + // J4_cmpeqi_f_jumpnv_t (canonical 00e04024) + assertPacketDecode("00e04224", 1, PB_S1_NREG0_R0, + "jump.if:t !cmp.eq(R0.new,#0x0),0x3ffffffc"); + // J4_cmpeqn1_t_jumpnv_t (canonical 00e00026) -- #-1 literal renders as "#-1" + assertPacketDecode("00e00226", 1, PB_S1_NREG0_R0, + "jump.if:t cmp.eq(R0.new,#-1),0x3ffffffc"); + // J4_cmpeqn1_f_jumpnv_t (canonical 00e04026) + assertPacketDecode("00e04226", 1, PB_S1_NREG0_R0, + "jump.if:t !cmp.eq(R0.new,#-1),0x3ffffffc"); + } + + /** Pin the J4_cmpgt*_*_jumpnv_* families. */ + @Test + public void testPacketCoverage_NVCmpJump_gt() { + // J4_cmpgt_t_jumpnv_t (canonical 00e08020) + assertPacketDecode("00e08220", 1, PB_S1_NREG0_R0, + "jump.if:t cmp.gt(R0.new,R0),0x3ffffffc"); + // J4_cmpgt_t_jumpnv_nt + assertPacketDecode("00c08220", 1, PB_S1_NREG0_R0, + "jump.if:nt cmp.gt(R0.new,R0),0x3ffffffc"); + // J4_cmpgt_f_jumpnv_t (canonical 00e0c020) + assertPacketDecode("00e0c220", 1, PB_S1_NREG0_R0, + "jump.if:t !cmp.gt(R0.new,R0),0x3ffffffc"); + // J4_cmpgti_t_jumpnv_t (canonical 00e08024) + assertPacketDecode("00e08224", 1, PB_S1_NREG0_R0, + "jump.if:t cmp.gt(R0.new,#0x0),0x3ffffffc"); + // J4_cmpgtn1_t_jumpnv_t (canonical 00e08026) -- #-1 literal + assertPacketDecode("00e08226", 1, PB_S1_NREG0_R0, + "jump.if:t cmp.gt(R0.new,#-1),0x3ffffffc"); + } + + /** Pin the J4_cmpgtu*_*_jumpnv_* families. */ + @Test + public void testPacketCoverage_NVCmpJump_gtu() { + // J4_cmpgtu_t_jumpnv_t (canonical 00e00021) + assertPacketDecode("00e00221", 1, PB_S1_NREG0_R0, + "jump.if:t cmp.gtu(R0.new,R0),0x3ffffffc"); + // J4_cmpgtu_t_jumpnv_nt (canonical 00c00021) + assertPacketDecode("00c00221", 1, PB_S1_NREG0_R0, + "jump.if:nt cmp.gtu(R0.new,R0),0x3ffffffc"); + // J4_cmpgtu_f_jumpnv_t (canonical 00e04021) + assertPacketDecode("00e04221", 1, PB_S1_NREG0_R0, + "jump.if:t !cmp.gtu(R0.new,R0),0x3ffffffc"); + // J4_cmpgtui_t_jumpnv_t (canonical 00e00025) + assertPacketDecode("00e00225", 1, PB_S1_NREG0_R0, + "jump.if:t cmp.gtu(R0.new,#0x0),0x3ffffffc"); + // J4_cmpgtui_f_jumpnv_t (canonical 00e04025) + assertPacketDecode("00e04225", 1, PB_S1_NREG0_R0, + "jump.if:t !cmp.gtu(R0.new,#0x0),0x3ffffffc"); + } + + // --------------------------------------------------------------------- + // NV-store families (S2_storer{b,h,i}new_*, S4_storer{b,h,i}new_*) + // --------------------------------------------------------------------- + // + // Encoding layout (S2_storerXnew_io, _pi, _pci, _rr): + // 31..28 : iclass = 1010 (0xa) for S2_*; 0011_1011 etc. for S4_* + // 27..22 : subclass selecting size & addressing mode + // 21..16 : Rs32 (base reg) + // 15..14 : parse bits + // 13..11 : 000 + // 10..8 : Nt8 (3-bit new-value source) + // 7..3 : Ii immediate + // + // Canonical Nt8 = 000; we set bit 9 of word -> Nt8 = 010. In LE memory + // order, byte1 |= 0x02. With imm=0 the rendering omits the offset: + // "memb (R0),R0.new" not "memb (R0+#0x0),R0.new". + + /** Pin S2_storerbnew_io / S2_storerbnew_pi / S4_storerbnew_rr. */ + @Test + public void testPacketCoverage_NVStore_byte() { + // S2_storerbnew_io (canonical 00c0a0a1) imm=0 -> "(R0)" only + assertPacketDecode("00c2a0a1", 1, PB_S1_NREG0_R0, + "memb (R0),R0.new"); + // S2_storerbnew_pi (canonical 00c0a0ab) + assertPacketDecode("00c2a0ab", 1, PB_S1_NREG0_R0, + "memb (R0++#0x0),R0.new"); + // S4_storerbnew_rr (canonical 00c0a03b) + // Note: this constructor uses Nreg0002 (Nt8 at bits 0..2), NOT Nreg0810, + // so we set bit 1 (byte0 |= 0x02) rather than bit 9. + // With shift = 0 the rendering omits the "<<#0x0" suffix. + assertPacketDecode("02c0a03b", 1, PB_S1_NREG0_R0, + "memb (R0+R0),R0.new"); + } + + /** Pin S2_storerhnew_io / S2_storerhnew_pi / S4_storerhnew_rr. */ + @Test + public void testPacketCoverage_NVStore_half() { + // S2_storerhnew_io (canonical 00c8a0a1) + assertPacketDecode("00caa0a1", 1, PB_S1_NREG0_R0, + "memh (R0),R0.new"); + // S2_storerhnew_pi (canonical 00c8a0ab) + assertPacketDecode("00caa0ab", 1, PB_S1_NREG0_R0, + "memh (R0++#0x0),R0.new"); + // S4_storerhnew_rr (canonical 08c0a03b) -- Nreg0002 -> set bit 1 + assertPacketDecode("0ac0a03b", 1, PB_S1_NREG0_R0, + "memh (R0+R0),R0.new"); + } + + /** Pin S2_storerinew_io / S2_storerinew_pi / S4_storerinew_rr. */ + @Test + public void testPacketCoverage_NVStore_word() { + // S2_storerinew_io (canonical 00d0a0a1) + assertPacketDecode("00d2a0a1", 1, PB_S1_NREG0_R0, + "memw (R0),R0.new"); + // S2_storerinew_pi (canonical 00d0a0ab) + assertPacketDecode("00d2a0ab", 1, PB_S1_NREG0_R0, + "memw (R0++#0x0),R0.new"); + // S4_storerinew_rr (canonical 10c0a03b) -- Nreg0002 -> set bit 1 + assertPacketDecode("12c0a03b", 1, PB_S1_NREG0_R0, + "memw (R0+R0),R0.new"); + } + + // --------------------------------------------------------------------- + // HVX V6_vS32b_new_* (vector NV stores) + // --------------------------------------------------------------------- + // + // Encoding for V6_vS32b_new_ai (canonical 20c02028 -> word 0x2820c020): + // 31..28 : iclass = 0010 + // 27..21 : 1010_000 (vS32b family subclass) + // ... + // 2..0 : Os8 (3-bit new vector reg, via VNreg0002 sub-constructor) + // + // Canonical Os8 = 0; we set bit 1 of word -> Os8 = 010. + // In LE memory order, byte0 |= 0x02. + + // --------------------------------------------------------------------- + // Predicated NV-store families (S4_pstorer{b,h,i}new{t,f}{,new}_*) + // --------------------------------------------------------------------- + // + // Encoding for S4_pstorerbnewt_io (predicated NV store): + // 31..28 : iclass = 0100 (0x4) [for the io form] + // ... + // 10..8 : Nt8 (Nreg0810) + // We set Nt8 = 010 -> bit 9 of word, byte1 |= 0x02. + // + // For S4_pstorerXnewtnew_rr / S4_pstorerXnewfnew_rr (P.new + register + // indexed) the canonical bytes from JSON include the + // {@code <<#0x0} shift = 0; we set bit 1 (Nreg0002) on those. + + /** Pin S4 predicated NV-store family. */ + @Test + public void testPacketCoverage_PStoreNew_pred() { + // S4_pstorerbnewt_rr (canonical 00c0a034) -- if (P0) memb(...) = R0.new + // Uses Nreg0002, set bit 1 + assertPacketDecode("02c0a034", 1, PB_S1_NREG0_R0, + "memb.if(P0) (R0+R0),R0.new"); + // S4_pstorerbnewf_rr (canonical 00c0a035) -- if (!P0) + assertPacketDecode("02c0a035", 1, PB_S1_NREG0_R0, + "memb.if(!P0) (R0+R0),R0.new"); + // S4_pstorerinewtnew_rr (canonical 10c0a036) -- if (P0.new) + assertPacketDecode("12c0a036", 1, PB_S1_NREG0_R0, + "memw.if(P0.new) (R0+R0),R0.new"); + // S4_pstorerhnewfnew_rr (canonical 08c0a037) -- if (!P0.new) + assertPacketDecode("0ac0a037", 1, PB_S1_NREG0_R0, + "memh.if(!P0.new) (R0+R0),R0.new"); + } + + // --------------------------------------------------------------------- + // L4_return_*new_p* (dealloc_return predicated by P.new) + // --------------------------------------------------------------------- + // + // Encoding for "if (Ps4.new) dealloc_return:t" (LLVM L4_return_tnew_pt): + // 31..28 : 1001 (iclass=9) + // 27..21 : 0110000 (op2127=0x30) + // 20..16 : 11110 (op1620=0x1e -- fixed pattern, NOT a register + // field; this distinguishes the form from + // "Rdd32 = dealloc_return(Rs32)" which puts an + // rs5 register here) + // 15..14 : PP (parse bits) + // 13 : 0/1 = jump:t/jump:nt (DRTaken12) + // 12..11 : 11 (selects "if (..new)" path) + // 10..9 : 10/01 selecting P.new and/or "!" + // 7..1 : (zero in our test) + // 4..0 : 11110 (op0007=0x1e fixed) + // + // Note: LLVM canonical encoding sets the 5 bits at op1620 and the low 5 + // bits of op0007 to 0 (these encode placeholder operands like Rs32 and + // Rdd32 in LLVM's view). Mainline matches the predicated-with-rs5 form + // (which has rs5 at bits 16..20 and op0007=0x1e fixed), so canonical + // bytes 00d80096 from compare_3way.json don't decode in mainline: + // the low-5 bits 0x00 don't match op0007=0x1e. We supply byte0=0x1e. + // + // rs5 = 0 (R0) -> byte2 bits 0..4 = 0; byte2 bits 5..7 = 0 (op2127 low). + // byte3 bits 0..3 = 0110 (op2127 high), bits 4..7 = 1001 (iclass=9) -> + // byte3 = 0x96. So bytes for "if (P0.new) dealloc_return:t R0": + // byte0 = 0x1e, byte1 = 0xd8, byte2 = 0x00, byte3 = 0x96 -> "1ed80096". + + // NOTE: a non-canonical dealloc_return renders with its explicit destination + // pair (":raw Rdd,Rs"); the bare "dealloc_return" spelling is reserved for the + // canonical Rs=R30 / Rdd=R31:30 encoding. These use rs5=R0, so they decode as + // ":raw R31R30,R0". + /** Pin L4_return_*new_p* (dealloc_return predicated by P.new) family. */ + @Test + public void testPacketCoverage_DeallocReturnNew() { + // L4_return_tnew_pt -- "if (P0.new) dealloc_return:t" rs5=R0 + // byte1: parse=11, bit13=0, bit12=1, bit11=1, bit10=0, bits9..8=00 = 0xd8 + assertPacketDecode("1ed80096", 1, PB_S1_NREG0_R0, + "dealloc_return.if(P0.new):t:raw R31R30,R0"); + // L4_return_tnew_pnt -- "if (P0.new) dealloc_return:nt" + // bit12=0 -> byte1 = 11_0_0_1_0_00 = 0xc8 + assertPacketDecode("1ec80096", 1, PB_S1_NREG0_R0, + "dealloc_return.if(P0.new):nt:raw R31R30,R0"); + // L4_return_fnew_pt -- "if (!P0.new) dealloc_return:t" + // bit13=1 -> byte1 = 11_1_1_1_0_00 = 0xf8 + assertPacketDecode("1ef80096", 1, PB_S1_NREG0_R0, + "dealloc_return.if(!P0.new):t:raw R31R30,R0"); + // L4_return_fnew_pnt -- "if (!P0.new) dealloc_return:nt" + // bit13=1, bit12=0 -> byte1 = 11_1_0_1_0_00 = 0xe8 + assertPacketDecode("1ee80096", 1, PB_S1_NREG0_R0, + "dealloc_return.if(!P0.new):nt:raw R31R30,R0"); + } + + /** Pin HVX V6_vS32b_new_* family. */ + @Test + public void testPacketCoverage_HVX_VS32bNew() { + // V6_vS32b_new_ai (canonical 20c02028) -> set bit 1: byte0 = 0x22 + assertPacketDecode("22c02028", 1, PB_S1_NREG0_R0, + "vmem (R0),V0.new"); + // V6_vS32b_new_pi (canonical 20c02029) + assertPacketDecode("22c02029", 1, PB_S1_NREG0_R0, + "vmem (R0++#0x0),V0.new"); + // V6_vS32b_new_pred_ai (canonical 40c0a028) -- mainline renders as + // "vmem.if(P0)" rather than "if (P0) vmem". + assertPacketDecode("42c0a028", 1, PB_S1_NREG0_R0, + "vmem.if(P0) (R0),V0.new"); + // V6_vS32b_new_npred_ai (canonical 68c0a028) + assertPacketDecode("6ac0a028", 1, PB_S1_NREG0_R0, + "vmem.if(!P0) (R0),V0.new"); + // V6_vS32b_new_ppu (canonical 20c0202b) -- Mu2 post-increment + assertPacketDecode("22c0202b", 1, PB_S1_NREG0_R0, + "vmem (R0++M0),V0.new"); + // V6_vS32b_new_pred_pi (canonical 40c0a029) -- predicated post-inc imm + assertPacketDecode("42c0a029", 1, PB_S1_NREG0_R0, + "vmem.if(P0) (R0++#0x0),V0.new"); + } +} diff --git a/Ghidra/Processors/Hexagon/src/test/java/ghidra/app/plugin/assembler/sleigh/HexagonStubCoverageTest.java b/Ghidra/Processors/Hexagon/src/test/java/ghidra/app/plugin/assembler/sleigh/HexagonStubCoverageTest.java new file mode 100644 index 0000000000..6d038db5bf --- /dev/null +++ b/Ghidra/Processors/Hexagon/src/test/java/ghidra/app/plugin/assembler/sleigh/HexagonStubCoverageTest.java @@ -0,0 +1,246 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.assembler.sleigh; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import ghidra.app.util.PseudoInstruction; +import ghidra.program.model.lang.LanguageID; + +/** + * Regression-pin coverage test for recently added decoder stubs. + * + *

Each assertion records, as a baseline, the disassembly text produced by + * the current Hexagon SLEIGH spec for a representative encoding of one stub + * family. The intent is purely to detect silent behavioral regressions + * (e.g. a future edit that accidentally removes or shadows a decoder + * constructor): if the decoded mnemonic changes for any pinned encoding, + * the corresponding assertion will fail and force a deliberate review. + * + *

Encodings are taken from real LLVM-MC sample bytes for V69/V73 HVX and + * system instructions; the expected text is what the spec currently emits + * (which for some families is a deliberately simplified or stubbed form). + * + *

Unlike {@link HexagonAssemblyTest} this class does not exercise + * the assembler -- many of the stubbed mnemonics use punctuation + * (e.g. {@code |=}, {@code &=}, {@code ^=}, {@code +=}, {@code :rnd:sat}) + * that does not necessarily round-trip through the assembler grammar. + * We only assert byte-to-text disassembly via {@link #disassemble}. + */ +public class HexagonStubCoverageTest extends AbstractAssemblyTest { + + @Override + protected LanguageID getLanguageID() { + return new LanguageID("Hexagon:LE:32:default"); + } + + /** + * Decode a single 4-byte little-endian Hexagon word and assert the + * disassembly text (trimmed) matches {@code expected}. + * + * @param hexBytes 8 hex chars representing the 4 bytes in little-endian + * order as they appear in memory (the same form that the + * LLVM-MC corpus prints) + * @param expected the expected disassembly, without trailing whitespace + */ + protected void assertDecode(String hexBytes, String expected) { + if (hexBytes.length() != 8) { + throw new IllegalArgumentException( + "expected 4-byte hex word (8 chars), got: " + hexBytes); + } + byte[] bytes = new byte[4]; + for (int i = 0; i < 4; i++) { + bytes[i] = (byte) Integer.parseInt(hexBytes.substring(i * 2, i * 2 + 2), 16); + } + // Default context for a single-instruction packet; the parse-bits + // in the encoding itself mark end-of-packet. + byte[] ctx = context.getDefaultAt(lang.getDefaultSpace().getAddress(DEFAULT_ADDR)) + .fillMask() + .getVals(); + PseudoInstruction pi; + try { + pi = disassemble(DEFAULT_ADDR, bytes, ctx); + } + catch (Exception e) { + throw new AssertionError( + "disassembly threw for bytes " + hexBytes + " (expected: " + expected + ")", e); + } + String actual = pi.toString().trim(); + assertEquals("bytes " + hexBytes, expected, actual); + } + + @Test + public void testStubCoverage_HVX_vmpy_integer() { + // V6_vmpyhus + assertDecode("40c0201c", "vmpy V1V0.w,V0.h,V0.uh"); + // V6_vmpyhus_acc + assertDecode("20e0201c", "vmpy+= V1V0.w,V0.h,V0.uh"); + // V6_vmpyhv + assertDecode("e0c0001c", "vmpy V1V0.w,V0.h,V0.h"); + // V6_vmpyhv_acc + assertDecode("e0e0001c", "vmpy+= V1V0.w,V0.h,V0.h"); + // V6_vmpyhvsrs + assertDecode("20c0201c", "vmpy:<<1:rnd:sat V0.h,V0.h,V0.h"); + } + + @Test + public void testStubCoverage_HVX_vrmpy_rtt() { + // V6_vrmpybub_rtt + assertDecode("a0c0c019", "vrmpy V1V0.w,V0.b,R1R0.ub"); + // V6_vrmpybub_rtt_acc + assertDecode("00e0a019", "vrmpy+= V1V0.w,V0.b,R1R0.ub"); + // V6_vrmpyub_rtt + assertDecode("80c0c019", "vrmpy V1V0.uw,V0.ub,R1R0.ub"); + // V6_vrmpyub_rtt_acc + assertDecode("e0e0a019", "vrmpy+= V1V0.uw,V0.ub,R1R0.ub"); + } + + @Test + public void testStubCoverage_HVX_hf_sf_arith() { + // V6_vdmpy_sf_hf_acc + assertDecode("60e0401c", "vdmpy+= V0.sf,V0.hf,V0.hf"); + // V6_vmpy_hf_hf_acc + assertDecode("40e0401c", "vmpy+= V0.hf,V0.hf,V0.hf"); + // V6_vmpy_sf_hf_acc + assertDecode("20e0401c", "vmpy+= V1V0.sf,V0.hf,V0.hf"); + // V6_vfmax_hf + assertDecode("40e0601c", "vfmax V0.hf,V0.hf,V0.hf"); + // V6_vfmax_sf + assertDecode("60e0601c", "vfmax V0.sf,V0.sf,V0.sf"); + // V6_vfmin_hf + assertDecode("00e0601c", "vfmin V0.hf,V0.hf,V0.hf"); + // V6_vfmin_sf + assertDecode("20e0601c", "vfmin V0.sf,V0.sf,V0.sf"); + } + + @Test + public void testStubCoverage_HVX_bf_arith() { + // V6_vadd_sf_bf + assertDecode("c0e0401d", "vadd V1V0.sf,V0.bf,V0.bf"); + // V6_vsub_sf_bf + assertDecode("a0e0401d", "vsub V1V0.sf,V0.bf,V0.bf"); + // V6_vmpy_sf_bf + assertDecode("80e0401d", "vmpy V1V0.sf,V0.bf,V0.bf"); + // V6_vmpy_sf_bf_acc + assertDecode("00e0001d", "vmpy+= V1V0.sf,V0.bf,V0.bf"); + // V6_vmax_bf + assertDecode("e0e0401d", "vmax V0.bf,V0.bf,V0.bf"); + // V6_vmin_bf + assertDecode("00e0401d", "vmin V0.bf,V0.bf,V0.bf"); + // V6_vcvt_bf_sf + assertDecode("60e0401d", "vcvt V0.bf,V0.sf,V0.sf"); + } + + @Test + public void testStubCoverage_HVX_bf_compare() { + // V6_vgtbf + assertDecode("78e0801c", "vcmp.gt Q0,V0.bf,V0.bf"); + // V6_vgtbf_and + assertDecode("d0e0801c", "vcmp.gt&= Q0,V0.bf,V0.bf"); + // V6_vgtbf_or + assertDecode("38e0801c", "vcmp.gt|= Q0,V0.bf,V0.bf"); + // V6_vgtbf_xor + assertDecode("f0e0801c", "vcmp.gt^= Q0,V0.bf,V0.bf"); + } + + @Test + public void testStubCoverage_HVX_hf_sf_eq_compare() { + // V6_veqhf_and + assertDecode("1ce0801c", "vcmp.eq&= Q0,V0.hf,V0.hf"); + // V6_veqhf_or + assertDecode("5ce0801c", "vcmp.eq|= Q0,V0.hf,V0.hf"); + // V6_veqhf_xor + assertDecode("9ce0801c", "vcmp.eq^= Q0,V0.hf,V0.hf"); + // V6_veqsf_and + assertDecode("0ce0801c", "vcmp.eq&= Q0,V0.sf,V0.sf"); + // V6_veqsf_or + assertDecode("4ce0801c", "vcmp.eq|= Q0,V0.sf,V0.sf"); + // V6_veqsf_xor + assertDecode("8ce0801c", "vcmp.eq^= Q0,V0.sf,V0.sf"); + } + + @Test + public void testStubCoverage_HVX_f8_and_cvt2() { + // V6_vcvt2_b_hf + assertDecode("c0e0c01a", "vcvt2 V0.b,V0.hf,V0.hf"); + // V6_vcvt2_ub_hf + assertDecode("e0e0c01a", "vcvt2 V0.ub,V0.hf,V0.hf"); + // V6_vfmax_f8 + assertDecode("a0e0601c", "vfmax V0.f8,V0.f8,V0.f8"); + // V6_vfmin_f8 + assertDecode("80e0601c", "vfmin V0.f8,V0.f8,V0.f8"); + // V6_vabs_f8 -- previously broken (typo bug), now decoded + assertDecode("c0e0661c", "vabs V0.f8,V0.f8"); + // V6_vfneg_f8 -- previously broken (typo bug), now decoded + assertDecode("e0e0661c", "vfneg V0.f8,V0.f8"); + } + + @Test + public void testStubCoverage_HVX_vhist() { + // V6_vhist -- previously broken (typo bug), now decoded + assertDecode("80e0001e", "vhist"); + // V6_vhistq -- previously broken (typo bug), now decoded + assertDecode("80e0021e", "vhist Q0"); + } + + @Test + public void testStubCoverage_HVX_qfext_and_align() { + // V6_get_qfext + assertDecode("e0c0c019", "vgetqfext V0,V0.x,R0"); + // V6_get_qfext_oracc + assertDecode("c0c0c019", "vgetqfext|= V0,V0.x,R0"); + // V6_set_qfext + assertDecode("60c0c019", "vsetqfext V0.x,V0,R0"); + // V6_valign4 + assertDecode("a0c00018", "valign4 V0,V0,V0,R0"); + } + + @Test + public void testStubCoverage_scalar_and_pair() { + // A6_vminub_RdP + assertDecode("00c0e0ea", "vminub R1R0,P0,R1R0,R1R0"); + // L2_loadw_aq + assertDecode("00c80092", "memw_aq R0,R0"); + // L6_memcpy + assertDecode("40c00092", "memcpy R0,R0,M0"); + // S6_vtrunehb_ppp -- previously broken (typo bug), now decoded + assertDecode("60c080c1", "vtrunehb R1R0,R1R0,R1R0"); + // S6_vtrunohb_ppp -- previously broken (typo bug), now decoded + assertDecode("a0c080c1", "vtrunohb R1R0,R1R0,R1R0"); + } + + @Test + public void testStubCoverage_system_and_dma() { + // Y2_icdataw + assertDecode("00e0c055", "icdataw R0,R0"); + // Y2_tlbpp + assertDecode("00c0606c", "tlbp R0,R1R0"); + // Y6_dmlink + assertDecode("40c000a6", "dmlink R0,R0"); + // Y6_dmpause + assertDecode("60c000a8", "dmpause R0"); + // Y6_dmpoll + assertDecode("40c000a8", "dmpoll R0"); + // Y6_dmresume + assertDecode("80c000a6", "dmresume R0"); + // Y6_dmstart + assertDecode("20c000a6", "dmstart R0"); + // Y6_dmwait + assertDecode("20c000a8", "dmwait R0"); + } +}