#include "common/fmt_binary.h" #include "common/op_str.h" #include "common/video_character_display.h" #include "vm.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; } 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( vcd_width_in_px, vcd_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, vcd_width_in_px, vcd_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 < vcd_ch_height; ++ch_y) { for (int ch_x = 0; ch_x < vcd_ch_width; ++ch_x) { bool ch = charset[value] >> (ch_y * vcd_ch_width + (vcd_ch_width - ch_x - 1)) & 1; for (int px_y = 0; px_y < vcd_px_height; ++px_y) { for (int px_x = 0; px_x < vcd_px_width; ++px_x) { int x = (offset % vcd_width_in_ch * vcd_ch_width + ch_x) * vcd_px_width + px_x; int y = (offset / vcd_width_in_ch * vcd_ch_height + ch_y) * vcd_px_height + px_y; buffer[y * vcd_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); } typedef struct { Drive drive; FILE* fp; } FileDrive; void filedrive_construct( FileDrive* drive, FILE* fp, uint16_t block_size, uint16_t block_amount); void filedrive_drive_read(Drive* in_drive, uint8_t* block, uint16_t i); void filedrive_drive_write(Drive* in_drive, const uint8_t* block, uint16_t i); void filedrive_construct( FileDrive* drive, FILE* fp, uint16_t block_size, uint16_t block_amount) { *drive = (FileDrive) { .drive = (Drive) { .self = drive, .block_size = block_size, .block_amount = block_amount, .read = filedrive_drive_read, .write = filedrive_drive_write, }, .fp = fp, }; } void filedrive_drive_read(Drive* in_drive, uint8_t* block, uint16_t i) { FileDrive* drive = in_drive->self; fseek(drive->fp, drive->drive.block_size * i, SEEK_SET); fread(block, sizeof(uint8_t), drive->drive.block_size, drive->fp); } void filedrive_drive_write(Drive* in_drive, const uint8_t* block, uint16_t i) { FileDrive* drive = in_drive->self; fseek(drive->fp, drive->drive.block_size * i, SEEK_SET); fwrite(block, sizeof(uint8_t), drive->drive.block_size, drive->fp); } __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)); } } typedef struct { const char* disk_file; } Args; static inline Args parse_args(int argc, char** argv); int main(int argc, char** argv) { Args args = parse_args(argc, argv); int res; int label_ids = 0; SdlDevice io_device; res = sdldevice_construct(&io_device); if (res != 0) { exit(1); } FILE* fp = fopen(args.disk_file, "rb"); if (!fp) { fprintf(stderr, "error: could not open %s: %s\n", args.disk_file, strerror(errno)); exit(1); } FileDrive drive; filedrive_construct(&drive, fp, 512, 32); vm_start(&drive.drive, &io_device.io_device); fclose(fp); sdldevice_destroy(&io_device); } static inline Args parse_args(int argc, char** argv) { const char* disk_file = "build/image"; for (int i = 1; i < argc; ++i) { if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--disk-file") == 0) { if (i + 1 >= argc) { fprintf( stderr, "error: expected filename after '%s'\n", argv[i]); exit(1); } disk_file = argv[i + 1]; i += 1; } else { fprintf(stderr, "error: unrecognized argument '%s'\n", argv[i]); exit(1); } } return (Args) { disk_file, }; } 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, };