diff --git a/src/vm.py b/src/vm.py index 5d96de5..b2d9b8e 100644 --- a/src/vm.py +++ b/src/vm.py @@ -1,10 +1,12 @@ -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import ClassVar, Callable from ctypes import c_uint32, c_int32 import struct from optable import OPCODES, OpcodeDescription, OpL, OpA, OpF, OpD from enum import IntFlag, Enum, auto +WORD_SIZE: int = 4 + class VMFlags(IntFlag): # if last instruction is big, then you should # skip more memory on step @@ -83,10 +85,10 @@ class VM: self._math_quick_callback_gen(lambda lhs, rhs: lhs // rhs), # 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), - OpD(OpF.UNEXPANDED, OpL.MATH, OpA.OR): + OpD(OpF.UNEXPANDED, OpL.MATH, OpA.OR): self._math_callback_gen(lambda lhs, rhs: lhs | rhs), OpD(OpF.UNEXPANDED, OpL.MATH, OpA.XOR): @@ -120,21 +122,19 @@ class VM: """ 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( VMExceptionType.END_OF_MEM, self.pc.value ) - opcode = self.mem[self.pc.value * 4] - opdesc = self._fetch_opcode_desc(opcode) - args = self._parse_arguments(opdesc) - # По какой-то причине адрессация работает - # так, будто мы на 1 слово впереди опкода - self.pc = c_uint32(self.pc.value + 1) + opcode, *_ = instr = self._fetch_instr() + opdesc = self._get_opcode_desc(opcode) + args: tuple[int, ...] = self._parse_instr_fields(bytes(instr)) + if not (OpF.UNEXPANDED in opdesc.flags or + OpF.QUICK in opdesc.flags): + extra = struct.unpack(">i", self._fetch_instr())[0] + args = (*args, extra) 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.AFTER_BRANCH) @@ -142,7 +142,7 @@ class VM: """ 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: raise Breakpoint(self.pc.value) self.step() @@ -152,25 +152,47 @@ class VM: Run from very beginning """ 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_() - 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: raise VMException( VMExceptionType.INVALID_OPCODE, self.pc.value ) 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: - return main_part - upper_part = struct.unpack(">i", self.mem[addr+4:addr+8]) - return (*main_part, *upper_part) + def _to_raw_bytes_offset(self, pointer: int | c_uint32 | c_int32) -> int: + """ + Переводит адресс из указателей в рамках процессора, которые + являются 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( self, @@ -182,7 +204,7 @@ class VM: if opdesc.layout == OpL.MATH: assert len(args) == 4 _, r3, r1, r2_or_i8 = args - # поскольку этот колбэк сгенерирован, + # поскольку этот колбэк сгенерирован, # ему необходимо в явном виде указывать # аргумент self self.instr_callbacks[opdesc]( @@ -235,7 +257,7 @@ class VM: lhs = self.registers[r1].value rhs = self.registers[r2].value self.registers[r3] = c_int32(operation(lhs, rhs)) - + return callback def _math_quick_callback_gen( @@ -267,14 +289,18 @@ class VM: return callback 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( - 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: - addr = (self.registers[r1].value + disp) * 4 - self.mem[addr:addr+4] = struct.pack(">i", self.registers[r3].value) + addr = self._to_raw_bytes_offset(self.registers[r1].value + disp) + self.mem[addr:addr+WORD_SIZE] = struct.pack( + ">i", + self.registers[r3].value + ) def _branch_callback(self, cond: int, disp: int) -> None: c = Condition(cond) @@ -291,3 +317,6 @@ class VM: addr = self.registers[r1].value + disp self.pc = c_uint32(addr) + def _increment_pc(self): + self.pc = c_uint32(self.pc.value + 1) +