auto scroll

This commit is contained in:
sfja 2026-01-29 23:19:00 +01:00
parent 25f7c12e97
commit c1c8bcf0b3
12 changed files with 305 additions and 185 deletions

View File

@ -1,7 +1,7 @@
MAKEFLAGS += -j16
CXXFLAGS := -std=c++23 -Wall -Wextra -pedantic-errors -fsanitize=address
CXXFLAGS := -std=c++23 -Wall -Wextra -pedantic-errors -fsanitize=address -g -ggdb
LDFLAGS :=
CXXFLAGS += $(shell pkgconf sdl2 --cflags)

View File

@ -1,10 +1,12 @@
const fl_zero 0x1
const fl_eq 0x2
const fl_be 0x4
const fl_lt 0x8
const fl_err 0xa
const fl_zero 1 << 0
const fl_eq 1 << 1
const fl_be 1 << 2
const fl_ab 1 << 3
const fl_lt 1 << 4
const fl_gt 1 << 5
const fl_err 1 << 6
const vcd_base 0x2000
@ -66,7 +68,7 @@ key_press_int:
mov [rsp], r1
add rsp, 2
call print_u16
call term_putc
sub rsp, 2
jmp .leave
@ -81,7 +83,9 @@ key_press_int:
.leave:
reti
const term_width 20
const term_height 12
term_y:
dw 0
@ -95,23 +99,112 @@ term_putc:
mov r1, [rbp-6]
mov r0, [counter]
add r0, vcd_base
cmp r1, '\n'
mov r0, rfl
and r0, fl_eq
jnz r0, .next_row
cmp r1, ' '
mov r2, rfl
and r2, fl_eq
jnz r2, .incr
mov r0, rfl
and r0, fl_eq
jnz r0, .next_col
cmp r1, '\0'
mov r0, rfl
and r0, fl_eq
jnz r0, .next_col
mov r0, [term_y]
mul r0, term_width
add r0, vcd_base
mov r2, [term_x]
add r0, r2
mov byte [r0], r1
.incr:
mov r0, [counter]
add r0, 1
mov [counter], r0
.next_col:
mov r2, [term_x]
add r2, 1
mov [term_x], r2
cmp r2, term_width
mov r0, rfl
and r0, fl_lt
jnz r0, .leave
.next_row:
mov r0, 0
mov [term_x], r0
mov r2, [term_y]
add r2, 1
cmp r2, term_height
mov r0, rfl
and r0, fl_lt
jnz r0, .increment_y
call term_scroll
jmp .leave
.increment_y:
mov [term_y], r2
.leave:
mov rsp, rbp
sub rsp, 2
mov rbp, [rsp]
ret
term_scroll:
mov r1, 0 ; row
jmp .l1_cond
.l1_body:
mov r2, 0 ; col
jmp .l2_cond
.l2_body:
mov r0, term_width
mul r0, r1
add r0, r2
mov r4, byte [vcd_base + term_width + r0]
mov byte [vcd_base + r0], r4
add r2, 1
.l2_cond:
cmp r2, term_width
mov r0, rfl
and r0, fl_lt
jnz r0, .l2_body
add r1, 1
.l1_cond:
cmp r1, term_height - 1
mov r0, rfl
and r0, fl_lt
jnz r0, .l1_body
mov r2, 0
jmp .l3_cond
.l3_body:
mov r0, ' '
mov byte [vcd_base + (term_height - 1) * term_width + r2], r0
add r2, 1
.l3_cond:
cmp r2, term_width
mov r0, rfl
and r0, fl_lt
jnz r0, .l3_body
ret
print_u16:
mov [rsp], rbp
add rsp, 2

52
programs/term.txt Normal file
View File

@ -0,0 +1,52 @@
const term_width: u16 = 20;
const term_height: u16 = 12;
static term_x: u16 = 0;
static term_y: u16 = 0;
static vcd: *u16 = 0x2000;
fn term_putc(ch: u16)
{
if (ch == '\n')
continue 'next_line;
if (ch == '\0')
continue 'next_col;
if (ch == ' ')
continue 'next_col;
vcd[term_y * term_width + term_x] = ch;
'next_col:
term_x += 1;
if (term_x < term_width)
return
'next_line:
term_x = 0;
if (term_y + 1 >= term_height) {
term_scroll();
} else {
term_y += 1;
}
}
fn term_scroll()
{
for row: u16 in 0..term_height - 1 {
for col: u16 in 0..term_width {
vcd[row * term_width + col]
= vcd[(row + 1) * term_width + col];
}
}
for col: u16 in 0..term_width {
vcd[(term_height - 1) * term_width + col] = ' ';
}
}
// vim: syntax=rust

View File

@ -245,6 +245,8 @@ enum class Mnemonic {
add,
sub,
rsub,
mul,
imul,
call,
ret,
reti,
@ -276,6 +278,8 @@ static const auto mnemonic_map
{ "add", M::add },
{ "sub", M::sub },
{ "rsub", M::rsub },
{ "mul", M::mul },
{ "imul", M::imul },
{ "call", M::call },
{ "ret", M::ret },
{ "reti", M::reti },
@ -459,7 +463,9 @@ void Assembler::assemble_line(const Line& line)
case M::rshr:
case M::add:
case M::sub:
case M::rsub: {
case M::rsub:
case M::mul:
case M::imul: {
auto dst = std::unique_ptr<EvaledOperand> {};
auto op1_store = std::unique_ptr<EvaledOperand> {};
auto op2 = std::unique_ptr<EvaledOperand> {};
@ -519,6 +525,12 @@ void Assembler::assemble_line(const Line& line)
case Mnemonic::rsub:
l.rsub_reg(dst->as_reg(), op1->as_reg(), op2->as_reg());
break;
case M::mul:
l.mul_reg(dst->as_reg(), op1->as_reg(), op2->as_reg());
break;
case M::imul:
l.imul_reg(dst->as_reg(), op1->as_reg(), op2->as_reg());
break;
default:
assert(false && "unhandled");
}
@ -554,6 +566,12 @@ void Assembler::assemble_line(const Line& line)
case Mnemonic::rsub:
l.rsub_imm(dst->as_reg(), op1->as_reg(), op2->as_imm());
break;
case M::mul:
l.mul_imm(dst->as_reg(), op1->as_reg(), op2->as_imm());
break;
case M::imul:
l.imul_imm(dst->as_reg(), op1->as_reg(), op2->as_imm());
break;
default:
assert(false && "unhandled");
}
@ -672,6 +690,9 @@ auto Assembler::eval_operand(const Expr& expr) -> std::unique_ptr<EvaledOperand>
return eval_operand_mem(*expr.as_unary());
case Expr::Ty::MemByte: {
auto evaled = eval_operand_mem(*expr.as_unary());
if (not evaled)
return nullptr;
switch (evaled->ty) {
case EOT::MemWordImm:
evaled->ty = EOT::MemByteImm;
@ -695,7 +716,10 @@ auto Assembler::eval_operand(const Expr& expr) -> std::unique_ptr<EvaledOperand>
case Expr::Ty::Shl:
case Expr::Ty::Shr:
case Expr::Ty::Add:
case Expr::Ty::Sub: {
case Expr::Ty::Sub:
case Expr::Ty::Mul:
case Expr::Ty::Div:
case Expr::Ty::Mod: {
return eval_operand_to_imm(expr);
}
}
@ -715,7 +739,10 @@ auto Assembler::eval_operand_mem(const Expr& expr)
case Expr::Ty::Xor:
case Expr::Ty::And:
case Expr::Ty::Shl:
case Expr::Ty::Shr: {
case Expr::Ty::Shr:
case Expr::Ty::Mul:
case Expr::Ty::Div:
case Expr::Ty::Mod: {
auto op = eval_operand_to_imm(expr);
if (not op)
return nullptr;
@ -822,7 +849,9 @@ auto Assembler::eval_operand_to_imm(const Expr& expr)
return std::make_unique<EO>(loc, EOT::Imm, *result);
}
case Expr::Ty::Reg:
error(loc, "registers cannot be part of an expression");
if (not m_second_pass) {
error(loc, "registers cannot be part of an expression");
}
return nullptr;
case Expr::Ty::Int:
return std::make_unique<EO>(
@ -843,7 +872,10 @@ auto Assembler::eval_operand_to_imm(const Expr& expr)
case Expr::Ty::Shl:
case Expr::Ty::Shr:
case Expr::Ty::Add:
case Expr::Ty::Sub: {
case Expr::Ty::Sub:
case Expr::Ty::Mul:
case Expr::Ty::Div:
case Expr::Ty::Mod: {
auto& [left_expr, right_expr] = expr.as_binary();
auto left = eval_operand_to_imm(*left_expr);
if (not left)
@ -886,15 +918,12 @@ auto Assembler::binary_op(Expr::Ty exprTy, uint16_t left, uint16_t right)
return (uint16_t)((int16_t)left + (int16_t)right);
case Expr::Ty::Sub:
return (uint16_t)((int16_t)left - (int16_t)right);
// taken from vc3
// case Expr::Ty::Mul:
// return (uint16_t)((int16_t)left * (int16_t)right);
// case Expr::Ty::Div:
// return (uint16_t)((int16_t)left / (int16_t)right);
// case Expr::Ty::Mod:
// return (uint16_t)((int16_t)left % (int16_t)right);
case Expr::Ty::Mul:
return (uint16_t)((int16_t)left * (int16_t)right);
case Expr::Ty::Div:
return (uint16_t)((int16_t)left / (int16_t)right);
case Expr::Ty::Mod:
return (uint16_t)((int16_t)left % (int16_t)right);
default:
assert(false && "unhandled");
}
@ -1087,13 +1116,16 @@ auto Parser::parse_binary(int prec) -> std::unique_ptr<Expr>
using Op = std::tuple<TT, T, int>;
constexpr auto ops = std::array {
Op { TT::Pipe, T::Or, 5 },
Op { TT::Hat, T::Xor, 4 },
Op { TT::Ampersand, T::And, 3 },
Op { TT::LtLt, T::Shl, 2 },
Op { TT::GtGt, T::Shr, 2 },
Op { TT::Plus, T::Add, 1 },
Op { TT::Minus, T::Sub, 1 },
Op { TT::Pipe, T::Or, 6 },
Op { TT::Hat, T::Xor, 5 },
Op { TT::Ampersand, T::And, 4 },
Op { TT::LtLt, T::Shl, 3 },
Op { TT::GtGt, T::Shr, 3 },
Op { TT::Plus, T::Add, 2 },
Op { TT::Minus, T::Sub, 2 },
Op { TT::Aster, T::Mul, 1 },
Op { TT::Slash, T::Div, 1 },
Op { TT::Perc, T::Mod, 1 },
};
if (prec == 0) {

View File

@ -44,6 +44,9 @@ namespace asmer {
Shr,
Add,
Sub,
Mul,
Div,
Mod,
};
using Ptr = std::unique_ptr<Expr>;

View File

@ -1,3 +1,4 @@
#define DEFINE_BINARY_INSTRUCTIONS
#include "builder.hpp"
#include "vm.hpp"
#include <cstdint>
@ -235,125 +236,21 @@ void Builder::cmp_imm(Reg op1, uint16_t op2)
i.build(*this);
}
void Builder::or_reg(Reg dst, Reg op1, Reg op2)
{
LOG;
binary_reg(dst, op1, op2, Op::Or);
}
#define X(NAME, OP) \
void Builder::NAME##_reg(Reg dst, Reg op1, Reg op2) \
{ \
LOG; \
binary_reg(dst, op1, op2, Op::OP); \
} \
void Builder::NAME##_imm(Reg dst, Reg op1, uint16_t op2) \
{ \
LOG; \
binary_imm(dst, op1, op2, Op::OP); \
}
void Builder::and_reg(Reg dst, Reg op1, Reg op2)
{
LOG;
binary_reg(dst, op1, op2, Op::And);
}
BINARY_INSTRUCTIONS
void Builder::xor_reg(Reg dst, Reg op1, Reg op2)
{
LOG;
binary_reg(dst, op1, op2, Op::Xor);
}
void Builder::shl_reg(Reg dst, Reg op1, Reg op2)
{
LOG;
binary_reg(dst, op1, op2, Op::Shl);
}
void Builder::rshl_reg(Reg dst, Reg op1, Reg op2)
{
LOG;
binary_reg(dst, op1, op2, Op::RShl);
}
void Builder::shr_reg(Reg dst, Reg op1, Reg op2)
{
LOG;
binary_reg(dst, op1, op2, Op::Shr);
}
void Builder::rshr_reg(Reg dst, Reg op1, Reg op2)
{
LOG;
binary_reg(dst, op1, op2, Op::RShr);
}
void Builder::add_reg(Reg dst, Reg op1, Reg op2)
{
LOG;
binary_reg(dst, op1, op2, Op::Add);
}
void Builder::sub_reg(Reg dst, Reg op1, Reg op2)
{
LOG;
binary_reg(dst, op1, op2, Op::Sub);
}
void Builder::rsub_reg(Reg dst, Reg op1, Reg op2)
{
LOG;
binary_reg(dst, op1, op2, Op::RSub);
}
void Builder::or_imm(Reg dst, Reg op1, uint16_t op2)
{
LOG;
binary_imm(dst, op1, op2, Op::Xor);
}
void Builder::and_imm(Reg dst, Reg op1, uint16_t op2)
{
LOG;
binary_imm(dst, op1, op2, Op::And);
}
void Builder::xor_imm(Reg dst, Reg op1, uint16_t op2)
{
LOG;
binary_imm(dst, op1, op2, Op::Xor);
}
void Builder::shl_imm(Reg dst, Reg op1, uint16_t op2)
{
LOG;
binary_imm(dst, op1, op2, Op::Shl);
}
void Builder::rshl_imm(Reg dst, Reg op1, uint16_t op2)
{
LOG;
binary_imm(dst, op1, op2, Op::RShl);
}
void Builder::shr_imm(Reg dst, Reg op1, uint16_t op2)
{
LOG;
binary_imm(dst, op1, op2, Op::Shr);
}
void Builder::rshr_imm(Reg dst, Reg op1, uint16_t op2)
{
LOG;
binary_imm(dst, op1, op2, Op::RShr);
}
void Builder::add_imm(Reg dst, Reg op1, uint16_t op2)
{
LOG;
binary_imm(dst, op1, op2, Op::Add);
}
void Builder::sub_imm(Reg dst, Reg op1, uint16_t op2)
{
LOG;
binary_imm(dst, op1, op2, Op::Sub);
}
void Builder::rsub_imm(Reg dst, Reg op1, uint16_t op2)
{
LOG;
binary_imm(dst, op1, op2, Op::RSub);
}
#undef X
void Builder::binary_reg(Reg dst, Reg op1, Reg op2, Op op)
{

View File

@ -4,6 +4,20 @@
#include <cstdint>
#include <print>
#define BINARY_INSTRUCTIONS \
X(or, Or) \
X(and, And) \
X(xor, Xor) \
X(shl, Shl) \
X(rshl, RShl) \
X(shr, Shr) \
X(rshr, RShr) \
X(add, Add) \
X(sub, Sub) \
X(rsub, RSub) \
X(mul, Mul) \
X(imul, IMul)
namespace vc5::tools {
using namespace vc5;
@ -33,26 +47,15 @@ public:
void stb_imm(uint16_t dst, Reg op2);
void cmp_reg(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);
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);
#define X(NAME, OP) \
void NAME##_reg(Reg dst, Reg op1, Reg op2); \
void NAME##_imm(Reg dst, Reg op1, uint16_t op2);
BINARY_INSTRUCTIONS
#undef X
void call_reg(Reg op1);
void call_imm(uint16_t op1);
void ret();
@ -103,3 +106,7 @@ private:
};
}
#ifndef DEFINE_BINARY_INSTRUCTIONS
#undef BINARY_INSTRUCTIONS
#endif

View File

@ -131,7 +131,7 @@ auto Scanner::next() -> Tok
}
return tok(TT::Gt, loc);
}
if (test_in("\n()[].,:|^+-!")) {
if (test_in("\n()[].,:|^+-*/%!")) {
auto ty = static_cast<TT>(current());
step();
return tok(ty, loc);

View File

@ -54,6 +54,9 @@ struct Tok {
Ampersand = '&',
Plus = '+',
Minus = '-',
Aster = '*',
Slash = '/',
Perc = '%',
Exclam = '!',
Lt = '<',
Gt = '>',

View File

@ -411,11 +411,21 @@ int VM::run_instruction()
} else {
*m_rfl &= (uint16_t)~(1u << std::to_underlying(Flag::Be));
}
if (op1 > op2) {
*m_rfl |= 1u << std::to_underlying(Flag::Ab);
} else {
*m_rfl &= (uint16_t)~(1u << std::to_underlying(Flag::Ab));
}
if ((int16_t)op1 < (int16_t)op2) {
*m_rfl |= 1u << std::to_underlying(Flag::Lt);
} else {
*m_rfl &= (uint16_t)~(1u << std::to_underlying(Flag::Lt));
}
if ((int16_t)op1 > (int16_t)op2) {
*m_rfl |= 1u << std::to_underlying(Flag::Gt);
} else {
*m_rfl &= (uint16_t)~(1u << std::to_underlying(Flag::Gt));
}
break;
}
case Op::Or:
@ -427,13 +437,18 @@ int VM::run_instruction()
case Op::RShr:
case Op::Add:
case Op::Sub:
case Op::RSub: {
case Op::RSub:
case Op::Mul:
case Op::IMul: {
auto op1 = ins.op1();
auto op2 = ins.op2_or_imm();
auto dst_reg = ins.dst_reg();
uint16_t* dst = &m_regs[std::to_underlying(dst_reg)];
auto as_u16 = [](auto v) { return static_cast<uint16_t>(v); };
auto as_i16 = [](auto v) { return static_cast<int16_t>(v); };
switch (op) {
case Op::Or:
*dst = op1 | op2;
@ -445,16 +460,16 @@ int VM::run_instruction()
*dst = op1 & op2;
break;
case Op::Shl:
*dst = static_cast<uint16_t>(op1 << op2);
*dst = as_u16(op1 << op2);
break;
case Op::RShl:
*dst = static_cast<uint16_t>(op2 << op1);
*dst = as_u16(op2 << op1);
break;
case Op::Shr:
*dst = static_cast<uint16_t>(op1 >> op2);
*dst = as_u16(op1 >> op2);
break;
case Op::RShr:
*dst = static_cast<uint16_t>(op2 >> op1);
*dst = as_u16(op2 >> op1);
break;
case Op::Add:
*dst = op1 + op2;
@ -465,6 +480,12 @@ int VM::run_instruction()
case Op::RSub:
*dst = op2 - op1;
break;
case Op::Mul:
*dst = op1 * op2;
break;
case Op::IMul:
*dst = as_u16(as_i16(op1) * as_i16(op2));
break;
default:
break;
}
@ -647,6 +668,10 @@ auto vc5::op_str(Op op) -> std::string_view
return "Sub";
case Op::RSub:
return "RSub";
case Op::Mul:
return "Mul";
case Op::IMul:
return "IMul";
case Op::Call:
return "Call";
case Op::Ret:

View File

@ -33,6 +33,8 @@ enum class Op : uint16_t {
Add,
Sub,
RSub,
Mul,
IMul,
Call,
Ret,
@ -82,8 +84,10 @@ enum class Flag : uint16_t {
Zero = 0,
Eq = 1,
Be = 2,
Lt = 3,
Err = 4,
Ab = 3,
Lt = 4,
Gt = 5,
Err = 6,
};
struct VcdDescript {

View File

@ -8,12 +8,17 @@ if exists("b:current_syntax")
endif
syn keyword Keyword const align db dw
syn keyword Keyword nop hlt jmp jnz mov cmp or and xor shl rshl shr rshr add sub rsub
syn keyword Keyword nop hlt jmp jnz mov cmp
syn keyword Keyword or and xor shl rshl shr rshr add sub rsub mul imul
syn keyword Keyword call ret reti lkbd lvcd dskr dskw
syn keyword Type byte word
syn keyword Operator r0 r1 r2 r3 r4 r5 rbp rsp rfl rip
syn match Operator '+'
syn match Operator '-'
syn match Operator '*'
syn match Operator '/'
syn match Operator '%'
syn match Operator '='
syn match Operator '<'
syn match Operator '>'
@ -21,7 +26,6 @@ syn match Operator '|'
syn match Operator '^'
syn match Operator '&'
syn match Number '0'
syn match Number '[1-9][0-9]*'
syn match Number '0b[01]*'