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.

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