init
This commit is contained in:
commit
b74c99b05e
14
.clang-format
Normal file
14
.clang-format
Normal 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
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
build/
|
||||
bin/
|
||||
|
52
Makefile
Normal file
52
Makefile
Normal 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
330
Session.vim
Normal 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
13
compile_flags.txt
Normal 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
398
vm/asm.c
Normal 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
69
vm/asm.h
Normal 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
509
vm/main.c
Normal 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
536
vm/vm.c
Normal 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
104
vm/vm.h
Normal 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);
|
Loading…
x
Reference in New Issue
Block a user