This commit is contained in:
2026-05-17 09:36:15 -05:00
parent e2692a589a
commit 6f0c3a729b
11 changed files with 342 additions and 49 deletions

View File

@@ -25,6 +25,7 @@ struct Code {
uint32_t* const_addr;
uint32_t fn_count;
uint32_t* fn_addr;
uint32_t* fn_sz;
};
Code* code_new(void)
@@ -37,6 +38,7 @@ void code_destroy(Code* code)
{
free(code->const_addr);
free(code->fn_addr);
free(code->fn_sz);
free(code);
}
@@ -91,12 +93,16 @@ TYC_RESULT code_load_bytecode(Code* code, uint8_t const* bytecode, size_t byteco
addr += 4;
code->fn_addr = xcalloc(code->fn_count, sizeof(uint32_t));
code->fn_sz = xcalloc(code->fn_count, sizeof(uint32_t));
code->fn_addr[0] = addr;
uint32_t addr_next;
for (size_t i = 1; i < code->fn_count; ++i) {
uint32_t addr_next;
memcpy(&addr_next, &bytecode[addr], sizeof(uint32_t));
code->fn_sz[i-1] = addr_next - addr - 4;
addr = code->fn_addr[i] = addr_next;
}
memcpy(&addr_next, &bytecode[addr], sizeof(uint32_t));
code->fn_sz[code->fn_count-1] = addr_next - addr - 4;
return T_OK;
}
@@ -132,6 +138,11 @@ uint32_t code_n_functions(Code const* code)
return code->fn_count;
}
uint32_t code_function_sz(Code const* code, uint32_t f_id)
{
return code->fn_sz[f_id];
}
Instruction code_next_instruction(Code const* code, uint32_t function_id, uint32_t pc)
{
uint32_t addr = code->fn_addr[function_id] + 4 + pc;
@@ -161,4 +172,102 @@ Instruction code_next_instruction(Code const* code, uint32_t function_id, uint32
.operand = operand,
.sz = sz,
};
}
}
#ifdef DEBUG_ASSEMBLY
void code_decompile(Code const* code)
{
if (code_n_consts(code) > 0)
printf(".const\n");
for (size_t const_id = 0; const_id < code_n_consts(code); ++const_id) {
TYC_CONST_TYPE type = code_const_type(code, const_id);
if (type == TC_STRING)
printf(" %03zu: \"%s\"\n", const_id, code_const_string(code, const_id));
else if (type == TC_REAL)
printf(" %03zu: %f\n", const_id, (double) code_const_real(code, const_id));
}
for (uint32_t f_id = 0; f_id < code_n_functions(code); ++f_id) {
printf(".func %d\n", f_id);
uint32_t pc = 0;
while (pc < code_function_sz(code, f_id)) {
Instruction inst = code_next_instruction(code, f_id, pc);
char buf[50];
code_parse_instruction(inst, buf, sizeof buf);
printf(" %s ; %d\n", buf, pc);
pc += inst.sz;
}
}
}
void code_parse_instruction(Instruction inst, char* outbuf, size_t sz)
{
int n;
switch (inst.operator) {
case TO_PUSHI: n = snprintf(outbuf, sz, "pushi "); break;
case TO_PUSHC: n = snprintf(outbuf, sz, "pushc "); break;
case TO_PUSHF: n = snprintf(outbuf, sz, "pushf "); break;
case TO_PUSHN: n = snprintf(outbuf, sz, "pushn "); break;
case TO_PUSHZ: n = snprintf(outbuf, sz, "pushz "); break;
case TO_PUSHT: n = snprintf(outbuf, sz, "pusht "); break;
case TO_NEWA: n = snprintf(outbuf, sz, "newa "); break;
case TO_NEWT: n = snprintf(outbuf, sz, "newt "); break;
case TO_POP: n = snprintf(outbuf, sz, "pop "); break;
case TO_DUP: n = snprintf(outbuf, sz, "dup "); break;
case TO_PUSHV: n = snprintf(outbuf, sz, "pushv "); break;
case TO_SET: n = snprintf(outbuf, sz, "set "); break;
case TO_DUPV: n = snprintf(outbuf, sz, "dupv "); break;
case TO_SETG: n = snprintf(outbuf, sz, "setg "); break;
case TO_GETG: n = snprintf(outbuf, sz, "getg "); break;
case TO_CALL: n = snprintf(outbuf, sz, "call "); break;
case TO_RET: n = snprintf(outbuf, sz, "ret "); break;
case TO_RETI: n = snprintf(outbuf, sz, "reti "); break;
case TO_GETKV: n = snprintf(outbuf, sz, "getkv "); break;
case TO_SETKV: n = snprintf(outbuf, sz, "setkv "); break;
case TO_GETI: n = snprintf(outbuf, sz, "geti "); break;
case TO_SETI: n = snprintf(outbuf, sz, "seti "); break;
case TO_APPND: n = snprintf(outbuf, sz, "appnd "); break;
case TO_NEXT: n = snprintf(outbuf, sz, "next "); break;
case TO_SMT: n = snprintf(outbuf, sz, "smt "); break;
case TO_MT: n = snprintf(outbuf, sz, "mt "); break;
case TO_SUM: n = snprintf(outbuf, sz, "sum "); break;
case TO_SUB: n = snprintf(outbuf, sz, "sub "); break;
case TO_MUL: n = snprintf(outbuf, sz, "mul "); break;
case TO_DIV: n = snprintf(outbuf, sz, "div "); break;
case TO_IDIV: n = snprintf(outbuf, sz, "idiv "); break;
case TO_MOD: n = snprintf(outbuf, sz, "mod "); break;
case TO_EQ: n = snprintf(outbuf, sz, "eq "); break;
case TO_NEQ: n = snprintf(outbuf, sz, "neq "); break;
case TO_LT: n = snprintf(outbuf, sz, "lt "); break;
case TO_LTE: n = snprintf(outbuf, sz, "lte "); break;
case TO_GT: n = snprintf(outbuf, sz, "gt "); break;
case TO_GTE: n = snprintf(outbuf, sz, "gte "); break;
case TO_AND: n = snprintf(outbuf, sz, "and "); break;
case TO_OR: n = snprintf(outbuf, sz, "or "); break;
case TO_XOR: n = snprintf(outbuf, sz, "xor "); break;
case TO_POW: n = snprintf(outbuf, sz, "pow "); break;
case TO_SHL: n = snprintf(outbuf, sz, "shl "); break;
case TO_SHR: n = snprintf(outbuf, sz, "shr "); break;
case TO_LEN: n = snprintf(outbuf, sz, "len "); break;
case TO_TYPE: n = snprintf(outbuf, sz, "type "); break;
case TO_CAST: n = snprintf(outbuf, sz, "cast "); break;
case TO_VER: n = snprintf(outbuf, sz, "ver "); break;
case TO_CMPL: n = snprintf(outbuf, sz, "cmpl "); break;
case TO_ASMBL: n = snprintf(outbuf, sz, "asmbl "); break;
case TO_LOAD: n = snprintf(outbuf, sz, "load "); break;
case TO_BZ: n = snprintf(outbuf, sz, "bz "); break;
case TO_BNZ: n = snprintf(outbuf, sz, "bnz "); break;
case TO_JMP: n = snprintf(outbuf, sz, "jmp "); break;
case TO_GC: n = snprintf(outbuf, sz, "gc "); break;
default: n = snprintf(outbuf, sz, "??? "); break;
}
if (inst.operator >= OP_8BIT_OPERAND)
snprintf(&outbuf[n], sz + (size_t) n, "%2d", inst.operand);
else
snprintf(&outbuf[n], sz + (size_t) n, " ");
}
#endif

View File

@@ -95,10 +95,10 @@ local instructions = {
-- local variables
pushv = 0xa3,
set = 0xa4,
dupv = 0xa5,
setg = 0xa6,
getg = 0xa7,
set = 0xae,
dupv = 0xa4,
setg = 0xa5,
getg = 0xa6,
-- function operations
call = 0xa7,
@@ -138,7 +138,7 @@ local instructions = {
-- other value operations
len = 0x40,
type = 0x41,
cast = 0xaa,
cast = 0xad,
ver = 0x42,
-- external code
@@ -244,7 +244,7 @@ local function assemble(proto)
-- add labels
if inst.labels then
for _, lbl in ipairs(inst.labels) do
labels[lbl] = #bin - function_start - 1
labels[lbl] = #bin - function_start
end
end

View File

@@ -26,10 +26,10 @@ typedef enum {
// LOCAL VARIABLES
TO_PUSHV = 0XA3,
TO_SET = 0XA4,
TO_DUPV = 0XA5,
TO_SETG = 0XA6,
TO_GETG = 0XA7,
TO_SET = 0XAE,
TO_DUPV = 0XA4,
TO_SETG = 0XA5,
TO_GETG = 0XA6,
// FUNCTION OPERATIONS
TO_CALL = 0XA7,
@@ -69,7 +69,7 @@ typedef enum {
// OTHER VALUE OPERATIONS
TO_LEN = 0X40,
TO_TYPE = 0X41,
TO_CAST = 0XAA,
TO_CAST = 0XAD,
TO_VER = 0X42,
// EXTERNAL CODE
@@ -156,7 +156,7 @@ TYC_RESULT stack_push(Stack* s, VALUE v);
TYC_RESULT stack_peek(Stack const* s, VALUE* v_out);
TYC_RESULT stack_pop(Stack* s, VALUE* v_out);
size_t stack_len(Stack const* s);
size_t stack_size(Stack const* s);
TYC_RESULT stack_at(Stack const* s, int32_t key, VALUE* v);
TYC_RESULT stack_set(Stack* s, int32_t key, VALUE v);
@@ -224,8 +224,12 @@ T_REAL code_const_real(Code const* code, size_t n);
const char* code_const_string(Code const* code, size_t n);
uint32_t code_n_functions(Code const* code);
uint32_t code_function_sz(Code const* code, uint32_t f_id);
Instruction code_next_instruction(Code const* code, uint32_t function_id, uint32_t pc);
void code_decompile(Code const* code);
void code_parse_instruction(Instruction inst, char* outbuf, size_t sz);
//
// EXPRESSIONS
//

View File

@@ -74,7 +74,7 @@ TYC_RESULT stack_pop(Stack* s, VALUE* v_out)
return T_OK;
}
size_t stack_len(Stack const* s)
size_t stack_size(Stack const* s)
{
return s->stack_n - stack_top_fp(s);
}
@@ -139,7 +139,7 @@ size_t stack_fp_level(Stack const* s)
size_t stack_collectable_array(Stack const* s, VALUE** values)
{
size_t j = 0;
*values = xmalloc(stack_len(s) * sizeof(VALUE));
*values = xmalloc(stack_size(s) * sizeof(VALUE));
for (size_t i = 0; i < s->stack_n; ++i)
if (type_is_collectable(s->stack[i].type))

View File

@@ -1,6 +1,7 @@
#ifndef TYCHE_TYCHE_H
#define TYCHE_TYCHE_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
@@ -33,6 +34,10 @@ typedef struct TycheVM TycheVM;
TycheVM* tyc_new(void);
void tyc_destroy(TycheVM* t);
// debugging (DEBUG_ASSEMBLY needs to be setup in compilation options)
void tyc_debug_to_console(TycheVM* T, bool activate);
void tyc_assembly_decompile(TycheVM* T);
// code loading and execution
TYC_RESULT tyc_load_bytecode(TycheVM* T, uint8_t const* bytecode, size_t bytecode_sz);
TYC_RESULT tyc_call(TycheVM* t, uint16_t n_pars);

135
lib/vm.c
View File

@@ -1,6 +1,7 @@
#include "priv.h"
#include <stdlib.h>
#include <stdio.h>
typedef struct Location {
uint32_t function_id;
@@ -18,6 +19,7 @@ struct TycheVM {
Heap* heap;
Code* code;
LocationStack location_stack;
bool debug;
};
static TYC_RESULT step(TycheVM* T);
@@ -39,6 +41,7 @@ TycheVM* tyc_new(void)
.cap = 4,
.sz = 0,
};
t->debug = false;
expr_init();
@@ -54,6 +57,95 @@ void tyc_destroy(TycheVM* t)
free(t);
}
//
// DEBUGGING
//
void tyc_debug_to_console(TycheVM* T, bool activate)
{
T->debug = activate;
}
#ifdef DEBUG_ASSEMBLY
static void debug_instruction(TycheVM* T, Location* loc, Instruction inst)
{
if (!T->debug)
return;
char buf[50];
code_parse_instruction(inst, buf, sizeof(buf));
printf(": %02d-%04d %s ", loc->function_id, loc->pc, buf);
}
static void debug_value(TycheVM* T, VALUE a)
{
switch (value_type(a)) {
case TT_NIL:
printf("[nil]");
break;
case TT_INTEGER:
printf("[%d]", value_integer(a));
break;
case TT_REAL:
printf("[%f]", (double) value_real(a));
break;
case TT_STRING: {
const char* str;
if (heap_get_string(T->heap, value_idx(a), &str) == T_OK)
printf("[\"%s\"]", str);
else
printf("[\"(not found)\"]");
break;
}
case TT_STRING_CONST: {
if (code_const_type(T->code, value_idx(a)) != TC_STRING)
printf("[\"(const not a string)\"]");
else
printf("[\"%s\"]", code_const_string(T->code, value_idx(a)));
break;
}
case TT_ARRAY:
printf("[(not implemented)]\n");
abort();
case TT_TABLE:
printf("[(not implemented )]\n");
abort();
case TT_FUNCTION:
printf("[func %d]", value_idx(a));
break;
case TT_NATIVE_PTR:
printf("[ptr %p]", (void *) (intptr_t) value_idx(a));
break;
case TT_COUNT__:
__builtin_unreachable();
}
}
static void debug_stack(TycheVM* T)
{
if (!T->debug)
return;
if (stack_size(T->stack) == 0) {
printf("|empty|\n");
return;
}
for (size_t i = 0; i < stack_size(T->stack); ++i) {
VALUE a;
stack_at(T->stack, (int32_t) i, &a);
debug_value(T, a);
printf(" ");
}
printf("\n");
}
void tyc_assembly_decompile(TycheVM* T)
{
code_decompile(T->code);
}
#endif
//
// LOCATION STACK
//
@@ -152,7 +244,7 @@ TYC_RESULT tyc_call(TycheVM* T, uint16_t n_pars)
size_t tyc_stack_size(TycheVM* T)
{
return stack_len(T->stack);
return stack_size(T->stack);
}
void tyc_pushnil(TycheVM* T)
@@ -211,7 +303,9 @@ static TYC_RESULT step(TycheVM* T)
Location* loc = location_top(T);
Instruction inst = code_next_instruction(T->code, loc->function_id, loc->pc);
// TODO - debug instruction
#ifdef DEBUG_ASSEMBLY
debug_instruction(T, loc, inst);
#endif
switch (inst.operator) {
@@ -299,8 +393,37 @@ static TYC_RESULT step(TycheVM* T)
TRY(stack_pop_fp(T->stack))
TRY(stack_push(T->stack, a))
location_pop(T);
// TODO - print stack
return T_OK;
goto dont_update_pc;
//
// jumps/branching
//
case TO_JMP:
if (inst.operand < 0)
return T_ERR_VALUE_OUT_OF_RANGE; // TODO - also check function size
loc->pc = (uint32_t) inst.operand;
goto dont_update_pc;
case TO_BZ:
if (inst.operand < 0)
return T_ERR_VALUE_OUT_OF_RANGE; // TODO - also check function size
TRY(stack_pop(T->stack, &a))
if (value_is_zero(a)) {
loc->pc = (uint32_t) inst.operand;
goto dont_update_pc;
}
break;
case TO_BNZ:
if (inst.operand < 0)
return T_ERR_VALUE_OUT_OF_RANGE; // TODO - also check function size
TRY(stack_pop(T->stack, &a))
if (!value_is_zero(a)) {
loc->pc = (uint32_t) inst.operand;
goto dont_update_pc;
}
break;
default:
return T_ERR_INVALID_OPCODE;
@@ -309,5 +432,9 @@ static TYC_RESULT step(TycheVM* T)
// TODO - print stack
loc->pc += inst.sz;
dont_update_pc:
#ifdef DEBUG_ASSEMBLY
debug_stack(T);
#endif
return T_OK;
}