fix: полностью переработана система изменения счетчика

Выяснилось, что в DP32 счетчик команд ведет себя совершенно не так, как у меня в виртуальной машине, поэтому логика его работы была полностью переписана
This commit is contained in:
ElectronixTM
2025-03-30 11:54:50 +03:00
parent ca2e19ae0a
commit 9392326337

View File

@ -1,10 +1,12 @@
from dataclasses import dataclass, field from dataclasses import dataclass
from typing import ClassVar, Callable from typing import ClassVar, Callable
from ctypes import c_uint32, c_int32 from ctypes import c_uint32, c_int32
import struct import struct
from optable import OPCODES, OpcodeDescription, OpL, OpA, OpF, OpD from optable import OPCODES, OpcodeDescription, OpL, OpA, OpF, OpD
from enum import IntFlag, Enum, auto from enum import IntFlag, Enum, auto
WORD_SIZE: int = 4
class VMFlags(IntFlag): class VMFlags(IntFlag):
# if last instruction is big, then you should # if last instruction is big, then you should
# skip more memory on step # skip more memory on step
@ -83,10 +85,10 @@ class VM:
self._math_quick_callback_gen(lambda lhs, rhs: lhs // rhs), self._math_quick_callback_gen(lambda lhs, rhs: lhs // rhs),
# logical ops # logical ops
OpD(OpF.UNEXPANDED, OpL.MATH, OpA.AND): OpD(OpF.UNEXPANDED, OpL.MATH, OpA.AND):
self._math_callback_gen(lambda lhs, rhs: lhs & rhs), self._math_callback_gen(lambda lhs, rhs: lhs & rhs),
OpD(OpF.UNEXPANDED, OpL.MATH, OpA.OR): OpD(OpF.UNEXPANDED, OpL.MATH, OpA.OR):
self._math_callback_gen(lambda lhs, rhs: lhs | rhs), self._math_callback_gen(lambda lhs, rhs: lhs | rhs),
OpD(OpF.UNEXPANDED, OpL.MATH, OpA.XOR): OpD(OpF.UNEXPANDED, OpL.MATH, OpA.XOR):
@ -120,21 +122,19 @@ class VM:
""" """
Make one step (only step into) Make one step (only step into)
""" """
if self.pc.value * 4 > len(self.mem) - 4: if self._to_raw_bytes_offset(self.pc) > len(self.mem) - WORD_SIZE:
raise VMException( raise VMException(
VMExceptionType.END_OF_MEM, VMExceptionType.END_OF_MEM,
self.pc.value self.pc.value
) )
opcode = self.mem[self.pc.value * 4] opcode, *_ = instr = self._fetch_instr()
opdesc = self._fetch_opcode_desc(opcode) opdesc = self._get_opcode_desc(opcode)
args = self._parse_arguments(opdesc) args: tuple[int, ...] = self._parse_instr_fields(bytes(instr))
# По какой-то причине адрессация работает if not (OpF.UNEXPANDED in opdesc.flags or
# так, будто мы на 1 слово впереди опкода OpF.QUICK in opdesc.flags):
self.pc = c_uint32(self.pc.value + 1) extra = struct.unpack(">i", self._fetch_instr())[0]
args = (*args, extra)
self._run_callback(opdesc, args) self._run_callback(opdesc, args)
if (VMFlags.EXPANDED_INSTR in self._vm_flags
and not VMFlags.AFTER_BRANCH in self._vm_flags):
self.pc = c_uint32(self.pc.value + 1)
self._vm_flags &= ~(VMFlags.EXPANDED_INSTR) self._vm_flags &= ~(VMFlags.EXPANDED_INSTR)
self._vm_flags &= ~(VMFlags.AFTER_BRANCH) self._vm_flags &= ~(VMFlags.AFTER_BRANCH)
@ -142,7 +142,7 @@ class VM:
""" """
Continue from current breakpoint Continue from current breakpoint
""" """
while self.pc.value * 4 < len(self.mem): while self._to_raw_bytes_offset(self.pc.value) < len(self.mem):
if self.pc.value in self.breakpoints: if self.pc.value in self.breakpoints:
raise Breakpoint(self.pc.value) raise Breakpoint(self.pc.value)
self.step() self.step()
@ -152,25 +152,47 @@ class VM:
Run from very beginning Run from very beginning
""" """
self.pc = c_uint32(0) self.pc = c_uint32(0)
while (self.pc.value < len(self.mem) // 4): while (self._to_raw_bytes_offset(self.pc.value) < len(self.mem)):
self.continue_() self.continue_()
def _fetch_opcode_desc(self, opcode: int): def _fetch_instr(self) -> bytearray:
"""
Берет из памяти следущее слово, возвращает его
ВАЖНО: инкрементирует счетчик команд на каждом вызове
"""
addr = self._to_raw_bytes_offset(self.pc)
instr = self.mem[addr:addr+WORD_SIZE]
self._increment_pc()
return instr
def _get_opcode_desc(self, opcode: int):
"""
Обертка над обращением к таблице опкодов из файлика optable.py.
Нужна чтобы прокидывать исключения виртуальной машины, а не
KeyError
"""
if not opcode in OPCODES: if not opcode in OPCODES:
raise VMException( raise VMException(
VMExceptionType.INVALID_OPCODE, VMExceptionType.INVALID_OPCODE,
self.pc.value self.pc.value
) )
return OPCODES[opcode] return OPCODES[opcode]
def _parse_arguments(self, opdesc: OpcodeDescription) -> tuple[int, ...]:
addr = self.pc.value * 4
main_part = struct.unpack(">BBBb", self.mem[addr:addr+4])
if OpF.UNEXPANDED in opdesc.flags or OpF.QUICK in opdesc.flags: def _to_raw_bytes_offset(self, pointer: int | c_uint32 | c_int32) -> int:
return main_part """
upper_part = struct.unpack(">i", self.mem[addr+4:addr+8]) Переводит адресс из указателей в рамках процессора, которые
return (*main_part, *upper_part) являются 32-битными, в формат смещения в сырых байтах в памяти
"""
if isinstance(pointer, int):
return pointer * WORD_SIZE
return pointer.value * WORD_SIZE
def _parse_instr_fields(
self,
instr: bytes
) -> tuple[int, int, int, int]:
return struct.unpack(">BBBb", instr)
def _run_callback( def _run_callback(
self, self,
@ -182,7 +204,7 @@ class VM:
if opdesc.layout == OpL.MATH: if opdesc.layout == OpL.MATH:
assert len(args) == 4 assert len(args) == 4
_, r3, r1, r2_or_i8 = args _, r3, r1, r2_or_i8 = args
# поскольку этот колбэк сгенерирован, # поскольку этот колбэк сгенерирован,
# ему необходимо в явном виде указывать # ему необходимо в явном виде указывать
# аргумент self # аргумент self
self.instr_callbacks[opdesc]( self.instr_callbacks[opdesc](
@ -235,7 +257,7 @@ class VM:
lhs = self.registers[r1].value lhs = self.registers[r1].value
rhs = self.registers[r2].value rhs = self.registers[r2].value
self.registers[r3] = c_int32(operation(lhs, rhs)) self.registers[r3] = c_int32(operation(lhs, rhs))
return callback return callback
def _math_quick_callback_gen( def _math_quick_callback_gen(
@ -267,14 +289,18 @@ class VM:
return callback return callback
def _load_callback(self, r3: int, r1: int, disp: int) -> None: def _load_callback(self, r3: int, r1: int, disp: int) -> None:
addr = (self.registers[r1].value + disp) * 4 addr = self._to_raw_bytes_offset(self.registers[r1].value + disp)
self.registers[r3] = c_int32( self.registers[r3] = c_int32(
struct.unpack(">i", self.mem[addr:addr+4])[0] struct.unpack(
">i", self.mem[addr:addr+WORD_SIZE])[0]
) )
def _store_callback(self, r3: int, r1: int, disp: int) -> None: def _store_callback(self, r3: int, r1: int, disp: int) -> None:
addr = (self.registers[r1].value + disp) * 4 addr = self._to_raw_bytes_offset(self.registers[r1].value + disp)
self.mem[addr:addr+4] = struct.pack(">i", self.registers[r3].value) self.mem[addr:addr+WORD_SIZE] = struct.pack(
">i",
self.registers[r3].value
)
def _branch_callback(self, cond: int, disp: int) -> None: def _branch_callback(self, cond: int, disp: int) -> None:
c = Condition(cond) c = Condition(cond)
@ -291,3 +317,6 @@ class VM:
addr = self.registers[r1].value + disp addr = self.registers[r1].value + disp
self.pc = c_uint32(addr) self.pc = c_uint32(addr)
def _increment_pc(self):
self.pc = c_uint32(self.pc.value + 1)