vc3/vm/vm.c
2025-03-31 19:49:36 +02:00

556 lines
16 KiB
C

#include "vm.h"
#include "common/arch.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct {
uint16_t regs[10];
uint8_t* mem;
Drive* boot_drive;
uint16_t seg_count;
uint16_t seg_size;
IODevice* io_device;
uint16_t int_table;
int interrupt_timeout;
uint16_t keyboard_port_input;
} VM;
static inline void run_arithm_ins(VM* vm, uint16_t ins);
static inline void handle_device_read(VM* vm, Reg dst_reg, uint16_t device_id);
static inline void handle_device_write(
VM* vm, uint16_t op1, uint16_t device_id);
static inline int handle_interrupt(VM* vm, uint8_t int_id);
static inline int jump_to_interrupt_handler(VM* vm, uint8_t int_id);
static inline void maybe_update_vcd(VM* vm, uint16_t addr);
static inline uint16_t eat_uint16(VM* vm);
static inline uint16_t read_seg_uint16(VM* vm, uint16_t ptr);
static inline Op ins_op(uint16_t ins);
static inline Reg ins_dst_reg(uint16_t ins);
static inline Reg ins_op1_reg(uint16_t ins);
static inline Reg ins_op2_reg(uint16_t ins);
static inline uint16_t ins_op1(VM* vm, uint16_t ins);
static inline uint16_t ins_op2(VM* vm, uint16_t ins);
static inline uint16_t ins_reg_val_or_imm(
VM* vm, uint16_t ins, uint16_t is_imm_bit, uint16_t reg_bit, uint16_t mask);
static inline uint16_t ins_op1_or_imm(VM* vm, uint16_t ins);
static inline uint16_t ins_op2_or_imm(VM* vm, uint16_t ins);
void vm_start(Drive* boot_drive, IODevice* io_device)
{
const uint16_t seg_count = 16;
const uint16_t seg_size = 4096;
VM vm_inst = {
.regs = { 0 },
.mem = calloc(seg_count * seg_size, sizeof(uint8_t)),
.boot_drive = boot_drive,
.seg_count = seg_count,
.seg_size = seg_size,
.io_device = io_device,
.int_table = 0,
.interrupt_timeout = 10,
.keyboard_port_input = 0,
};
VM* vm = &vm_inst;
uint16_t* rbp = &vm->regs[Rbp];
uint16_t* rsp = &vm->regs[Rsp];
uint16_t* rfl = &vm->regs[Rfl];
uint16_t* rip = &vm->regs[Rip];
uint16_t* rcs = &vm->regs[Rcs];
const uint16_t bootloader_size = 512;
const uint16_t block_size = vm->boot_drive->block_size;
for (uint16_t i = 0; i * block_size < bootloader_size; ++i) {
vm->boot_drive->read(vm->boot_drive, &vm->mem[i * block_size], i);
}
while (true) {
uint16_t ins = eat_uint16(vm);
Op op = ins_op(ins);
// printf("[%3d] = %3d %s\n", *rip - 2, op, op_str(op));
if (*rip >= 1024) {
printf("killed: rip >= 1024\n");
exit(0);
}
switch (op) {
case Op_Nop:
break;
case Op_Hlt:
vm->io_device->wait_for_interrupt(vm->io_device);
vm->interrupt_timeout = 0;
break;
case Op_Jmp: {
bool is_farjump = ins >> 7 & 1;
if (is_farjump) {
uint16_t cs = ins_reg_val_or_imm(vm, ins, 8, 13, 0x7);
uint16_t op1 = ins_op1_or_imm(vm, ins);
*rcs = cs;
*rip = op1;
} else {
uint16_t op1 = ins_op1_or_imm(vm, ins);
*rip = op1;
}
break;
}
case Op_Jnz: {
uint16_t op1 = ins_op1(vm, ins);
uint16_t op2 = ins_op2_or_imm(vm, ins);
if (op1 != 0) {
*rip = op2;
}
break;
}
case Op_Test: {
uint16_t op1 = ins_op1(vm, ins);
if (op1 == 0) {
*rfl |= 1u << Fl_Zero;
} else {
*rfl &= (uint16_t)~(1u << Fl_Zero);
}
break;
}
case Op_Cmp: {
uint16_t op1 = ins_op1(vm, ins);
uint16_t op2 = ins_op2_or_imm(vm, ins);
if (op1 == op2) {
*rfl |= 1u << Fl_Eq;
} else {
*rfl &= (uint16_t)~(1u << Fl_Eq);
}
if (op1 < op2) {
*rfl |= 1u << Fl_Be;
} else {
*rfl &= (uint16_t)~(1u << Fl_Be);
}
if ((int16_t)op1 < (int16_t)op2) {
*rfl |= 1u << Fl_Lt;
} else {
*rfl &= (uint16_t)~(1u << Fl_Lt);
}
break;
}
case Op_Mov8: {
bool is_memory = ins >> 10 & 1;
bool addr_is_reg = ins >> 11 & 1;
bool is_store = ins >> 12 & 1;
if (!is_memory) {
uint16_t src = ins_reg_val_or_imm(vm, ins, 6, 7, 0xf);
Reg dst = ins >> 12 & 0xf;
vm->regs[dst] = (uint8_t)src;
break;
}
uint16_t addr;
if (addr_is_reg) {
Reg reg = is_store ? ins_dst_reg(ins) : ins_op2_reg(ins);
int16_t offset = (int16_t)eat_uint16(vm);
addr = (uint16_t)((int16_t)vm->regs[reg] + offset);
} else {
addr = eat_uint16(vm);
}
if (is_store) {
uint16_t src = ins_op2_or_imm(vm, ins);
vm->mem[addr] = (uint8_t)src;
maybe_update_vcd(vm, addr);
} else {
Reg reg = is_store ? ins_op2_reg(ins) : ins_dst_reg(ins);
vm->regs[reg] = (uint16_t)vm->mem[addr];
}
break;
}
case Op_Mov16: {
bool is_memory = ins >> 10 & 1;
bool addr_is_reg = ins >> 11 & 1;
bool is_store = ins >> 12 & 1;
if (!is_memory) {
uint16_t src = ins_reg_val_or_imm(vm, ins, 6, 7, 0xf);
Reg dst = ins >> 12 & 0xf;
vm->regs[dst] = src;
break;
}
uint16_t addr;
if (addr_is_reg) {
Reg reg = is_store ? ins_dst_reg(ins) : ins_op2_reg(ins);
int16_t offset = (int16_t)eat_uint16(vm);
addr = (uint16_t)((int16_t)vm->regs[reg] + offset);
} else {
addr = eat_uint16(vm);
}
if (addr % 2 != 0) {
fprintf(stderr,
"error: invalid address alignment, halting "
"execution.\n");
vm->regs[Rfl] |= 1 << Fl_Err;
goto halt_execution;
}
if (is_store) {
uint16_t src = ins_op2_or_imm(vm, ins);
*(uint16_t*)(&vm->mem[addr]) = src;
maybe_update_vcd(vm, addr);
maybe_update_vcd(vm, addr + 1);
} else {
Reg reg = is_store ? ins_op2_reg(ins) : ins_dst_reg(ins);
vm->regs[reg] = *(uint16_t*)(&vm->mem[addr]);
}
break;
}
case Op_In: {
Reg dst_reg = ins_dst_reg(ins);
uint16_t device_id = ins_op1_or_imm(vm, ins);
handle_device_read(vm, dst_reg, device_id);
break;
}
case Op_Out: {
uint16_t op1 = ins_op2(vm, ins);
uint16_t device_id = ins_op1_or_imm(vm, ins);
handle_device_write(vm, op1, device_id);
break;
}
case Op_Call: {
bool is_far_call = ins >> 13 & 1;
uint16_t op1 = ins_op1_or_imm(vm, ins);
if (is_far_call) {
uint16_t op2 = ins_reg_val_or_imm(vm, ins, 14, 7, 0x7);
*rsp += 2;
*(uint16_t*)&vm->mem[*rsp] = *rcs;
*rsp += 2;
*(uint16_t*)&vm->mem[*rsp] = *rip;
*rip = op1;
*rcs = op2;
} else {
*rsp += 2;
*(uint16_t*)&vm->mem[*rsp] = *rip;
*rip = op1;
}
break;
}
case Op_Ret: {
bool is_far_return = ins >> 6 & 1;
if (is_far_return) {
*rip = *(uint16_t*)&vm->mem[*rsp];
*rsp -= 2;
*rcs = *(uint16_t*)&vm->mem[*rsp];
*rsp -= 2;
} else {
*rip = *(uint16_t*)&vm->mem[*rsp];
*rsp -= 2;
}
break;
}
case Op_Lit: {
uint16_t op2 = ins_op1_or_imm(vm, ins);
vm->int_table = op2;
break;
}
case Op_Int: {
uint8_t int_id = (uint8_t)(ins >> 8 & 0xff);
int res = handle_interrupt(vm, int_id);
if (res != 0) {
goto halt_execution;
}
break;
}
case Op_Or:
case Op_Xor:
case Op_And:
case Op_Shl:
case Op_RShl:
case Op_Shr:
case Op_RShr:
case Op_Add:
case Op_Sub:
case Op_RSub:
case Op_Mul:
case Op_IMul:
case Op_Div:
case Op_IDiv:
case Op_RDiv:
case Op_RIDiv:
case Op_Mod:
case Op_RMod:
run_arithm_ins(vm, ins);
break;
}
Interrupt interrupt
= vm->io_device->maybe_next_interrupt(vm->io_device);
switch (interrupt.type) {
case InterruptType_None:
break;
case InterruptType_Shutdown:
goto halt_execution;
case InterruptType_KeyEvent: {
if (vm->interrupt_timeout <= 0 && (*rfl >> Fl_Int & 1)) {
int res = jump_to_interrupt_handler(vm, Int_Key);
if (res != 0) {
goto halt_execution;
}
vm->keyboard_port_input = interrupt.keycode;
vm->interrupt_timeout = 10;
}
break;
}
}
vm->interrupt_timeout -= 1;
}
halt_execution:
return;
}
static inline void run_arithm_ins(VM* vm, uint16_t ins)
{
typedef uint16_t u;
typedef int16_t s;
Op op = ins_op(ins);
uint16_t op1 = ins_op1(vm, ins);
uint16_t op2 = ins_op2_or_imm(vm, ins);
Reg dst_reg = ins_dst_reg(ins);
uint16_t* dst = &vm->regs[dst_reg];
switch (op) {
case Op_Or:
*dst = op1 | op2;
break;
case Op_Xor:
*dst = op1 ^ op2;
break;
case Op_And:
*dst = op1 & op2;
break;
case Op_Shl:
*dst = (u)(op1 << op2);
break;
case Op_RShl:
*dst = (u)(op2 << op1);
break;
case Op_Shr:
*dst = (u)(op1 >> op2);
break;
case Op_RShr:
*dst = (u)(op2 >> op1);
break;
case Op_Add:
*dst = op1 + op2;
break;
case Op_Sub:
*dst = op1 - op2;
break;
case Op_RSub:
*dst = op2 - op1;
break;
case Op_Mul:
*dst = op1 * op2;
break;
case Op_IMul:
*dst = (u)((s)op1 * (s)op2);
break;
case Op_Div:
*dst = op1 / op2;
break;
case Op_IDiv:
*dst = (u)((s)op1 / (s)op2);
break;
case Op_RDiv:
*dst = op2 / op1;
break;
case Op_RIDiv:
*dst = (u)((s)op2 / (s)op1);
break;
case Op_Mod:
*dst = op1 % op2;
break;
case Op_RMod:
*dst = op2 % op1;
break;
default:
break;
}
}
static inline void handle_device_read(VM* vm, Reg dst_reg, uint16_t device_id)
{
switch (device_id) {
case Device_Keyboard:
vm->regs[dst_reg] = vm->keyboard_port_input;
break;
default:
fprintf(stderr, "warning: no input device %d\n", device_id);
break;
}
}
static inline void handle_device_write(VM* vm, uint16_t op1, uint16_t device_id)
{
switch (device_id) {
default:
fprintf(stderr, "warning: no output device %d\n", device_id);
break;
}
}
static inline int handle_interrupt(VM* vm, uint8_t int_id)
{
switch ((VM_Int)int_id) {
case Int_DiskRead: {
uint16_t addr = vm->regs[R0];
uint16_t block = vm->regs[R1];
vm->boot_drive->read(vm->boot_drive, &vm->mem[addr], block);
break;
}
case Int_DiskWrite: {
uint16_t addr = vm->regs[R0];
uint16_t block = vm->regs[R1];
vm->boot_drive->write(vm->boot_drive, &vm->mem[addr], block);
break;
}
default:
return jump_to_interrupt_handler(vm, int_id);
}
return 0;
}
static inline int jump_to_interrupt_handler(VM* vm, uint8_t int_id)
{
uint16_t* rsp = &vm->regs[Rsp];
uint16_t* rfl = &vm->regs[Rfl];
uint16_t* rip = &vm->regs[Rip];
uint16_t* rcs = &vm->regs[Rcs];
if ((*rfl >> Fl_Int & 1) == 0) {
fprintf(stderr, "error: interrupt with unset flag\n");
vm->regs[Rfl] |= 1 << Fl_Err;
return -1;
}
uint16_t offset = int_id - 32;
uint16_t int_table_size = *(uint16_t*)&vm->mem[vm->int_table];
if (offset >= int_table_size) {
fprintf(stderr, "error: interrupt outside table (%d)\n", int_id);
vm->regs[Rfl] |= 1 << Fl_Err;
return -1;
}
uint16_t int_addr = *(uint16_t*)&vm->mem[vm->int_table + offset * 2 + 2];
*rsp += 2;
*(uint16_t*)&vm->mem[*rsp] = *rcs;
*rsp += 2;
*(uint16_t*)&vm->mem[*rsp] = *rip;
*rcs = 0;
*rip = int_addr;
return 0;
}
static inline void maybe_update_vcd(VM* vm, uint16_t addr)
{
if (!vm->io_device)
return;
if ((vm->regs[Rfl] >> Fl_Vcd & 1) == 0)
return;
if (!(addr >= VCD_BUFFER_OFFSET
&& addr < VCD_BUFFER_OFFSET + VCD_BUFFER_SIZE))
return;
uint16_t offset = addr - VCD_BUFFER_OFFSET;
vm->io_device->set_char(vm->io_device, offset, vm->mem[addr]);
}
static inline uint16_t eat_uint16(VM* vm)
{
uint16_t* rip = &vm->regs[Rip];
uint16_t ins = read_seg_uint16(vm, *rip);
*rip += 2;
return ins;
}
static inline uint16_t read_seg_uint16(VM* vm, uint16_t ptr)
{
return *(uint16_t*)&vm->mem[vm->regs[Rcs] * vm->seg_size + ptr];
}
static inline Op ins_op(uint16_t ins)
{
return ins & 0x3F;
}
static inline Reg ins_dst_reg(uint16_t ins)
{
return ins >> 13 & 0x7;
}
static inline Reg ins_op1_reg(uint16_t ins)
{
return ins >> 10 & 0x7;
}
static inline Reg ins_op2_reg(uint16_t ins)
{
return ins >> 7 & 0x7;
}
static inline uint16_t ins_op1(VM* vm, uint16_t ins)
{
return vm->regs[ins_op1_reg(ins)];
}
static inline uint16_t ins_op2(VM* vm, uint16_t ins)
{
return vm->regs[ins_op2_reg(ins)];
}
static inline uint16_t ins_reg_val_or_imm(
VM* vm, uint16_t ins, uint16_t is_imm_bit, uint16_t reg_bit, uint16_t mask)
{
bool is_imm = (ins >> is_imm_bit & 1) != 0;
if (is_imm) {
return eat_uint16(vm);
} else {
return vm->regs[ins >> reg_bit & mask];
}
}
static inline uint16_t ins_op1_or_imm(VM* vm, uint16_t ins)
{
return ins_reg_val_or_imm(vm, ins, 6, 10, 0x7);
}
static inline uint16_t ins_op2_or_imm(VM* vm, uint16_t ins)
{
return ins_reg_val_or_imm(vm, ins, 6, 7, 0x7);
}