This commit is contained in:
sfja 2026-01-06 22:40:11 +01:00
parent 3116ec1a13
commit 65713e6b9f
5 changed files with 504 additions and 215 deletions

View File

@ -1,185 +1,329 @@
#include "builder.hpp"
#include "vm.hpp"
#include <cstdint>
#include <print>
#include <utility>
using namespace vc5;
namespace {
class InsBuilder {
public:
InsBuilder(Op op)
: m_ins(std::to_underlying(op))
{
}
void build(Builder& vm) const
{
vm.push(m_ins);
if (m_has_imm) {
vm.push(m_imm);
}
}
void set(uint16_t bit)
{
m_ins |= 1 << bit;
}
void reg(Reg reg, uint16_t bit, uint16_t mask)
{
m_ins |= (std::to_underlying(reg) & mask) << bit;
}
void op1_reg(Reg reg)
{
this->reg(reg, 10, 0b111);
}
void op2_reg(Reg reg)
{
this->reg(reg, 7, 0b111);
}
void dst_reg(Reg reg)
{
this->reg(reg, 13, 0b111);
}
void imm_without_flag(uint16_t imm)
{
m_imm = imm;
m_has_imm = true;
}
void imm(uint16_t imm)
{
imm_without_flag(imm);
set(6);
}
private:
uint16_t m_ins;
uint16_t m_imm = 0;
bool m_has_imm = false;
};
}
void Builder::nop()
{
auto ins = std::to_underlying(Op::Nop);
push(ins);
auto i = InsBuilder(Op::Nop);
i.build(*this);
}
void Builder::hlt()
{
auto ins = std::to_underlying(Op::Nop);
push(ins);
auto i = InsBuilder(Op::Hlt);
i.build(*this);
}
void Builder::jmp_reg(Reg op1)
{
auto i = InsBuilder(Op::Jmp);
i.op1_reg(op1);
i.build(*this);
}
void Builder::jmp_imm(uint16_t op1)
{
auto i = InsBuilder(Op::Jmp);
i.imm(op1);
i.build(*this);
}
void Builder::jnz_reg(Reg op1, Reg op2)
{
auto i = InsBuilder(Op::Jnz);
i.op1_reg(op1);
i.op2_reg(op2);
i.build(*this);
}
void Builder::jnz_imm(Reg op1, uint16_t op2)
{
auto i = InsBuilder(Op::Jnz);
i.op1_reg(op1);
i.imm(op2);
i.build(*this);
}
void Builder::mov_word_load_reg(Reg dst, Reg src)
void Builder::mov_reg(Reg dst, Reg src)
{
auto i = InsBuilder(Op::Mov);
i.reg(dst, 12, 0b1111);
i.reg(src, 7, 0b1111);
i.build(*this);
}
void Builder::mov_word_load_imm(Reg dst, uint16_t imm)
void Builder::mov_imm(Reg dst, uint16_t imm)
{
auto i = InsBuilder(Op::Mov);
i.reg(dst, 12, 0b1111);
i.imm(imm);
i.build(*this);
}
void Builder::mov_word_load_mem_reg(Reg dst, Reg addr, uint16_t offset)
void Builder::load_word_reg(Reg dst, Reg addr, uint16_t offset)
{
auto i = InsBuilder(Op::LoadWord);
i.dst_reg(dst);
i.op1_reg(addr);
i.imm_without_flag(offset);
i.build(*this);
}
void Builder::mov_word_load_mem_imm(Reg dst, uint16_t addr)
void Builder::load_word_imm(Reg dst, uint16_t addr)
{
auto i = InsBuilder(Op::LoadWord);
i.dst_reg(dst);
i.imm(addr);
i.build(*this);
}
void Builder::mov_word_store_reg_reg(Reg dst, uint16_t offset, Reg op2)
void Builder::store_word_reg(Reg dst, uint16_t offset, Reg op2)
{
auto i = InsBuilder(Op::StoreWord);
i.dst_reg(dst);
i.imm_without_flag(offset);
i.op2_reg(op2);
i.build(*this);
}
void Builder::mov_word_store_reg_imm(Reg dst, uint16_t offset, uint16_t op2)
void Builder::store_word_imm(uint16_t dst, Reg op2)
{
auto i = InsBuilder(Op::StoreWord);
i.imm(dst);
i.op2_reg(op2);
i.build(*this);
}
void Builder::mov_word_store_imm_reg(uint16_t dst, Reg op2)
void Builder::load_byte_reg(Reg dst, Reg addr, uint16_t offset)
{
auto i = InsBuilder(Op::LoadByte);
i.dst_reg(dst);
i.op1_reg(addr);
i.imm_without_flag(offset);
i.build(*this);
}
void Builder::mov_word_store_imm_imm(uint16_t dst, uint16_t op2)
void Builder::load_byte_imm(Reg dst, uint16_t addr)
{
auto i = InsBuilder(Op::LoadByte);
i.dst_reg(dst);
i.imm(addr);
i.build(*this);
}
void Builder::mov_byte_load_reg(Reg dst, Reg src)
void Builder::store_byte_reg(Reg dst, uint16_t offset, Reg op2)
{
auto i = InsBuilder(Op::StoreByte);
i.dst_reg(dst);
i.imm_without_flag(offset);
i.op2_reg(op2);
i.build(*this);
}
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::store_byte_imm(uint16_t dst, Reg op2)
{
auto i = InsBuilder(Op::StoreByte);
i.imm(dst);
i.op2_reg(op2);
i.build(*this);
}
void Builder::cmp_reg(Reg op1, Reg op2)
{
auto i = InsBuilder(Op::Cmp);
i.op1_reg(op1);
i.op2_reg(op2);
i.build(*this);
}
void Builder::cmp_imm(Reg op1, Reg op2)
void Builder::cmp_imm(Reg op1, uint16_t op2)
{
auto i = InsBuilder(Op::Cmp);
i.op1_reg(op1);
i.imm(op2);
i.build(*this);
}
void Builder::or_reg(Reg dst, Reg op1, Reg op2)
{
binary_reg(dst, op1, op2, Op::Or);
}
void Builder::and_reg(Reg dst, Reg op1, Reg op2)
{
binary_reg(dst, op1, op2, Op::And);
}
void Builder::xor_reg(Reg dst, Reg op1, Reg op2)
{
binary_reg(dst, op1, op2, Op::Xor);
}
void Builder::shl_reg(Reg dst, Reg op1, Reg op2)
{
binary_reg(dst, op1, op2, Op::Shl);
}
void Builder::rshl_reg(Reg dst, Reg op1, Reg op2)
{
binary_reg(dst, op1, op2, Op::RShl);
}
void Builder::shr_reg(Reg dst, Reg op1, Reg op2)
{
binary_reg(dst, op1, op2, Op::Shr);
}
void Builder::rshr_reg(Reg dst, Reg op1, Reg op2)
{
binary_reg(dst, op1, op2, Op::RShr);
}
void Builder::add_reg(Reg dst, Reg op1, Reg op2)
{
binary_reg(dst, op1, op2, Op::Add);
}
void Builder::sub_reg(Reg dst, Reg op1, Reg op2)
{
binary_reg(dst, op1, op2, Op::Sub);
}
void Builder::rsub_reg(Reg dst, Reg op1, Reg op2)
{
binary_reg(dst, op1, op2, Op::RSub);
}
void Builder::or_imm(Reg dst, Reg op1, uint16_t op2)
{
binary_imm(dst, op1, op2, Op::Xor);
}
void Builder::and_imm(Reg dst, Reg op1, uint16_t op2)
{
binary_imm(dst, op1, op2, Op::And);
}
void Builder::xor_imm(Reg dst, Reg op1, uint16_t op2)
{
binary_imm(dst, op1, op2, Op::Xor);
}
void Builder::shl_imm(Reg dst, Reg op1, uint16_t op2)
{
binary_imm(dst, op1, op2, Op::Shl);
}
void Builder::rshl_imm(Reg dst, Reg op1, uint16_t op2)
{
binary_imm(dst, op1, op2, Op::RShl);
}
void Builder::shr_imm(Reg dst, Reg op1, uint16_t op2)
{
binary_imm(dst, op1, op2, Op::Shr);
}
void Builder::rshr_imm(Reg dst, Reg op1, uint16_t op2)
{
binary_imm(dst, op1, op2, Op::RShr);
}
void Builder::add_imm(Reg dst, Reg op1, uint16_t op2)
{
binary_imm(dst, op1, op2, Op::Add);
}
void Builder::sub_imm(Reg dst, Reg op1, uint16_t op2)
{
binary_imm(dst, op1, op2, Op::Sub);
}
void Builder::rsub_imm(Reg dst, Reg op1, uint16_t op2)
{
binary_imm(dst, op1, op2, Op::RSub);
}
void Builder::binary_reg(Reg dst, Reg op1, Reg op2, Op op)
{
auto i = InsBuilder(op);
i.dst_reg(dst);
i.op1_reg(op1);
i.op2_reg(op2);
i.build(*this);
}
void Builder::binary_imm(Reg dst, Reg op1, uint16_t op2, Op op)
{
auto i = InsBuilder(op);
i.dst_reg(dst);
i.op1_reg(op1);
i.imm(op2);
i.build(*this);
}

View File

@ -2,9 +2,12 @@
#include "vm.hpp"
#include <cstdint>
#include <print>
namespace vc5 {
using namespace vc5;
class Builder {
public:
Builder(uint8_t* data)
@ -18,24 +21,18 @@ public:
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 mov_reg(Reg dst, Reg src);
void mov_imm(Reg dst, uint16_t imm);
void load_word_reg(Reg dst, Reg addr, uint16_t offset);
void load_word_imm(Reg dst, uint16_t addr);
void store_word_reg(Reg dst, uint16_t offset, Reg op2);
void store_word_imm(uint16_t dst, Reg op2);
void load_byte_reg(Reg dst, Reg addr, uint16_t offset);
void load_byte_imm(Reg dst, uint16_t addr);
void store_byte_reg(Reg dst, uint16_t offset, Reg op2);
void store_byte_imm(uint16_t dst, Reg op2);
void cmp_reg(Reg op1, Reg op2);
void cmp_imm(Reg op1, Reg op2);
void cmp_imm(Reg op1, uint16_t 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);
@ -57,14 +54,27 @@ public:
void sub_imm(Reg dst, Reg op1, uint16_t op2);
void rsub_imm(Reg dst, Reg op1, uint16_t op2);
private:
uint16_t ip() const
{
return m_ip & 0xffff;
}
void set_ip(uint16_t ip)
{
m_ip = ip;
}
inline void push(uint16_t v)
{
m_data[m_ip] = v >> 16;
m_data[m_ip] = v >> 8;
m_data[m_ip + 1] = v & 0xff;
m_ip += 2;
}
private:
void binary_reg(Reg dst, Reg op1, Reg op2, Op op);
void binary_imm(Reg dst, Reg op1, uint16_t op2, Op op);
uint8_t* m_data;
size_t m_ip = 0;
};

View File

@ -1,6 +1,66 @@
#include "builder.hpp"
#include <array>
#include <cstdint>
#include <print>
#include <utility>
using namespace vc5;
using R = Reg;
int main()
{
std::println("hello world");
constexpr R r0 = R::R0;
constexpr R r1 = R::R1;
constexpr R r2 = R::R2;
constexpr R r3 = R::R3;
constexpr R r4 = R::R4;
constexpr R r5 = R::R5;
constexpr R rbp = R::Rbp;
constexpr R rsp = R::Rsp;
constexpr R rfl = R::Rfl;
constexpr R rip = R::Rip;
auto program = std::array<uint8_t, 65535>();
auto l = Builder(program.data());
l.mov_imm(r0, 4);
auto l0 = l.ip();
l.cmp_imm(r0, 0);
l.mov_reg(r1, rfl);
l.and_imm(r1, r1, 1 << std::to_underlying(Flag::Eq));
auto i0 = l.ip();
l.jnz_imm(r1, 0);
l.sub_imm(r0, r0, 1);
l.jmp_imm(l0);
auto l1 = l.ip();
l.hlt();
l.set_ip(i0 + 2);
l.push(l1);
auto vm = VM();
vm.load(0, program.data(), program.size());
vm.run();
std::println("--- regs ---");
std::println("r0\t{}", vm.reg(r0));
std::println("r1\t{}", vm.reg(r1));
std::println("r2\t{}", vm.reg(r2));
std::println("r3\t{}", vm.reg(r3));
std::println("r4\t{}", vm.reg(r4));
std::println("r5\t{}", vm.reg(r5));
std::println("rbp\t{}", vm.reg(rbp));
std::println("rsp\t{}", vm.reg(rsp));
std::println("rfl\t{}", vm.reg(rfl));
std::println("rip\t{}", vm.reg(rip));
std::println("--- mem ---");
for (uint16_t i = 0; i < 32; i += 2) {
std::println("{: 4x}\t{}", i, vm.word(i));
}
}

View File

@ -4,6 +4,26 @@
using namespace vc5;
namespace {
uint16_t mem_addr(Ins& ins, uint16_t imm)
{
bool is_imm = ins >> 6 & 1;
if (is_imm) {
return imm;
}
auto base = ins.op2();
auto offset = static_cast<int16_t>(imm);
if (offset >= 0) {
return base + static_cast<uint16_t>(offset);
} else {
return base - static_cast<uint16_t>(-offset);
}
}
}
int VM::run()
{
m_halted = false;
@ -17,8 +37,17 @@ int VM::run()
int VM::run_instruction()
{
auto ins = eat_ins();
auto ip = *m_rip;
if (ip > 64)
return -1;
auto ins = Ins(*this);
auto op = static_cast<Op>(ins & 0b11'1111);
std::println("[{: 4x}]: {:04x} {}, r0 = {}",
ip,
ins.operator uint16_t(),
op_str(op),
m_regs[reg_id(Reg::R0)]);
switch (op) {
case Op::Nop:
@ -40,85 +69,57 @@ int VM::run_instruction()
}
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);
case Op::Mov: {
auto dst = static_cast<uint16_t>(ins >> 12 & 0b1111);
auto src = ins.reg_val_or_imm(6, 7, 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();
}
case Op::LoadWord: {
auto addr = mem_addr(ins, eat());
if ((addr & 0b1) != 0) {
std::println(stderr, "error: invalid address alignment");
*m_rfl |= 1 << flag(Flag::Err);
*m_rfl |= 1 << flag_id(Flag::Err);
goto halt_execution;
}
if (is_store) {
auto src = ins.op2_or_imm();
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::StoreWord: {
auto addr = mem_addr(ins, eat());
if ((addr & 0b1) != 0) {
std::println(stderr, "error: invalid address alignment");
*m_rfl |= 1 << flag_id(Flag::Err);
goto halt_execution;
}
auto src = ins.op2();
m_mem[addr] = src >> 16;
m_mem[addr + 1] = src & 0xff;
} else {
break;
}
case Op::LoadByte: {
auto addr = mem_addr(ins, eat());
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;
}
m_regs[reg] = m_mem[addr];
break;
}
case Op::MovByte: {
bool is_memory = ins >> 10 & 1;
bool addr_is_reg = ins >> 11 & 1;
bool is_store = ins >> 12 & 1;
case Op::StoreByte: {
auto addr = mem_addr(ins, eat());
auto src = ins.op2();
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: {
@ -126,19 +127,19 @@ int VM::run_instruction()
auto op2 = ins.op2_or_imm();
if (op1 == op2) {
*m_rfl |= 1u << flag(Flag::Eq);
*m_rfl |= 1u << flag_id(Flag::Eq);
} else {
*m_rfl &= (uint16_t)~(1u << flag(Flag::Eq));
*m_rfl &= (uint16_t)~(1u << flag_id(Flag::Eq));
}
if (op1 < op2) {
*m_rfl |= 1u << flag(Flag::Be);
*m_rfl |= 1u << flag_id(Flag::Be);
} else {
*m_rfl &= (uint16_t)~(1u << flag(Flag::Be));
*m_rfl &= (uint16_t)~(1u << flag_id(Flag::Be));
}
if ((int16_t)op1 < (int16_t)op2) {
*m_rfl |= 1u << flag(Flag::Lt);
*m_rfl |= 1u << flag_id(Flag::Lt);
} else {
*m_rfl &= (uint16_t)~(1u << flag(Flag::Lt));
*m_rfl &= (uint16_t)~(1u << flag_id(Flag::Lt));
}
break;
}
@ -200,3 +201,50 @@ int VM::run_instruction()
halt_execution:
return 0;
}
const char* vc5::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::Mov:
return "Mov";
case Op::LoadWord:
return "LoadWord";
case Op::StoreWord:
return "StoreWord";
case Op::LoadByte:
return "LoadByte";
case Op::StoreByte:
return "StoreByte";
case Op::Cmp:
return "Cmp";
case Op::Or:
return "Or";
case Op::And:
return "And";
case Op::Xor:
return "Xor";
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";
}
return ">.<";
}

View File

@ -3,6 +3,8 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <span>
#include <utility>
namespace vc5 {
@ -12,8 +14,11 @@ enum class Op : uint16_t {
Hlt,
Jmp,
Jnz,
MovWord,
MovByte,
Mov,
LoadWord,
StoreWord,
LoadByte,
StoreByte,
Cmp,
Or,
And,
@ -27,6 +32,8 @@ enum class Op : uint16_t {
RSub,
};
const char* op_str(Op op);
enum class Reg : uint16_t {
R0 = 0,
R1 = 1,
@ -48,12 +55,71 @@ enum class Flag : uint16_t {
Err = 4,
};
struct Ins;
class VM {
public:
void load(uint16_t offset, const uint8_t* data, size_t data_size)
{
std::memcpy(&m_mem[offset], data, data_size);
}
int run();
uint16_t reg(Reg reg) const
{
return m_regs[reg_id(reg)];
};
uint16_t word(uint16_t addr) const
{
uint16_t value = 0;
value |= static_cast<uint16_t>(m_mem[addr]) << 8;
value |= m_mem[addr + 1];
return value;
}
uint8_t byte(uint16_t addr) const
{
return m_mem[addr];
}
private:
friend struct Ins;
static constexpr uint16_t reg_id(Reg reg)
{
return std::to_underlying(reg);
}
static constexpr uint16_t flag_id(Flag flag)
{
return std::to_underlying(flag);
}
int run_instruction();
inline uint16_t eat()
{
uint16_t ins = 0;
ins |= static_cast<uint16_t>(m_mem[*m_rip]) << 8;
ins |= m_mem[*m_rip + 1];
*m_rip += 2;
return ins;
}
std::array<uint8_t, 65536> m_mem = {};
std::array<uint16_t, 10> m_regs = {};
uint16_t* m_rfl = &m_regs[reg_id(Reg::Rfl)];
uint16_t* m_rip = &m_regs[reg_id(Reg::Rip)];
bool m_halted = false;
};
struct Ins {
Ins(VM& vm, uint16_t ins)
Ins(VM& vm)
: vm(&vm)
, ins(ins)
, ins(vm.eat())
{
}
@ -113,43 +179,4 @@ public:
}
};
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;
};
}