This commit is contained in:
sfja 2026-01-04 23:28:32 +01:00
commit 3116ec1a13
9 changed files with 672 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

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build/

27
Makefile Normal file
View File

@ -0,0 +1,27 @@
MAKEFLAGS += -j16
CXXFLAGS := -std=c++23 -Wall -Wextra -pedantic-errors -fsanitize=address,undefined
LDFLAGS :=
build_dir = build
obj_dir = $(build_dir)/obj
sources := $(shell find src/ -name *.cpp)
all: $(build_dir)/vc5
$(build_dir)/vc5: $(sources:%.cpp=$(obj_dir)/%.o)
@mkdir -p $(dir $@)
g++ $^ -o $@ $(CXXFLAGS) $(LDFLAGS)
$(obj_dir)/%.o: %.cpp
@mkdir -p $(dir $@)
g++ $< -c -o $@ -MMD -MP $(CXXFLAGS)
.PHONY: clean
clean:
rm -rf $(build_dir)
-include $(sources:%.cpp=$(obj_dir)/%.d)

10
compile_flags.txt Normal file
View File

@ -0,0 +1,10 @@
-xc++
-std=c++23
-Wall
-Wextra
-Wpedantic
-Wconversion
-pedantic
-pedantic-errors
-Wno-unused-variable

185
src/builder.cpp Normal file
View File

@ -0,0 +1,185 @@
#include "builder.hpp"
#include "vm.hpp"
#include <utility>
using namespace vc5;
void Builder::nop()
{
auto ins = std::to_underlying(Op::Nop);
push(ins);
}
void Builder::hlt()
{
auto ins = std::to_underlying(Op::Nop);
push(ins);
}
void Builder::jmp_reg(Reg op1)
{
}
void Builder::jmp_imm(uint16_t op1)
{
}
void Builder::jnz_reg(Reg op1, Reg op2)
{
}
void Builder::jnz_imm(Reg op1, uint16_t op2)
{
}
void Builder::mov_word_load_reg(Reg dst, Reg src)
{
}
void Builder::mov_word_load_imm(Reg dst, uint16_t imm)
{
}
void Builder::mov_word_load_mem_reg(Reg dst, Reg addr, uint16_t offset)
{
}
void Builder::mov_word_load_mem_imm(Reg dst, uint16_t addr)
{
}
void Builder::mov_word_store_reg_reg(Reg dst, uint16_t offset, Reg op2)
{
}
void Builder::mov_word_store_reg_imm(Reg dst, uint16_t offset, uint16_t op2)
{
}
void Builder::mov_word_store_imm_reg(uint16_t dst, Reg op2)
{
}
void Builder::mov_word_store_imm_imm(uint16_t dst, uint16_t op2)
{
}
void Builder::mov_byte_load_reg(Reg dst, Reg src)
{
}
void Builder::mov_byte_load_imm(Reg dst, uint8_t imm)
{
}
void Builder::mov_byte_load_mem_reg(Reg dst, Reg addr, uint16_t offset)
{
}
void Builder::mov_byte_load_mem_imm(Reg dst, uint16_t addr)
{
}
void Builder::mov_byte_store_reg_reg(Reg dst, uint16_t offset, Reg op2)
{
}
void Builder::mov_byte_store_reg_imm(Reg dst, uint16_t offset, uint8_t op2)
{
}
void Builder::mov_byte_store_imm_reg(uint16_t dst, Reg op2)
{
}
void Builder::mov_byte_store_imm_imm(uint16_t dst, uint8_t op2)
{
}
void Builder::cmp_reg(Reg op1, Reg op2)
{
}
void Builder::cmp_imm(Reg op1, Reg op2)
{
}
void Builder::or_reg(Reg dst, Reg op1, Reg op2)
{
}
void Builder::and_reg(Reg dst, Reg op1, Reg op2)
{
}
void Builder::xor_reg(Reg dst, Reg op1, Reg op2)
{
}
void Builder::shl_reg(Reg dst, Reg op1, Reg op2)
{
}
void Builder::rshl_reg(Reg dst, Reg op1, Reg op2)
{
}
void Builder::shr_reg(Reg dst, Reg op1, Reg op2)
{
}
void Builder::rshr_reg(Reg dst, Reg op1, Reg op2)
{
}
void Builder::add_reg(Reg dst, Reg op1, Reg op2)
{
}
void Builder::sub_reg(Reg dst, Reg op1, Reg op2)
{
}
void Builder::rsub_reg(Reg dst, Reg op1, Reg op2)
{
}
void Builder::or_imm(Reg dst, Reg op1, uint16_t op2)
{
}
void Builder::and_imm(Reg dst, Reg op1, uint16_t op2)
{
}
void Builder::xor_imm(Reg dst, Reg op1, uint16_t op2)
{
}
void Builder::shl_imm(Reg dst, Reg op1, uint16_t op2)
{
}
void Builder::rshl_imm(Reg dst, Reg op1, uint16_t op2)
{
}
void Builder::shr_imm(Reg dst, Reg op1, uint16_t op2)
{
}
void Builder::rshr_imm(Reg dst, Reg op1, uint16_t op2)
{
}
void Builder::add_imm(Reg dst, Reg op1, uint16_t op2)
{
}
void Builder::sub_imm(Reg dst, Reg op1, uint16_t op2)
{
}
void Builder::rsub_imm(Reg dst, Reg op1, uint16_t op2)
{
}

72
src/builder.hpp Normal file
View File

@ -0,0 +1,72 @@
#pragma once
#include "vm.hpp"
#include <cstdint>
namespace vc5 {
class Builder {
public:
Builder(uint8_t* data)
: m_data(data)
{
}
void nop();
void hlt();
void jmp_reg(Reg op1);
void jmp_imm(uint16_t op1);
void jnz_reg(Reg op1, Reg op2);
void jnz_imm(Reg op1, uint16_t op2);
void mov_word_load_reg(Reg dst, Reg src);
void mov_word_load_imm(Reg dst, uint16_t imm);
void mov_word_load_mem_reg(Reg dst, Reg addr, uint16_t offset);
void mov_word_load_mem_imm(Reg dst, uint16_t addr);
void mov_word_store_reg_reg(Reg dst, uint16_t offset, Reg op2);
void mov_word_store_reg_imm(Reg dst, uint16_t offset, uint16_t op2);
void mov_word_store_imm_reg(uint16_t dst, Reg op2);
void mov_word_store_imm_imm(uint16_t dst, uint16_t op2);
void mov_byte_load_reg(Reg dst, Reg src);
void mov_byte_load_imm(Reg dst, uint8_t imm);
void mov_byte_load_mem_reg(Reg dst, Reg addr, uint16_t offset);
void mov_byte_load_mem_imm(Reg dst, uint16_t addr);
void mov_byte_store_reg_reg(Reg dst, uint16_t offset, Reg op2);
void mov_byte_store_reg_imm(Reg dst, uint16_t offset, uint8_t op2);
void mov_byte_store_imm_reg(uint16_t dst, Reg op2);
void mov_byte_store_imm_imm(uint16_t dst, uint8_t op2);
void cmp_reg(Reg op1, Reg op2);
void cmp_imm(Reg op1, Reg op2);
void or_reg(Reg dst, Reg op1, Reg op2);
void and_reg(Reg dst, Reg op1, Reg op2);
void xor_reg(Reg dst, Reg op1, Reg op2);
void shl_reg(Reg dst, Reg op1, Reg op2);
void rshl_reg(Reg dst, Reg op1, Reg op2);
void shr_reg(Reg dst, Reg op1, Reg op2);
void rshr_reg(Reg dst, Reg op1, Reg op2);
void add_reg(Reg dst, Reg op1, Reg op2);
void sub_reg(Reg dst, Reg op1, Reg op2);
void rsub_reg(Reg dst, Reg op1, Reg op2);
void or_imm(Reg dst, Reg op1, uint16_t op2);
void and_imm(Reg dst, Reg op1, uint16_t op2);
void xor_imm(Reg dst, Reg op1, uint16_t op2);
void shl_imm(Reg dst, Reg op1, uint16_t op2);
void rshl_imm(Reg dst, Reg op1, uint16_t op2);
void shr_imm(Reg dst, Reg op1, uint16_t op2);
void rshr_imm(Reg dst, Reg op1, uint16_t op2);
void add_imm(Reg dst, Reg op1, uint16_t op2);
void sub_imm(Reg dst, Reg op1, uint16_t op2);
void rsub_imm(Reg dst, Reg op1, uint16_t op2);
private:
inline void push(uint16_t v)
{
m_data[m_ip] = v >> 16;
m_data[m_ip + 1] = v & 0xff;
m_ip += 2;
}
uint8_t* m_data;
size_t m_ip = 0;
};
}

6
src/main.cpp Normal file
View File

@ -0,0 +1,6 @@
#include <print>
int main()
{
std::println("hello world");
}

202
src/vm.cpp Normal file
View File

@ -0,0 +1,202 @@
#include "vm.hpp"
#include <cstdint>
#include <print>
using namespace vc5;
int VM::run()
{
m_halted = false;
while (not m_halted) {
int result = run_instruction();
if (result != 0)
return result;
}
return 0;
}
int VM::run_instruction()
{
auto ins = eat_ins();
auto op = static_cast<Op>(ins & 0b11'1111);
switch (op) {
case Op::Nop:
break;
case Op::Hlt:
m_halted = true;
break;
case Op::Jmp: {
auto op1 = ins.op1_or_imm();
*m_rip = op1;
break;
}
case Op::Jnz: {
auto op1 = ins.op1();
auto op2 = ins.op2_or_imm();
if (op1 != 0) {
*m_rip = op2;
}
break;
}
case Op::MovWord: {
bool is_memory = ins >> 10 & 1;
bool addr_is_reg = ins >> 11 & 1;
bool is_store = ins >> 12 & 1;
if (!is_memory) {
auto src = ins.reg_val_or_imm(6, 7, 0b1111);
auto dst = static_cast<uint16_t>(ins >> 12 & 0b1111);
m_regs[dst] = src;
break;
}
uint16_t addr;
if (addr_is_reg) {
auto reg = is_store ? ins.dst_reg() : ins.op2_reg();
auto offset = static_cast<int16_t>(eat());
addr = static_cast<uint16_t>(
static_cast<int16_t>(m_regs[reg]) + offset);
} else {
addr = eat();
}
if ((addr & 0b1) != 0) {
std::println(stderr, "error: invalid address alignment");
*m_rfl |= 1 << flag(Flag::Err);
goto halt_execution;
}
if (is_store) {
auto src = ins.op2_or_imm();
m_mem[addr] = src >> 16;
m_mem[addr + 1] = src & 0xff;
} else {
auto reg = ins.dst_reg();
uint16_t value = 0;
value |= static_cast<uint16_t>(m_mem[addr]) << 16;
value |= m_mem[addr + 1];
m_regs[reg] = value;
}
break;
}
case Op::MovByte: {
bool is_memory = ins >> 10 & 1;
bool addr_is_reg = ins >> 11 & 1;
bool is_store = ins >> 12 & 1;
if (!is_memory) {
auto src = ins.reg_val_or_imm(6, 7, 0b1111);
auto dst = static_cast<uint16_t>(ins >> 12 & 0b1111);
m_regs[dst] = src & 0xff;
break;
}
uint16_t addr;
if (addr_is_reg) {
auto reg = is_store ? ins.dst_reg() : ins.op2_reg();
auto offset = static_cast<int16_t>(eat());
addr = static_cast<uint16_t>(
static_cast<int16_t>(m_regs[reg]) + offset);
} else {
addr = eat();
}
if (is_store) {
auto src = ins.op2_or_imm();
m_mem[addr] = src & 0xff;
} else {
auto reg = ins.dst_reg();
uint16_t value = 0;
value |= static_cast<uint16_t>(m_mem[addr]) << 16;
value |= m_mem[addr + 1];
m_regs[reg] = value;
}
break;
}
case Op::Cmp: {
auto op1 = ins.op1();
auto op2 = ins.op2_or_imm();
if (op1 == op2) {
*m_rfl |= 1u << flag(Flag::Eq);
} else {
*m_rfl &= (uint16_t)~(1u << flag(Flag::Eq));
}
if (op1 < op2) {
*m_rfl |= 1u << flag(Flag::Be);
} else {
*m_rfl &= (uint16_t)~(1u << flag(Flag::Be));
}
if ((int16_t)op1 < (int16_t)op2) {
*m_rfl |= 1u << flag(Flag::Lt);
} else {
*m_rfl &= (uint16_t)~(1u << flag(Flag::Lt));
}
break;
}
case Op::Or:
case Op::And:
case Op::Xor:
case Op::Shl:
case Op::RShl:
case Op::Shr:
case Op::RShr:
case Op::Add:
case Op::Sub:
case Op::RSub: {
auto op1 = ins.op1();
auto op2 = ins.op2_or_imm();
auto dst_reg = ins.dst_reg();
uint16_t* dst = &m_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 = static_cast<uint16_t>(op1 << op2);
break;
case Op::RShl:
*dst = static_cast<uint16_t>(op2 << op1);
break;
case Op::Shr:
*dst = static_cast<uint16_t>(op1 >> op2);
break;
case Op::RShr:
*dst = static_cast<uint16_t>(op2 >> op1);
break;
case Op::Add:
*dst = op1 + op2;
break;
case Op::Sub:
*dst = op1 - op2;
break;
case Op::RSub:
*dst = op2 - op1;
break;
default:
break;
}
break;
}
}
halt_execution:
return 0;
}

155
src/vm.hpp Normal file
View File

@ -0,0 +1,155 @@
#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include <utility>
namespace vc5 {
enum class Op : uint16_t {
Nop,
Hlt,
Jmp,
Jnz,
MovWord,
MovByte,
Cmp,
Or,
And,
Xor,
Shl,
RShl,
Shr,
RShr,
Add,
Sub,
RSub,
};
enum class Reg : uint16_t {
R0 = 0,
R1 = 1,
R2 = 2,
R3 = 3,
R4 = 4,
R5 = 5,
Rbp = 6,
Rsp = 7,
Rfl = 8,
Rip = 9,
};
enum class Flag : uint16_t {
Zero = 0,
Eq = 1,
Be = 2,
Lt = 3,
Err = 4,
};
class VM {
public:
struct Ins {
Ins(VM& vm, uint16_t ins)
: vm(&vm)
, ins(ins)
{
}
VM* vm;
uint16_t ins;
operator uint16_t()
{
return ins;
}
inline uint16_t dst_reg() const
{
return ins >> 13 & 0b111;
}
inline uint16_t op1_reg() const
{
return ins >> 10 & 0b111;
}
inline uint16_t op2_reg() const
{
return ins >> 7 & 0b111;
}
inline uint16_t op1() const
{
return vm->m_regs[op1_reg()];
}
inline uint16_t op2() const
{
return vm->m_regs[op2_reg()];
}
inline uint16_t op1_or_imm()
{
return reg_val_or_imm(6, 10, 0b111);
}
inline uint16_t op2_or_imm()
{
return reg_val_or_imm(6, 7, 0b111);
}
inline uint16_t reg_val_or_imm(
uint16_t is_imm_bit, uint16_t reg_bit, uint16_t reg_mask)
{
bool is_imm = (ins >> is_imm_bit & 1) != 0;
if (is_imm) {
return vm->eat();
} else {
return vm->m_regs[ins >> reg_bit & reg_mask];
}
}
};
int run();
private:
int run_instruction();
inline uint16_t eat()
{
uint16_t ins = 0;
ins |= static_cast<uint16_t>(m_mem[*m_rip]) << 16;
ins |= m_mem[*m_rip + 1];
*m_rip += 2;
return ins;
}
inline Ins eat_ins()
{
auto ins = eat();
return Ins(*this, ins);
}
static constexpr uint16_t reg(Reg reg)
{
return std::to_underlying(reg);
}
static constexpr uint16_t flag(Flag flag)
{
return std::to_underlying(flag);
}
std::array<uint8_t, 65536> m_mem = {};
std::array<uint16_t, 8> m_regs = {};
uint16_t* m_rfl = &m_regs[reg(Reg::Rfl)];
uint16_t* m_rip = &m_regs[reg(Reg::Rip)];
bool m_halted = false;
};
}