Source code for ducky.cpu.coprocessor.math_copro

"""
Stack-based coprocessor, providing several arithmetic operations with "long"
numbers.

Coprocessor's instructions operates on a stack of (by default) 8 slots.
Operations to move values between math stack and registers/data stack are also
available.

In the following documentation several different data types are used:

 - ``int`` - standard `word`, 32-bit wide integer
 - ``long`` - long integer, 64-bit wide

Unless said otherwise, instruction takes its arguments from the stack, removing
the values in the process, and pushes the result - if any - back on the stack.
"""

import enum

from ...interfaces import ISnapshotable
from . import Coprocessor
from ...errors import CoprocessorError
from ..instructions import InstructionSet, SIS, INSTRUCTION_SETS, Descriptor_R_R, REGISTER_NAMES
from ...mm import u32_t, i64_t, u64_t, UINT64_FMT
from ...snapshot import SnapshotNode

#: Number of available spots on the math stack.
STACK_DEPTH = 8

[docs]class MathCoprocessorState(SnapshotNode): """ Snapshot node holding the state of math coprocessor. """ def __init__(self): super(MathCoprocessorState, self).__init__('stack')
[docs]class EmptyMathStackError(CoprocessorError): """ Raised when operation expects at least one value on math stack but stack is empty. """ def __init__(self, *args, **kwargs): super(EmptyMathStackError, self).__init__('Math stack is empty', *args, **kwargs)
[docs]class FullMathStackError(CoprocessorError): """ Raised when operation tries to put value on math stack but there is no empty spot available. """ def __init__(self, *args, **kwargs): super(FullMathStackError, self).__init__('Math stack is full', *args, **kwargs)
[docs]class RegisterSet(ISnapshotable): """ Math stack wrapping class. Provides basic push/pop access, and direct access to a top of the stack. :param ducky.cpu.CPUCore core: CPU core registers belong to. """ def __init__(self, core): super(RegisterSet, self).__init__() self.core = core self.stack = [] self.DEBUG = core.DEBUG
[docs] def save_state(self, parent): self.DEBUG('RegisterSet.save_state') state = parent.add_child('math_coprocessor', MathCoprocessorState()) state.stack = [] for lr in self.stack: state.stack.append(int(lr.value))
[docs] def load_state(self, state): self.DEBUG('RegisterSet.load_state') for lr in state.depth: self.stack.append(u64_t(lr))
[docs] def push(self, v): """ Push new value on top of the stack. :raises ducky.cpu.coprocessor.math_copro.FullMathStackError: if there is no space available on the stack. """ self.DEBUG('%s.push: v=%s', self.__class__.__name__, UINT64_FMT(v)) if len(self.stack) == STACK_DEPTH: raise FullMathStackError() self.stack.append(v)
[docs] def pop(self): """ Pop the top value from stack and return it. :raises ducky.cpu.coprocessor.math_copro.EmptyMathStackError: if there are no values on the stack. """ self.DEBUG('%s.pop', self.__class__.__name__) if not self.stack: raise EmptyMathStackError() return self.stack.pop()
[docs] def tos(self): """ Return the top of the stack, without removing it from a stack. :raises ducky.cpu.coprocessor.math_copro.EmptyMathStackError: if there are no values on the stack. """ self.DEBUG('%s.tos', self.__class__.__name__) if not self.stack: raise EmptyMathStackError() return self.stack[-1]
[docs] def tos1(self): """ Return the item below the top of the stack, without removing it from a stack. :raises ducky.cpu.coprocessor.math_copro.EmptyMathStackError: if there are no values on the stack. """ self.DEBUG('%s.tos', self.__class__.__name__) if len(self.stack) < 2: raise EmptyMathStackError() return self.stack[-2]
[docs]class MathCoprocessor(ISnapshotable, Coprocessor): """ Coprocessor itself, includes its register set ("math stack"). :param ducky.cpu.CPUCore core: CPU core coprocessor belongs to. """ def __init__(self, core, *args, **kwargs): super(MathCoprocessor, self).__init__(core, *args, **kwargs) self.registers = RegisterSet(core)
[docs] def save_state(self, parent): self.registers.save_state(parent)
[docs] def load_state(self, state): self.registers.load_state(state)
[docs] def dump_stack(self): """ Log content of the stack using parent's ``DEBUG`` method. """ D = self.core.DEBUG D('Math stack:') for index, lr in enumerate(self.registers.stack): D('#%02i: %s', index, UINT64_FMT(lr.value)) D('---')
[docs] def sign_extend_with_push(self, i32): v = u64_t(i32) if i32 & 0x80000000: v.value |= 0xFFFFFFFF00000000 self.registers.push(v)
[docs] def extend_with_push(self, u32): self.registers.push(u64_t(u32))
# # Instruction set #
[docs]class MathCoprocessorOpcodes(enum.IntEnum): """ Math coprocessor's instruction opcodes. """ POPW = 0 POPUW = 1 PUSHW = 2 SAVEW = 3 LOADW = 4 LOADUW = 5 POPL = 6 SAVE = 7 PUSHL = 8 LOAD = 9 MULL = 10 DIVL = 11 MODL = 12 SYMDIVL = 13 SYMMODL = 14 UDIVL = 15 UMODL = 16 DUP = 20 DUP2 = 21 SWP = 22 DROP = 23 INCL = 30 DECL = 31 ADDL = 32 SIS = 63
[docs]class MathCoprocessorInstructionSet(InstructionSet): """ Math coprocessor's instruction set. """ instruction_set_id = 1 opcodes = MathCoprocessorOpcodes
[docs]class Descriptor_MATH(Descriptor_R_R): operands = '' @staticmethod
[docs] def assemble_operands(ctx, inst, operands): pass
@staticmethod
[docs] def disassemble_operands(logger, inst): pass
[docs]class PUSHW(Descriptor_MATH): """ Downsize TOS to ``int``, and push the result on the data stack. """ mnemonic = 'pushw' opcode = MathCoprocessorOpcodes.PUSHW @staticmethod
[docs] def execute(core, inst): core._raw_push(u32_t(core.math_coprocessor.registers.pop().value & 0xFFFFFFFF).value)
[docs]class SAVEW(Descriptor_MATH): """ Downsize TOS to ``int``, and store the result in register. """ mnemonic = 'savew' opcode = MathCoprocessorOpcodes.SAVEW operands = 'r' @staticmethod
[docs] def assemble_operands(ctx, inst, operands): ctx.encode(inst, 'reg1', 5, operands[0].operand)
@staticmethod
[docs] def disassemble_operands(logger, inst): return [REGISTER_NAMES[inst.reg1]]
@staticmethod
[docs] def execute(core, inst): core.registers[inst.reg1] = core.math_coprocessor.registers.pop().value & 0xFFFFFFFF
@staticmethod
[docs] def jit(core, inst): regset = core.registers reg = inst.reg1 pop = core.math_coprocessor.registers.pop def __jit_savew(): regset[reg] = pop().value & 0xFFFFFFFF return __jit_savew
[docs]class POPW(Descriptor_MATH): """ Pop the ``int`` from data stack, extend it to ``long``, and make the result TOS. """ mnemonic = 'popw' opcode = MathCoprocessorOpcodes.POPW @staticmethod
[docs] def execute(core, inst): core.math_coprocessor.sign_extend_with_push(core._raw_pop())
@staticmethod
[docs] def jit(core, inst): raw_pop = core._raw_pop push = core.math_coprocessor.sign_extend_with_push def __jit_popw(): push(raw_pop()) return __jit_popw
[docs]class LOADW(Descriptor_MATH): """ Take a value from register, extend it to ``long``, and make the result TOS. """ mnemonic = 'loadw' opcode = MathCoprocessorOpcodes.LOADW operands = 'r' @staticmethod
[docs] def assemble_operands(ctx, inst, operands): ctx.encode(inst, 'reg1', 5, operands[0].operand)
@staticmethod
[docs] def disassemble_operands(logger, inst): return [REGISTER_NAMES[inst.reg1]]
@staticmethod
[docs] def execute(core, inst): core.math_coprocessor.sign_extend_with_push(core.registers[inst.reg1])
@staticmethod
[docs] def jit(core, inst): push = core.math_coprocessor.sign_extend_with_push regset = core.registers reg = inst.reg1 def __jit_loadw(): push(regset[reg]) return __jit_loadw
[docs]class POPUW(Descriptor_MATH): """ Pop the ``int``from data stack, extend it to ``long``, and make the result TOS. """ mnemonic = 'popuw' opcode = MathCoprocessorOpcodes.POPUW @staticmethod
[docs] def execute(core, inst): core.math_coprocessor.extend_with_push(core._raw_pop())
[docs]class LOADUW(Descriptor_MATH): """ Take a value from register, extend it to ``long``, and make the result TOS. """ mnemonic = 'loaduw' opcode = MathCoprocessorOpcodes.LOADUW operands = 'r' @staticmethod
[docs] def assemble_operands(ctx, inst, operands): ctx.encode(inst, 'reg1', 5, operands[0].operand)
@staticmethod
[docs] def disassemble_operands(logger, inst): return [REGISTER_NAMES[inst.reg1]]
@staticmethod
[docs] def execute(core, inst): core.math_coprocessor.extend_with_push(core.registers[inst.reg1])
@staticmethod
[docs] def jit(core, inst): push = core.math_coprocessor.extend_with_push regset = core.registers reg = inst.reg1 def __jit_loaduw(): push(regset[reg]) return __jit_loaduw
[docs]class PUSHL(Descriptor_MATH): """ Push the TOS on the data stack. """ mnemonic = 'pushl' opcode = MathCoprocessorOpcodes.PUSHL @staticmethod
[docs] def execute(core, inst): v = core.math_coprocessor.registers.pop() core._raw_push(v.value & 0xFFFFFFFF) core._raw_push(v.value >> 32)
[docs]class SAVE(Descriptor_MATH): """ Store TOS in two registers. """ mnemonic = 'save' opcode = MathCoprocessorOpcodes.SAVE operands = 'r,r' @staticmethod
[docs] def assemble_operands(ctx, inst, operands): ctx.encode(inst, 'reg1', 5, operands[0].operand) ctx.encode(inst, 'reg2', 5, operands[1].operand)
@staticmethod
[docs] def disassemble_operands(logger, inst): return [REGISTER_NAMES[inst.reg1], REGISTER_NAMES[inst.reg1]]
@staticmethod
[docs] def execute(core, inst): v = core.math_coprocessor.registers.pop().value core.registers[inst.reg1] = (v >> 32) % 4294967296 core.registers[inst.reg2] = v & 0xFFFFFFFF
@staticmethod
[docs] def jit(core, inst): pop = core.math_coprocessor.registers.pop regset = core.registers reg1, reg2 = inst.reg1, inst.reg2 def __jit_save(): v = pop().value regset[reg1] = (v >> 32) % 4294967296 regset[reg2] = v & 0xFFFFFFFF return __jit_save
[docs]class POPL(Descriptor_MATH): """ Pop the ``long`` from data stack, and make it new TOS. """ mnemonic = 'popl' opcode = MathCoprocessorOpcodes.POPL @staticmethod
[docs] def execute(core, inst): hi = core._raw_pop() lo = core._raw_pop() core.math_coprocessor.registers.push(u64_t((hi << 32) | lo))
[docs]class LOAD(Descriptor_MATH): """ Merge two registers together, and make the result new TOS. """ mnemonic = 'load' opcode = MathCoprocessorOpcodes.LOAD operands = 'r,r' @staticmethod
[docs] def assemble_operands(ctx, inst, operands): ctx.encode(inst, 'reg1', 5, operands[0].operand) ctx.encode(inst, 'reg2', 5, operands[1].operand)
@staticmethod
[docs] def disassemble_operands(logger, inst): return [REGISTER_NAMES[inst.reg1], REGISTER_NAMES[inst.reg1]]
@staticmethod
[docs] def execute(core, inst): hi = core.registers[inst.reg1] lo = core.registers[inst.reg2] core.math_coprocessor.registers.push(u64_t((hi << 32) | lo))
[docs]class INCL(Descriptor_MATH): """ Increment top of the stack by one. """ mnemonic = 'incl' opcode = MathCoprocessorOpcodes.INCL @staticmethod
[docs] def execute(core, inst): core.math_coprocessor.registers.tos().value += 1
[docs]class DECL(Descriptor_MATH): """ Decrement top of the stack by one. """ mnemonic = 'decl' opcode = MathCoprocessorOpcodes.DECL @staticmethod
[docs] def execute(core, inst): core.math_coprocessor.registers.tos().value -= 1
[docs]class ADDL(Descriptor_MATH): mnemonic = 'addl' opcode = MathCoprocessorOpcodes.ADDL @staticmethod
[docs] def execute(core, inst): RS = core.math_coprocessor.registers a = RS.pop() b = RS.pop() RS.push(u64_t(a.value + b.value))
[docs]class MULL(Descriptor_MATH): """ Multiply two top-most numbers on the stack. """ mnemonic = 'mull' opcode = MathCoprocessorOpcodes.MULL @staticmethod
[docs] def execute(core, inst): RS = core.math_coprocessor.registers a = RS.pop() b = RS.pop() RS.push(u64_t(a.value * b.value))
@staticmethod
[docs] def jit(core, inst): pop, push = core.math_coprocessor.registers.pop, core.math_coprocessor.registers.push def __jit_mull(): a, b = pop(), pop() push(u64_t(a.value * b.value)) return __jit_mull
[docs]class DIVL(Descriptor_MATH): """ Divide the value below the top of the math stack by the topmost value. """ mnemonic = 'divl' opcode = MathCoprocessorOpcodes.DIVL @staticmethod
[docs] def execute(core, inst): RS = core.math_coprocessor.registers divider = i64_t(RS.pop().value) tos = i64_t(RS.pop().value) RS.push(u64_t(tos.value // divider.value))
[docs]class MODL(Descriptor_MATH): mnemonic = 'modl' opcode = MathCoprocessorOpcodes.MODL @staticmethod
[docs] def execute(core, inst): RS = core.math_coprocessor.registers divider = i64_t(RS.pop().value) tos = i64_t(RS.pop().value) RS.push(u64_t(tos.value % divider.value))
[docs]class UDIVL(Descriptor_MATH): """ Divide the value below the top of the math stack by the topmost value. """ mnemonic = 'udivl' opcode = MathCoprocessorOpcodes.UDIVL @staticmethod
[docs] def execute(core, inst): RS = core.math_coprocessor.registers divider = RS.pop() tos = RS.pop() RS.push(u64_t(tos.value // divider.value))
[docs]class UMODL(Descriptor_MATH): mnemonic = 'umodl' opcode = MathCoprocessorOpcodes.UMODL @staticmethod
[docs] def execute(core, inst): RS = core.math_coprocessor.registers divider = RS.pop() tos = RS.pop() RS.push(u64_t(tos.value % divider.value))
[docs]class DUP(Descriptor_MATH): mnemonic = 'dup' opcode = MathCoprocessorOpcodes.DUP @staticmethod
[docs] def execute(core, inst): M = core.math_coprocessor M.registers.push(u64_t(M.registers.tos().value))
[docs]class DUP2(Descriptor_MATH): mnemonic = 'dup2' opcode = MathCoprocessorOpcodes.DUP2 @staticmethod
[docs] def execute(core, inst): M = core.math_coprocessor a = M.registers.pop() b = M.registers.pop() M.registers.push(b) M.registers.push(a) M.registers.push(u64_t(b.value)) M.registers.push(u64_t(a.value))
[docs]class SWP(Descriptor_MATH): mnemonic = 'swpl' opcode = MathCoprocessorOpcodes.SWP @staticmethod
[docs] def execute(core, inst): M = core.math_coprocessor a = M.registers.pop() b = M.registers.pop() M.registers.push(a) M.registers.push(b)
[docs]class DROP(Descriptor_MATH): mnemonic = 'drop' opcode = MathCoprocessorOpcodes.DROP @staticmethod
[docs] def execute(core, inst): M = core.math_coprocessor M.registers.pop()
[docs]class SYMDIVL(Descriptor_MATH): """ The same operation like ``DIVL`` but provides symmetric results. """ mnemonic = 'symdivl' opcode = MathCoprocessorOpcodes.SYMDIVL @staticmethod
[docs] def execute(core, inst): RS = core.math_coprocessor.registers divider = i64_t(RS.pop().value) tos = i64_t(RS.pop().value) RS.push(u64_t(int(float(tos.value) / float(divider.value))))
[docs]class SYMMODL(Descriptor_MATH): mnemonic = 'symmodl' opcode = MathCoprocessorOpcodes.SYMMODL @staticmethod
[docs] def execute(core, inst): import math RS = core.math_coprocessor.registers divider = i64_t(RS.pop().value).value tos = i64_t(RS.pop().value).value if (tos < 0) == (divider < 0): RS.push(u64_t(tos % divider)) else: RS.push(u64_t(int(math.fmod(tos, divider))))
ADDL(MathCoprocessorInstructionSet) INCL(MathCoprocessorInstructionSet) DECL(MathCoprocessorInstructionSet) MULL(MathCoprocessorInstructionSet) DIVL(MathCoprocessorInstructionSet) MODL(MathCoprocessorInstructionSet) UDIVL(MathCoprocessorInstructionSet) UMODL(MathCoprocessorInstructionSet) SYMDIVL(MathCoprocessorInstructionSet) SYMMODL(MathCoprocessorInstructionSet) DUP(MathCoprocessorInstructionSet) DUP2(MathCoprocessorInstructionSet) SWP(MathCoprocessorInstructionSet) DROP(MathCoprocessorInstructionSet) PUSHW(MathCoprocessorInstructionSet) SAVEW(MathCoprocessorInstructionSet) POPW(MathCoprocessorInstructionSet) LOADW(MathCoprocessorInstructionSet) POPUW(MathCoprocessorInstructionSet) LOADUW(MathCoprocessorInstructionSet) PUSHL(MathCoprocessorInstructionSet) SAVE(MathCoprocessorInstructionSet) POPL(MathCoprocessorInstructionSet) LOAD(MathCoprocessorInstructionSet) SIS(MathCoprocessorInstructionSet) MathCoprocessorInstructionSet.init() INSTRUCTION_SETS[MathCoprocessorInstructionSet.instruction_set_id] = MathCoprocessorInstructionSet