Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
36 changes: 29 additions & 7 deletions id/src/main/java/com/ledmington/cpu/InstructionDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand All @@ -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();
}
Expand Down
32 changes: 27 additions & 5 deletions id/src/main/java/com/ledmington/cpu/InstructionEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -2007,7 +2008,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 {
Expand All @@ -2032,6 +2035,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);
Expand Down Expand Up @@ -2422,11 +2434,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;
};
}
Expand All @@ -2450,7 +2472,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;
Expand Down
12 changes: 12 additions & 0 deletions id/src/main/java/com/ledmington/cpu/x86/InstructionBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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(op1, op2, op3);
}

return new GeneralInstruction(prefix, opcode, destinationMask, destinationMaskZero, op1, op2, op3, op4);
}

Expand Down
68 changes: 68 additions & 0 deletions id/src/main/java/com/ledmington/cpu/x86/Instructions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* emu - Processor Emulator
* Copyright (C) 2023-2025 Filippo Barbari <[email protected]>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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 <i>effectively equal</i>, 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();
}
}
12 changes: 12 additions & 0 deletions id/src/main/java/com/ledmington/cpu/x86/Opcode.java
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down
94 changes: 94 additions & 0 deletions id/src/main/java/com/ledmington/cpu/x86/WeirdVpcmpeqb.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* emu - Processor Emulator
* Copyright (C) 2023-2025 Filippo Barbari <[email protected]>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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'.
*/
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.");
}
}
7 changes: 4 additions & 3 deletions id/src/test/java/com/ledmington/cpu/TestDecoding.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,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 {
Expand Down Expand Up @@ -143,9 +145,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));
}
Expand Down
Loading
Loading