Disassembler.java

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
Location : disassembleOne
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:unknownOpcodeByteProducesAOneByteFallbackInstruction()]
Replaced bitwise AND with OR → KILLED

34

1.1
Location : disassembleOne
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:unknownOpcodeByteProducesAOneByteFallbackInstruction()]
replaced return value with null for com/rox/cpu/mos6502/assembler/Disassembler::disassembleOne → KILLED

42

1.1
Location : disassembleOne
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:absoluteYModeAppendsCommaY()]
negated conditional → KILLED

2.2
Location : disassembleOne
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:absoluteYModeAppendsCommaY()]
changed conditional boundary → KILLED

43

1.1
Location : disassembleOne
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:absoluteYModeAppendsCommaY()]
Replaced integer addition with subtraction → KILLED

2.2
Location : disassembleOne
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:absoluteYModeAppendsCommaY()]
Replaced integer addition with subtraction → KILLED

3.3
Location : disassembleOne
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:absoluteYModeAppendsCommaY()]
Replaced bitwise AND with OR → KILLED

4.4
Location : disassembleOne
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:absoluteYModeAppendsCommaY()]
Replaced bitwise AND with OR → KILLED

47

1.1
Location : disassembleOne
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:relativeModeResolvesABackwardBranchToAnAbsoluteTarget()]
Replaced integer addition with subtraction → KILLED

2.2
Location : disassembleOne
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:accumulatorModeFormatsWithAnExplicitA()]
negated conditional → KILLED

49

1.1
Location : disassembleOne
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:absoluteYModeAppendsCommaY()]
replaced return value with null for com/rox/cpu/mos6502/assembler/Disassembler::disassembleOne → KILLED

56

1.1
Location : disassembleForward
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:disassembleForwardDoesNotDecodeBrksSignatureByteAsAnOpcode()]
Replaced bitwise AND with OR → KILLED

57

1.1
Location : disassembleForward
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:disassembleForwardWalksEachInstructionsOwnLength()]
changed conditional boundary → KILLED

2.2
Location : disassembleForward
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:disassembleForwardDoesNotDecodeBrksSignatureByteAsAnOpcode()]
negated conditional → KILLED

60

1.1
Location : disassembleForward
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:disassembleForwardDoesNotDecodeBrksSignatureByteAsAnOpcode()]
Replaced bitwise AND with OR → KILLED

2.2
Location : disassembleForward
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:disassembleForwardDoesNotDecodeBrksSignatureByteAsAnOpcode()]
Replaced integer addition with subtraction → KILLED

62

1.1
Location : disassembleForward
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:disassembleForwardDoesNotDecodeBrksSignatureByteAsAnOpcode()]
replaced return value with Collections.emptyList for com/rox/cpu/mos6502/assembler/Disassembler::disassembleForward → KILLED

67

1.1
Location : format
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:absoluteYModeAppendsCommaY()]
replaced return value with "" for com/rox/cpu/mos6502/assembler/Disassembler::format → KILLED

85

1.1
Location : absoluteAddress
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:absoluteYModeAppendsCommaY()]
Replaced Shift Left with Shift Right → KILLED

2.2
Location : absoluteAddress
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:absoluteYModeAppendsCommaY()]
Replaced bitwise OR with AND → KILLED

3.3
Location : absoluteAddress
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:absoluteYModeAppendsCommaY()]
replaced int return with 0 for com/rox/cpu/mos6502/assembler/Disassembler::absoluteAddress → KILLED

91

1.1
Location : relativeTarget
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:relativeModeResolvesABackwardBranchToAnAbsoluteTarget()]
replaced int return with 0 for com/rox/cpu/mos6502/assembler/Disassembler::relativeTarget → KILLED

2.2
Location : relativeTarget
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:relativeModeResolvesABackwardBranchToAnAbsoluteTarget()]
Replaced integer addition with subtraction → KILLED

3.3
Location : relativeTarget
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:relativeModeResolvesABackwardBranchToAnAbsoluteTarget()]
Replaced bitwise AND with OR → KILLED

4.4
Location : relativeTarget
Killed by : com.rox.cpu.mos6502.assembler.DisassemblerTest.[engine:junit-jupiter]/[class:com.rox.cpu.mos6502.assembler.DisassemblerTest]/[method:relativeModeResolvesABackwardBranchToAnAbsoluteTarget()]
Replaced integer addition with subtraction → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1