13 Commits

Author SHA1 Message Date
9561d5cacd . 2026-05-10 07:43:04 -05:00
0116a214f6 . 2026-05-09 15:07:36 -05:00
a70abe76ad . 2026-05-09 15:05:55 -05:00
e2f930641e . 2026-05-09 15:04:44 -05:00
85443ded9d . 2026-05-09 14:23:01 -05:00
6762c49ed3 . 2026-05-09 13:50:16 -05:00
610491c1d7 . 2026-05-09 13:26:13 -05:00
554a7b55c5 . 2026-05-09 11:34:51 -05:00
8a26ba5351 . 2026-05-09 10:37:02 -05:00
83b80f6e7d . 2026-05-09 10:24:09 -05:00
a6adb9b723 . 2026-05-09 09:24:46 -05:00
19b51fcaa0 . 2026-05-09 08:53:11 -05:00
27164aaac3 . 2026-05-08 20:36:21 -05:00
5 changed files with 446 additions and 96 deletions

2
.idea/tyche.iml generated Normal file
View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CIDR" type="CPP_MODULE" version="4" />

View File

@@ -11,11 +11,11 @@ Progress of the Lua port:
- [x] Control flow - [x] Control flow
- [x] Labels in Assembly - [x] Labels in Assembly
- [x] Recursion - [x] Recursion
- [ ] Strings - [x] Strings
- [ ] From constants - [x] From constants
- [ ] Garbage collection - [x] Garbage collection
- [ ] Arrays - [x] Arrays
- [ ] Garbage collection - [x] Garbage collection
- [ ] Tables - [ ] Tables
- [ ] Garbage collection - [ ] Garbage collection
- [ ] Metatables - [ ] Metatables
@@ -28,3 +28,10 @@ Progress of the Lua port:
- [ ] Assembler generate bytecode - [ ] Assembler generate bytecode
- [ ] VM interpret it - [ ] VM interpret it
## C interface
- [ ] Error management (decision)
- [ ] Format for value and heap value
- [ ] Transparency and log levels

View File

@@ -22,12 +22,13 @@ Stack operations:
a0 c0 e0 pushi [int] Push int a0 c0 e0 pushi [int] Push int
a1 c1 e1 pushc [index] Push constant a1 c1 e1 pushc [index] Push constant
a2 c2 e2 pushf [function] Push function id a2 c2 e2 pushf [function] Push function id
00 pushz Push zero (or false) 00 pushn Push nil
01 pusht Push true 01 pushz Push zero (or false)
02 newa Push (create) empty array 02 pusht Push true
03 newt Push (create) empty table 03 newa Push (create) empty array
04 pop 04 newt Push (create) empty table
05 dup 05 pop
06 dup
Local variables: Local variables:
a3 c3 e3 pushv [int] Push n nil values into the stack (used to init local vars) a3 c3 e3 pushv [int] Push n nil values into the stack (used to init local vars)
@@ -44,12 +45,12 @@ Function operations:
Table and array operations: Table and array operations:
16 getkv Get table's value based on key (pull 1 value, push 1 value) 16 getkv Get table's value based on key (pull 1 value, push 1 value)
17 setkv Set table's key and value (pull 2 values from stack) 17 setkv Set table's key and value (pull 2 values from stack)
18 geta Get array's position value a8 c8 e8 geti Get array's position value
19 seta Set array's position value (pull 2 values from stack) a9 c9 e9 seti Set array's position value
1a appnd Add value to the end of array 18 appnd Add value to the end of array
1b next Push the next pair into the stack (for loops) 19 next Push the next pair into the stack (for loops)
1c smt Set value metatable 1a smt Set value metatable
1d mt Get value metatable 1b mt Get value metatable
Logical/arithmetic: Logical/arithmetic:
20 sum Sum top 2 values in stack 20 sum Sum top 2 values in stack
@@ -74,7 +75,7 @@ Logical/arithmetic:
Other value operations: Other value operations:
40 len Get table, array or string size 40 len Get table, array or string size
41 type Get type from value at the top of the stack 41 type Get type from value at the top of the stack
b0 cast [type] Cast type to another type aa cast [type] Cast type to another type
42 ver Return VM version 42 ver Return VM version
External code: External code:
@@ -83,11 +84,13 @@ External code:
4a load Load bytecode as function (will place function on stack) 4a load Load bytecode as function (will place function on stack)
Control flow (the destination is always a 16-bit field): Control flow (the destination is always a 16-bit field):
c8 bz [pc] Branch if zero ca bz [pc] Branch if zero
c9 bnz [pc] Branch if not zero cb bnz [pc] Branch if not zero
ca jmp [pc] Unconditional jump cc jmp [pc] Unconditional jump
* Jumps can only happen within the same function. * Jumps can only happen within the same function.
Memory management:
4b gc Call garbage collector
Error handling: (0xa0~0xaf) Error handling: (0xa0~0xaf)
??? ???

203
lua-temp/tests.lua Normal file → Executable file
View File

@@ -1,3 +1,5 @@
#!/usr/bin/env lua
local pprint = require('pprint') local pprint = require('pprint')
local assemble = require('tyche-as') local assemble = require('tyche-as')
local VM = require('tyche-vm') local VM = require('tyche-vm')
@@ -146,7 +148,7 @@ end
---------------------- ----------------------
-- -- -- --
-- VM ARITH -- -- VM --
-- -- -- --
---------------------- ----------------------
@@ -240,8 +242,7 @@ do TEST "VM: functions"
assert_eq(vm:to_integer(-1), -1) assert_eq(vm:to_integer(-1), -1)
end end
do do TEST "VM: jumps (jmp + bnz)"
TEST "VM: jumps (jmp + bnz)"
local vm = VM.new():load(assemble [[ local vm = VM.new():load(assemble [[
.func 0 .func 0
jmp @x1 jmp @x1
@@ -249,6 +250,7 @@ do
@x1: @x1:
pushi 1 pushi 1
bnz @x2 bnz @x2
pushi 1
bz @x3 bz @x3
@x2: @x2:
pushi 6 pushi 6
@@ -261,31 +263,15 @@ do
assert_eq(vm:to_integer(-1), 6) assert_eq(vm:to_integer(-1), 6)
end end
do do TEST "VM: jumps (bz)"
TEST "VM: jumps (bz)" local vm = VM.new():load(assemble [[
pprint(assemble [[
.func 0
jmp @x1
pushi 5
@x1:
pushi 0
bnz @x2
pushi 0
bz @x3
@x2:
pushi 6
ret
@x3:
pushi 7
ret
]])
local vm = VM.new():set_debug(true):load(assemble [[
.func 0 .func 0
jmp @x1 jmp @x1
pushi 5 pushi 5
@x1: @x1:
pushi 0 pushi 0
bnz @x2 bnz @x2
pushi 0
bz @x3 bz @x3
@x2: @x2:
pushi 6 pushi 6
@@ -298,5 +284,178 @@ do
assert_eq(vm:to_integer(-1), 7) assert_eq(vm:to_integer(-1), 7)
end end
do TEST "VM: string from const"
local vm = VM.new():load(assemble [[
.const
0: "Hello"
.func 0
pushc 0
ret
]]):call(0)
assert_eq(vm:to_string(-1), "Hello")
end
do TEST "VM: managed strings"
local vm = VM.new():push_string("Hello")
-- print(vm:debug_heap())
assert_eq(vm:to_string(-1), "Hello")
end
do TEST "VM: concatenate strings (GC won't delete)"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushc 0
pushc 1
sum
gc
ret
]]):call(0)
-- print(vm:debug_heap())
assert_eq(vm:to_string(-1), "Hello world")
assert_eq(vm.heap:size(), 1)
end
do TEST "VM: GC strings"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushn
pushc 0
pushc 1
sum
pop
gc
ret
]]):call(0)
-- print(vm:debug_heap())
assert_eq(vm:is(-1, 'nil'), true)
assert_eq(vm.heap:size(), 0)
end
do TEST "VM: arrays"
local vm = VM.new():load(assemble [[
.func 0
newa
pushi 10
seti 0
pushi 20
seti 1
pushi 30
seti 2
geti 1
ret
]]):call(0)
-- print(vm:debug_heap())
assert_eq(vm:to_integer(-1), 20)
assert_eq(vm.heap:size(), 1)
end
do TEST "VM: arrays GC"
local vm = VM.new():load(assemble [[
.func 0
pushn
newa
pushi 10
seti 0
pop
gc
ret
]]):call(0)
assert_eq(vm.heap:size(), 0)
end
do TEST "VM: GC items (1st level) - no items removed"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushn
newa
pushc 0
pushc 1
sum
seti 0
gc
pop
ret
]]):call(0)
assert_eq(vm.heap:size(), 2)
end
do TEST "VM: GC items (1st level) - all items removed"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushn
newa
pushc 0
pushc 1
sum
seti 0
pop
gc
ret
]]):call(0)
assert_eq(vm.heap:size(), 0)
end
do TEST "VM: GC items (2nd level) - no items removed"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushn
newa
newa
pushc 0
pushc 1
sum
seti 0
seti 0
gc
pop
ret
]]):call(0)
assert_eq(vm.heap:size(), 3)
end
do TEST "VM: GC items (1st level) - all items removed"
local vm = VM.new():load(assemble [[
.const
0: "Hello "
1: "world"
.func 0
pushn
newa
newa
pushc 0
pushc 1
sum
seti 0
seti 0
pop
gc
ret
]]):call(0)
assert_eq(vm.heap:size(), 0)
end
print('End.') print('End.')

View File

@@ -8,26 +8,14 @@ local ARITH_LOGIC_OPS = {
['and']=true, ['or']=true, xor=true, pow=true, shl=true, shr=true, mod=true ['and']=true, ['or']=true, xor=true, pow=true, shl=true, shr=true, mod=true
} }
math.randomseed(os.time())
---------------------- ----------------------
-- -- -- --
-- UTIL -- -- UTIL --
-- -- -- --
---------------------- ----------------------
local function format_value(v)
if v.type == 'integer' or v.type == 'real' then
return tostring(v.value)
elseif v.type == 'string' then
return '"' .. v.value .. '"'
elseif v.type == 'function' then
return '@' .. tostring(v.value)
elseif v.type == 'nil' then
return 'nil'
else
return pprint.pformat(v)
end
end
local function validate_value(v) local function validate_value(v)
assert(v, "value cannot be nil") assert(v, "value cannot be nil")
assert(type(v) == 'table', assert(type(v) == 'table',
@@ -39,6 +27,10 @@ local function validate_value(v)
assert(type(v.value) == 'number') assert(type(v.value) == 'number')
elseif v.type == 'function' then elseif v.type == 'function' then
assert(type(v.value) == 'number' and v.value >= 0, "function must be a positive number") assert(type(v.value) == 'number' and v.value >= 0, "function must be a positive number")
elseif v.type == 'string' then
assert(type(v.ref) == 'number' or type(v.const_ref) == 'number')
elseif v.type == 'array' then
assert(type(v.ref) == 'number')
end end
end end
@@ -134,17 +126,6 @@ function Stack:fp_level()
return #self.fps return #self.fps
end end
function Stack:debug()
if #self.stack == 0 then return "empty" end
local ss = {}
for i,v in ipairs(self.stack) do
for _,fp in pairs(self.fps) do
if i == fp then table.insert(ss, '^ ') end
end
table.insert(ss, '[' .. format_value(v) .. '] ')
end
return table.concat(ss)
end
---------------------- ----------------------
-- -- -- --
@@ -206,24 +187,86 @@ for op,_ in pairs(ARITH_LOGIC_OPS) do
end end
end end
EXPR.sum.integer.integer = function(vm, b, a) vm:push_integer(a + b) end EXPR.sum.integer.integer = function(vm, b, a) vm:push_integer(a.value + b.value) end
EXPR.sub.integer.integer = function(vm, b, a) vm:push_integer(a - b) end EXPR.sum.string.string = function(vm, b, a) vm:push_string(vm:_extract_string(a) ..vm:_extract_string(b)) end
EXPR.mul.integer.integer = function(vm, b, a) vm:push_integer(a * b) end EXPR.sub.integer.integer = function(vm, b, a) vm:push_integer(a.value - b.value) end
EXPR.mul.integer.integer = function(vm, b, a) vm:push_integer(a.value * b.value) end
-- TODO - div -- TODO - div
EXPR.idiv.integer.integer = function(vm, b, a) vm:push_integer(math.floor(a / b)) end EXPR.idiv.integer.integer = function(vm, b, a) vm:push_integer(math.floor(a.value / b.value)) end
EXPR.mod.integer.integer = function(vm, b, a) vm:push_integer(a % b) end EXPR.mod.integer.integer = function(vm, b, a) vm:push_integer(a.value % b.value) end
EXPR.eq.integer.integer = function(vm, b, a) vm:push_integer((a == b) and 1 or 0) end EXPR.eq.integer.integer = function(vm, b, a) vm:push_integer((a.value == b.value) and 1 or 0) end
EXPR.neq.integer.integer = function(vm, b, a) vm:push_integer((a ~= b) and 1 or 0) end EXPR.neq.integer.integer = function(vm, b, a) vm:push_integer((a.value ~= b.value) and 1 or 0) end
EXPR.lt.integer.integer = function(vm, b, a) vm:push_integer((a < b) and 1 or 0) end EXPR.lt.integer.integer = function(vm, b, a) vm:push_integer((a.value < b.value) and 1 or 0) end
EXPR.lte.integer.integer = function(vm, b, a) vm:push_integer((a <= b) and 1 or 0) end EXPR.lte.integer.integer = function(vm, b, a) vm:push_integer((a.value <= b.value) and 1 or 0) end
EXPR.gt.integer.integer = function(vm, b, a) vm:push_integer((a > b) and 1 or 0) end EXPR.gt.integer.integer = function(vm, b, a) vm:push_integer((a.value > b.value) and 1 or 0) end
EXPR.gte.integer.integer = function(vm, b, a) vm:push_integer((a >= b) and 1 or 0) end EXPR.gte.integer.integer = function(vm, b, a) vm:push_integer((a.value >= b.value) and 1 or 0) end
EXPR['and'].integer.integer = function(vm, b, a) vm:push_integer(a & b) end EXPR['and'].integer.integer = function(vm, b, a) vm:push_integer(a.value & b.value) end
EXPR['or'].integer.integer = function(vm, b, a) vm:push_integer(a | b) end EXPR['or'].integer.integer = function(vm, b, a) vm:push_integer(a.value | b.value) end
EXPR.xor.integer.integer = function(vm, b, a) vm:push_integer(a ~ b) end EXPR.xor.integer.integer = function(vm, b, a) vm:push_integer(a.value ~ b.value) end
EXPR.pow.integer.integer = function(vm, b, a) vm:push_integer(a ^ b) end EXPR.pow.integer.integer = function(vm, b, a) vm:push_integer(a.value ^ b.value) end
EXPR.shl.integer.integer = function(vm, b, a) vm:push_integer(a << b) end EXPR.shl.integer.integer = function(vm, b, a) vm:push_integer(a.value << b.value) end
EXPR.shr.integer.integer = function(vm, b, a) vm:push_integer(a >> b) end EXPR.shr.integer.integer = function(vm, b, a) vm:push_integer(a.value >> b.value) end
----------------------
-- --
-- HEAP --
-- --
----------------------
local Heap = {}
Heap.__index = Heap
function Heap.new()
return setmetatable({
items = {}
}, Heap)
end
function Heap:add_value(value)
assert(value.type and (value.type == 'string' or value.type == 'array' or value.type == 'table'))
assert(value.value)
local key = math.random(1, math.maxinteger)
while self.items[key] do key = math.random(1, math.maxinteger) end
self.items[key] = value
return key
end
function Heap:get_value(key)
assert(type(key) == 'number')
return self.items[key]
end
function Heap:size()
local n = 0
for _ in pairs(self.items) do n = n + 1 end
return n
end
function Heap:call_gc(roots)
-- mark
local marked = {}
local function mark(v)
if v.type == 'string' then
if v.ref then marked[v.ref] = true end
elseif v.type == 'array' then
marked[v.ref] = true
for _,vv in ipairs(self.items[v.ref].value) do mark(vv) end
end
end
for _,v in ipairs(roots) do -- TODO - recursive, add support to array
mark(v)
end
-- sweep
for key,_ in pairs(self.items) do
if not marked[key] then
self.items[key] = nil
end
end
end
---------------------- ----------------------
-- -- -- --
@@ -237,6 +280,7 @@ VM.__index = VM
function VM.new() function VM.new()
return setmetatable({ return setmetatable({
stack = Stack.new(), stack = Stack.new(),
heap = Heap.new(),
code = Code.new(), code = Code.new(),
loc = {}, loc = {},
debug = false, debug = false,
@@ -264,10 +308,22 @@ end
function VM:push_integer(n) function VM:push_integer(n)
self.stack:push({ type = 'integer', value = n }) self.stack:push({ type = 'integer', value = n })
return self
end
function VM:push_string(str)
self.stack:push({ type = 'string', ref = self.heap:add_value({ type='string', value=str }) })
return self
end end
function VM:push_nil() function VM:push_nil()
self.stack:push({ type = 'nil' }) self.stack:push({ type = 'nil' })
return self
end
function VM:new_array()
self.stack:push({ type = 'array', ref = self.heap:add_value({ type='array', value={} }) })
return self
end end
-- --
@@ -279,6 +335,8 @@ function VM:stack_sz()
end end
function VM:is(idx, type_) function VM:is(idx, type_)
assert(type(idx) == "number")
assert(TYPE_MAP[type_])
return self.stack[idx].type == type_ return self.stack[idx].type == type_
end end
@@ -288,6 +346,81 @@ function VM:to_integer(idx)
return value.value return value.value
end end
function VM:_extract_string(value)
assert(value)
assert(value.type == 'string')
if value.const_ref then
return self.code.bytecode.constants[value.const_ref]
elseif value.ref then
return self.heap:get_value(value.ref).value
else
error("Incorrect string value (nor 'const_ref' or 'ref')")
end
end
function VM:_extract_array(value)
assert(value)
assert(value.type == 'array')
local array = self.heap:get_value(value.ref)
if type(array) ~= 'table' then error('Expected array') end
return self.heap:get_value(value.ref).value
end
function VM:to_string(idx)
local value = self.stack[idx]
if value.type ~= 'string' then error("Type error: not a string") end
return self:_extract_string(value)
end
function VM:format_value(v)
if v.type == 'integer' or v.type == 'real' then
return tostring(v.value)
elseif v.type == 'string' then
return '"' .. self:_extract_string(v) .. '"'
elseif v.type == 'array' then
local array = self:_extract_array(v)
local tbl = {}
for _,vv in ipairs(array) do table.insert(tbl, self:format_value(vv)) end
return "[" .. table.concat(tbl, ', ') .. "]"
elseif v.type == 'function' then
return '@' .. tostring(v.value)
elseif v.type == 'nil' then
return 'nil'
else
print('warning: cannot convert from type ' .. tostring(v.type))
return pprint.pformat(v)
end
end
function VM:debug_stack()
if #self.stack.stack == 0 then return "empty" end
local ss = {}
for i,v in ipairs(self.stack.stack) do
for _,fp in pairs(self.stack.fps) do
if i == fp then table.insert(ss, '^ ') end
end
table.insert(ss, self:format_value(v) .. ' ')
end
return table.concat(ss)
end
function VM:debug_heap()
local ss = { "Heap:\n" }
for k,v in pairs(self.heap.items) do
if v.type == 'string' then
table.insert(ss, string.format(' [%X] = "%s"', k, v.value))
elseif v.type == 'array' then
table.insert(ss, string.format(' [%X] = [', k))
local t = {}; for _,vv in ipairs(v.value) do t[#t+1] = self:format_value(vv) end
table.insert(ss, table.concat(t, ", ") .. ']')
else
error('Unsupported type in heap')
end
table.insert(ss, "\n")
end
return table.concat(ss)
end
-- --
-- code execution -- code execution
-- --
@@ -329,9 +462,9 @@ function VM:_run_until_return()
end end
end end
function VM:_debug_stack() function VM:_print_stack()
if self.debug then if self.debug then
print(self.stack:debug()) print(self:debug_stack())
end end
end end
@@ -345,13 +478,30 @@ function VM:_step()
-- stack operations -- stack operations
-- --
if op.operator == 'pushi' then if op.operator == 'pushn' then
self:push_nil()
elseif op.operator == 'pushi' then
self:push_integer(op.operand) self:push_integer(op.operand)
elseif op.operator == 'pushf' then elseif op.operator == 'pushf' then
assert(op.operand >= 0) assert(op.operand >= 0)
self.stack:push({ type = 'function', value = op.operand }) self.stack:push({ type = 'function', value = op.operand })
elseif op.operator == 'pushc' then
local c = self.code.bytecode.constants[op.operand]
if type(c) == 'string' then
self.stack:push({ type = 'string', const_ref = op.operand })
elseif type(c) == 'number' then
error('REAL consts not supported for now.')
end
elseif op.operator == 'newa' then
self:new_array()
elseif op.operator == 'pop' then
self.stack:pop()
elseif op.operator == 'dup' then elseif op.operator == 'dup' then
self.stack:push(self.stack:peek()) self.stack:push(self.stack:peek())
@@ -375,6 +525,20 @@ function VM:_step()
local a = self.stack[op.operand] local a = self.stack[op.operand]
self.stack:push(a) self.stack:push(a)
--
-- table and array operations
--
elseif op.operator == 'seti' then
local array_ref = self.stack[-2]
local array = self:_extract_array(array_ref)
array[op.operand+1] = self.stack:pop()
elseif op.operator == 'geti' then
local array_ref = self.stack[-1]
local array = self:_extract_array(array_ref)
self.stack:push(array[op.operand+1])
-- --
-- logic/arithmetic operations -- logic/arithmetic operations
-- --
@@ -382,7 +546,7 @@ function VM:_step()
elseif ARITH_LOGIC_OPS[op.operator] then elseif ARITH_LOGIC_OPS[op.operator] then
local a = self.stack:pop() local a = self.stack:pop()
local b = self.stack:pop() local b = self.stack:pop()
EXPR[op.operator][a.type][b.type](self, a.value, b.value) EXPR[op.operator][a.type][b.type](self, a, b)
-- --
-- function management -- function management
@@ -397,7 +561,7 @@ function VM:_step()
self.stack:pop_fp() self.stack:pop_fp()
self.stack:push(v) self.stack:push(v)
table.remove(self.loc) table.remove(self.loc)
self:_debug_stack() self:_print_stack()
return return
-- --
@@ -406,14 +570,14 @@ function VM:_step()
elseif op.operator == 'jmp' then elseif op.operator == 'jmp' then
loc.pc = self.code:find_label(loc.f_id, op.operand) loc.pc = self.code:find_label(loc.f_id, op.operand)
self:_debug_stack() self:_print_stack()
return return
elseif op.operator == 'bz' then elseif op.operator == 'bz' then
local v = self.stack:pop() local v = self.stack:pop()
if is_zero(v) then if is_zero(v) then
loc.pc = self.code:find_label(loc.f_id, op.operand) loc.pc = self.code:find_label(loc.f_id, op.operand)
self:_debug_stack() self:_print_stack()
return return
end end
@@ -421,10 +585,25 @@ function VM:_step()
local v = self.stack:pop() local v = self.stack:pop()
if not is_zero(v) then if not is_zero(v) then
loc.pc = self.code:find_label(loc.f_id, op.operand) loc.pc = self.code:find_label(loc.f_id, op.operand)
self:_debug_stack() self:_print_stack()
return return
end end
--
-- memory management
--
elseif op.operator == 'gc' then
-- if self.debug then
-- print('About to run GC, current heap:')
-- print(self:debug_heap())
-- end
self.heap:call_gc(self.stack.stack)
-- if self.debug then
-- print('GC executed, this is the heap:')
-- print(self:debug_heap())
-- end
-- --
-- instruction not found -- instruction not found
-- --
@@ -433,7 +612,7 @@ function VM:_step()
error("Unknown operator '" .. tostring(op.operator) .. "'") error("Unknown operator '" .. tostring(op.operator) .. "'")
end end
self:_debug_stack() self:_print_stack()
loc.pc = loc.pc + op.instruction_size loc.pc = loc.pc + op.instruction_size
end end