from dataclasses import replace
|
|
from enum import Enum
|
|
from random import choice, randint, random, randrange
|
|
from typing import List, Tuple, Type, TypeVar, Union
|
|
|
|
from gbso.cpu.insn import ALL_INSN_CLASSES, Insn, Operand, UNUSED, get_signature_class
|
|
from gbso.cpu.regs import R16, R16_WITHOUT_SP, R8
|
|
from gbso.program.program import Program
|
|
|
|
|
|
class Mutation(Enum):
|
|
OPCODE = "OPCODE"
|
|
OPERAND = "OPERAND"
|
|
SWAP = "SWAP"
|
|
INSTRUCTION = "INSTRUCTION"
|
|
NONE = "NONE"
|
|
|
|
|
|
def mutate_program(
|
|
prgm: Program,
|
|
prob_opcode: float,
|
|
prob_operand: float,
|
|
prob_swap: float,
|
|
prob_insn: float,
|
|
prob_insn_unused: float,
|
|
) -> Program:
|
|
prob_none = max(0, 1 - prob_opcode - prob_operand - prob_swap - prob_insn)
|
|
mutation = sample_probs(
|
|
[
|
|
(Mutation.OPCODE, prob_opcode),
|
|
(Mutation.OPERAND, prob_operand),
|
|
(Mutation.SWAP, prob_swap),
|
|
(Mutation.INSTRUCTION, prob_insn),
|
|
(Mutation.NONE, prob_none),
|
|
]
|
|
)
|
|
new_prgm = prgm
|
|
|
|
if mutation == Mutation.OPCODE:
|
|
new_prgm = mutate_opcode(prgm)
|
|
elif mutation == Mutation.OPERAND:
|
|
new_prgm = mutate_operand(prgm)
|
|
elif mutation == Mutation.SWAP:
|
|
new_prgm = mutate_swap(prgm)
|
|
elif mutation == Mutation.INSTRUCTION:
|
|
new_prgm = mutate_insn(prgm, prob_insn_unused)
|
|
|
|
return new_prgm
|
|
|
|
|
|
def mutate_opcode(prgm: Program) -> Program:
|
|
i = randrange(len(prgm.insns))
|
|
next_insn_cls = choice(get_signature_class(prgm.insns[i]))
|
|
|
|
insns = prgm.insns.copy()
|
|
insns[i] = next_insn_cls(*vars(prgm.insns[i]).values())
|
|
|
|
return Program(insns=insns)
|
|
|
|
|
|
# God, this is ugly... a better approach would be to store operands as a tuple
|
|
# instead of individual named fields.
|
|
def mutate_operand(prgm: Program) -> Program:
|
|
insn_index = randrange(len(prgm.insns))
|
|
insn = prgm.insns[insn_index]
|
|
|
|
if len(insn.signature()) == 0:
|
|
return prgm
|
|
|
|
operand_index = randrange(len(insn.signature()))
|
|
operand_type = insn.signature()[operand_index]
|
|
operand_name = list(vars(insn).keys())[operand_index]
|
|
|
|
insns = prgm.insns.copy()
|
|
insns[insn_index] = replace(
|
|
insn, **{operand_name: create_random_operand(operand_type)}
|
|
)
|
|
|
|
return Program(insns=insns)
|
|
|
|
|
|
def mutate_swap(prgm: Program) -> Program:
|
|
i = randrange(len(prgm.insns))
|
|
j = randrange(len(prgm.insns))
|
|
|
|
insns = prgm.insns.copy()
|
|
insn = insns[i]
|
|
insns[i] = insns[j]
|
|
insns[j] = insn
|
|
|
|
return Program(insns=insns)
|
|
|
|
|
|
def mutate_insn(prgm: Program, prob_unused: float) -> Program:
|
|
unused = random()
|
|
next_insn: Insn = UNUSED()
|
|
if unused > prob_unused:
|
|
cls = choice(ALL_INSN_CLASSES)
|
|
next_insn = create_random_insn(cls)
|
|
|
|
insns = prgm.insns.copy()
|
|
insn_index = randrange(len(prgm.insns))
|
|
insns[insn_index] = next_insn
|
|
|
|
return Program(insns=insns)
|
|
|
|
|
|
A = TypeVar("A")
|
|
|
|
|
|
# NOTE: List must be non-empty and probabilities must sum to 1
|
|
def sample_probs(probs: List[Tuple[A, float]]) -> A:
|
|
r = random()
|
|
cum_prob = 0.0
|
|
|
|
for val, prob in probs[:-1]:
|
|
cum_prob += prob
|
|
if r < cum_prob:
|
|
return val
|
|
|
|
val, _ = probs[-1]
|
|
return val
|
|
|
|
|
|
def create_random_insn(insn_cls: Type[Insn]) -> Insn:
|
|
args = [create_random_operand(op) for op in insn_cls.signature()]
|
|
return insn_cls(*args) # type: ignore
|
|
|
|
|
|
def create_random_operand(op: Operand) -> Union[int, R8, R16]:
|
|
value: Union[int, R8, R16]
|
|
|
|
if op == Operand.R8:
|
|
value = choice(list(R8))
|
|
elif op == Operand.R16:
|
|
value = choice(list(R16))
|
|
elif op == Operand.R16_NO_SP:
|
|
value = choice(R16_WITHOUT_SP)
|
|
elif op == Operand.IMM3:
|
|
value = randrange(8)
|
|
elif op == Operand.IMM8:
|
|
value = randrange(256)
|
|
elif op == Operand.IMM16:
|
|
value = randrange(256)
|
|
elif op == Operand.SIMM8:
|
|
value = randint(-128, 127)
|
|
|
|
return value
|