vc3/vm/main.c
2025-03-31 18:47:41 +02:00

609 lines
16 KiB
C

#include "vm.h"
#include "vm/asm.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_error.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_keycode.h>
#include <SDL2/SDL_pixels.h>
#include <SDL2/SDL_render.h>
#include <SDL2/SDL_video.h>
#include <bits/pthreadtypes.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
Interrupt* data;
size_t capacity;
size_t front;
size_t back;
} InterruptQueue;
void int_queue_construct(InterruptQueue* queue, size_t capacity)
{
*queue = (InterruptQueue) {
.data = malloc(sizeof(Interrupt) * capacity),
.capacity = capacity,
.back = 0,
.front = 0,
};
}
void int_queue_destroy(InterruptQueue* queue)
{
free(queue->data);
}
void int_queue_push(InterruptQueue* queue, Interrupt req)
{
size_t front = queue->front + 1;
if (front >= queue->capacity) {
front = 0;
}
if (front == queue->back) {
fprintf(stderr, "error: queue overflow\n");
exit(1);
}
queue->data[queue->front] = req;
queue->front = front;
}
size_t int_queue_size(const InterruptQueue* queue)
{
return queue->front >= queue->back
? queue->front - queue->back
: (queue->capacity - queue->back) + queue->front;
}
Interrupt int_queue_pop(InterruptQueue* queue)
{
if (queue->back == queue->front) {
fprintf(stderr, "error: queue underflow\n");
exit(1);
}
Interrupt val = queue->data[queue->back];
size_t back = queue->back + 1;
if (back >= queue->capacity) {
back = 0;
}
queue->back = back;
return val;
}
#define ch_width 8
#define ch_height 8
static const int width_in_ch = 40;
static const int height_in_ch = 12;
static const int px_width = 4;
static const int px_height = 8;
static const int width_in_px = width_in_ch * ch_width * px_width;
static const int height_in_px = height_in_ch * ch_height * px_height;
typedef struct {
IODevice io_device;
SDL_Window* window;
SDL_Renderer* renderer;
SDL_Texture* buffer_texture;
pthread_t render_thread;
atomic_bool should_run;
pthread_mutex_t mutex;
pthread_cond_t interrupt_waiter;
InterruptQueue int_queue;
} SdlDevice;
int sdldevice_construct(SdlDevice* device);
void sdldevice_destroy(SdlDevice* device);
void sdldevice_set_char(
IODevice* device_device, uint16_t offset, uint8_t value);
void sdldevice_wait_for_interrupt(IODevice* io_device);
void* sdldevice_thread_entry(void* data);
void sdldevice_poll_events(SdlDevice* device);
Interrupt sdldevice_maybe_next_interrupt(IODevice* io_device);
int sdldevice_construct(SdlDevice* device)
{
int res;
res = SDL_Init(SDL_INIT_VIDEO);
if (res != 0) {
fprintf(stderr, "error: could not init sdl: %s\n", SDL_GetError());
return -1;
}
SDL_Window* window;
SDL_Renderer* renderer;
res = SDL_CreateWindowAndRenderer(
width_in_px, height_in_px, 0, &window, &renderer);
if (res != 0) {
fprintf(stderr,
"error: could not create window/renderer: %s\n",
SDL_GetError());
return -1;
}
SDL_Texture* buffer_texture = SDL_CreateTexture(renderer,
SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STREAMING,
width_in_px,
height_in_px);
if (buffer_texture == NULL) {
fprintf(stderr,
"error: could not create buffer texture: %s\n",
SDL_GetError());
return -1;
}
*device = (SdlDevice) {
.io_device = (IODevice) {
.self = device,
.set_char = sdldevice_set_char,
.wait_for_interrupt = sdldevice_wait_for_interrupt,
.maybe_next_interrupt = sdldevice_maybe_next_interrupt,
},
.window = window,
.renderer = renderer,
.buffer_texture = buffer_texture,
.render_thread = (pthread_t) { 0 },
.should_run = true,
.mutex = PTHREAD_MUTEX_INITIALIZER,
.interrupt_waiter = PTHREAD_COND_INITIALIZER,
.int_queue = {0},
};
SDL_RenderPresent(device->renderer);
pthread_create(
&device->render_thread, NULL, sdldevice_thread_entry, device);
int_queue_construct(&device->int_queue, 128);
return 0;
}
void sdldevice_destroy(SdlDevice* device)
{
device->should_run = false;
pthread_join(device->render_thread, NULL);
pthread_mutex_destroy(&device->mutex);
pthread_cond_destroy(&device->interrupt_waiter);
int_queue_destroy(&device->int_queue);
SDL_DestroyTexture(device->buffer_texture);
SDL_DestroyRenderer(device->renderer);
SDL_DestroyWindow(device->window);
SDL_Quit();
}
extern const uint64_t charset[];
void sdldevice_set_char(IODevice* io_device, uint16_t offset, uint8_t value)
{
if (!((value >= 'A' && value <= 'Z') || value == ' ')) {
printf("sdldevice: invalid char value = %d\n", value);
return;
}
SdlDevice* device = io_device->self;
pthread_mutex_lock(&device->mutex);
SDL_Color* buffer;
int pitch;
int res = SDL_LockTexture(
device->buffer_texture, NULL, (void**)&buffer, &pitch);
if (res != 0) {
fprintf(stderr, "error: could not lock texture: %s\n", SDL_GetError());
pthread_mutex_unlock(&device->mutex);
return;
}
for (int ch_y = 0; ch_y < ch_height; ++ch_y) {
for (int ch_x = 0; ch_x < ch_width; ++ch_x) {
bool ch
= charset[value] >> (ch_y * ch_width + (ch_width - ch_x - 1))
& 1;
for (int px_y = 0; px_y < px_height; ++px_y) {
for (int px_x = 0; px_x < px_width; ++px_x) {
int x = (offset % width_in_ch * ch_width + ch_x) * px_width
+ px_x;
int y
= (offset / width_in_ch * ch_height + ch_y) * px_height
+ px_y;
buffer[y * width_in_px + x] = ch
? (SDL_Color) { 0xff, 0xff, 0xff, 0xff }
: (SDL_Color) { 0x00, 0x00, 0x00, 0xff };
}
}
}
}
SDL_UnlockTexture(device->buffer_texture);
SDL_RenderCopy(device->renderer, device->buffer_texture, NULL, NULL);
SDL_RenderPresent(device->renderer);
pthread_mutex_unlock(&device->mutex);
}
void sdldevice_wait_for_interrupt(IODevice* io_device)
{
SdlDevice* device = io_device->self;
pthread_mutex_lock(&device->mutex);
// printf("vm: waiting for interrupt...\n");
pthread_cond_wait(&device->interrupt_waiter, &device->mutex);
// printf("vm: got interrupt!\n");
pthread_mutex_unlock(&device->mutex);
}
void* sdldevice_thread_entry(void* data)
{
SdlDevice* device = (SdlDevice*)data;
while (device->should_run) {
sdldevice_poll_events(device);
if (!device->should_run)
break;
SDL_Delay(6);
}
return NULL;
}
static inline bool is_exit_event(SDL_Event* event)
{
return event->type == SDL_QUIT
|| (event->type == SDL_KEYDOWN
&& event->key.keysym.scancode == SDL_SCANCODE_ESCAPE
// && event->key.keysym.mod & KMOD_CTRL
);
}
void sdldevice_poll_events(SdlDevice* device)
{
pthread_mutex_lock(&device->mutex);
bool should_notify = false;
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (is_exit_event(&event)) {
device->should_run = false;
int_queue_push(&device->int_queue,
(Interrupt) {
.type = InterruptType_Shutdown,
});
should_notify = true;
break;
} else if (event.type == SDL_KEYDOWN) {
int_queue_push(&device->int_queue,
(Interrupt) {
.type = InterruptType_KeyEvent,
.keycode = (uint16_t)event.key.keysym.scancode,
});
should_notify = true;
}
}
pthread_mutex_unlock(&device->mutex);
if (should_notify) {
// printf("sdldevice: interrupt occured!\n");
pthread_cond_signal(&device->interrupt_waiter);
}
}
Interrupt sdldevice_maybe_next_interrupt(IODevice* io_device)
{
SdlDevice* device = io_device->self;
pthread_mutex_lock(&device->mutex);
Interrupt val = {
.type = InterruptType_None,
};
if (int_queue_size(&device->int_queue) > 0) {
val = int_queue_pop(&device->int_queue);
}
pthread_mutex_unlock(&device->mutex);
return val;
}
typedef struct {
Drive drive;
uint64_t* mem;
} MemDrive;
void memdrive_construct(MemDrive* drive, uint64_t* mem, size_t mem_size);
void memdrive_drive_read(Drive* in_drive, uint8_t* block, uint16_t i);
void memdrive_drive_write(Drive* in_drive, const uint8_t* block, uint16_t i);
void memdrive_construct(MemDrive* drive, uint64_t* mem, size_t mem_size)
{
const uint16_t block_size = 512;
*drive = (MemDrive) {
.drive = (Drive) {
.self = drive,
.block_size = block_size,
.block_amount = (uint16_t)(mem_size / block_size),
.read = memdrive_drive_read,
.write = memdrive_drive_write,
},
.mem = mem,
};
}
void memdrive_drive_read(Drive* in_drive, uint8_t* block, uint16_t i)
{
MemDrive* drive = in_drive->self;
memcpy(block,
&drive->mem[i * drive->drive.block_size],
drive->drive.block_size);
}
void memdrive_drive_write(Drive* in_drive, const uint8_t* block, uint16_t i)
{
MemDrive* drive = in_drive->self;
memcpy(&drive->mem[i * drive->drive.block_size],
block,
drive->drive.block_size);
}
__attribute__((unused)) static inline void dump_program(uint16_t* program)
{
for (size_t rip = 20; rip < 60; ++rip) {
uint16_t val = program[rip];
printf("[%02lx] = %02x %02x %c%c%c%c %c%c%c%c %c%c%c%c %c%c%c%c "
"%s\n",
rip * 2,
val >> 8,
val & 0xff,
fmt_binary(val >> 8),
fmt_binary(val & 0xff),
op_str(val & 0x3f));
}
}
int main(void)
{
int res;
int label_ids = 0;
int main_loop = label_ids++;
int interrupt_table = label_ids++;
int keyboard_interrupt = label_ids++;
int keyboard_interrupt_0 = label_ids++;
int keyboard_interrupt_1 = label_ids++;
int keyboard_interrupt_2 = label_ids++;
int keyboard_interrupt_3 = label_ids++;
int keyboard_interrupt_4 = label_ids++;
int put_char = label_ids++;
int put_char_0 = label_ids++;
int put_char_1 = label_ids++;
int screen_x = label_ids++;
int screen_y = label_ids++;
#define L(LABEL) s_label(LABEL)
Line program_asm[] = {
// clang-format off
// rsp points *at* the top element
s_mov16_r_i(Rbp, 2048),
s_mov16_r_i(Rsp, 2048 - 2),
s_lit_l(interrupt_table),
s_or_i(Rfl, Rfl, 1 << Fl_Int),
s_or_i(Rfl, Rfl, 1 << Fl_Vcd),
s_mov16_r_i(R0, 512),
s_mov16_r_i(R1, 1),
s_int(Int_DiskRead),
L(main_loop),
s_hlt(),
s_jmp_l(main_loop),
L(interrupt_table),
// size
s_data_i(1),
s_data_l(keyboard_interrupt),
s_nop(),
L(keyboard_interrupt),
s_and_i(Rfl, Rfl, (uint16_t)~(1 << Fl_Int)),
s_push_r(Rbp),
s_mov16_r_r(Rbp, Rsp),
s_push_r(R0),
s_push_r(R1),
s_push_r(R2),
s_push_r(R3),
s_in_i(R0, Device_Keyboard),
s_cmp_i(R0, 44),
s_mov16_r_r(R1, Rfl),
s_and_i(R1, R1, 1 << Fl_Eq),
s_jnz_l(R1, keyboard_interrupt_0),
s_cmp_i(R0, 42),
s_mov16_r_r(R1, Rfl),
s_and_i(R1, R1, 1 << Fl_Eq),
s_jnz_l(R1, keyboard_interrupt_1),
s_cmp_i(R0, 40),
s_mov16_r_r(R1, Rfl),
s_and_i(R1, R1, 1 << Fl_Eq),
s_jnz_l(R1, keyboard_interrupt_2),
s_jmp_l(keyboard_interrupt_3),
L(keyboard_interrupt_0),
s_mov16_r_i(R0, ' '),
s_call_l(put_char),
s_jmp_l(keyboard_interrupt_4),
L(keyboard_interrupt_1),
s_mov16_r_ml(R1, screen_x),
s_cmp_i(R1, 0),
s_mov16_r_r(R2, Rfl),
s_and_i(R2, R2, 1 << Fl_Eq),
s_jnz_l(R2, keyboard_interrupt_4),
s_sub_i(R1, R1, 1),
s_mov16_ml_r(screen_x, R1),
s_mov16_r_i(R0, ' '),
s_call_l(put_char),
s_mov16_r_ml(R1, screen_x),
s_sub_i(R1, R1, 1),
s_mov16_ml_r(screen_x, R1),
s_jmp_l(keyboard_interrupt_4),
L(keyboard_interrupt_2),
s_mov16_r_ml(R1, screen_y),
s_add_i(R1, R1, 1),
s_mov16_ml_r(screen_y, R1),
s_mov16_r_i(R1, 0),
s_mov16_ml_r(screen_x, R1),
s_jmp_l(keyboard_interrupt_4),
L(keyboard_interrupt_3),
s_add_i(R0, R0, 'A' - 4),
s_call_l(put_char),
s_jmp_l(keyboard_interrupt_4),
L(keyboard_interrupt_4),
s_pop_r(R3),
s_pop_r(R2),
s_pop_r(R1),
s_pop_r(R0),
s_mov16_r_r(Rsp, Rbp),
s_pop_r(Rbp),
s_or_i(Rfl, Rfl, 1 << Fl_Int),
s_iret(),
L(put_char),
s_push_r(Rbp),
s_mov16_r_r(Rbp, Rsp),
s_push_r(R1),
s_push_r(R2),
s_mov16_r_ml(R2, screen_y),
s_mul_i(R2, R2, width_in_ch),
s_mov16_r_ml(R1, screen_x),
s_add_i(R1, R1, 0x0c00),
s_add_r(R1, R1, R2),
s_mov8_mr_r(R1, R0),
s_mov16_r_ml(R1, screen_x),
s_add_i(R1, R1, 1),
s_mov16_ml_r(screen_x, R1),
s_cmp_i(R1, width_in_ch),
s_mov16_r_r(R2, Rfl),
s_and_i(R2, R2, 1 << Fl_Eq),
s_jnz_l(R2, put_char_0),
s_jmp_l(put_char_1),
L(put_char_0),
s_mov16_r_ml(R1, screen_y),
s_add_i(R1, R1, 1),
s_mov16_ml_r(screen_y, R1),
s_mov16_r_i(R1, 0),
s_mov16_ml_r(screen_x, R1),
L(put_char_1),
s_pop_r(R1),
s_pop_r(R2),
s_mov16_r_r(Rsp, Rbp),
s_pop_r(Rbp),
s_ret(),
L(screen_x),
s_data_i(0),
L(screen_y),
s_data_i(0),
// clang-format on
};
size_t program_asm_size = sizeof(program_asm) / sizeof(program_asm[0]);
uint16_t* program = calloc(512 * 2, sizeof(uint16_t));
uint16_t program_size
= assemble_to_binary(program, program_asm, program_asm_size);
printf("program size = %d\n", program_size);
// dump_program(program);
// return 0;
MemDrive drive;
memdrive_construct(&drive, (uint64_t*)program, 512);
SdlDevice io_device;
res = sdldevice_construct(&io_device);
if (res != 0) {
exit(1);
}
vm_start(&drive.drive, &io_device.io_device);
free(program);
sdldevice_destroy(&io_device);
}
const char* __asan_default_options(void)
{
return "detect_leaks=0";
}
const uint64_t charset[] = {
[' '] = 0x0000000000000000,
['A'] = 0x66667E66667E1800,
['B'] = 0x7C7E667C667E7800,
['C'] = 0x3C7E6060607E3C00,
['D'] = 0x7C7E6666667E7C00,
['E'] = 0x7E7E607E607E7E00,
['F'] = 0x60607878607E7E00,
['G'] = 0x7E7E666E607E7E00,
['H'] = 0x6666667E7E666600,
['I'] = 0x7E7E1818187E7E00,
['J'] = 0x3C7E6606067E7E00,
['K'] = 0x666E7C7C6E666600,
['L'] = 0x7E7E606060606000,
['M'] = 0xC3C3C3C3DBFFE700,
['N'] = 0x6666666E7E766600,
['O'] = 0x3C66666666663C00,
['P'] = 0x60607C7E667C7C00,
['Q'] = 0x3F7E6E66667E3C00,
['R'] = 0x666C7C7E667E7C00,
['S'] = 0x3C66063C60663C00,
['T'] = 0x18181818187E7E00,
['U'] = 0x3C7E666666666600,
['V'] = 0x183C7E6666666600,
['W'] = 0xE7FFDBDBC3C3C300,
['X'] = 0x6666663C3C666600,
['Y'] = 0x181818183C666600,
['Z'] = 0x7E7E30180C7E7E00,
};