From b74c99b05e53e591dcf78f5d3dd4e0815e301507 Mon Sep 17 00:00:00 2001 From: sfja Date: Sun, 30 Mar 2025 05:37:39 +0200 Subject: [PATCH] init --- .clang-format | 14 ++ .gitignore | 3 + Makefile | 52 +++++ Session.vim | 330 ++++++++++++++++++++++++++++ compile_flags.txt | 13 ++ vm/asm.c | 398 ++++++++++++++++++++++++++++++++++ vm/asm.h | 69 ++++++ vm/main.c | 509 +++++++++++++++++++++++++++++++++++++++++++ vm/vm.c | 536 ++++++++++++++++++++++++++++++++++++++++++++++ vm/vm.h | 104 +++++++++ 10 files changed, 2028 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 Session.vim create mode 100644 compile_flags.txt create mode 100644 vm/asm.c create mode 100644 vm/asm.h create mode 100644 vm/main.c create mode 100644 vm/vm.c create mode 100644 vm/vm.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a56cbd0 --- /dev/null +++ b/.clang-format @@ -0,0 +1,14 @@ +Language: Cpp +BasedOnStyle: WebKit +IndentWidth: 4 +ColumnLimit: 80 +IndentCaseLabels: true +InsertNewlineAtEOF: true +AllowShortFunctionsOnASingleLine: None + +BinPackArguments: false +AllowAllArgumentsOnNextLine: true + +BinPackParameters: false +AllowAllParametersOfDeclarationOnNextLine: true + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e50caec --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +bin/ + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0b76af9 --- /dev/null +++ b/Makefile @@ -0,0 +1,52 @@ + +MAKEFLAGS += -j $(shell nproc) + +CC = gcc + +C_FLAGS = \ + -std=c17 \ + -Wall -Wextra -Wpedantic -Wconversion \ + -pedantic -pedantic-errors \ + -Wno-unused-variable \ + -I. \ + +L_FLAGS = -pthread + + +C_FLAGS += $(shell pkg-config sdl2 --cflags) +L_FLAGS += $(shell pkg-config sdl2 --libs) + +F_FLAGS = +OPTIMIZATION = + +RELEASE=0 + +ifeq ($(RELEASE),1) + C_FLAGS += -Werror + F_FLAGS += -flto=auto + OPTIMIZATION += -O3 +else + C_FLAGS += -g + F_FLAGS += -fsanitize=address,undefined,leak + OPTIMIZATION += -Og +endif + +HEADERS = $(shell find . -name *.h) + +VM_SOURCES = $(shell find vm/ -name *.c) +VM_OBJECTS = $(patsubst %.c,build/%.o,$(VM_SOURCES)) + +all: bin/vm + +bin/vm: $(VM_OBJECTS) + @mkdir -p $(dir $@) + $(CC) $^ -o $@ $(F_FLAGS) $(OPTIMIZATION) $(L_FLAGS) + +build/%.o: %.c $(HEADERS) + @mkdir -p $(dir $@) + $(CC) $< -c -o $@ $(C_FLAGS) $(OPTIMIZATION) $(F_FLAGS) + + +clean: + rm -rf build/ bin/ + diff --git a/Session.vim b/Session.vim new file mode 100644 index 0000000..3a87670 --- /dev/null +++ b/Session.vim @@ -0,0 +1,330 @@ +let SessionLoad = 1 +let s:so_save = &g:so | let s:siso_save = &g:siso | setg so=0 siso=0 | setl so=-1 siso=-1 +let v:this_session=expand(":p") +silent only +silent tabonly +cd ~/Workspace/vc3 +if expand('%') == '' && !&modified && line('$') <= 1 && getline(1) == '' + let s:wipebuf = bufnr('%') +endif +let s:shortmess_save = &shortmess +if &shortmess =~ 'A' + set shortmess=aoOA +else + set shortmess=aoO +endif +badd +397 vm/main.c +badd +1 Makefile +badd +30 vm/vm.h +badd +225 vm/vm.c +badd +53 vm/asm.h +badd +178 vm/asm.c +argglobal +%argdel +$argadd vm/main.c +set stal=2 +tabnew +setlocal\ bufhidden=wipe +tabnew +setlocal\ bufhidden=wipe +tabnew +setlocal\ bufhidden=wipe +tabrewind +edit vm/main.c +let s:save_splitbelow = &splitbelow +let s:save_splitright = &splitright +set splitbelow splitright +wincmd _ | wincmd | +vsplit +1wincmd h +wincmd w +let &splitbelow = s:save_splitbelow +let &splitright = s:save_splitright +wincmd t +let s:save_winminheight = &winminheight +let s:save_winminwidth = &winminwidth +set winminheight=0 +set winheight=1 +set winminwidth=0 +set winwidth=1 +exe 'vert 1resize ' . ((&columns * 127 + 128) / 256) +exe 'vert 2resize ' . ((&columns * 128 + 128) / 256) +argglobal +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +silent! normal! zE +let &fdl = &fdl +let s:l = 397 - ((1 * winheight(0) + 32) / 65) +if s:l < 1 | let s:l = 1 | endif +keepjumps exe s:l +normal! zt +keepjumps 397 +normal! 012| +wincmd w +argglobal +if bufexists(fnamemodify("Makefile", ":p")) | buffer Makefile | else | edit Makefile | endif +if &buftype ==# 'terminal' + silent file Makefile +endif +balt vm/main.c +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +silent! normal! zE +let &fdl = &fdl +let s:l = 1 - ((0 * winheight(0) + 32) / 65) +if s:l < 1 | let s:l = 1 | endif +keepjumps exe s:l +normal! zt +keepjumps 1 +normal! 0 +wincmd w +exe 'vert 1resize ' . ((&columns * 127 + 128) / 256) +exe 'vert 2resize ' . ((&columns * 128 + 128) / 256) +tabnext +edit vm/vm.h +let s:save_splitbelow = &splitbelow +let s:save_splitright = &splitright +set splitbelow splitright +wincmd _ | wincmd | +vsplit +1wincmd h +wincmd w +let &splitbelow = s:save_splitbelow +let &splitright = s:save_splitright +wincmd t +let s:save_winminheight = &winminheight +let s:save_winminwidth = &winminwidth +set winminheight=0 +set winheight=1 +set winminwidth=0 +set winwidth=1 +exe 'vert 1resize ' . ((&columns * 127 + 128) / 256) +exe 'vert 2resize ' . ((&columns * 128 + 128) / 256) +argglobal +balt Makefile +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +silent! normal! zE +let &fdl = &fdl +let s:l = 26 - ((25 * winheight(0) + 32) / 65) +if s:l < 1 | let s:l = 1 | endif +keepjumps exe s:l +normal! zt +keepjumps 26 +normal! 0 +wincmd w +argglobal +if bufexists(fnamemodify("vm/vm.c", ":p")) | buffer vm/vm.c | else | edit vm/vm.c | endif +if &buftype ==# 'terminal' + silent file vm/vm.c +endif +balt vm/vm.h +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +silent! normal! zE +let &fdl = &fdl +let s:l = 70 - ((18 * winheight(0) + 32) / 65) +if s:l < 1 | let s:l = 1 | endif +keepjumps exe s:l +normal! zt +keepjumps 70 +normal! 026| +wincmd w +exe 'vert 1resize ' . ((&columns * 127 + 128) / 256) +exe 'vert 2resize ' . ((&columns * 128 + 128) / 256) +tabnext +edit vm/asm.h +let s:save_splitbelow = &splitbelow +let s:save_splitright = &splitright +set splitbelow splitright +wincmd _ | wincmd | +vsplit +wincmd _ | wincmd | +vsplit +2wincmd h +wincmd w +wincmd w +let &splitbelow = s:save_splitbelow +let &splitright = s:save_splitright +wincmd t +let s:save_winminheight = &winminheight +let s:save_winminwidth = &winminwidth +set winminheight=0 +set winheight=1 +set winminwidth=0 +set winwidth=1 +exe 'vert 1resize ' . ((&columns * 73 + 128) / 256) +exe 'vert 2resize ' . ((&columns * 86 + 128) / 256) +exe 'vert 3resize ' . ((&columns * 95 + 128) / 256) +argglobal +balt vm/vm.c +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +silent! normal! zE +let &fdl = &fdl +let s:l = 53 - ((52 * winheight(0) + 32) / 65) +if s:l < 1 | let s:l = 1 | endif +keepjumps exe s:l +normal! zt +keepjumps 53 +normal! 026| +wincmd w +argglobal +if bufexists(fnamemodify("vm/asm.c", ":p")) | buffer vm/asm.c | else | edit vm/asm.c | endif +if &buftype ==# 'terminal' + silent file vm/asm.c +endif +balt vm/asm.h +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +silent! normal! zE +let &fdl = &fdl +let s:l = 178 - ((18 * winheight(0) + 32) / 65) +if s:l < 1 | let s:l = 1 | endif +keepjumps exe s:l +normal! zt +keepjumps 178 +normal! 013| +wincmd w +argglobal +if bufexists(fnamemodify("vm/vm.c", ":p")) | buffer vm/vm.c | else | edit vm/vm.c | endif +if &buftype ==# 'terminal' + silent file vm/vm.c +endif +balt vm/asm.c +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +silent! normal! zE +let &fdl = &fdl +let s:l = 104 - ((1 * winheight(0) + 32) / 65) +if s:l < 1 | let s:l = 1 | endif +keepjumps exe s:l +normal! zt +keepjumps 104 +normal! 043| +wincmd w +2wincmd w +exe 'vert 1resize ' . ((&columns * 73 + 128) / 256) +exe 'vert 2resize ' . ((&columns * 86 + 128) / 256) +exe 'vert 3resize ' . ((&columns * 95 + 128) / 256) +tabnext +edit vm/asm.c +let s:save_splitbelow = &splitbelow +let s:save_splitright = &splitright +set splitbelow splitright +wincmd _ | wincmd | +vsplit +1wincmd h +wincmd w +let &splitbelow = s:save_splitbelow +let &splitright = s:save_splitright +wincmd t +let s:save_winminheight = &winminheight +let s:save_winminwidth = &winminwidth +set winminheight=0 +set winheight=1 +set winminwidth=0 +set winwidth=1 +exe 'vert 1resize ' . ((&columns * 119 + 128) / 256) +exe 'vert 2resize ' . ((&columns * 136 + 128) / 256) +argglobal +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +silent! normal! zE +let &fdl = &fdl +let s:l = 354 - ((61 * winheight(0) + 32) / 65) +if s:l < 1 | let s:l = 1 | endif +keepjumps exe s:l +normal! zt +keepjumps 354 +normal! 031| +wincmd w +argglobal +if bufexists(fnamemodify("vm/vm.c", ":p")) | buffer vm/vm.c | else | edit vm/vm.c | endif +if &buftype ==# 'terminal' + silent file vm/vm.c +endif +balt vm/asm.c +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +silent! normal! zE +let &fdl = &fdl +let s:l = 423 - ((31 * winheight(0) + 32) / 65) +if s:l < 1 | let s:l = 1 | endif +keepjumps exe s:l +normal! zt +keepjumps 423 +normal! 019| +wincmd w +exe 'vert 1resize ' . ((&columns * 119 + 128) / 256) +exe 'vert 2resize ' . ((&columns * 136 + 128) / 256) +tabnext 3 +set stal=1 +if exists('s:wipebuf') && len(win_findbuf(s:wipebuf)) == 0 && getbufvar(s:wipebuf, '&buftype') isnot# 'terminal' + silent exe 'bwipe ' . s:wipebuf +endif +unlet! s:wipebuf +set winheight=1 winwidth=20 +let &shortmess = s:shortmess_save +let &winminheight = s:save_winminheight +let &winminwidth = s:save_winminwidth +let s:sx = expand(":p:r")."x.vim" +if filereadable(s:sx) + exe "source " . fnameescape(s:sx) +endif +let &g:so = s:so_save | let &g:siso = s:siso_save +set hlsearch +doautoall SessionLoadPost +unlet SessionLoad +" vim: set ft=vim : diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..e7d8f39 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,13 @@ +-xc +-std=c17 +-Wall +-Wextra +-Wpedantic +-Wconversion +-pedantic +-pedantic-errors +-Wno-unused-variable +# -Wno-unused-parameter +# -Wno-unused-function +-I. + diff --git a/vm/asm.c b/vm/asm.c new file mode 100644 index 0000000..2e141bd --- /dev/null +++ b/vm/asm.c @@ -0,0 +1,398 @@ +#include "asm.h" +#include "vm.h" +#include +#include +#include +#include +#include + +Line s_label(int label) +{ + return (Line) { + .ty = LineTy_Label, + .op1 = (Ex) { .label = label }, + }; +} +Line s_data_i(uint16_t data) +{ + return (Line) { + .ty = LineTy_DataImm, + .op1 = (Ex) { .imm = data }, + }; +} +Line s_data_l(int label) +{ + return (Line) { + .ty = LineTy_DataLabel, + .op1 = (Ex) { .label = label }, + + }; +} +Line s_nop(void) +{ + return (Line) { .ty = LineTy_Nop }; +} +Line s_hlt(void) +{ + return (Line) { .ty = LineTy_Hlt }; +} +Line s_jmp_l(int op1_label) +{ + return (Line) { + .ty = LineTy_Jmp_Label, + .op1 = (Ex) { .label = op1_label }, + }; +} +Line s_mov8_mi_i(uint16_t dst_imm, uint16_t op2_imm) +{ + return (Line) { + .ty = LineTy_Mov8_MemImm_Imm, + .dst = (Ex) { .imm = dst_imm }, + .op2 = (Ex) { .imm = op2_imm }, + }; +} +Line s_mov8_mi_reg(uint16_t dst_imm, Reg op2_reg) +{ + return (Line) { + .ty = LineTy_Mov8_MemImm_Imm, + .dst = (Ex) { .imm = dst_imm }, + .op2 = (Ex) { .reg = (uint16_t)op2_reg }, + }; +} +Line s_mov16_r_r(Reg dst_reg, Reg op2_reg) +{ + return (Line) { + .ty = LineTy_Mov16_Reg_Reg, + .dst = (Ex) { .reg = (uint16_t)dst_reg }, + .op2 = (Ex) { .reg = (uint16_t)op2_reg }, + }; +} +Line s_mov16_r_i(Reg dst_reg, uint16_t op2_imm) +{ + return (Line) { + .ty = LineTy_Mov16_Reg_Imm, + .dst = (Ex) { .reg = (uint16_t)dst_reg }, + .op2 = (Ex) { .imm = op2_imm }, + }; +} +Line s_mov16_r_mr(Reg dst_reg, Reg op2_reg, uint16_t op2_offset) +{ + return (Line) { + .ty = LineTy_Mov16_Reg_MemReg, + .dst = (Ex) { .reg = (uint16_t)dst_reg }, + .op2 = (Ex) { .reg = (uint16_t)op2_reg }, + .offset = op2_offset, + }; +} +Line s_mov16_mr_r(Reg dst_reg, uint16_t dst_offset, Reg op2_reg) +{ + return (Line) { + .ty = LineTy_Mov16_MemReg_Reg, + .dst = (Ex) { .reg = (uint16_t)dst_reg }, + .op2 = (Ex) { .reg = (uint16_t)op2_reg }, + .offset = dst_offset, + }; +} +Line s_in_i(Reg dst_reg, uint16_t op1_imm) +{ + return (Line) { + .ty = LineTy_In_Imm, + .dst = (Ex) { .reg = (uint16_t)dst_reg }, + .op1 = (Ex) { .imm = op1_imm }, + }; +} +Line s_lit_i(uint16_t op1_imm) +{ + return (Line) { + .ty = LineTy_Lit_Imm, + .op1 = (Ex) { .imm = op1_imm }, + }; +} +Line s_lit_l(int op1_label) +{ + return (Line) { + .ty = LineTy_Lit_Label, + .op1 = (Ex) { .label = op1_label }, + }; +} +Line s_iret(void) +{ + return (Line) { .ty = LineTy_IRet }; +} + +#define DEFINE_BINARY_R_I(FN, LINETY) \ + Line s_##FN##_r_i(Reg dst_reg, Reg op1_reg, uint16_t op2_imm) \ + { \ + return (Line) { \ + .ty = LineTy_##LINETY##_Reg_Imm, \ + .dst = (Ex) { .reg = (uint16_t)dst_reg }, \ + .op1 = (Ex) { .reg = (uint16_t)op1_reg }, \ + .op2 = (Ex) { .imm = op2_imm }, \ + }; \ + } + +DEFINE_BINARY_R_I(or, Or) +DEFINE_BINARY_R_I(and, And) +DEFINE_BINARY_R_I(add, Add) +DEFINE_BINARY_R_I(sub, Sub) + +static inline void add_dst_reg(uint32_t* ins, uint16_t reg); +static inline void add_op1_reg(uint32_t* ins, uint16_t reg); +static inline void add_op2_reg(uint32_t* ins, uint16_t reg); +static inline void set_is_imm(uint32_t* ins); +static inline void set_mov_is_memory(uint32_t* ins); +static inline void set_mov_addr_is_reg(uint32_t* ins); +static inline void set_mov_is_store(uint32_t* ins); +static inline uint16_t linety_arithm_ins(LineTy ty); + +void assemble_to_binary(uint16_t* out, const Line* lines, size_t lines_size) +{ + uint16_t ip = 0; + + typedef struct { + int label; + uint16_t ptr; + } UnresolvedLabel; + typedef struct { + int label; + uint16_t ip; + } ResolvedLabel; + + UnresolvedLabel* unres_labels = malloc(sizeof(UnresolvedLabel) * 64); + size_t unres_labels_size = 0; + + ResolvedLabel* res_labels = malloc(sizeof(ResolvedLabel) * 64); + size_t res_labels_size = 0; + +#define ADD_LABEL(LABEL) \ + unres_labels[unres_labels_size++] = (UnresolvedLabel) { LABEL, ip }; \ + out[ip++] = 0; + + for (size_t i = 0; i < lines_size; ++i) { + const Line* line = &lines[i]; + switch (line->ty) { + case LineTy_Label: { + res_labels[res_labels_size++] + = (ResolvedLabel) { line->op1.label, ip * 2 }; + break; + } + case LineTy_DataImm: { + out[ip++] = line->op1.imm; + break; + } + case LineTy_DataLabel: { + ADD_LABEL(line->op1.label); + break; + } + case LineTy_Nop: { + out[ip++] = Op_Nop; + break; + } + case LineTy_Hlt: { + out[ip++] = Op_Hlt; + break; + } + case LineTy_Jmp_Label: { + uint32_t ins = Op_Jmp; + set_is_imm(&ins); + out[ip++] = (uint16_t)ins; + ADD_LABEL(line->op1.label); + break; + } + case LineTy_Mov8_MemImm_Imm: { + uint16_t dst = line->dst.imm; + uint16_t op2 = line->op2.imm; + + uint32_t ins = Op_Mov8; + set_is_imm(&ins); + set_mov_is_memory(&ins); + set_mov_is_store(&ins); + + out[ip++] = (uint16_t)ins; + out[ip++] = dst; + out[ip++] = op2; + break; + } + case LineTy_Mov8_MemImm_Reg: { + uint16_t dst = line->dst.imm; + uint16_t op2 = line->op2.reg; + + uint32_t ins = Op_Mov8; + add_op2_reg(&ins, op2); + set_mov_is_memory(&ins); + set_mov_is_store(&ins); + + out[ip++] = (uint16_t)ins; + out[ip++] = dst; + break; + } + case LineTy_Mov16_Reg_Reg: { + uint16_t dst = line->dst.reg; + uint16_t op2 = line->op2.reg; + + uint32_t ins = Op_Mov16; + ins |= (op2 & 0xfu) << 6; + ins |= (dst & 0xfu) << 12; + + out[ip++] = (uint16_t)ins; + break; + } + case LineTy_Mov16_Reg_Imm: { + uint16_t dst = line->dst.reg; + uint16_t op2 = line->op2.imm; + + uint32_t ins = Op_Mov16; + set_is_imm(&ins); + ins |= (dst & 0xfu) << 12; + + out[ip++] = (uint16_t)ins; + out[ip++] = op2; + break; + } + case LineTy_Mov16_Reg_MemReg: { + uint16_t dst = line->dst.reg; + uint16_t op2 = line->op2.reg; + + uint32_t ins = Op_Mov16; + add_op2_reg(&ins, op2); + set_mov_is_memory(&ins); + set_mov_addr_is_reg(&ins); + add_dst_reg(&ins, dst); + + out[ip++] = (uint16_t)ins; + out[ip++] = line->offset; + break; + } + case LineTy_Mov16_MemReg_Reg: { + uint16_t dst = line->dst.reg; + uint16_t op2 = line->op2.reg; + + uint32_t ins = Op_Mov16; + add_op2_reg(&ins, op2); + set_mov_is_memory(&ins); + set_mov_addr_is_reg(&ins); + set_mov_is_store(&ins); + add_dst_reg(&ins, dst); + + out[ip++] = (uint16_t)ins; + out[ip++] = line->offset; + break; + } + case LineTy_In_Imm: { + uint16_t dst = line->dst.reg; + uint16_t op1 = line->op1.imm; + + uint32_t ins = Op_Lit; + set_is_imm(&ins); + add_dst_reg(&ins, dst); + + out[ip++] = (uint16_t)ins; + out[ip++] = op1; + break; + } + case LineTy_Lit_Imm: { + uint16_t op1 = line->op1.imm; + + uint32_t ins = Op_Lit; + set_is_imm(&ins); + + out[ip++] = (uint16_t)ins; + out[ip++] = op1; + break; + } + case LineTy_Lit_Label: { + int op1 = line->op1.label; + + uint32_t ins = Op_Lit; + set_is_imm(&ins); + + out[ip++] = (uint16_t)ins; + ADD_LABEL(op1); + break; + } + case LineTy_IRet: { + out[ip++] = Op_IRet; + break; + } + case LineTy_Or_Reg_Imm: + case LineTy_And_Reg_Imm: + case LineTy_Add_Reg_Imm: + case LineTy_Sub_Reg_Imm: { + uint16_t dst = line->dst.reg; + uint16_t op1 = line->op1.reg; + uint16_t op2 = line->op2.imm; + + uint32_t ins = linety_arithm_ins(line->ty); + set_is_imm(&ins); + add_op1_reg(&ins, op1); + add_dst_reg(&ins, dst); + + out[ip++] = (uint16_t)ins; + out[ip++] = op2; + break; + } + } + } + + for (size_t i = 0; i < unres_labels_size; ++i) { + bool found = false; + for (size_t j = 0; j < res_labels_size; ++j) { + if (res_labels[j].label == unres_labels[i].label) { + out[unres_labels[i].ptr] = res_labels[j].ip; + found = true; + break; + } + } + if (!found) { + fprintf(stderr, + "warning: label '%d' could not be resolved\n", + unres_labels[i].label); + } + } +} + +static inline void add_dst_reg(uint32_t* ins, uint16_t reg) +{ + *ins |= (reg & 0x7u) << 13; +} +static inline void add_op1_reg(uint32_t* ins, uint16_t reg) +{ + *ins |= (reg & 0x7u) << 10; +} +static inline void add_op2_reg(uint32_t* ins, uint16_t reg) +{ + *ins |= (reg & 0x7u) << 7; +} +static inline void set_is_imm(uint32_t* ins) +{ + *ins |= 1 << 6; +} +static inline void set_mov_is_memory(uint32_t* ins) +{ + *ins |= 1 << 10; +} +static inline void set_mov_addr_is_reg(uint32_t* ins) +{ + *ins |= 1 << 11; +} +static inline void set_mov_is_store(uint32_t* ins) +{ + *ins |= 1 << 12; +} + +static inline uint16_t linety_arithm_ins(LineTy ty) +{ + switch (ty) { + case LineTy_Or_Reg_Imm: + return Op_Or; + case LineTy_And_Reg_Imm: + return Op_And; + case LineTy_Add_Reg_Imm: + return Op_Add; + case LineTy_Sub_Reg_Imm: + return Op_Sub; + default: + fprintf(stderr, "error: line type '%d' not handled\n", ty); + exit(1); + } +} diff --git a/vm/asm.h b/vm/asm.h new file mode 100644 index 0000000..3ae460b --- /dev/null +++ b/vm/asm.h @@ -0,0 +1,69 @@ +#pragma once + +#include "vm.h" +#include +#include + +typedef enum { + LineTy_Label, + LineTy_DataImm, + LineTy_DataLabel, + LineTy_Nop, + LineTy_Hlt, + LineTy_Jmp_Label, + LineTy_Mov8_MemImm_Imm, + LineTy_Mov8_MemImm_Reg, + LineTy_Mov16_Reg_Reg, + LineTy_Mov16_Reg_Imm, + LineTy_Mov16_Reg_MemReg, + LineTy_Mov16_MemReg_Reg, + LineTy_In_Imm, + LineTy_Lit_Imm, + LineTy_Lit_Label, + LineTy_IRet, + LineTy_Or_Reg_Imm, + LineTy_And_Reg_Imm, + LineTy_Add_Reg_Imm, + LineTy_Sub_Reg_Imm, +} LineTy; + +typedef struct { + union { + int label; + struct { + uint16_t imm; + uint16_t reg; + }; + }; +} Ex; + +typedef struct { + LineTy ty; + Ex dst; + Ex op1; + Ex op2; + uint16_t offset; +} Line; + +Line s_label(int label); +Line s_data_i(uint16_t data); +Line s_data_l(int label); +Line s_nop(void); +Line s_hlt(void); +Line s_jmp_l(int op1_label); +Line s_mov8_mi_i(uint16_t dst_imm, uint16_t op2_imm); +Line s_mov8_mi_reg(uint16_t dst_imm, Reg op2_reg); +Line s_mov16_r_r(Reg dst_reg, Reg op2_reg); +Line s_mov16_r_i(Reg dst_reg, uint16_t op2_imm); +Line s_mov16_r_mr(Reg dst_reg, Reg op2_reg, uint16_t op2_offset); +Line s_mov16_mr_r(Reg dst_reg, uint16_t dst_offset, Reg op2_reg); +Line s_in_i(Reg dst_reg, uint16_t op1_imm); +Line s_lit_i(uint16_t op1_imm); +Line s_lit_l(int op1_label); +Line s_iret(void); +Line s_or_r_i(Reg dst_reg, Reg op1_reg, uint16_t op2_imm); +Line s_and_r_i(Reg dst_reg, Reg op1_reg, uint16_t op2_imm); +Line s_add_r_i(Reg dst_reg, Reg op1_reg, uint16_t op2_imm); +Line s_sub_r_i(Reg dst_reg, Reg op1_reg, uint16_t op2_imm); + +void assemble_to_binary(uint16_t* out, const Line* lines, size_t lines_size); diff --git a/vm/main.c b/vm/main.c new file mode 100644 index 0000000..af3b06e --- /dev/null +++ b/vm/main.c @@ -0,0 +1,509 @@ +#include "vm.h" +#include "vm/asm.h" +#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; +} + +#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) +{ + printf("value = %d '%c'\n", value, 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 = 0; rip < 80; ++rip) { + uint8_t* out = (uint8_t*)program; + // clang-format off + printf( + "out[%2ld] = %c%c%c%c %c%c%c%c\n", rip + , out[rip] >> 7 & 1 ? '1' : '0' + , out[rip] >> 6 & 1 ? '1' : '0' + , out[rip] >> 5 & 1 ? '1' : '0' + , out[rip] >> 4 & 1 ? '1' : '0' + , out[rip] >> 3 & 1 ? '1' : '0' + , out[rip] >> 2 & 1 ? '1' : '0' + , out[rip] >> 1 & 1 ? '1' : '0' + , out[rip] >> 0 & 1 ? '1' : '0' + ); + // clang-format on + } +} + +int main(void) +{ + int res; + + SdlDevice io_device; + res = sdldevice_construct(&io_device); + if (res != 0) { + exit(1); + } + + int main_loop = 1; + int interrupt_table = 2; + int keyboard_interrupt = 3; + +#define L(LABEL) s_label(LABEL) +#define s_push_r(REG) s_add_r_i(Rsp, Rsp, 2), s_mov16_mr_r(Rsp, 0, REG) +#define s_pop_r(REG) s_mov16_r_mr(REG, Rsp, 2), s_sub_r_i(Rsp, Rsp, 2) + + Line program_asm[] = { + // clang-format off + s_nop(), + + // set video character display flag + s_or_r_i(Rfl, Rfl, 1 << Fl_Vcd), + + // print ABC + s_mov8_mi_i(0x0c00 + 0, 'A'), + s_mov8_mi_i(0x0c00 + 1, 'B'), + s_mov8_mi_i(0x0c00 + 2, 'C'), + + // 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_r_i(Rfl, Rfl, 1 << Fl_Int), + 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), + // clear interrupt flag + s_and_r_i(Rfl, Rfl, (uint16_t)~(1 << Fl_Int)), + // setup stack frame + s_push_r(Rbp), + s_mov16_r_r(Rbp, Rsp), + // conserve registers + s_push_r(R0), + + // read keyboard port + s_in_i(R0, 0), + s_mov8_mi_i(0x0c00 + 4, R0), + + // tear down frame + s_pop_r(R0), + s_mov16_r_r(Rsp, Rbp), + s_pop_r(Rbp), + // set interrupt flag + s_or_r_i(Rfl, Rfl, 1 << Fl_Int), + // return from interrupt + s_iret(), + + // 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); + + MemDrive drive; + memdrive_construct(&drive, (uint64_t*)program, 512); + + vm_start(&drive.drive, &io_device.io_device); + + sdldevice_destroy(&io_device); +} + +const char* __asan_default_options(void) +{ + return "detect_leaks=0"; +} + +#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 _ _ }, + }, +}; diff --git a/vm/vm.c b/vm/vm.c new file mode 100644 index 0000000..b9c4c2a --- /dev/null +++ b/vm/vm.c @@ -0,0 +1,536 @@ +#include "vm.h" +#include +#include +#include +#include + +typedef struct { + uint16_t regs[10]; + uint8_t* mem; + Drive* boot_drive; + int16_t seg_count; + int16_t seg_size; + IODevice* io_device; + uint16_t int_table; + int interrupt_timeout; + uint16_t keyboard_port_input; +} VM; + +static inline int jump_to_interrupt(VM* vm, uint16_t int_id); +static inline void run_arithm_ins(VM* vm, uint16_t ins); +static inline void maybe_update_vcd(VM* vm, uint16_t addr); +static inline uint16_t eat_uint16(VM* vm); +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); + +static inline const char* op_str(Op op); + +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 = 0, + .keyboard_port_input = 0, + }; + + VM* vm = &vm_inst; + + uint16_t* rip = &vm->regs[Rip]; + uint16_t* rsp = &vm->regs[Rsp]; + uint16_t* rfl = &vm->regs[Rfl]; + uint16_t* rcs = &vm->regs[Rcs]; + + const uint16_t block_size = vm->boot_drive->block_size; + for (uint16_t i = 0; i * block_size < 512; ++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 >= 74) { + printf("rip >= 74\n"); + exit(0); + } + + switch (op) { + case Op_Nop: + break; + case Op_Hlt: + vm->io_device->wait_for_interrupt(vm->io_device); + break; + case Op_Jmp: { + bool is_farjump = ins >> 7 & 1; + if (is_farjump) { + uint16_t cs = ins_reg_val_or_imm(vm, ins, 8, 12, 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); + *rfl &= (uint16_t)~((op1 == 0 ? 1 : 0) << Fl_Zero); + break; + } + case Op_Cmp: { + uint16_t op1 = ins_op1(vm, ins); + uint16_t op2 = ins_op2_or_imm(vm, ins); + *rfl &= (uint16_t)~((op1 == op2 ? 1 : 0) << Fl_Eq + & (op1 < op2 ? 1 : 0) << Fl_Be + & ((int16_t)op1 < (int16_t)op2 ? 1 : 0) << 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); + uint16_t offset = eat_uint16(vm); + addr = 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); + uint16_t offset = eat_uint16(vm); + addr = 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); + + switch (device_id) { + case 0: + vm->regs[dst_reg] = vm->keyboard_port_input; + break; + default: + fprintf(stderr, + "warning: input, no device %d\n", + device_id); + break; + } + break; + } + case Op_Out: { + uint16_t op1 = ins_op2(vm, ins); + uint16_t device_id = ins_op1_or_imm(vm, ins); + + switch (device_id) { + default: + fprintf(stderr, + "warning: output, no device %d\n", + device_id); + break; + } + 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 = jump_to_interrupt(vm, int_id); + if (res != 0) { + goto halt_execution; + } + break; + } + case Op_IRet: { + *rip = *(uint16_t*)&vm->mem[*rsp]; + *rsp -= 2; + *rcs = *(uint16_t*)&vm->mem[*rsp]; + *rsp -= 2; + 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(vm, 0); + 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 int jump_to_interrupt(VM* vm, uint16_t int_id) +{ + uint16_t* rip = &vm->regs[Rip]; + uint16_t* rsp = &vm->regs[Rsp]; + uint16_t* rfl = &vm->regs[Rfl]; + 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 int_table_size = *(uint16_t*)&vm->mem[vm->int_table]; + + if (int_id >= int_table_size) { + fprintf(stderr, "error: interrupt outside table\n"); + vm->regs[Rfl] |= 1 << Fl_Err; + return -1; + } + + uint16_t int_addr = *(uint16_t*)&vm->mem[vm->int_table + int_id * 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 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 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* rcs = &vm->regs[Rcs]; + + uint16_t ins = *(uint16_t*)&vm->mem[*rcs * vm->seg_size + *rip]; + *rip += 2; + return ins; +} + +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); +} + +static inline const char* op_str(Op op) +{ + switch (op) { + case Op_Nop: + return "nop"; + case Op_Hlt: + return "hlt"; + case Op_Jmp: + return "jmp"; + case Op_Jnz: + return "jnz"; + case Op_Test: + return "test"; + case Op_Cmp: + return "cmp"; + case Op_Mov8: + return "mov8"; + case Op_Mov16: + return "mov16"; + case Op_In: + return "in"; + case Op_Out: + return "out"; + case Op_Lit: + return "lit"; + case Op_Int: + return "int"; + case Op_IRet: + return "iret"; + case Op_Or: + return "or"; + case Op_Xor: + return "xor"; + case Op_And: + return "and"; + case Op_Shl: + return "shl"; + case Op_RShl: + return "rshl"; + case Op_Shr: + return "shr"; + case Op_RShr: + return "rshr"; + case Op_Add: + return "add"; + case Op_Sub: + return "sub"; + case Op_RSub: + return "rsub"; + case Op_Mul: + return "mul"; + case Op_IMul: + return "imul"; + case Op_Div: + return "div"; + case Op_IDiv: + return "idiv"; + case Op_RDiv: + return "rdiv"; + case Op_RIDiv: + return "ridiv"; + case Op_Mod: + return "mod"; + case Op_RMod: + return "rmod"; + } + return ""; +} diff --git a/vm/vm.h b/vm/vm.h new file mode 100644 index 0000000..a2c9d19 --- /dev/null +++ b/vm/vm.h @@ -0,0 +1,104 @@ +#pragma once + +#include + +typedef enum { + R0 = 0, + R1 = 1, + R2 = 2, + R3 = 3, + Rbp = 4, + Rsp = 5, + Rip = 6, + Rfl = 7, + Rcs = 8, +} Reg; + +typedef enum { + Fl_Zero, + Fl_Eq, + Fl_Be, + Fl_Lt, + Fl_Err, + Fl_Int, + Fl_Vcd, +} Flag; + +typedef enum { + Op_Nop, + Op_Hlt, + Op_Jmp, + Op_Jnz, + Op_Test, + Op_Cmp, + Op_Mov8, + Op_Mov16, + Op_In, + Op_Out, + Op_Lit, + Op_Int, + Op_IRet, + Op_Or, + Op_Xor, + Op_And, + Op_Shl, + Op_RShl, + Op_Shr, + Op_RShr, + Op_Add, + Op_Sub, + Op_RSub, + Op_Mul, + Op_IMul, + Op_Div, + Op_IDiv, + Op_RDiv, + Op_RIDiv, + Op_Mod, + Op_RMod, +} Op; + +typedef struct Drive Drive; +typedef void (*DriveReadFn)(Drive* drive, uint8_t* block, uint16_t i); +typedef void (*DriveWriteFn)(Drive* drive, const uint8_t* block, uint16_t i); + +struct Drive { + void* self; + uint16_t block_size; + uint16_t block_amount; + DriveReadFn read; + DriveWriteFn write; +}; + +typedef enum { + InterruptType_None, + InterruptType_Shutdown, + InterruptType_KeyEvent, +} InterruptType; + +typedef struct { + InterruptType type; + union { + uint16_t keycode; + }; +} Interrupt; + +#define VCD_BUFFER_OFFSET 0x0c00 +#define VCD_BUFFER_SIZE 480 +#define VCD_SCREEN_WIDTH 40 +#define VCD_SCREEN_HEIGHT 12 + +typedef struct IODevice IODevice; +typedef void (*IODeviceSetCharFn)( + IODevice* device, uint16_t offset, uint8_t value); +typedef void (*IODeviceWaitForInterruptFn)(IODevice* device); +typedef Interrupt (*IODeviceMaybeNextInterruptFn)(IODevice* device); + +struct IODevice { + void* self; + IODeviceSetCharFn set_char; + IODeviceWaitForInterruptFn wait_for_interrupt; + IODeviceMaybeNextInterruptFn maybe_next_interrupt; +}; + +void vm_start(Drive* boot_drive, IODevice* io_device);