|
|
- 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"
-
-
- def mutate_program(
- prgm: Program,
- prob_opcode: float,
- prob_operand: float,
- prob_swap: float,
- prob_insn: float,
- prob_insn_unused: float,
- ) -> Program:
- mutation = sample_probs(
- [
- (Mutation.OPCODE, prob_opcode),
- (Mutation.OPERAND, prob_operand),
- (Mutation.SWAP, prob_swap),
- (Mutation.INSTRUCTION, prob_insn),
- ]
- )
- 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]
-
- 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(65536)
- elif op == Operand.SIMM8:
- value = randint(-128, 127)
-
- return value
|