def _pick_jump(self, base_addr: int, imm_optype: ImmOperandType, model: Model, program: Program, tgt_addr: Optional[int]) -> Optional[Tuple[int, int, int]]: '''Pick target and link register for a jump instruction For a JALR instruction, base_addr is the address stored in the register that we'll branch through. For a JAL instruction, it is zero: the PC-relative offset is encoded on the operand type itself. imm_optype is the OperandType for the immediate operand that we are generating. Returns (tgt, enc_offset, link_idx) where tgt is the target address, enc_offset is the offset (encoded as 2's complement if necessary for the immediate operand) and link_idx is the index of the chosen link register. ''' # Calculate the range of offsets we can encode (this includes any # PC-relative adjustment) # # We can assume that get_range() returns something, because it only # returns None if the operand has no width: not possible because we # know we have an encoding for the instruction. imm_rng = imm_optype.get_op_val_range(model.pc) assert imm_rng is not None imm_min, imm_max = imm_rng # Adjust for base_addr tgt_min = imm_min + base_addr tgt_max = imm_max + base_addr # If there is a desired target, check it's representable. If not, # return None. Otherwise, narrow the range to just that. if tgt_addr is not None: if tgt_min <= tgt_addr <= tgt_max: tgt_min = tgt_addr tgt_max = tgt_addr else: return None # Pick a branch target. "1" here is the minimum number of instructions # that must fit. One is enough (we'll just end up generating another # branch immediately) tgt = program.pick_branch_target(model.pc, 1, tgt_min, tgt_max) if tgt is None: return None assert tgt_min <= tgt <= tgt_max assert tgt & 3 == 0 # Adjust again for base_addr: we pick the offset from there op_val = tgt - base_addr enc_offset = imm_optype.op_val_to_enc_val(op_val, model.pc) assert enc_offset is not None # Pick a link register, not preferring any in particular. This should # never fail (it's a destination, not a source). link_reg_idx = model.pick_operand_value(self.jal.operands[0].op_type) assert link_reg_idx is not None return (tgt, enc_offset, link_reg_idx)
def _pick_imm_operand_value(self, op_type: ImmOperandType) -> Optional[int]: op_rng = op_type.get_op_val_range(self.pc) if op_rng is None: # If we don't know the width, the only immediate that we *know* # is going to be valid is 0. return 0 align = 1 << op_type.shift lo, hi = op_rng sh_lo = (lo + align - 1) // align sh_hi = hi // align op_val = random.randint(sh_lo, sh_hi) << op_type.shift return op_type.op_val_to_enc_val(op_val, self.pc)
def _pick_jump(self, base_addr: int, imm_optype: ImmOperandType, model: Model, program: Program) -> Optional[Tuple[int, int, int]]: '''Pick target and link register for a jump instruction base_addr is the starting address (either the current PC for a JAL or the value of a register for a JALR). imm_optype is the OperandType for the immediate operand that we are generating. Returns (tgt, enc_offset, link_idx) where tgt is the target address, enc_offset is the offset (encoded as 2's complement if necessary for the immediate operand) and link_idx is the index of the chosen link register. ''' # Calculate the range of addresses we can hit, starting at base_addr. # # We can assume that get_range() returns something, because it only # returns None if the operand has no width: not possible because we # know we have an encoding for the instruction. offset_range = imm_optype.get_range() assert offset_range offset_min, offset_max = offset_range tgt_min = base_addr + offset_min tgt_max = base_addr + offset_max # Pick a branch target. "1" here is the minimum number of instructions # that must fit. One is enough (we'll just end up generating another # branch immediately) tgt = program.pick_branch_target(1, tgt_min, tgt_max) if tgt is None: return None offset = tgt - base_addr assert offset_min <= offset <= offset_max enc_offset = imm_optype.encode_val(offset) # Pick a link register, not preferring any in particular. This should # never fail (it's a destination, not a source). link_reg_idx = model.pick_operand_value(self.jal.operands[0].op_type) assert link_reg_idx is not None return (tgt, enc_offset, link_reg_idx)
def _pick_loop_shape(self, op0_type: OperandType, op1_type: ImmOperandType, space_here: int, model: Model, program: Program) -> Optional[Tuple[int, int, int]]: '''Pick the size of loop and number of iterations op_type is the type of the first operand (either 'grs' for loop or 'iterations' for loopi). space_here is the number of instructions' space available at the current position. ''' # The first upper bound on bodysize is that we've got to have an empty # space for the loop body. # # Note: This doesn't allow us to generate a "loop" that encloses # previously generated code. So, for example, we couldn't do something # like # # loopi 10, 3 # jal x0, .+8 # jal x0, .+100 // an isolated instruction that ran earlier # addi x0, 0 // end of loop # # Since we can generate jumps in the loop, we might "fill in # the middle" afterwards. However, we'll never make a loop that # "contains" instructions we executed before. # # To weaken this, we would need to just require that the end of the # loop is not yet taken. But that sounds a bit hard: let's not worry # about it for now. assert 3 <= space_here max_bodysize = space_here - 2 # Another upper bound comes from program.get_insn_space_left(). If # bodysize is 2 or more, our body will need to generate at least 2 # instructions (either a straight line of length bodysize, or a jump # from the start and then a straight line instruction at the end). In # this case, we need space for at least 3 instructions (including the # LOOP/LOOPI instruction itself). # # We know that program.get_insn_space_left() is at least 2 (checked in # gen()), but if it's 2, we can only have a bodysize of 1. assert 2 <= program.space if program.space == 2: max_bodysize = min(max_bodysize, 1) bodysize_range = op1_type.get_op_val_range(model.pc) assert bodysize_range is not None bs_min, bs_max = bodysize_range if max_bodysize < max(1, bs_min): return None # Decide on the bodysize value. tail_pc is the address of the last # instruction in the loop body. bodysize = random.randint(max(1, bs_min), min(bs_max, max_bodysize)) tail_pc = model.pc + 4 * bodysize assert program.get_insn_space_at(tail_pc) >= 2 iters = self._pick_iterations(op0_type, bodysize, model) if iters is None: return None iter_opval, num_iters = iters return (iter_opval, num_iters, bodysize)