From b2d50c7766ccf6ece8ffe1dec6627f3b4c324f1d Mon Sep 17 00:00:00 2001 From: Ledmington Date: Sat, 18 Oct 2025 16:44:01 +0200 Subject: [PATCH 01/10] wip wip Separated Instruction into interface and GeneralInstruction wip --- .../ledmington/cpu/InstructionChecker.java | 1 + .../ledmington/cpu/InstructionDecoder.java | 36 +++++-- .../ledmington/cpu/InstructionEncoder.java | 31 +++++- .../cpu/x86/InstructionBuilder.java | 12 +++ .../com/ledmington/cpu/x86/Instructions.java | 68 +++++++++++++ .../java/com/ledmington/cpu/x86/Opcode.java | 12 +++ .../com/ledmington/cpu/x86/WeirdVpcmpeqb.java | 95 +++++++++++++++++++ .../java/com/ledmington/cpu/TestDecoding.java | 6 +- .../java/com/ledmington/cpu/X64Encodings.java | 18 ++++ 9 files changed, 264 insertions(+), 15 deletions(-) create mode 100644 id/src/main/java/com/ledmington/cpu/x86/Instructions.java create mode 100644 id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java diff --git a/id/src/main/java/com/ledmington/cpu/InstructionChecker.java b/id/src/main/java/com/ledmington/cpu/InstructionChecker.java index 7708e623..1f1aef88 100644 --- a/id/src/main/java/com/ledmington/cpu/InstructionChecker.java +++ b/id/src/main/java/com/ledmington/cpu/InstructionChecker.java @@ -294,6 +294,7 @@ public final class InstructionChecker { Map.entry(Opcode.VPMINUD, List.of(RY_RY_RY, RY_RY_M256)), Map.entry(Opcode.VPMOVMSKB, List.of(R32_RX, R32_RY)), Map.entry(Opcode.VPCMPEQB, List.of(RK_RX_RX, RK_RY_RY, RY_RY_M256, RK_RX_M128, RK_RY_M256)), + Map.entry(Opcode.VPCMPLTB, List.of(RK_RY_RY)), Map.entry(Opcode.VPCMPEQD, List.of(RK_RY_RY, RY_RY_M256, RK_RY_M256)), Map.entry(Opcode.VPCMPEQQ, List.of(RX_RX_M128)), Map.entry(Opcode.VPCMPNEQB, List.of(RK_RY_RY, RK_RY_M256)), diff --git a/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java b/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java index b3c4a874..e6206057 100644 --- a/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java +++ b/id/src/main/java/com/ledmington/cpu/InstructionDecoder.java @@ -4111,9 +4111,10 @@ private static Instruction parseEvexOpcodes( final byte VMOVAPS_OPCODE = (byte) 0x29; final byte VPMINUD_OPCODE = (byte) 0x3b; final byte VPCMPNEQUB_OPCODE = (byte) 0x3e; - final byte VPCMPEQB_OPCODE = (byte) 0x3f; + final byte VPCMPxxB_OPCODE = (byte) 0x3f; final byte VMOVQ_RX_M128_OPCODE = (byte) 0x6e; final byte VMOVDQU64_RZMM_M512_OPCODE = (byte) 0x6f; + final byte VPCMPEQB_OPCODE = (byte) 0x74; final byte VPBROADCASTB_OPCODE = (byte) 0x7a; final byte VPBROADCASTD_OPCODE = (byte) 0x7c; final byte VMOVQ_R64_RX_OPCODE = (byte) 0x7e; @@ -4251,7 +4252,7 @@ yield new GeneralInstruction( b.read1(); yield tmp; } - case VPCMPEQB_OPCODE -> { + case VPCMPxxB_OPCODE -> { final ModRM modrm = modrm(b); final byte r1 = or(evex.v1() ? 0 : (byte) 0b00010000, getByteFromV(evex)); final byte r2 = or(evex.x() ? 0 : (byte) 0b00010000, getByteFromRM(evex, modrm)); @@ -4267,13 +4268,34 @@ yield new GeneralInstruction( if (evex.a() != (byte) 0) { ib.mask(MaskRegister.fromByte(evex.a())); } - final byte nextByte = b.read1(); - switch (nextByte) { + final byte opcodeByte = b.read1(); + switch (opcodeByte) { case (byte) 0x00 -> ib.opcode(Opcode.VPCMPEQB); + case (byte) 0x01 -> ib.opcode(Opcode.VPCMPLTB); + case (byte) 0x02 -> ib.opcode(Opcode.VPCMPLEB); case (byte) 0x04 -> ib.opcode(Opcode.VPCMPNEQB); - default -> - throw new IllegalArgumentException( - String.format("Unknown opcode corresponding to byte 0x%02x.", nextByte)); + case (byte) 0x05 -> ib.opcode(Opcode.VPCMPNLTB); + case (byte) 0x06 -> ib.opcode(Opcode.VPCMPNLEB); + default -> throw new UnknownOpcode(opcodeByte); + } + yield ib.build(); + } + case VPCMPEQB_OPCODE -> { + final ModRM modrm = modrm(b); + final byte r1 = or(evex.v1() ? 0 : (byte) 0b00010000, getByteFromV(evex)); + final byte r2 = or(evex.x() ? 0 : (byte) 0b00010000, getByteFromRM(evex, modrm)); + final InstructionBuilder ib = Instruction.builder() + .opcode(Opcode.VPCMPEQB) + .op(MaskRegister.fromByte(getByteFromReg(evex, modrm))) + .op(evex.l() ? RegisterYMM.fromByte(r1) : RegisterXMM.fromByte(r1)) + .op( + isIndirectOperandNeeded(modrm) + ? parseIndirectOperand(b, pref, modrm) + .pointer(evex.l() ? PointerSize.YMMWORD_PTR : PointerSize.XMMWORD_PTR) + .build() + : (evex.l() ? RegisterYMM.fromByte(r2) : RegisterXMM.fromByte(r2))); + if (evex.a() != (byte) 0) { + ib.mask(MaskRegister.fromByte(evex.a())); } yield ib.build(); } diff --git a/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java b/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java index 290c1caf..ac56cef5 100644 --- a/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java +++ b/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java @@ -2007,7 +2007,9 @@ private static void encodeThreeOperandsInstruction(final WriteOnlyByteBuffer wb, case VPMINUD -> wb.write((byte) 0x3b); case VPCMPGTB -> wb.write((byte) 0x64); case VPCMPEQB -> { - if (isFirstMask(inst) && isSecondR(inst) && (isThirdM(inst) || isThirdR(inst))) { + if (inst instanceof WeirdVpcmpeqb) { + wb.write((byte) 0x74); + } else if (isFirstMask(inst) && isSecondR(inst) && (isThirdM(inst) || isThirdR(inst))) { wb.write((byte) 0x3f); lastByte = (byte) 0x00; } else { @@ -2032,6 +2034,15 @@ private static void encodeThreeOperandsInstruction(final WriteOnlyByteBuffer wb, lastByte = (byte) 0x04; } } + case VPCMPLTB -> { + if (isFirstMask(inst) + && inst.secondOperand() instanceof Register + && inst.thirdOperand() instanceof IndirectOperand) { + encodeEvexPrefix(wb, inst); + wb.write((byte) 0x3f); + lastByte = (byte) 0x01; + } + } case PEXTRW -> wb.write(DOUBLE_BYTE_OPCODE_PREFIX, (byte) 0xc5); case SARX -> wb.write((byte) 0xf7); case BZHI -> wb.write((byte) 0xf5); @@ -2422,11 +2433,21 @@ private static boolean is64Bits(final Instruction inst) { }; } - private static byte getEvexOpcodeMap(final Opcode opcode) { - return switch (opcode) { + private static byte getEvexOpcodeMap(final Instruction inst) { + return switch (inst.opcode()) { + // 0F map (mm = 01) case VMOVUPS, VMOVAPS, VMOVDQU8, VMOVDQU64, VMOVNTDQ, VMOVQ, VPXORQ, VPORQ, VPMINUB -> (byte) 0b001; + // 0F 38 map (mm = 10) case VBROADCASTSS, VPBROADCASTB, VPBROADCASTD, VPTESTMB, VPMINUD -> (byte) 0b010; - case VPCMPNEQUB, VPCMPEQB, VPCMPEQD, VPCMPNEQB, VPTERNLOGD -> (byte) 0b011; + // 0F 3A map (mm = 11) + case VPCMPNEQUB, VPCMPEQD, VPCMPNEQB, VPTERNLOGD -> (byte) 0b011; + case VPCMPEQB -> { + if (inst instanceof WeirdVpcmpeqb) { + yield (byte) 0b001; + } else { + yield (byte) 0b011; + } + } default -> (byte) 0b000; }; } @@ -2450,7 +2471,7 @@ && isFirstEER(inst) && isSecondR(inst) && (isThirdR(inst) || isThirdM(inst)) && inst.fourthOperand() instanceof Immediate), - getEvexOpcodeMap(inst.opcode())); + getEvexOpcodeMap(inst)); // TODO: refactor this chain of if-elses Register rvvvv = null; diff --git a/id/src/main/java/com/ledmington/cpu/x86/InstructionBuilder.java b/id/src/main/java/com/ledmington/cpu/x86/InstructionBuilder.java index 8730da72..e3838487 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/InstructionBuilder.java +++ b/id/src/main/java/com/ledmington/cpu/x86/InstructionBuilder.java @@ -135,6 +135,18 @@ public Instruction build() { } alreadyBuilt = true; + + if (prefix == null + && opcode == Opcode.VPCMPEQB + && destinationMask == null + && !destinationMaskZero + && op1 instanceof MaskRegister + && op2 instanceof RegisterYMM + && op3 instanceof RegisterYMM + && op4 == null) { + return new WeirdVpcmpeqb(opcode, op1, op2, op3); + } + return new GeneralInstruction(prefix, opcode, destinationMask, destinationMaskZero, op1, op2, op3, op4); } diff --git a/id/src/main/java/com/ledmington/cpu/x86/Instructions.java b/id/src/main/java/com/ledmington/cpu/x86/Instructions.java new file mode 100644 index 00000000..8374291a --- /dev/null +++ b/id/src/main/java/com/ledmington/cpu/x86/Instructions.java @@ -0,0 +1,68 @@ +/* + * emu - Processor Emulator + * Copyright (C) 2023-2025 Filippo Barbari + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.ledmington.cpu.x86; + +import java.util.Objects; + +/** A collection of common utilities for Instructions. */ +public final class Instructions { + + private Instructions() {} + + /** + * Returns true if the given instructions are effectively equal, meaning that the type of the class + * implementing {@link Instruction} may not be the same. This method is, therefore, slightly different from + * {@link Instruction#equals(Object)}. + * + * @param a An Instruction. + * @param b An Instruction to be compared with a for equality. + * @return true if the arguments are equal to each other and false otherwise + */ + public static boolean equals(final Instruction a, final Instruction b) { + Objects.requireNonNull(a); + Objects.requireNonNull(b); + + if (a.hasPrefix() != b.hasPrefix()) { + return false; + } + if (a.hasPrefix() && !a.getPrefix().equals(b.getPrefix())) { + return false; + } + + if (a.hasZeroDestinationMask() != b.hasZeroDestinationMask()) { + return false; + } + if (a.hasDestinationMask() != b.hasDestinationMask()) { + return false; + } + if (a.hasDestinationMask() && !a.getDestinationMask().equals(b.getDestinationMask())) { + return false; + } + + for (int i = 0; i < 4; i++) { + if (a.hasOperand(i) != b.hasOperand(i)) { + return false; + } + if (a.hasOperand(i) && !a.operand(i).equals(b.operand(i))) { + return false; + } + } + + return a.opcode() == b.opcode(); + } +} diff --git a/id/src/main/java/com/ledmington/cpu/x86/Opcode.java b/id/src/main/java/com/ledmington/cpu/x86/Opcode.java index 527a67b9..ce690d8a 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/Opcode.java +++ b/id/src/main/java/com/ledmington/cpu/x86/Opcode.java @@ -746,12 +746,24 @@ public enum Opcode { /** Packed compare implicit length strings, return index. */ VPCMPISTRI, + /** Compare packed data for less than or equal. */ + VPCMPLEB, + + /** Compare packed data for less than. */ + VPCMPLTB, + /** Compare packed data for not equal. */ VPCMPNEQB, /** Compare packed byte values into mask. */ VPCMPNEQUB, + /** Compare packed data for not less than or equal. */ + VPCMPNLEB, + + /** Compare packed data for not less than. */ + VPCMPNLTB, + /** Minimum of packed unsigned byte integers. */ VPMINUB, diff --git a/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java b/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java new file mode 100644 index 00000000..6a36864f --- /dev/null +++ b/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java @@ -0,0 +1,95 @@ +/* + * emu - Processor Emulator + * Copyright (C) 2023-2025 Filippo Barbari + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.ledmington.cpu.x86; + +/** + * This specific class represents the specific case of: 'vpcmpeqb k0,ymm16,ymm17' when encoded as '62 b1 7d 20 74 c1'. + */ +record WeirdVpcmpeqb(Opcode opcode, Operand firstOperand, Operand secondOperand, Operand thirdOperand) + implements Instruction { + + @Override + public Opcode opcode() { + return Opcode.VPCMPEQB; + } + + @Override + public boolean hasFirstOperand() { + return true; + } + + @Override + public boolean hasSecondOperand() { + return true; + } + + @Override + public boolean hasThirdOperand() { + return true; + } + + @Override + public boolean hasFourthOperand() { + return false; + } + + @Override + public Operand firstOperand() { + return firstOperand; + } + + @Override + public Operand secondOperand() { + return secondOperand; + } + + @Override + public Operand thirdOperand() { + return thirdOperand; + } + + @Override + public Operand fourthOperand() { + throw new IllegalArgumentException("No fourth operand."); + } + + @Override + public boolean hasPrefix() { + return false; + } + + @Override + public InstructionPrefix getPrefix() { + throw new IllegalArgumentException("No prefix."); + } + + @Override + public boolean hasDestinationMask() { + return false; + } + + @Override + public boolean hasZeroDestinationMask() { + return false; + } + + @Override + public MaskRegister getDestinationMask() { + throw new IllegalArgumentException("No destination mask."); + } +} diff --git a/id/src/test/java/com/ledmington/cpu/TestDecoding.java b/id/src/test/java/com/ledmington/cpu/TestDecoding.java index e34e0e9d..4d46d594 100644 --- a/id/src/test/java/com/ledmington/cpu/TestDecoding.java +++ b/id/src/test/java/com/ledmington/cpu/TestDecoding.java @@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; import java.util.stream.Collectors; @@ -143,9 +144,8 @@ void toIntelSyntax(final Instruction inst, final String expected) { @MethodSource("instAndIntelSyntax") void fromIntelSyntax(final Instruction expected, final String intelSyntax) { final Instruction actual = InstructionDecoder.fromIntelSyntax(intelSyntax); - assertEquals( - expected, - actual, + assertTrue( + Instructions.equals(expected, actual), () -> String.format( "Expected '%s' to be decoded into '%s' but was '%s'.", intelSyntax, expected, actual)); } diff --git a/id/src/test/java/com/ledmington/cpu/X64Encodings.java b/id/src/test/java/com/ledmington/cpu/X64Encodings.java index 46fcc5a2..18381658 100644 --- a/id/src/test/java/com/ledmington/cpu/X64Encodings.java +++ b/id/src/test/java/com/ledmington/cpu/X64Encodings.java @@ -10218,6 +10218,14 @@ Opcode.MOVABS, new SegmentedAddress(DS, new Immediate(0x12345678deadbeefL)), EAX .build(), "vpcmpeqb k0,ymm16,ymm18", "62 b3 7d 20 3f c2 00"), + test( + new WeirdVpcmpeqb(Opcode.VPCMPEQB, K0, YMM16, YMM17), + "vpcmpeqb k0,ymm16,ymm17", + "62 b1 7d 20 74 c1"), + test( + new WeirdVpcmpeqb(Opcode.VPCMPEQB, K0, YMM16, YMM18), + "vpcmpeqb k0,ymm16,ymm18", + "62 b1 7d 20 74 c2"), test( Instruction.builder() .opcode(Opcode.VPCMPEQB) @@ -10243,6 +10251,16 @@ Opcode.MOVABS, new SegmentedAddress(DS, new Immediate(0x12345678deadbeefL)), EAX .build(), "vpcmpeqb k1{k2},xmm17,xmm18", "62 b3 75 02 3f ca 00"), + // Vpcmpltb + test( + Instruction.builder() + .opcode(Opcode.VPCMPLTB) + .op(K0) + .op(YMM16) + .op(YMM18) + .build(), + "vpcmpltb k0,ymm16,ymm18", + "62 b3 7d 20 3f c2 01"), // Vpcmpeqd test( new GeneralInstruction( From f21d19b17e2549e6739dd01edd743d7d6ff7ef18 Mon Sep 17 00:00:00 2001 From: Ledmington Date: Sun, 9 Nov 2025 15:43:51 +0100 Subject: [PATCH 02/10] Minor fixes --- id/src/main/java/com/ledmington/cpu/InstructionEncoder.java | 1 + id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java | 2 +- id/src/test/java/com/ledmington/cpu/TestDecoding.java | 1 + id/src/test/java/com/ledmington/cpu/X64Encodings.java | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java b/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java index ac56cef5..7f952be7 100644 --- a/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java +++ b/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java @@ -50,6 +50,7 @@ import com.ledmington.cpu.x86.Registers; import com.ledmington.cpu.x86.SegmentRegister; import com.ledmington.cpu.x86.SegmentedAddress; +import com.ledmington.cpu.x86.WeirdVpcmpeqb; import com.ledmington.utils.WriteOnlyByteBuffer; import com.ledmington.utils.WriteOnlyByteBufferV1; diff --git a/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java b/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java index 6a36864f..58a83b47 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java +++ b/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java @@ -20,7 +20,7 @@ /** * This specific class represents the specific case of: 'vpcmpeqb k0,ymm16,ymm17' when encoded as '62 b1 7d 20 74 c1'. */ -record WeirdVpcmpeqb(Opcode opcode, Operand firstOperand, Operand secondOperand, Operand thirdOperand) +public record WeirdVpcmpeqb(Opcode opcode, Operand firstOperand, Operand secondOperand, Operand thirdOperand) implements Instruction { @Override diff --git a/id/src/test/java/com/ledmington/cpu/TestDecoding.java b/id/src/test/java/com/ledmington/cpu/TestDecoding.java index 4d46d594..c806a98e 100644 --- a/id/src/test/java/com/ledmington/cpu/TestDecoding.java +++ b/id/src/test/java/com/ledmington/cpu/TestDecoding.java @@ -32,6 +32,7 @@ import org.junit.jupiter.params.provider.MethodSource; import com.ledmington.cpu.x86.Instruction; +import com.ledmington.cpu.x86.Instructions; import com.ledmington.utils.BitUtils; final class TestDecoding extends X64Encodings { diff --git a/id/src/test/java/com/ledmington/cpu/X64Encodings.java b/id/src/test/java/com/ledmington/cpu/X64Encodings.java index 18381658..f1eeab37 100644 --- a/id/src/test/java/com/ledmington/cpu/X64Encodings.java +++ b/id/src/test/java/com/ledmington/cpu/X64Encodings.java @@ -166,6 +166,7 @@ import com.ledmington.cpu.x86.InstructionPrefix; import com.ledmington.cpu.x86.Opcode; import com.ledmington.cpu.x86.SegmentedAddress; +import com.ledmington.cpu.x86.WeirdVpcmpeqb; import com.ledmington.utils.BitUtils; import com.ledmington.utils.SuppressFBWarnings; From 02be6598eaeebb643e8ab06303b0e642230489d0 Mon Sep 17 00:00:00 2001 From: Ledmington Date: Wed, 4 Feb 2026 22:38:27 +0100 Subject: [PATCH 03/10] Minor fmt --- id/src/main/java/com/ledmington/cpu/x86/Instructions.java | 2 +- id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/id/src/main/java/com/ledmington/cpu/x86/Instructions.java b/id/src/main/java/com/ledmington/cpu/x86/Instructions.java index 8374291a..39e73814 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/Instructions.java +++ b/id/src/main/java/com/ledmington/cpu/x86/Instructions.java @@ -1,6 +1,6 @@ /* * emu - Processor Emulator - * Copyright (C) 2023-2025 Filippo Barbari + * Copyright (C) 2023-2026 Filippo Barbari * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java b/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java index 58a83b47..edcf95b7 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java +++ b/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java @@ -1,6 +1,6 @@ /* * emu - Processor Emulator - * Copyright (C) 2023-2025 Filippo Barbari + * Copyright (C) 2023-2026 Filippo Barbari * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From cfea51d435fd797ce49a15e7867e7d15429bc4ce Mon Sep 17 00:00:00 2001 From: Ledmington Date: Thu, 5 Feb 2026 12:05:24 +0100 Subject: [PATCH 04/10] Minor fixes --- .../ledmington/cpu/x86/InstructionBuilder.java | 2 +- .../com/ledmington/cpu/x86/Instructions.java | 6 +++--- .../com/ledmington/cpu/x86/WeirdVpcmpeqb.java | 7 +++++-- .../java/com/ledmington/cpu/X64Encodings.java | 18 ++++++------------ 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/id/src/main/java/com/ledmington/cpu/x86/InstructionBuilder.java b/id/src/main/java/com/ledmington/cpu/x86/InstructionBuilder.java index e3838487..9faf84f9 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/InstructionBuilder.java +++ b/id/src/main/java/com/ledmington/cpu/x86/InstructionBuilder.java @@ -144,7 +144,7 @@ public Instruction build() { && op2 instanceof RegisterYMM && op3 instanceof RegisterYMM && op4 == null) { - return new WeirdVpcmpeqb(opcode, op1, op2, op3); + return new WeirdVpcmpeqb(op1, op2, op3); } return new GeneralInstruction(prefix, opcode, destinationMask, destinationMaskZero, op1, op2, op3, op4); diff --git a/id/src/main/java/com/ledmington/cpu/x86/Instructions.java b/id/src/main/java/com/ledmington/cpu/x86/Instructions.java index 39e73814..58b47587 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/Instructions.java +++ b/id/src/main/java/com/ledmington/cpu/x86/Instructions.java @@ -27,11 +27,11 @@ private Instructions() {} /** * Returns true if the given instructions are effectively equal, meaning that the type of the class * implementing {@link Instruction} may not be the same. This method is, therefore, slightly different from - * {@link Instruction#equals(Object)}. + * {@link Object#equals(Object)}. * * @param a An Instruction. - * @param b An Instruction to be compared with a for equality. - * @return true if the arguments are equal to each other and false otherwise + * @param b An Instruction to be compared with a for equality. + * @return True if the arguments are equal to each other, false otherwise */ public static boolean equals(final Instruction a, final Instruction b) { Objects.requireNonNull(a); diff --git a/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java b/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java index edcf95b7..c9c831dc 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java +++ b/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java @@ -19,9 +19,12 @@ /** * This specific class represents the specific case of: 'vpcmpeqb k0,ymm16,ymm17' when encoded as '62 b1 7d 20 74 c1'. + * + * @param firstOperand The first operand of the instruction, usually a mask register. + * @param secondOperand The second operand of the instruction, usually an YMM register. + * @param thirdOperand The third operand of the instruction, usually an YMM register. */ -public record WeirdVpcmpeqb(Opcode opcode, Operand firstOperand, Operand secondOperand, Operand thirdOperand) - implements Instruction { +public record WeirdVpcmpeqb(Operand firstOperand, Operand secondOperand, Operand thirdOperand) implements Instruction { @Override public Opcode opcode() { diff --git a/id/src/test/java/com/ledmington/cpu/X64Encodings.java b/id/src/test/java/com/ledmington/cpu/X64Encodings.java index f1eeab37..e1584a56 100644 --- a/id/src/test/java/com/ledmington/cpu/X64Encodings.java +++ b/id/src/test/java/com/ledmington/cpu/X64Encodings.java @@ -192,7 +192,7 @@ public sealed class X64Encodings permits TestDecoding, TestDecodeIncompleteInstr // FIXME: this is ugly protected record X64EncodingTestCase(Instruction instruction, String intelSyntax, byte[] hex) { @SuppressWarnings("PMD.MethodReturnsInternalArray") - @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "This object as is it is for now.") + @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "This object is needed it is for now.") public byte[] hex() { return hex; } @@ -10215,18 +10215,12 @@ Opcode.MOVABS, new SegmentedAddress(DS, new Immediate(0x12345678deadbeefL)), EAX .opcode(Opcode.VPCMPEQB) .op(K0) .op(YMM16) - .op(YMM18) + .op(YMM19) .build(), - "vpcmpeqb k0,ymm16,ymm18", - "62 b3 7d 20 3f c2 00"), - test( - new WeirdVpcmpeqb(Opcode.VPCMPEQB, K0, YMM16, YMM17), - "vpcmpeqb k0,ymm16,ymm17", - "62 b1 7d 20 74 c1"), - test( - new WeirdVpcmpeqb(Opcode.VPCMPEQB, K0, YMM16, YMM18), - "vpcmpeqb k0,ymm16,ymm18", - "62 b1 7d 20 74 c2"), + "vpcmpeqb k0,ymm16,ymm19", + "62 b3 7d 20 3f c3 00"), + test(new WeirdVpcmpeqb(K0, YMM16, YMM17), "vpcmpeqb k0,ymm16,ymm17", "62 b1 7d 20 74 c1"), + test(new WeirdVpcmpeqb(K0, YMM16, YMM18), "vpcmpeqb k0,ymm16,ymm18", "62 b1 7d 20 74 c2"), test( Instruction.builder() .opcode(Opcode.VPCMPEQB) From 65694bc6271b5065b68982e4c95fbcb6b91e71ec Mon Sep 17 00:00:00 2001 From: Ledmington Date: Mon, 4 May 2026 17:30:46 +0200 Subject: [PATCH 05/10] wip --- .../java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java | 4 +++- id/src/test/java/com/ledmington/cpu/TestDecoding.java | 11 ++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java b/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java index c9c831dc..1fa45667 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java +++ b/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java @@ -18,7 +18,9 @@ package com.ledmington.cpu.x86; /** - * This specific class represents the specific case of: 'vpcmpeqb k0,ymm16,ymm17' when encoded as '62 b1 7d 20 74 c1'. + * This specific class represents the specific case of: 'vpcmpeqb k0,ymm16,ymm19' when encoded as '62 b1 7d 20 74 c3'. + * That instruction can also be encoded as '62 b3 7d 20 3f c3 00', however that case is handled by the class + * {@link GeneralInstruction}. * * @param firstOperand The first operand of the instruction, usually a mask register. * @param secondOperand The second operand of the instruction, usually an YMM register. diff --git a/id/src/test/java/com/ledmington/cpu/TestDecoding.java b/id/src/test/java/com/ledmington/cpu/TestDecoding.java index c806a98e..0ff21d05 100644 --- a/id/src/test/java/com/ledmington/cpu/TestDecoding.java +++ b/id/src/test/java/com/ledmington/cpu/TestDecoding.java @@ -76,14 +76,11 @@ void toHex(final Instruction inst, final byte[] expected) { break; } } - s = s - + String.format( - " First different byte is at index %,d: expected 0b%s but was 0b%s.", - i, BitUtils.toBinaryString(expected[i]), BitUtils.toBinaryString(actual[i])); + s += String.format( + " First different byte is at index %,d: expected 0b%s but was 0b%s.", + i, BitUtils.toBinaryString(expected[i]), BitUtils.toBinaryString(actual[i])); } else { - s = s - + String.format( - " Wrong lengths: expected %,d bytes but were %,d.", expected.length, actual.length); + s += String.format(" Wrong lengths: expected %,d bytes but were %,d.", expected.length, actual.length); } return s; }); From 93d330e64f0a07831bf3d42acad2b00cb2dd6909 Mon Sep 17 00:00:00 2001 From: Ledmington Date: Mon, 4 May 2026 18:24:37 +0200 Subject: [PATCH 06/10] Refactored tests to allow multiple encodings of the same instruction --- .../cpu/TestDecodeIncompleteInstruction.java | 5 +- .../java/com/ledmington/cpu/TestDecoding.java | 43 +++++++++----- .../java/com/ledmington/cpu/X64Encodings.java | 59 +++++++++++-------- 3 files changed, 64 insertions(+), 43 deletions(-) diff --git a/id/src/test/java/com/ledmington/cpu/TestDecodeIncompleteInstruction.java b/id/src/test/java/com/ledmington/cpu/TestDecodeIncompleteInstruction.java index 828ad313..94e9a675 100644 --- a/id/src/test/java/com/ledmington/cpu/TestDecodeIncompleteInstruction.java +++ b/id/src/test/java/com/ledmington/cpu/TestDecodeIncompleteInstruction.java @@ -35,9 +35,8 @@ final class TestDecodeIncompleteInstruction extends X64Encodings { private static Stream incompleteInstructions() { final Set> validInstructions = X64_ENCODINGS.stream() - .map(x -> IntStream.range(0, x.hex().length) - .mapToObj(i -> x.hex()[i]) - .toList()) + .flatMap(x -> x.allowedEncodings().stream()) + .map(hex -> IntStream.range(0, hex.length).mapToObj(i -> hex[i]).toList()) .collect(Collectors.toSet()); return validInstructions.stream() .flatMap(splitted -> { diff --git a/id/src/test/java/com/ledmington/cpu/TestDecoding.java b/id/src/test/java/com/ledmington/cpu/TestDecoding.java index 0ff21d05..427501f2 100644 --- a/id/src/test/java/com/ledmington/cpu/TestDecoding.java +++ b/id/src/test/java/com/ledmington/cpu/TestDecoding.java @@ -17,11 +17,11 @@ */ package com.ledmington.cpu; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -57,37 +57,48 @@ void checkReference(final Instruction inst) { InstructionEncoder.toIntelSyntax(inst))); } - private static Stream instAndHex() { - return X64_ENCODINGS.stream().map(x -> Arguments.of(x.instruction(), x.hex())); + private static Stream instructionAndAllowedEncodings() { + return X64_ENCODINGS.stream().map(x -> Arguments.of(x.instruction(), x.allowedEncodings())); } @ParameterizedTest - @MethodSource("instAndHex") - void toHex(final Instruction inst, final byte[] expected) { + @MethodSource("instructionAndAllowedEncodings") + void toHex(final Instruction inst, final List allowedEncodings) { final byte[] actual = InstructionEncoder.toHex(inst, true); - assertArrayEquals(expected, actual, () -> { + final boolean matchFound = allowedEncodings.stream().anyMatch(allowed -> Arrays.equals(allowed, actual)); + assertTrue(matchFound, () -> { + final String allowedStr = + allowedEncodings.stream().map(TestDecoding::asString).collect(Collectors.joining(", ")); String s = String.format( - "Expected '%s' to be encoded as '%s' but was '%s'.", - inst.toString(), asString(expected), asString(actual)); - if (expected.length == actual.length) { + "Expected '%s' to be encoded as one of [%s] but was '%s'.", + inst.toString(), allowedStr, asString(actual)); + final byte[] closestMatch = allowedEncodings.stream() + .filter(allowed -> allowed.length == actual.length) + .findFirst() + .orElse(null); + if (closestMatch != null) { int i = 0; - for (; i < expected.length; i++) { - if (expected[i] != actual[i]) { + for (; i < closestMatch.length; i++) { + if (closestMatch[i] != actual[i]) { break; } } s += String.format( - " First different byte is at index %,d: expected 0b%s but was 0b%s.", - i, BitUtils.toBinaryString(expected[i]), BitUtils.toBinaryString(actual[i])); + " First different byte (vs. same-length encoding) is at index %,d: expected 0b%s but was 0b%s.", + i, BitUtils.toBinaryString(closestMatch[i]), BitUtils.toBinaryString(actual[i])); } else { - s += String.format(" Wrong lengths: expected %,d bytes but were %,d.", expected.length, actual.length); + final String allowedLengths = allowedEncodings.stream() + .map(allowed -> String.valueOf(allowed.length)) + .collect(Collectors.joining(", ")); + s += String.format( + " Wrong length: allowed lengths are [%s] bytes but were %,d.", allowedLengths, actual.length); } return s; }); } @ParameterizedTest - @MethodSource("instAndHex") + @MethodSource("instructionAndAllowedEncodings") void fromHex(final Instruction expected, final byte[] hex) { final List inst = InstructionDecoder.fromHex(hex, hex.length, true); assertEquals( @@ -104,7 +115,7 @@ void fromHex(final Instruction expected, final byte[] hex) { } private static Stream onlyHex() { - return X64_ENCODINGS.stream().map(x -> Arguments.of((Object) x.hex())); + return X64_ENCODINGS.stream().flatMap(x -> x.allowedEncodings().stream().map(Arguments::of)); } @ParameterizedTest diff --git a/id/src/test/java/com/ledmington/cpu/X64Encodings.java b/id/src/test/java/com/ledmington/cpu/X64Encodings.java index e1584a56..9561891e 100644 --- a/id/src/test/java/com/ledmington/cpu/X64Encodings.java +++ b/id/src/test/java/com/ledmington/cpu/X64Encodings.java @@ -151,6 +151,7 @@ import static com.ledmington.cpu.x86.SegmentRegister.ES; import static com.ledmington.cpu.x86.SegmentRegister.GS; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -166,9 +167,7 @@ import com.ledmington.cpu.x86.InstructionPrefix; import com.ledmington.cpu.x86.Opcode; import com.ledmington.cpu.x86.SegmentedAddress; -import com.ledmington.cpu.x86.WeirdVpcmpeqb; import com.ledmington.utils.BitUtils; -import com.ledmington.utils.SuppressFBWarnings; @SuppressWarnings("PMD.TooManyStaticImports") public sealed class X64Encodings permits TestDecoding, TestDecodeIncompleteInstruction { @@ -190,11 +189,9 @@ public sealed class X64Encodings permits TestDecoding, TestDecodeIncompleteInstr .toList(); // FIXME: this is ugly - protected record X64EncodingTestCase(Instruction instruction, String intelSyntax, byte[] hex) { - @SuppressWarnings("PMD.MethodReturnsInternalArray") - @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "This object is needed it is for now.") - public byte[] hex() { - return hex; + protected record X64EncodingTestCase(Instruction instruction, String intelSyntax, List allowedEncodings) { + public List allowedEncodings() { + return allowedEncodings; } } @@ -204,7 +201,21 @@ private static X64EncodingTestCase test(final Instruction instruction, final Str for (int i = 0; i < splitted.length; i++) { code[i] = BitUtils.asByte(Integer.parseInt(splitted[i], 16)); } - return new X64EncodingTestCase(instruction, intelSyntax, code); + return new X64EncodingTestCase(instruction, intelSyntax, List.of(code)); + } + + private static X64EncodingTestCase test( + final Instruction instruction, final String intelSyntax, final List allowedEncodings) { + final List encodings = new ArrayList<>(); + for (final String hex : allowedEncodings) { + final String[] splitted = hex.strip().split(" "); + final byte[] code = new byte[splitted.length]; + for (int i = 0; i < splitted.length; i++) { + code[i] = BitUtils.asByte(Integer.parseInt(splitted[i], 16)); + } + encodings.add(code); + } + return new X64EncodingTestCase(instruction, intelSyntax, encodings); } private static List nop() { @@ -10218,9 +10229,7 @@ Opcode.MOVABS, new SegmentedAddress(DS, new Immediate(0x12345678deadbeefL)), EAX .op(YMM19) .build(), "vpcmpeqb k0,ymm16,ymm19", - "62 b3 7d 20 3f c3 00"), - test(new WeirdVpcmpeqb(K0, YMM16, YMM17), "vpcmpeqb k0,ymm16,ymm17", "62 b1 7d 20 74 c1"), - test(new WeirdVpcmpeqb(K0, YMM16, YMM18), "vpcmpeqb k0,ymm16,ymm18", "62 b1 7d 20 74 c2"), + List.of("62 b3 7d 20 3f c3 00", "62 b1 7d 20 74 c3")), test( Instruction.builder() .opcode(Opcode.VPCMPEQB) @@ -11101,29 +11110,31 @@ private static String asString(final byte[] v) { static { // Check that there are no duplicates - final Set inst = new HashSet<>(); - final Set is = new HashSet<>(); - final Set> hex = new HashSet<>(); + final Set allInstructions = new HashSet<>(); + final Set allInstructionStrings = new HashSet<>(); + final Set> allInstructionBytes = new HashSet<>(); for (final X64EncodingTestCase t : X64_ENCODINGS) { - if (inst.contains(t.instruction())) { + if (allInstructions.contains(t.instruction())) { throw new IllegalArgumentException( String.format("Duplicate instruction in test cases: '%s'.", t.instruction())); } - inst.add(t.instruction()); + allInstructions.add(t.instruction()); - if (is.contains(t.intelSyntax())) { + if (allInstructionStrings.contains(t.intelSyntax())) { throw new IllegalArgumentException( String.format("Duplicate intel syntax in test cases: '%s'.", t.intelSyntax())); } - is.add(t.intelSyntax()); + allInstructionStrings.add(t.intelSyntax()); - final int n = t.hex().length; - final List b = IntStream.range(0, n).mapToObj(i -> t.hex()[i]).toList(); - if (hex.contains(b)) { - throw new IllegalArgumentException( - String.format("Duplicate hex representation in test cases: '%s'.", asString(t.hex()))); + for (final byte[] hex : t.allowedEncodings()) { + final int n = hex.length; + final List b = IntStream.range(0, n).mapToObj(i -> hex[i]).toList(); + if (allInstructionBytes.contains(b)) { + throw new IllegalArgumentException( + String.format("Duplicate hex representation in test cases: '%s'.", asString(hex))); + } + allInstructionBytes.add(b); } - hex.add(b); } } } From 5733cd52bed03d8ce09f7cec65454006a43aa7cf Mon Sep 17 00:00:00 2001 From: Ledmington Date: Mon, 4 May 2026 18:27:46 +0200 Subject: [PATCH 07/10] Removed WeirdVpcmpeqb class --- .../ledmington/cpu/InstructionEncoder.java | 13 +-- .../cpu/x86/InstructionBuilder.java | 11 -- .../com/ledmington/cpu/x86/WeirdVpcmpeqb.java | 100 ------------------ 3 files changed, 2 insertions(+), 122 deletions(-) delete mode 100644 id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java diff --git a/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java b/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java index 7f952be7..1e9aad12 100644 --- a/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java +++ b/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java @@ -50,7 +50,6 @@ import com.ledmington.cpu.x86.Registers; import com.ledmington.cpu.x86.SegmentRegister; import com.ledmington.cpu.x86.SegmentedAddress; -import com.ledmington.cpu.x86.WeirdVpcmpeqb; import com.ledmington.utils.WriteOnlyByteBuffer; import com.ledmington.utils.WriteOnlyByteBufferV1; @@ -2008,9 +2007,7 @@ private static void encodeThreeOperandsInstruction(final WriteOnlyByteBuffer wb, case VPMINUD -> wb.write((byte) 0x3b); case VPCMPGTB -> wb.write((byte) 0x64); case VPCMPEQB -> { - if (inst instanceof WeirdVpcmpeqb) { - wb.write((byte) 0x74); - } else if (isFirstMask(inst) && isSecondR(inst) && (isThirdM(inst) || isThirdR(inst))) { + if (isFirstMask(inst) && isSecondR(inst) && (isThirdM(inst) || isThirdR(inst))) { wb.write((byte) 0x3f); lastByte = (byte) 0x00; } else { @@ -2442,13 +2439,7 @@ private static byte getEvexOpcodeMap(final Instruction inst) { case VBROADCASTSS, VPBROADCASTB, VPBROADCASTD, VPTESTMB, VPMINUD -> (byte) 0b010; // 0F 3A map (mm = 11) case VPCMPNEQUB, VPCMPEQD, VPCMPNEQB, VPTERNLOGD -> (byte) 0b011; - case VPCMPEQB -> { - if (inst instanceof WeirdVpcmpeqb) { - yield (byte) 0b001; - } else { - yield (byte) 0b011; - } - } + case VPCMPEQB -> (byte) 0b011; default -> (byte) 0b000; }; } diff --git a/id/src/main/java/com/ledmington/cpu/x86/InstructionBuilder.java b/id/src/main/java/com/ledmington/cpu/x86/InstructionBuilder.java index 9faf84f9..9100871d 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/InstructionBuilder.java +++ b/id/src/main/java/com/ledmington/cpu/x86/InstructionBuilder.java @@ -136,17 +136,6 @@ public Instruction build() { alreadyBuilt = true; - if (prefix == null - && opcode == Opcode.VPCMPEQB - && destinationMask == null - && !destinationMaskZero - && op1 instanceof MaskRegister - && op2 instanceof RegisterYMM - && op3 instanceof RegisterYMM - && op4 == null) { - return new WeirdVpcmpeqb(op1, op2, op3); - } - return new GeneralInstruction(prefix, opcode, destinationMask, destinationMaskZero, op1, op2, op3, op4); } diff --git a/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java b/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java deleted file mode 100644 index 1fa45667..00000000 --- a/id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * emu - Processor Emulator - * Copyright (C) 2023-2026 Filippo Barbari - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.ledmington.cpu.x86; - -/** - * This specific class represents the specific case of: 'vpcmpeqb k0,ymm16,ymm19' when encoded as '62 b1 7d 20 74 c3'. - * That instruction can also be encoded as '62 b3 7d 20 3f c3 00', however that case is handled by the class - * {@link GeneralInstruction}. - * - * @param firstOperand The first operand of the instruction, usually a mask register. - * @param secondOperand The second operand of the instruction, usually an YMM register. - * @param thirdOperand The third operand of the instruction, usually an YMM register. - */ -public record WeirdVpcmpeqb(Operand firstOperand, Operand secondOperand, Operand thirdOperand) implements Instruction { - - @Override - public Opcode opcode() { - return Opcode.VPCMPEQB; - } - - @Override - public boolean hasFirstOperand() { - return true; - } - - @Override - public boolean hasSecondOperand() { - return true; - } - - @Override - public boolean hasThirdOperand() { - return true; - } - - @Override - public boolean hasFourthOperand() { - return false; - } - - @Override - public Operand firstOperand() { - return firstOperand; - } - - @Override - public Operand secondOperand() { - return secondOperand; - } - - @Override - public Operand thirdOperand() { - return thirdOperand; - } - - @Override - public Operand fourthOperand() { - throw new IllegalArgumentException("No fourth operand."); - } - - @Override - public boolean hasPrefix() { - return false; - } - - @Override - public InstructionPrefix getPrefix() { - throw new IllegalArgumentException("No prefix."); - } - - @Override - public boolean hasDestinationMask() { - return false; - } - - @Override - public boolean hasZeroDestinationMask() { - return false; - } - - @Override - public MaskRegister getDestinationMask() { - throw new IllegalArgumentException("No destination mask."); - } -} From cf5feb6873cfebdaca419c5e666a3e06d36308ee Mon Sep 17 00:00:00 2001 From: Ledmington Date: Mon, 4 May 2026 21:17:42 +0200 Subject: [PATCH 08/10] Converted allowedEncodings to Set --- .../java/com/ledmington/cpu/TestDecoding.java | 17 +++++++++++++++-- .../java/com/ledmington/cpu/X64Encodings.java | 14 +++++++------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/id/src/test/java/com/ledmington/cpu/TestDecoding.java b/id/src/test/java/com/ledmington/cpu/TestDecoding.java index 427501f2..befe9fc9 100644 --- a/id/src/test/java/com/ledmington/cpu/TestDecoding.java +++ b/id/src/test/java/com/ledmington/cpu/TestDecoding.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -61,9 +62,16 @@ private static Stream instructionAndAllowedEncodings() { return X64_ENCODINGS.stream().map(x -> Arguments.of(x.instruction(), x.allowedEncodings())); } + /** + * This test checks that the given instruction maps to any of the given encodings. This is less strict than decoding + * from binary ({@link TestDecoding#fromHex(Instruction, byte[])}) since we do not care about exact binary encoding. + * + * @param inst The instruction to encode + * @param allowedEncodings The allowed encodings for the given instruction. + */ @ParameterizedTest @MethodSource("instructionAndAllowedEncodings") - void toHex(final Instruction inst, final List allowedEncodings) { + void toHex(final Instruction inst, final Set allowedEncodings) { final byte[] actual = InstructionEncoder.toHex(inst, true); final boolean matchFound = allowedEncodings.stream().anyMatch(allowed -> Arrays.equals(allowed, actual)); assertTrue(matchFound, () -> { @@ -97,8 +105,13 @@ void toHex(final Instruction inst, final List allowedEncodings) { }); } + private static Stream instructionAndHex() { + return X64_ENCODINGS.stream() + .flatMap(x -> x.allowedEncodings().stream().map(hex -> Arguments.of(x.instruction(), hex))); + } + @ParameterizedTest - @MethodSource("instructionAndAllowedEncodings") + @MethodSource("instructionAndHex") void fromHex(final Instruction expected, final byte[] hex) { final List inst = InstructionDecoder.fromHex(hex, hex.length, true); assertEquals( diff --git a/id/src/test/java/com/ledmington/cpu/X64Encodings.java b/id/src/test/java/com/ledmington/cpu/X64Encodings.java index 9561891e..018ae280 100644 --- a/id/src/test/java/com/ledmington/cpu/X64Encodings.java +++ b/id/src/test/java/com/ledmington/cpu/X64Encodings.java @@ -151,7 +151,6 @@ import static com.ledmington.cpu.x86.SegmentRegister.ES; import static com.ledmington.cpu.x86.SegmentRegister.GS; -import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -189,8 +188,9 @@ public sealed class X64Encodings permits TestDecoding, TestDecodeIncompleteInstr .toList(); // FIXME: this is ugly - protected record X64EncodingTestCase(Instruction instruction, String intelSyntax, List allowedEncodings) { - public List allowedEncodings() { + // TODO: do the allowed encodings need to be a set (instead of a list)? + protected record X64EncodingTestCase(Instruction instruction, String intelSyntax, Set allowedEncodings) { + public Set allowedEncodings() { return allowedEncodings; } } @@ -201,12 +201,12 @@ private static X64EncodingTestCase test(final Instruction instruction, final Str for (int i = 0; i < splitted.length; i++) { code[i] = BitUtils.asByte(Integer.parseInt(splitted[i], 16)); } - return new X64EncodingTestCase(instruction, intelSyntax, List.of(code)); + return new X64EncodingTestCase(instruction, intelSyntax, Set.of(code)); } private static X64EncodingTestCase test( - final Instruction instruction, final String intelSyntax, final List allowedEncodings) { - final List encodings = new ArrayList<>(); + final Instruction instruction, final String intelSyntax, final Set allowedEncodings) { + final Set encodings = new HashSet<>(); for (final String hex : allowedEncodings) { final String[] splitted = hex.strip().split(" "); final byte[] code = new byte[splitted.length]; @@ -10229,7 +10229,7 @@ Opcode.MOVABS, new SegmentedAddress(DS, new Immediate(0x12345678deadbeefL)), EAX .op(YMM19) .build(), "vpcmpeqb k0,ymm16,ymm19", - List.of("62 b3 7d 20 3f c3 00", "62 b1 7d 20 74 c3")), + Set.of("62 b3 7d 20 3f c3 00", "62 b1 7d 20 74 c3")), test( Instruction.builder() .opcode(Opcode.VPCMPEQB) From aad234a3ae2872fe1947d2d1fad9c9003ce6e989 Mon Sep 17 00:00:00 2001 From: Ledmington Date: Mon, 4 May 2026 21:55:58 +0200 Subject: [PATCH 09/10] Fixed VPCMPxxx encodings --- .../com/ledmington/cpu/InstructionEncoder.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java b/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java index 1e9aad12..1a74efd8 100644 --- a/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java +++ b/id/src/main/java/com/ledmington/cpu/InstructionEncoder.java @@ -237,7 +237,7 @@ public static String toIntelSyntax( if (inst.hasFirstOperand()) { if (opcode.length() < opcodePad) { - sb.append(" ".repeat(opcodePad - opcode.length())); + sb.repeat(" ", opcodePad - opcode.length()); } sb.append(' ').append(operandString(inst, inst.firstOperand(), shortHex)); if (inst.hasDestinationMask()) { @@ -382,7 +382,7 @@ private static Prefix checkRequiredPrefix(final Instruction inst) { ? Prefix.EVEX : ((isFirstMS(inst) || isSecondM(inst)) ? Prefix.VEX2 : Prefix.VEX3); case VPMINUB -> countEvexExtensions(inst) > 0 ? Prefix.EVEX : Prefix.VEX2; - case VPCMPEQB -> + case VPCMPEQB, VPCMPEQD, VPCMPNEQB -> (isFirstMask(inst) && isSecondEER(inst) && isThirdM(inst)) ? Prefix.EVEX : (isThirdMS(inst) ? Prefix.VEX2 : Prefix.VEX3); @@ -2016,27 +2016,21 @@ private static void encodeThreeOperandsInstruction(final WriteOnlyByteBuffer wb, } case VPCMPEQD -> { if (isFirstMask(inst) && isSecondR(inst) && isThirdM(inst)) { - encodeEvexPrefix(wb, inst); wb.write((byte) 0x1f); lastByte = (byte) 0x00; } else { - encodeVex2Prefix(wb, inst); wb.write((byte) 0x76); } } case VPCMPEQQ -> wb.write((byte) 0x29); case VPCMPNEQB -> { if (isFirstMask(inst) && isSecondR(inst) && inst.thirdOperand() instanceof IndirectOperand) { - encodeEvexPrefix(wb, inst); wb.write((byte) 0x3f); lastByte = (byte) 0x04; } } case VPCMPLTB -> { - if (isFirstMask(inst) - && inst.secondOperand() instanceof Register - && inst.thirdOperand() instanceof IndirectOperand) { - encodeEvexPrefix(wb, inst); + if (isFirstMask(inst) && isSecondR(inst) && (isThirdM(inst) || isThirdR(inst))) { wb.write((byte) 0x3f); lastByte = (byte) 0x01; } @@ -2306,6 +2300,7 @@ private static byte getImpliedPrefix(final Instruction inst) { VPCMPEQQ, VPCMPNEQB, VPCMPNEQUB, + VPCMPLTB, VPCMPGTB, VMOVD, VPBROADCASTB, @@ -2438,8 +2433,7 @@ private static byte getEvexOpcodeMap(final Instruction inst) { // 0F 38 map (mm = 10) case VBROADCASTSS, VPBROADCASTB, VPBROADCASTD, VPTESTMB, VPMINUD -> (byte) 0b010; // 0F 3A map (mm = 11) - case VPCMPNEQUB, VPCMPEQD, VPCMPNEQB, VPTERNLOGD -> (byte) 0b011; - case VPCMPEQB -> (byte) 0b011; + case VPCMPNEQUB, VPCMPEQD, VPCMPNEQB, VPTERNLOGD, VPCMPEQB, VPCMPLTB -> (byte) 0b011; default -> (byte) 0b000; }; } From b7948d56cc075726f612dff527418c41c559e5aa Mon Sep 17 00:00:00 2001 From: Ledmington Date: Mon, 4 May 2026 22:01:20 +0200 Subject: [PATCH 10/10] PMD fixes --- id/src/main/java/com/ledmington/cpu/x86/Instructions.java | 4 ++-- id/src/test/java/com/ledmington/cpu/X64Encodings.java | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/id/src/main/java/com/ledmington/cpu/x86/Instructions.java b/id/src/main/java/com/ledmington/cpu/x86/Instructions.java index 58b47587..57b59693 100644 --- a/id/src/main/java/com/ledmington/cpu/x86/Instructions.java +++ b/id/src/main/java/com/ledmington/cpu/x86/Instructions.java @@ -40,7 +40,7 @@ public static boolean equals(final Instruction a, final Instruction b) { if (a.hasPrefix() != b.hasPrefix()) { return false; } - if (a.hasPrefix() && !a.getPrefix().equals(b.getPrefix())) { + if (a.hasPrefix() && a.getPrefix() != b.getPrefix()) { return false; } @@ -50,7 +50,7 @@ public static boolean equals(final Instruction a, final Instruction b) { if (a.hasDestinationMask() != b.hasDestinationMask()) { return false; } - if (a.hasDestinationMask() && !a.getDestinationMask().equals(b.getDestinationMask())) { + if (a.hasDestinationMask() && a.getDestinationMask() != b.getDestinationMask()) { return false; } diff --git a/id/src/test/java/com/ledmington/cpu/X64Encodings.java b/id/src/test/java/com/ledmington/cpu/X64Encodings.java index 018ae280..c805c162 100644 --- a/id/src/test/java/com/ledmington/cpu/X64Encodings.java +++ b/id/src/test/java/com/ledmington/cpu/X64Encodings.java @@ -167,6 +167,7 @@ import com.ledmington.cpu.x86.Opcode; import com.ledmington.cpu.x86.SegmentedAddress; import com.ledmington.utils.BitUtils; +import com.ledmington.utils.SuppressFBWarnings; @SuppressWarnings("PMD.TooManyStaticImports") public sealed class X64Encodings permits TestDecoding, TestDecodeIncompleteInstruction { @@ -190,6 +191,7 @@ public sealed class X64Encodings permits TestDecoding, TestDecodeIncompleteInstr // FIXME: this is ugly // TODO: do the allowed encodings need to be a set (instead of a list)? protected record X64EncodingTestCase(Instruction instruction, String intelSyntax, Set allowedEncodings) { + @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "This object as is it is for now.") public Set allowedEncodings() { return allowedEncodings; }