| 1 | package com.rox.cpu.mos6502.assembler; | |
| 2 | ||
| 3 | import com.rox.cpu.mos6502.MOS6502OpCode; | |
| 4 | ||
| 5 | import java.util.ArrayList; | |
| 6 | import java.util.List; | |
| 7 | import java.util.function.IntUnaryOperator; | |
| 8 | ||
| 9 | /** | |
| 10 | * Decodes 6502 machine code into {@link DisassembledInstruction}s for display - forward-only, since a | |
| 11 | * variable-length instruction set can't be walked backward from an arbitrary address without ambiguity | |
| 12 | * (any preceding byte could be a real opcode or another instruction's operand). Bytes are read via a | |
| 13 | * caller-supplied {@code peekByte} function rather than any particular {@code Memory}/bus type, so a | |
| 14 | * caller reading from a live, running NES can substitute a side-effect-free peek (e.g. one that avoids | |
| 15 | * triggering read-clearing PPU/APU/controller registers) without this class needing to know about any | |
| 16 | * of that. | |
| 17 | */ | |
| 18 | public final class Disassembler { | |
| 19 | private static final int BYTE_MASK = 0xFF; | |
| 20 | private static final int ADDRESS_MASK = 0xFFFF; | |
| 21 | private static final String UNKNOWN_MNEMONIC = "???"; | |
| 22 | ||
| 23 | private Disassembler(){ | |
| 24 | } | |
| 25 | ||
| 26 | /** Decodes the single instruction starting at {@code address}. */ | |
| 27 | public static DisassembledInstruction disassembleOne(final IntUnaryOperator peekByte, final int address){ | |
| 28 |
1
1. disassembleOne : Replaced bitwise AND with OR → KILLED |
final int opcodeByte = peekByte.applyAsInt(address) & BYTE_MASK; |
| 29 | ||
| 30 | final MOS6502OpCode opcode; | |
| 31 | try { | |
| 32 | opcode = MOS6502OpCode.from(opcodeByte); | |
| 33 | } catch (IllegalArgumentException e){ | |
| 34 |
1
1. disassembleOne : replaced return value with null for com/rox/cpu/mos6502/assembler/Disassembler::disassembleOne → KILLED |
return new DisassembledInstruction(address, 1, UNKNOWN_MNEMONIC, new int[0], |
| 35 | String.format("%s ($%02X)", UNKNOWN_MNEMONIC, opcodeByte)); | |
| 36 | } | |
| 37 | ||
| 38 | final AddressingMode mode = OpCodeTable.modeOf(opcode); | |
| 39 | final String mnemonic = opcode.name().split("_", 2)[0]; | |
| 40 | final int operandByteCount = mode.operandByteCount(); | |
| 41 | final int[] operandBytes = new int[operandByteCount]; | |
| 42 |
2
1. disassembleOne : negated conditional → KILLED 2. disassembleOne : changed conditional boundary → KILLED |
for (int i = 0; i < operandByteCount; i++){ |
| 43 |
4
1. disassembleOne : Replaced integer addition with subtraction → KILLED 2. disassembleOne : Replaced integer addition with subtraction → KILLED 3. disassembleOne : Replaced bitwise AND with OR → KILLED 4. disassembleOne : Replaced bitwise AND with OR → KILLED |
operandBytes[i] = peekByte.applyAsInt((address + 1 + i) & ADDRESS_MASK) & BYTE_MASK; |
| 44 | } | |
| 45 | //BRK is implied-mode but the CPU skips the "signature" byte after it (BRK_IMP's first cycle | |
| 46 | //increments PC past it), so execution - and forward decoding - resumes 2 bytes on | |
| 47 |
2
1. disassembleOne : Replaced integer addition with subtraction → KILLED 2. disassembleOne : negated conditional → KILLED |
final int length = opcode == MOS6502OpCode.BRK_IMP ? 2 : 1 + operandByteCount; |
| 48 | ||
| 49 |
1
1. disassembleOne : replaced return value with null for com/rox/cpu/mos6502/assembler/Disassembler::disassembleOne → KILLED |
return new DisassembledInstruction(address, length, mnemonic, operandBytes, |
| 50 | format(mnemonic, mode, operandBytes, address, length)); | |
| 51 | } | |
| 52 | ||
| 53 | /** Decodes {@code count} instructions forward from {@code startAddress}, wrapping at $FFFF. */ | |
| 54 | public static List<DisassembledInstruction> disassembleForward(final IntUnaryOperator peekByte, final int startAddress, final int count){ | |
| 55 | final List<DisassembledInstruction> instructions = new ArrayList<>(count); | |
| 56 |
1
1. disassembleForward : Replaced bitwise AND with OR → KILLED |
int address = startAddress & ADDRESS_MASK; |
| 57 |
2
1. disassembleForward : changed conditional boundary → KILLED 2. disassembleForward : negated conditional → KILLED |
for (int i = 0; i < count; i++){ |
| 58 | final DisassembledInstruction instruction = disassembleOne(peekByte, address); | |
| 59 | instructions.add(instruction); | |
| 60 |
2
1. disassembleForward : Replaced bitwise AND with OR → KILLED 2. disassembleForward : Replaced integer addition with subtraction → KILLED |
address = (address + instruction.length()) & ADDRESS_MASK; |
| 61 | } | |
| 62 |
1
1. disassembleForward : replaced return value with Collections.emptyList for com/rox/cpu/mos6502/assembler/Disassembler::disassembleForward → KILLED |
return instructions; |
| 63 | } | |
| 64 | ||
| 65 | private static String format(final String mnemonic, final AddressingMode mode, final int[] operand, | |
| 66 | final int address, final int length){ | |
| 67 |
1
1. format : replaced return value with "" for com/rox/cpu/mos6502/assembler/Disassembler::format → KILLED |
return switch (mode){ |
| 68 | case IMPLIED -> mnemonic; | |
| 69 | case ACCUMULATOR -> mnemonic + " A"; | |
| 70 | case IMMEDIATE -> String.format("%s #$%02X", mnemonic, operand[0]); | |
| 71 | case ZERO_PAGE -> String.format("%s $%02X", mnemonic, operand[0]); | |
| 72 | case ZERO_PAGE_X -> String.format("%s $%02X,X", mnemonic, operand[0]); | |
| 73 | case ZERO_PAGE_Y -> String.format("%s $%02X,Y", mnemonic, operand[0]); | |
| 74 | case ABSOLUTE -> String.format("%s $%04X", mnemonic, absoluteAddress(operand)); | |
| 75 | case ABSOLUTE_X -> String.format("%s $%04X,X", mnemonic, absoluteAddress(operand)); | |
| 76 | case ABSOLUTE_Y -> String.format("%s $%04X,Y", mnemonic, absoluteAddress(operand)); | |
| 77 | case INDIRECT -> String.format("%s ($%04X)", mnemonic, absoluteAddress(operand)); | |
| 78 | case INDIRECT_X -> String.format("%s ($%02X,X)", mnemonic, operand[0]); | |
| 79 | case INDIRECT_Y -> String.format("%s ($%02X),Y", mnemonic, operand[0]); | |
| 80 | case RELATIVE -> String.format("%s $%04X", mnemonic, relativeTarget(address, length, operand[0])); | |
| 81 | }; | |
| 82 | } | |
| 83 | ||
| 84 | private static int absoluteAddress(final int[] operand){ | |
| 85 |
3
1. absoluteAddress : Replaced Shift Left with Shift Right → KILLED 2. absoluteAddress : Replaced bitwise OR with AND → KILLED 3. absoluteAddress : replaced int return with 0 for com/rox/cpu/mos6502/assembler/Disassembler::absoluteAddress → KILLED |
return operand[0] | (operand[1] << 8); |
| 86 | } | |
| 87 | ||
| 88 | /** A branch's target is relative to the address of the *next* instruction, not the branch opcode's own address. */ | |
| 89 | private static int relativeTarget(final int address, final int length, final int offsetByte){ | |
| 90 | final byte signedOffset = (byte) offsetByte; | |
| 91 |
4
1. relativeTarget : replaced int return with 0 for com/rox/cpu/mos6502/assembler/Disassembler::relativeTarget → KILLED 2. relativeTarget : Replaced integer addition with subtraction → KILLED 3. relativeTarget : Replaced bitwise AND with OR → KILLED 4. relativeTarget : Replaced integer addition with subtraction → KILLED |
return (address + length + signedOffset) & ADDRESS_MASK; |
| 92 | } | |
| 93 | } | |
Mutations | ||
| 28 |
1.1 |
|
| 34 |
1.1 |
|
| 42 |
1.1 2.2 |
|
| 43 |
1.1 2.2 3.3 4.4 |
|
| 47 |
1.1 2.2 |
|
| 49 |
1.1 |
|
| 56 |
1.1 |
|
| 57 |
1.1 2.2 |
|
| 60 |
1.1 2.2 |
|
| 62 |
1.1 |
|
| 67 |
1.1 |
|
| 85 |
1.1 2.2 3.3 |
|
| 91 |
1.1 2.2 3.3 4.4 |