"""
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