vc3/vm/main.c
2025-03-31 15:11:59 +02:00

785 lines
20 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 bool charset[][ch_height][ch_width];
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_x];
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 screen_x = label_ids++;
int screen_y = label_ids++;
#define L(LABEL) s_label(LABEL)
Line program_asm[] = {
// clang-format off
s_nop(),
// set video character display flag
s_or_i(Rfl, Rfl, 1 << Fl_Vcd),
// setup stack
s_mov16_r_i(Rbp, 2048),
// rsp points *at* the top element
s_mov16_r_i(Rsp, 2048 - 2),
// load interrupt table
s_lit_l(interrupt_table),
// set interrupt flag
s_or_i(Rfl, Rfl, 1 << Fl_Int),
L(main_loop),
s_hlt(),
s_jmp_l(main_loop),
s_nop(),
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),
// read keyboard port
s_in_i(R0, 0),
s_add_i(R0, R0, 'A' - 4),
s_cmp_i(R0, 105),
s_mov16_r_r(R1, Rfl),
s_and_i(R1, R1, 1 << Fl_Eq),
s_jnz_l(R1, keyboard_interrupt_0),
s_jmp_l(keyboard_interrupt_1),
L(keyboard_interrupt_0),
s_mov16_r_i(R0, ' '),
s_jmp_l(keyboard_interrupt_1),
L(keyboard_interrupt_1),
s_mov16_r_ml(R1, screen_x),
s_add_i(R1, R1, 0x0c00),
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_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(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, sizeof(uint16_t));
assemble_to_binary(program, program_asm, program_asm_size);
// dump_program(program);
for (int i = 'A'; i <= 'Z'; ++i) {
uint64_t ch = 0;
for (int y = 0; y < ch_height; ++y) {
for (int x = 0; x < ch_width; ++x) {
if (charset[i][y][x]) {
ch |= 1 << (y * ch_width + (ch_height - x - 1));
}
}
}
printf("['%c'] = 0x%lX,\n", i, ch);
}
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);
sdldevice_destroy(&io_device);
}
const char* __asan_default_options(void)
{
return "detect_leaks=0";
}
const uint64_t charset_2[] = {
[' '] = 0x0000000000000000,
};
#define _ false,
#define t true,
const bool charset[][ch_height][ch_width] = {
[' '] = {
{ _ _ _ _ _ _ _ _ },
{ _ _ _ _ _ _ _ _ },
{ _ _ _ _ _ _ _ _ },
{ _ _ _ _ _ _ _ _ },
{ _ _ _ _ _ _ _ _ },
{ _ _ _ _ _ _ _ _ },
{ _ _ _ _ _ _ _ _ },
{ _ _ _ _ _ _ _ _ },
},
['A'] = {
{ _ _ _ _ _ _ _ _ },
{ _ _ _ t t _ _ _ },
{ _ t t t t t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t t t t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
},
['B'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t t t _ _ _ },
{ _ t t t t t t _ },
{ _ t t _ _ t t _ },
{ _ t t t t t _ _ },
{ _ t t _ _ t t _ },
{ _ t t t t t t _ },
{ _ t t t t t _ _ },
},
['C'] = {
{ _ _ _ _ _ _ _ _ },
{ _ _ t t t t _ _ },
{ _ t t t t t t _ },
{ _ t t _ _ _ _ _ },
{ _ t t _ _ _ _ _ },
{ _ t t _ _ _ _ _ },
{ _ t t t t t t _ },
{ _ _ t t t t _ _ },
},
['D'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t t t t _ _ },
{ _ t t t t t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t t t t t _ },
{ _ t t t t t _ _ },
},
['E'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t t t t t _ },
{ _ t t t t t t _ },
{ _ t t _ _ _ _ _ },
{ _ t t t t t t _ },
{ _ t t _ _ _ _ _ },
{ _ t t t t t t _ },
{ _ t t t t t t _ },
},
['F'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t t t t t _ },
{ _ t t t t t t _ },
{ _ t t _ _ _ _ _ },
{ _ t t t t _ _ _ },
{ _ t t t t _ _ _ },
{ _ t t _ _ _ _ _ },
{ _ t t _ _ _ _ _ },
},
['G'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t t t t t _ },
{ _ t t t t t t _ },
{ _ t t _ _ _ _ _ },
{ _ t t _ t t t _ },
{ _ t t _ _ t t _ },
{ _ t t t t t t _ },
{ _ t t t t t t _ },
},
['H'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t t t t t _ },
{ _ t t t t t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
},
['I'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t t t t t _ },
{ _ t t t t t t _ },
{ _ _ _ t t _ _ _ },
{ _ _ _ t t _ _ _ },
{ _ _ _ t t _ _ _ },
{ _ t t t t t t _ },
{ _ t t t t t t _ },
},
['J'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t t t t t _ },
{ _ t t t t t t _ },
{ _ _ _ _ _ t t _ },
{ _ _ _ _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t t t t t _ },
{ _ _ t t t t _ _ },
},
['K'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ t t t _ },
{ _ t t t t t _ _ },
{ _ t t t t t _ _ },
{ _ t t _ t t t _ },
{ _ t t _ _ t t _ },
},
['L'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t _ _ _ _ _ },
{ _ t t _ _ _ _ _ },
{ _ t t _ _ _ _ _ },
{ _ t t _ _ _ _ _ },
{ _ t t _ _ _ _ _ },
{ _ t t t t t t _ },
{ _ t t t t t t _ },
},
['M'] = {
{ _ _ _ _ _ _ _ _ },
{ t t t _ _ t t t },
{ t t t t t t t t },
{ t t _ t t _ t t },
{ t t _ _ _ _ t t },
{ t t _ _ _ _ t t },
{ t t _ _ _ _ t t },
{ t t _ _ _ _ t t },
},
['N'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t _ _ t t _ },
{ _ t t t _ t t _ },
{ _ t t t t t t _ },
{ _ t t _ t t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
},
['O'] = {
{ _ _ _ _ _ _ _ _ },
{ _ _ t t t t _ _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ _ t t t t _ _ },
},
['P'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t t t t _ _ },
{ _ t t t t t _ _ },
{ _ t t _ _ t t _ },
{ _ t t t t t t _ },
{ _ t t t t t _ _ },
{ _ t t _ _ _ _ _ },
{ _ t t _ _ _ _ _ },
},
['Q'] = {
{ _ _ _ _ _ _ _ _ },
{ _ _ t t t t _ _ },
{ _ t t t t t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ t t t _ },
{ _ t t t t t t _ },
{ _ _ t t t t t t },
},
['R'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t t t t _ _ },
{ _ t t t t t t _ },
{ _ t t _ _ t t _ },
{ _ t t t t t t _ },
{ _ t t t t t _ _ },
{ _ t t _ t t _ _ },
{ _ t t _ _ t t _ },
},
['S'] = {
{ _ _ _ _ _ _ _ _ },
{ _ _ t t t t _ _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ _ _ _ },
{ _ _ t t t t _ _ },
{ _ _ _ _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ _ t t t t _ _ },
},
['T'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t t t t t _ },
{ _ t t t t t t _ },
{ _ _ _ t t _ _ _ },
{ _ _ _ t t _ _ _ },
{ _ _ _ t t _ _ _ },
{ _ _ _ t t _ _ _ },
{ _ _ _ t t _ _ _ },
},
['U'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t t t t t _ },
{ _ _ t t t t _ _ },
},
['V'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t t t t t _ },
{ _ _ t t t t _ _ },
{ _ _ _ t t _ _ _ },
},
['W'] = {
{ _ _ _ _ _ _ _ _ },
{ t t _ _ _ _ t t },
{ t t _ _ _ _ t t },
{ t t _ _ _ _ t t },
{ t t _ t t _ t t },
{ t t _ t t _ t t },
{ t t t t t t t t },
{ t t t _ _ t t t },
},
['X'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ _ t t t t _ _ },
{ _ _ t t t t _ _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
},
['Y'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t _ _ t t _ },
{ _ t t _ _ t t _ },
{ _ _ t t t t _ _ },
{ _ _ _ t t _ _ _ },
{ _ _ _ t t _ _ _ },
{ _ _ _ t t _ _ _ },
{ _ _ _ t t _ _ _ },
},
['Z'] = {
{ _ _ _ _ _ _ _ _ },
{ _ t t t t t t _ },
{ _ t t t t t t _ },
{ _ _ _ _ t t _ _ },
{ _ _ _ t t _ _ _ },
{ _ _ t t _ _ _ _ },
{ _ t t t t t t _ },
{ _ t t t t t t _ },
},
};