gameboy superoptimizer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

142 lines
3.7 KiB

  1. from dataclasses import replace
  2. from enum import Enum
  3. from random import choice, randint, random, randrange
  4. from typing import List, Tuple, Type, TypeVar, Union
  5. from gbso.cpu.insn import ALL_INSN_CLASSES, Insn, Operand, UNUSED, get_signature_class
  6. from gbso.cpu.regs import R16, R16_WITHOUT_SP, R8
  7. from gbso.program.program import Program
  8. class Mutation(Enum):
  9. OPCODE = "OPCODE"
  10. OPERAND = "OPERAND"
  11. SWAP = "SWAP"
  12. INSTRUCTION = "INSTRUCTION"
  13. def mutate_program(
  14. prgm: Program,
  15. prob_opcode: float,
  16. prob_operand: float,
  17. prob_swap: float,
  18. prob_insn: float,
  19. prob_insn_unused: float,
  20. ) -> Program:
  21. mutation = sample_probs(
  22. [
  23. (Mutation.OPCODE, prob_opcode),
  24. (Mutation.OPERAND, prob_operand),
  25. (Mutation.SWAP, prob_swap),
  26. (Mutation.INSTRUCTION, prob_insn),
  27. ]
  28. )
  29. new_prgm = prgm
  30. if mutation == Mutation.OPCODE:
  31. new_prgm = mutate_opcode(prgm)
  32. elif mutation == Mutation.OPERAND:
  33. new_prgm = mutate_operand(prgm)
  34. elif mutation == Mutation.SWAP:
  35. new_prgm = mutate_swap(prgm)
  36. elif mutation == Mutation.INSTRUCTION:
  37. new_prgm = mutate_insn(prgm, prob_insn_unused)
  38. return new_prgm
  39. def mutate_opcode(prgm: Program) -> Program:
  40. i = randrange(len(prgm.insns))
  41. next_insn_cls = choice(get_signature_class(prgm.insns[i]))
  42. insns = prgm.insns.copy()
  43. insns[i] = next_insn_cls(*vars(prgm.insns[i]).values())
  44. return Program(insns=insns)
  45. # God, this is ugly... a better approach would be to store operands as a tuple
  46. # instead of individual named fields.
  47. def mutate_operand(prgm: Program) -> Program:
  48. insn_index = randrange(len(prgm.insns))
  49. insn = prgm.insns[insn_index]
  50. operand_index = randrange(len(insn.signature()))
  51. operand_type = insn.signature()[operand_index]
  52. operand_name = list(vars(insn).keys())[operand_index]
  53. insns = prgm.insns.copy()
  54. insns[insn_index] = replace(
  55. insn, **{operand_name: create_random_operand(operand_type)}
  56. )
  57. return Program(insns=insns)
  58. def mutate_swap(prgm: Program) -> Program:
  59. i = randrange(len(prgm.insns))
  60. j = randrange(len(prgm.insns))
  61. insns = prgm.insns.copy()
  62. insn = insns[i]
  63. insns[i] = insns[j]
  64. insns[j] = insn
  65. return Program(insns=insns)
  66. def mutate_insn(prgm: Program, prob_unused: float) -> Program:
  67. unused = random()
  68. next_insn: Insn = UNUSED()
  69. if unused > prob_unused:
  70. cls = choice(ALL_INSN_CLASSES)
  71. next_insn = create_random_insn(cls)
  72. insns = prgm.insns.copy()
  73. insn_index = randrange(len(prgm.insns))
  74. insns[insn_index] = next_insn
  75. return Program(insns=insns)
  76. A = TypeVar("A")
  77. # NOTE: List must be non-empty and probabilities must sum to 1
  78. def sample_probs(probs: List[Tuple[A, float]]) -> A:
  79. r = random()
  80. cum_prob = 0.0
  81. for val, prob in probs[:-1]:
  82. cum_prob += prob
  83. if r < cum_prob:
  84. return val
  85. val, _ = probs[-1]
  86. return val
  87. def create_random_insn(insn_cls: Type[Insn]) -> Insn:
  88. args = [create_random_operand(op) for op in insn_cls.signature()]
  89. return insn_cls(*args) # type: ignore
  90. def create_random_operand(op: Operand) -> Union[int, R8, R16]:
  91. value: Union[int, R8, R16]
  92. if op == Operand.R8:
  93. value = choice(list(R8))
  94. elif op == Operand.R16:
  95. value = choice(list(R16))
  96. elif op == Operand.R16_NO_SP:
  97. value = choice(R16_WITHOUT_SP)
  98. elif op == Operand.IMM3:
  99. value = randrange(8)
  100. elif op == Operand.IMM8:
  101. value = randrange(256)
  102. elif op == Operand.IMM16:
  103. value = randrange(65536)
  104. elif op == Operand.SIMM8:
  105. value = randint(-128, 127)
  106. return value