.
This commit is contained in:
6
TODO.md
6
TODO.md
@@ -26,9 +26,9 @@ Decisions:
|
|||||||
- [x] Local variables
|
- [x] Local variables
|
||||||
- [x] Functions
|
- [x] Functions
|
||||||
- [x] With parameters
|
- [x] With parameters
|
||||||
- [ ] Debug VM execution
|
- [x] Debug VM execution
|
||||||
- [ ] Control flow
|
- [x] Control flow
|
||||||
- [ ] Recursion
|
- [x] Recursion
|
||||||
- [ ] Strings
|
- [ ] Strings
|
||||||
- [ ] From constants
|
- [ ] From constants
|
||||||
- [ ] Garbage collection
|
- [ ] Garbage collection
|
||||||
|
|||||||
12
lib/code.c
12
lib/code.c
@@ -176,6 +176,18 @@ Instruction code_next_instruction(Code const* code, uint32_t function_id, uint32
|
|||||||
|
|
||||||
#ifdef DEBUG_ASSEMBLY
|
#ifdef DEBUG_ASSEMBLY
|
||||||
|
|
||||||
|
void code_debug_bytecode(Code const* code)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < code->bytecode_sz; ++i) {
|
||||||
|
if (i % 16 == 0)
|
||||||
|
printf("%04X : ", i);
|
||||||
|
printf("%02X ", code->bytecode[i]);
|
||||||
|
if (i % 16 == 15)
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
void code_decompile(Code const* code)
|
void code_decompile(Code const* code)
|
||||||
{
|
{
|
||||||
if (code_n_consts(code) > 0)
|
if (code_n_consts(code) > 0)
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ local function assemble(proto)
|
|||||||
if type(bin[i]) == 'string' then
|
if type(bin[i]) == 'string' then
|
||||||
local label_addr = labels[bin[i]]
|
local label_addr = labels[bin[i]]
|
||||||
if label_addr == nil then error("Label not found: " .. bin[i]) end
|
if label_addr == nil then error("Label not found: " .. bin[i]) end
|
||||||
replace32(i, label_addr)
|
replace16(i, label_addr)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -227,6 +227,7 @@ uint32_t code_n_functions(Code const* code);
|
|||||||
uint32_t code_function_sz(Code const* code, uint32_t f_id);
|
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);
|
Instruction code_next_instruction(Code const* code, uint32_t function_id, uint32_t pc);
|
||||||
|
|
||||||
|
void code_debug_bytecode(Code const* code);
|
||||||
void code_decompile(Code const* code);
|
void code_decompile(Code const* code);
|
||||||
void code_parse_instruction(Instruction inst, char* outbuf, size_t sz);
|
void code_parse_instruction(Instruction inst, char* outbuf, size_t sz);
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ void tyc_destroy(TycheVM* t);
|
|||||||
// debugging (DEBUG_ASSEMBLY needs to be setup in compilation options)
|
// debugging (DEBUG_ASSEMBLY needs to be setup in compilation options)
|
||||||
void tyc_debug_to_console(TycheVM* T, bool activate);
|
void tyc_debug_to_console(TycheVM* T, bool activate);
|
||||||
void tyc_assembly_decompile(TycheVM* T);
|
void tyc_assembly_decompile(TycheVM* T);
|
||||||
|
void tyc_print_bytecode(TycheVM* T);
|
||||||
|
|
||||||
// code loading and execution
|
// code loading and execution
|
||||||
TYC_RESULT tyc_load_bytecode(TycheVM* T, uint8_t const* bytecode, size_t bytecode_sz);
|
TYC_RESULT tyc_load_bytecode(TycheVM* T, uint8_t const* bytecode, size_t bytecode_sz);
|
||||||
@@ -48,6 +49,7 @@ void tyc_pushnil(TycheVM* T);
|
|||||||
void tyc_pushinteger(TycheVM* T, int32_t value);
|
void tyc_pushinteger(TycheVM* T, int32_t value);
|
||||||
TYC_RESULT tyc_type(TycheVM* T, int idx, TYC_TYPE* type);
|
TYC_RESULT tyc_type(TycheVM* T, int idx, TYC_TYPE* type);
|
||||||
TYC_RESULT tyc_tointeger(TycheVM* T, int idx, int32_t* value);
|
TYC_RESULT tyc_tointeger(TycheVM* T, int idx, int32_t* value);
|
||||||
|
TYC_RESULT tyc_tostring(TycheVM* T, int idx, const char** str);
|
||||||
TYC_RESULT tyc_expr(TycheVM* T, TYC_EXPR expr);
|
TYC_RESULT tyc_expr(TycheVM* T, TYC_EXPR expr);
|
||||||
|
|
||||||
#endif //TYCHE_TYCHE_H
|
#endif //TYCHE_TYCHE_H
|
||||||
|
|||||||
48
lib/vm.c
48
lib/vm.c
@@ -144,6 +144,11 @@ void tyc_assembly_decompile(TycheVM* T)
|
|||||||
code_decompile(T->code);
|
code_decompile(T->code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void tyc_print_bytecode(TycheVM* T)
|
||||||
|
{
|
||||||
|
code_debug_bytecode(T->code);
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -269,13 +274,26 @@ TYC_RESULT tyc_type(TycheVM* T, int idx, TYC_TYPE* type)
|
|||||||
TYC_RESULT tyc_tointeger(TycheVM* T, int idx, int32_t* value)
|
TYC_RESULT tyc_tointeger(TycheVM* T, int idx, int32_t* value)
|
||||||
{
|
{
|
||||||
VALUE v;
|
VALUE v;
|
||||||
TYC_RESULT r = stack_at(T->stack, idx, &v);
|
TYC_RESULT r;
|
||||||
if (r == T_OK) {
|
TRY(stack_at(T->stack, idx, &v))
|
||||||
if (v.type != TT_INTEGER)
|
if (v.type != TT_INTEGER)
|
||||||
return T_ERR_TYPE_UNEXPECTED;
|
return T_ERR_TYPE_UNEXPECTED;
|
||||||
*value = value_integer(v);
|
*value = value_integer(v);
|
||||||
|
return T_OK;
|
||||||
}
|
}
|
||||||
return r;
|
|
||||||
|
TYC_RESULT tyc_tostring(TycheVM* T, int idx, const char** str)
|
||||||
|
{
|
||||||
|
VALUE v;
|
||||||
|
TYC_RESULT r;
|
||||||
|
TRY(stack_at(T->stack, idx, &v))
|
||||||
|
if (v.type == TT_STRING)
|
||||||
|
return heap_get_string(T->heap, value_idx(v), str);
|
||||||
|
else if (v.type == TT_STRING_CONST)
|
||||||
|
*str = code_const_string(T->code, value_idx(v));
|
||||||
|
else
|
||||||
|
return T_ERR_TYPE_UNEXPECTED;
|
||||||
|
return T_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
TYC_RESULT tyc_expr(TycheVM* T, TYC_EXPR op)
|
TYC_RESULT tyc_expr(TycheVM* T, TYC_EXPR op)
|
||||||
@@ -322,11 +340,21 @@ static TYC_RESULT step(TycheVM* T)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TO_PUSHF:
|
case TO_PUSHF:
|
||||||
if (inst.operand < 0 || inst.operand > (int) code_n_functions(T->code))
|
if (inst.operand < 0 || inst.operand >= (int) code_n_functions(T->code))
|
||||||
return T_ERR_VALUE_OUT_OF_RANGE;
|
return T_ERR_VALUE_OUT_OF_RANGE;
|
||||||
TRY(stack_push(T->stack, create_value_idx(TT_FUNCTION, (uint32_t) inst.operand)))
|
TRY(stack_push(T->stack, create_value_idx(TT_FUNCTION, (uint32_t) inst.operand)))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case TO_PUSHC:
|
||||||
|
if (inst.operand < 0 || inst.operand >= (int) code_n_consts(T->code))
|
||||||
|
return T_ERR_VALUE_OUT_OF_RANGE;
|
||||||
|
if (code_const_type(T->code, (size_t) inst.operand) == TC_STRING) {
|
||||||
|
TRY(stack_push(T->stack, create_value_idx(TT_STRING_CONST, inst.operand)))
|
||||||
|
} else {
|
||||||
|
abort(); // REAL consts not supported for now
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case TO_POP:
|
case TO_POP:
|
||||||
TRY(stack_pop(T->stack, NULL))
|
TRY(stack_pop(T->stack, NULL))
|
||||||
break;
|
break;
|
||||||
@@ -400,14 +428,14 @@ static TYC_RESULT step(TycheVM* T)
|
|||||||
//
|
//
|
||||||
|
|
||||||
case TO_JMP:
|
case TO_JMP:
|
||||||
if (inst.operand < 0)
|
if (inst.operand < 0 || inst.operand >= code_function_sz(T->code, loc->function_id))
|
||||||
return T_ERR_VALUE_OUT_OF_RANGE; // TODO - also check function size
|
return T_ERR_VALUE_OUT_OF_RANGE;
|
||||||
loc->pc = (uint32_t) inst.operand;
|
loc->pc = (uint32_t) inst.operand;
|
||||||
goto dont_update_pc;
|
goto dont_update_pc;
|
||||||
|
|
||||||
case TO_BZ:
|
case TO_BZ:
|
||||||
if (inst.operand < 0)
|
if (inst.operand < 0 || inst.operand >= code_function_sz(T->code, loc->function_id))
|
||||||
return T_ERR_VALUE_OUT_OF_RANGE; // TODO - also check function size
|
return T_ERR_VALUE_OUT_OF_RANGE;
|
||||||
TRY(stack_pop(T->stack, &a))
|
TRY(stack_pop(T->stack, &a))
|
||||||
if (value_is_zero(a)) {
|
if (value_is_zero(a)) {
|
||||||
loc->pc = (uint32_t) inst.operand;
|
loc->pc = (uint32_t) inst.operand;
|
||||||
@@ -416,8 +444,8 @@ static TYC_RESULT step(TycheVM* T)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TO_BNZ:
|
case TO_BNZ:
|
||||||
if (inst.operand < 0)
|
if (inst.operand < 0 || inst.operand >= code_function_sz(T->code, loc->function_id))
|
||||||
return T_ERR_VALUE_OUT_OF_RANGE; // TODO - also check function size
|
return T_ERR_VALUE_OUT_OF_RANGE;
|
||||||
TRY(stack_pop(T->stack, &a))
|
TRY(stack_pop(T->stack, &a))
|
||||||
if (!value_is_zero(a)) {
|
if (!value_is_zero(a)) {
|
||||||
loc->pc = (uint32_t) inst.operand;
|
loc->pc = (uint32_t) inst.operand;
|
||||||
|
|||||||
@@ -93,8 +93,40 @@ return {
|
|||||||
pushi 7 ; 18
|
pushi 7 ; 18
|
||||||
ret ; 20
|
ret ; 20
|
||||||
]],
|
]],
|
||||||
decompile = true,
|
--debug_bytecode = true,
|
||||||
debug = true,
|
--decompile = true,
|
||||||
|
--debug = true,
|
||||||
expected_stack_top = 6,
|
expected_stack_top = 6,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name = "VM: jumps (bz)",
|
||||||
|
code = [[
|
||||||
|
.func 0
|
||||||
|
jmp @x1
|
||||||
|
pushi 5
|
||||||
|
@x1:
|
||||||
|
pushi 0
|
||||||
|
bnz @x2
|
||||||
|
pushi 0
|
||||||
|
bz @x3
|
||||||
|
@x2:
|
||||||
|
pushi 6
|
||||||
|
ret
|
||||||
|
@x3:
|
||||||
|
pushi 7
|
||||||
|
ret
|
||||||
|
]],
|
||||||
|
expected_stack_top = 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "VM: string from const",
|
||||||
|
code = [[
|
||||||
|
.const
|
||||||
|
0: "Hello"
|
||||||
|
.func 0
|
||||||
|
pushc 0
|
||||||
|
ret
|
||||||
|
]],
|
||||||
|
expected_stack_top = "Hello"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
73
test/tests.c
73
test/tests.c
@@ -14,8 +14,8 @@
|
|||||||
|
|
||||||
static void run_assembly_tests(void);
|
static void run_assembly_tests(void);
|
||||||
static void run_assembly_test(lua_State* L);
|
static void run_assembly_test(lua_State* L);
|
||||||
static void run_assembly_test_code(lua_State* L, bool debug, bool decompile);
|
static void run_assembly_test_code(lua_State* L, bool debug, bool decompile, bool debug_bytecode);
|
||||||
static void run_assembly_test_template(lua_State* L, bool debug, bool decompile);
|
static void run_assembly_test_template(lua_State* L, bool debug, bool decompile, bool debug_bytecode);
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
@@ -357,16 +357,21 @@ static void run_assembly_test(lua_State* L)
|
|||||||
bool debug = lua_isboolean(L, -1) && lua_toboolean(L, -1);
|
bool debug = lua_isboolean(L, -1) && lua_toboolean(L, -1);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
// decompile
|
// decompile?
|
||||||
lua_getfield(L, -1, "decompile");
|
lua_getfield(L, -1, "decompile");
|
||||||
bool decompile = lua_isboolean(L, -1) && lua_toboolean(L, -1);
|
bool decompile = lua_isboolean(L, -1) && lua_toboolean(L, -1);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
// decompile?
|
||||||
|
lua_getfield(L, -1, "debug_bytecode");
|
||||||
|
bool debug_bytecode = lua_isboolean(L, -1) && lua_toboolean(L, -1);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
// has code?
|
// has code?
|
||||||
lua_getfield(L, -1, "code");
|
lua_getfield(L, -1, "code");
|
||||||
if (!lua_isnil(L, -1)) {
|
if (!lua_isnil(L, -1)) {
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
run_assembly_test_code(L, debug, decompile);
|
run_assembly_test_code(L, debug, decompile, debug_bytecode);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
@@ -376,13 +381,35 @@ static void run_assembly_test(lua_State* L)
|
|||||||
lua_getfield(L, -1, "template");
|
lua_getfield(L, -1, "template");
|
||||||
if (!lua_isnil(L, -1)) {
|
if (!lua_isnil(L, -1)) {
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
run_assembly_test_template(L, debug, decompile);
|
run_assembly_test_template(L, debug, decompile, debug_bytecode);
|
||||||
} else {
|
} else {
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void run_assembly_test_code(lua_State* L, bool debug, bool decompile)
|
static void check_expected_top(lua_State* L, TycheVM* T)
|
||||||
|
{
|
||||||
|
// check stack size
|
||||||
|
lua_getfield(L, -1, "expected_stack_size");
|
||||||
|
if (!lua_isnil(L, -1))
|
||||||
|
assert(tyc_stack_size(T) == (size_t) lua_tointeger(L, -1));
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
// check stack top
|
||||||
|
lua_getfield(L, -1, "expected_stack_top");
|
||||||
|
if (lua_isinteger(L, -1)) {
|
||||||
|
TYC_TYPE type; assert(tyc_type(T, -1, &type) == T_OK); assert(type == TT_INTEGER);
|
||||||
|
int32_t v; assert(tyc_tointeger(T, -1, &v) == T_OK); assert(v == lua_tointeger(L, -1));
|
||||||
|
} else if (lua_isstring(L, -1)) {
|
||||||
|
TYC_TYPE type; assert(tyc_type(T, -1, &type) == T_OK); assert(type == TT_STRING || type == TT_STRING_CONST);
|
||||||
|
const char* str; assert(tyc_tostring(T, -1, &str) == T_OK); assert(strcmp(str, lua_tostring(L, -1)) == 0);
|
||||||
|
} else if (!lua_isnil(L, -1)) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void run_assembly_test_code(lua_State* L, bool debug, bool decompile, bool debug_bytecode)
|
||||||
{
|
{
|
||||||
TycheVM* T = tyc_new();
|
TycheVM* T = tyc_new();
|
||||||
tyc_debug_to_console(T, debug);
|
tyc_debug_to_console(T, debug);
|
||||||
@@ -395,32 +422,21 @@ static void run_assembly_test_code(lua_State* L, bool debug, bool decompile)
|
|||||||
|
|
||||||
// run code
|
// run code
|
||||||
assert(tyc_load_bytecode(T, bytecode, bytecode_sz) == T_OK);
|
assert(tyc_load_bytecode(T, bytecode, bytecode_sz) == T_OK);
|
||||||
|
if (debug_bytecode)
|
||||||
|
tyc_print_bytecode(T);
|
||||||
if (decompile)
|
if (decompile)
|
||||||
tyc_assembly_decompile(T);
|
tyc_assembly_decompile(T);
|
||||||
assert(tyc_call(T, 0) == T_OK);
|
assert(tyc_call(T, 0) == T_OK);
|
||||||
|
|
||||||
// check stack size
|
// assert
|
||||||
lua_getfield(L, -1, "expected_stack_size");
|
check_expected_top(L, T);
|
||||||
if (!lua_isnil(L, -1))
|
|
||||||
assert(tyc_stack_size(T) == (size_t) lua_tointeger(L, -1));
|
|
||||||
lua_pop(L, 1);
|
|
||||||
|
|
||||||
// check stack top
|
|
||||||
lua_getfield(L, -1, "expected_stack_top");
|
|
||||||
if (lua_isinteger(L, -1)) {
|
|
||||||
TYC_TYPE type; assert(tyc_type(T, -1, &type) == T_OK); assert(type == TT_INTEGER);
|
|
||||||
int32_t v; assert(tyc_tointeger(T, -1, &v) == T_OK); assert(v == lua_tointeger(L, -1));
|
|
||||||
} else if (!lua_isnil(L, -1)) {
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
free(bytecode);
|
free(bytecode);
|
||||||
tyc_destroy(T);
|
tyc_destroy(T);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void run_assembly_test_template(lua_State* L, bool debug, bool decompile)
|
static void run_assembly_test_template(lua_State* L, bool debug, bool decompile, bool debug_bytecode)
|
||||||
{
|
{
|
||||||
lua_getfield(L, -1, "template");
|
lua_getfield(L, -1, "template");
|
||||||
char* template = strdup(lua_tostring(L, -1));
|
char* template = strdup(lua_tostring(L, -1));
|
||||||
@@ -459,19 +475,14 @@ static void run_assembly_test_template(lua_State* L, bool debug, bool decompile)
|
|||||||
uint8_t* bytecode; size_t bytecode_sz;
|
uint8_t* bytecode; size_t bytecode_sz;
|
||||||
assert(code_assemble(formatted_code, &bytecode, &bytecode_sz) == T_OK);
|
assert(code_assemble(formatted_code, &bytecode, &bytecode_sz) == T_OK);
|
||||||
assert(tyc_load_bytecode(T, bytecode, bytecode_sz) == T_OK);
|
assert(tyc_load_bytecode(T, bytecode, bytecode_sz) == T_OK);
|
||||||
|
if (debug_bytecode)
|
||||||
|
tyc_print_bytecode(T);
|
||||||
if (decompile)
|
if (decompile)
|
||||||
tyc_assembly_decompile(T);
|
tyc_assembly_decompile(T);
|
||||||
assert(tyc_call(T, 0) == T_OK);
|
assert(tyc_call(T, 0) == T_OK);
|
||||||
|
|
||||||
// check stack top
|
// assert
|
||||||
lua_getfield(L, -1, "expected_stack_top");
|
check_expected_top(L, T);
|
||||||
if (lua_isinteger(L, -1)) {
|
|
||||||
TYC_TYPE type; assert(tyc_type(T, -1, &type) == T_OK); assert(type == TT_INTEGER);
|
|
||||||
int32_t v; assert(tyc_tointeger(T, -1, &v) == T_OK); assert(v == lua_tointeger(L, -1));
|
|
||||||
} else if (!lua_isnil(L, -1)) {
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
lua_pop(L, 1);
|
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
free(bytecode);
|
free(bytecode);
|
||||||
|
|||||||
Reference in New Issue
Block a user