This commit is contained in:
sfja 2025-03-30 05:37:39 +02:00
commit b74c99b05e
10 changed files with 2028 additions and 0 deletions

14
.clang-format Normal file
View File

@ -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

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
build/
bin/

52
Makefile Normal file
View File

@ -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/

330
Session.vim Normal file
View File

@ -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("<sfile>: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("<sfile>: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 :

13
compile_flags.txt Normal file
View File

@ -0,0 +1,13 @@
-xc
-std=c17
-Wall
-Wextra
-Wpedantic
-Wconversion
-pedantic
-pedantic-errors
-Wno-unused-variable
# -Wno-unused-parameter
# -Wno-unused-function
-I.

398
vm/asm.c Normal file
View File

@ -0,0 +1,398 @@
#include "asm.h"
#include "vm.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
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);
}
}

69
vm/asm.h Normal file
View File

@ -0,0 +1,69 @@
#pragma once
#include "vm.h"
#include <stddef.h>
#include <stdint.h>
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);

509
vm/main.c Normal file
View File

@ -0,0 +1,509 @@
#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)
{
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 _ _ },
},
};

536
vm/vm.c Normal file
View File

@ -0,0 +1,536 @@
#include "vm.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;
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 "<unknown>";
}

104
vm/vm.h Normal file
View File

@ -0,0 +1,104 @@
#pragma once
#include <stdint.h>
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);