def _codegen_jmp(context: Context, op: Op) -> Tuple[Context, Op]: """JMP pseudoinstruction: several underlying variants.""" op = op._replace(args=parse_args_if_able( _PARSE_OPTIONS, context, op, Type.ADDRESS | Type.DEREF_REGISTER | Type.DEREF_ADDRESS)) # Since this pseudoinstruction can produce code of different lengths, we # handle updating pos when "not all_args_parsed" in a special way. if not all_args_parsed(op.args): advance = 4 if op.args[0].argtype & Type.ADDRESS else 2 return context.advance_by_bytes(advance), op # We are branching to an address literal. if op.args[0].argtype & Type.ADDRESS: _jmpdestcheck(op.args[0]) op = op._replace(todo=None, hex='D001{:04X}'.format(op.args[0].integer)) # We are branching to an address stored at a memory location in a register. # (To branch to an address inside a register, use RET). elif op.args[0].argtype & Type.DEREF_REGISTER: _regderefcheck(op.args[0], postcrem_from=0, postcrem_to=0) op = op._replace(todo=None, hex='D0{:X}8'.format(op.args[0].integer)) # We are branching to an address stored at a low memory location. else: _lowwordaddrcheck(op.args[0]) op = op._replace(todo=None, hex='20{:02X}'.format(op.args[0].integer // 2)) return context.advance(op.hex), op
def _codegen_org(context: Context, op: Op) -> Tuple[Context, Op]: """ORG pseudoinstruction: set current output stream position.""" # Try to parse our one argument. If successful, update our stream position. # Otherwise, leaving the op's `todo` unchanged means we'll try again later. op = op._replace(args=parse_args_if_able( _PARSE_OPTIONS, context, op, Type.ADDRESS)) if all_args_parsed(op.args): op = op._replace(hex='', todo=None) context = context._replace(pos=op.args[0].integer) return context, op
def codegen_immed_to_reg(context: Context, op: Op) -> Tuple[Context, Op]: op = op._replace(args=parse_args_if_able(_PARSE_OPTIONS, context, op, Type.REGISTER, Type.NUMBER)) if all_args_parsed(op.args): _regcheck(op.args[0]) _bytecheck(op.args[1]) digits = (nybble, op.args[0].integer, op.args[1].integer % 256) op = op._replace(todo=None, hex='{:X}{:X}{:02X}'.format(*digits)) # We can still update pos whether we've parsed all args or not. return context.advance_by_bytes(2), op
def codegen_onereg(context: Context, op: Op) -> Tuple[Context, Op]: # Both register arguments to this opcode should be parseable. op = op._replace(args=parse_args_if_able(_PARSE_OPTIONS, context, op, Type.REGISTER)) if all_args_parsed(op.args): _regcheck(*op.args) digits = (nybble_1, op.args[0].integer, nybble_2) op = op._replace(todo=None, hex=template.format(*digits)) # We can still update pos whether we've parsed all args or not. return context.advance_by_bytes(2), op
def _codegen_ctrl(context: Context, op: Op) -> Tuple[Context, Op]: """CTRL instruction.""" op = op._replace(args=parse_args_if_able(_PARSE_OPTIONS, context, op, Type.ADDRESS, Type.NUMBER)) if all_args_parsed(op.args): _devcheck(op.args[0]) _bytecheck(op.args[1]) digits = (op.args[0].integer, op.args[1].integer % 256) op = op._replace(todo=None, hex='1{:X}{:02X}'.format(*digits)) # We can still update pos whether we've parsed all args or not. return context.advance_by_bytes(2), op
def _codegen_putb(context: Context, op: Op) -> Tuple[Context, Op]: """PUTB instruction.""" op = op._replace(args=parse_args_if_able( _PARSE_OPTIONS, context, op, Type.ADDRESS, Type.DEREF_REGISTER)) if all_args_parsed(op.args): _devcheck(op.args[0]) _regderefcheck(op.args[1], postcrem_from=-4, postcrem_to=4) modifier = _postcrement_to_modifier(op.args[1].postcrement) digits = (op.args[0].integer, op.args[1].integer, modifier) op = op._replace(todo=None, hex='4{:X}{:X}{:X}'.format(*digits)) # We can still update pos whether we've parsed all args or not. return context.advance_by_bytes(2), op
def _codegen_lwi(context: Context, op: Op) -> Tuple[Context, Op]: """LWI pseudoinstruction: MOVE RX, (RO)+; DW i.""" op = op._replace(args=parse_args_if_able(_PARSE_OPTIONS, context, op, Type.REGISTER, Type.NUMBER)) if all_args_parsed(op.args): _regcheck(op.args[0]) if not -32767 <= op.args[1].integer <= 65535: raise ValueError( 'Halfword literal {} not in range -32768..65535'.format( op.args[1].stripped)) digits = (op.args[0].integer, op.args[1].integer % 65536) op = op._replace(todo=None, hex='D{:X}01{:04X}'.format(*digits)) # We can still update pos whether we've parsed all args or not. return context.advance_by_bytes(4), op
def _codegen_call(context: Context, op: Op) -> Tuple[Context, Op]: """CALL pseudoinstruction: several underlying variants.""" op = op._replace(args=parse_args_if_able( _PARSE_OPTIONS, context, op, Type.ADDRESS | Type.REGISTER | Type.DEREF_REGISTER | Type.DEREF_ADDRESS, Type.REGISTER)) # Since this pseudoinstruction can produce code of different lengths, we # handle updating pos when "not all_args_parsed" in a special way. if not all_args_parsed(op.args): advance = 6 if op.args[0].argtype & Type.ADDRESS else 4 return context.advance_by_bytes(advance), op # We are calling an address literal. Note that there is a way to do this in # two halfwords: for that, use the RCALL pseudoinstruction. if op.args[0].argtype & Type.ADDRESS: _jmpdestcheck(op.args[0]) _regcheck(op.args[1]) digits_a = (op.args[1].integer, op.args[1].integer, op.args[0].integer) op = op._replace(todo=None, hex='0{:X}03D0{:X}1{:04X}'.format(*digits_a)) # We are calling an address stored inside a register. elif op.args[0].argtype & Type.REGISTER: _callregcheck(op.args[0], op.args[1]) digits_r = (op.args[1].integer, op.args[0].integer) op = op._replace(todo=None, hex='0{:X}0300{:X}4'.format(*digits_r)) # We are calling an address stored at a memory location in a register. elif op.args[0].argtype & Type.DEREF_REGISTER: _callregcheck(op.args[0], op.args[1]) _regderefcheck(op.args[0], postcrem_from=-2, postcrem_to=2) # Words. modifier = _postcrement_to_modifier(2 * op.args[0].postcrement) digits_d = (op.args[1].integer, op.args[0].integer, modifier) op = op._replace(todo=None, hex='0{:X}03D0{:X}{:X}'.format(*digits_d)) # We are calling an address stored at a low memory location. else: _regcheck(op.args[1]) _lowwordaddrcheck(op.args[0]) assert op.opcode is not None # mypy... if op.args[0].precrement or op.args[0].postcrement: raise ValueError( 'No (in/de)crementation is allowed for address dereference arguments ' 'to {}'.format(op.opcode.upper())) digits = (op.args[1].integer, op.args[0].integer // 2) op = op._replace(todo=None, hex='0{:X}0320{:02X}'.format(*digits)) return context.advance(op.hex), op
def _codegen_move(context: Context, op: Op) -> Tuple[Context, Op]: """MOVE instruction.""" op = op._replace( args=parse_args_if_able( # Note fractional crements enabled. _PARSE_OPTIONS._replace( fractional_crements=True), context, op, Type.ADDRESS | Type.REGISTER | Type.DEREF_REGISTER, Type.ADDRESS | Type.REGISTER | Type.DEREF_REGISTER)) if all_args_parsed(op.args): if not any(arg.argtype & Type.REGISTER for arg in op.args): raise ValueError( 'At least one argument to MOVE must be a register') # This is a move between registers. elif op.args[0].argtype == op.args[1].argtype == Type.REGISTER: _regcheck(*op.args) digits_r = (op.args[0].integer, op.args[1].integer) op = op._replace(todo=None, hex='0{:X}{:X}4'.format(*digits_r)) # This is a move from/to an address found at a specified memory location. elif any(arg.argtype == Type.ADDRESS for arg in op.args): nybble, argaddr, argreg = ((2, op.args[1], op.args[0]) if op.args[0].argtype & Type.REGISTER else (3, op.args[0], op.args[1])) _regcheck(argreg) _lowwordaddrcheck(argaddr) digits_a = (nybble, argreg.integer, argaddr.integer // 2) op = op._replace(todo=None, hex='{:X}{:X}{:02X}'.format(*digits_a)) # This is a move from/to an address found in a register. else: nybble, argderef, argreg = ((5, op.args[0], op.args[1]) if op.args[0].argtype & Type.DEREF_REGISTER else (0xD, op.args[1], op.args[0])) _regcheck(argreg) _regderefcheck(argderef, postcrem_from=-2, postcrem_to=2) # Words. modifier = _postcrement_to_modifier(2 * argderef.postcrement) digits_d = (nybble, op.args[1].integer, op.args[0].integer, modifier) op = op._replace(todo=None, hex='{:X}{:X}{:X}{:X}'.format(*digits_d)) # We can still update pos whether we've parsed all args or not. return context.advance_by_bytes(2), op
def _codegen_bra(context: Context, op: Op) -> Tuple[Context, Op]: """BRA pseudoinstruction: ADD/SUB R0,#<dist>.""" op = op._replace( args=parse_args_if_able(_PARSE_OPTIONS, context, op, Type.ADDRESS)) if all_args_parsed(op.args) and context.pos is not None: _jmpdestcheck(op.args[0]) offset = _reljmpoffset(context, op.args[0]) if offset == 0: logging.warning( 'Line %d: A BRA of +2 bytes (so, an ordinary PC increment) is not ' 'supported by the usual relative jump techniques; generating a NOP ' '(MOVE R0, R0) instead', op.lineno) op = op._replace(todo=None, hex='0004') else: digits = (0xA, offset - 1) if offset > 0 else (0xF, -offset - 1) op = op._replace(todo=None, hex='{:X}0{:02X}'.format(*digits)) # We can still update pos whether we've parsed all args or not. return context.advance_by_bytes(2), op
def _codegen_movb(context: Context, op: Op) -> Tuple[Context, Op]: """MOVB instruction.""" op = op._replace( args=parse_args_if_able(_PARSE_OPTIONS, context, op, Type.REGISTER | Type.DEREF_REGISTER, Type.REGISTER | Type.DEREF_REGISTER)) # Both arguments to this opcode should be parseable. if op.args[0].argtype == op.args[1].argtype: raise ValueError( 'One MOVB argument should be a register, and the other should be a ' 'register dereference') if all_args_parsed(op.args): nybble, argderef, argreg = ((6, op.args[1], op.args[0]) if op.args[0].argtype & Type.REGISTER else (7, op.args[0], op.args[1])) _regcheck(argreg) _regderefcheck(argderef, postcrem_from=-4, postcrem_to=4) modifier = _postcrement_to_modifier(argderef.postcrement) digits = (nybble, argreg.integer, argderef.integer, modifier) op = op._replace(todo=None, hex='{:X}{:X}{:X}{:X}'.format(*digits)) # We can still update pos whether we've parsed all args or not. return context.advance_by_bytes(2), op
def _codegen_rcall(context: Context, op: Op) -> Tuple[Context, Op]: """RCALL (R=relocatable) pseudoinstruction: INC2 Rx,R0; BRA <addr>.""" op = op._replace(args=parse_args_if_able(_PARSE_OPTIONS, context, op, Type.ADDRESS, Type.REGISTER)) assert op.args is not None if all_args_parsed(op.args) and context.pos is not None: _jmpdestcheck(op.args[0]) _regcheck(op.args[1]) offset = _reljmpoffset(context, op.args[0]) if offset == 0: logging.warning( 'Line %d: A +2-byte RCALL (so, an ordinary PC increment) is not ' 'supported by the usual relative jump techniques; generating a NOP ' '(MOVE R0, R0) instead', op.lineno) op = op._replace(todo=None, hex='0{:X}030004'.format(op.args[1].integer)) else: digits = ((op.args[1].integer, 0xA, offset - 1) if offset > 0 else (op.args[1].integer, 0xF, -offset - 1)) op = op._replace(todo=None, hex='0{:X}03{:X}0{:02X}'.format(*digits)) # We can still update pos whether we've parsed all args or not. return context.advance_by_bytes(4), op
def codegen_add_or_sub(context: Context, op: Op) -> Tuple[Context, Op]: op = op._replace(args=parse_args_if_able(_PARSE_OPTIONS, context, op, Type.REGISTER, Type.NUMBER | Type.REGISTER)) if all_args_parsed(op.args): _regcheck(op.args[0]) # Adding/subtracting an immediate value to/from a register. if op.args[1].argtype & Type.NUMBER: if not 0 <= op.args[1].integer <= 256: raise ValueError('Literal {!r} not in range 0..256'.format( op.args[1].stripped)) elif op.args[1].integer == 0: assert op.opcode is not None # mypy... logging.warning( 'Line %d: A #0 literal argument to %s is not supported by the %s ' 'instruction; generating a NOP (MOVE R0, R0) instead', op.lineno, op.opcode.upper(), op.opcode.upper()) op = op._replace(todo=None, hex='0004') else: digits = (0xA if add_or_sub == 'add' else 0xF, op.args[0].integer, (op.args[1].integer - 1) % 256) op = op._replace(todo=None, hex='{:X}{:X}{:02X}'.format(*digits)) # Adding/subtracting the LSB of one register to/from another register. else: _regcheck(op.args[1]) digits = (op.args[0].integer, op.args[1].integer, 8 if add_or_sub == 'add' else 9) op = op._replace(todo=None, hex='0{:X}{:X}{:X}'.format(*digits)) # We can still update pos whether we've parsed all args or not. return context.advance_by_bytes(2), op
def _codegen_nop(context: Context, op: Op) -> Tuple[Context, Op]: """NOP pseudoinstruction: MOVE R0, R0.""" # This opcode takes no arguments. We still parse to make sure there are none. op = op._replace(args=parse_args_if_able(_PARSE_OPTIONS, context, op)) op = op._replace(todo=None, hex='0004') return context.advance(op.hex), op