Production pass: floats, GC, type checking, and tooling

Adds floats, bitwise ops, UTF-8 strings with methods, try/catch,
closures, generics validation, a compile-time type checker, a
mark-and-sweep GC, source-level debug info, constant folding, and
the repl/fmt/package-manager commands.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
This commit is contained in:
allexanderbergmns
2026-08-25 14:29:15 +02:00
parent b0a4da7e2f
commit 9b22be48a5
27 changed files with 3638 additions and 166 deletions
+211
View File
@@ -0,0 +1,211 @@
// gc.v — mark-and-sweep garbage collector for the VuurRaaf VM.
//
// The collector runs between opcodes (never mid-instruction, so no live value
// is ever hidden in a temporary). Roots are the value stack, which also holds
// every frame's locals (they live at bp+idx). Arrays and structs are traced
// transitively. The sweep compacts each pool and remaps surviving handles.
//
// String constants baked into the bytecode (op_push_s operands) live in
// strings[0..const_strs] and are never collected; only runtime-allocated
// strings participate in the cycle.
module vm
const gc_alloc_trigger = 4096
// collect marks all heap values reachable from the stack, then sweeps and
// compacts the pools, remapping handles on the stack and inside live
// containers.
fn (mut v Vm) collect() {
// ---- mark ----
mut str_mark := []bool{len: v.strings.len}
mut arr_mark := []bool{len: v.arrays.len}
mut struct_mark := []bool{len: v.structs.len}
mut float_mark := []bool{len: v.floats.len}
mut closure_mark := []bool{len: v.closures.len}
for i in 0..v.sp {
v.mark_value(v.stack[i], mut str_mark, mut arr_mark, mut struct_mark, mut float_mark, mut closure_mark)
}
// ---- remap tables: old index -> new index (-1 = collected) ----
mut str_new := []int{len: v.strings.len, init: -1}
mut arr_new := []int{len: v.arrays.len, init: -1}
mut struct_new := []int{len: v.structs.len, init: -1}
mut float_new := []int{len: v.floats.len, init: -1}
mut closure_new := []int{len: v.closures.len, init: -1}
mut nstr := v.const_strs
for i in v.const_strs..v.strings.len {
if str_mark[i] {
str_new[i] = nstr
nstr++
}
}
mut narr := 0
for i in 0..v.arrays.len {
if arr_mark[i] {
arr_new[i] = narr
narr++
}
}
mut nstruct := 0
for i in 0..v.structs.len {
if struct_mark[i] {
struct_new[i] = nstruct
nstruct++
}
}
mut nfloat := 0
for i in 0..v.floats.len {
if float_mark[i] {
float_new[i] = nfloat
nfloat++
}
}
mut nclosure := 0
for i in 0..v.closures.len {
if closure_mark[i] {
closure_new[i] = nclosure
nclosure++
}
}
// ---- rewrite live references ----
for i in 0..v.sp {
v.stack[i] = v.remap(v.stack[i], str_new, arr_new, struct_new, float_new, closure_new)
}
for h in 0..v.arrays.len {
if arr_mark[h] {
for j in 0..v.arrays[h].len {
v.arrays[h][j] = v.remap(v.arrays[h][j], str_new, arr_new, struct_new, float_new, closure_new)
}
}
}
for h in 0..v.structs.len {
if struct_mark[h] {
for j in 0..v.structs[h].fields.len {
v.structs[h].fields[j].val = v.remap(v.structs[h].fields[j].val, str_new, arr_new, struct_new, float_new, closure_new)
}
}
}
// ---- compact pools ----
mut strings := v.strings[..v.const_strs]
for i in v.const_strs..v.strings.len {
if str_mark[i] {
strings << v.strings[i]
}
}
v.strings = strings
mut arrays := [][]i64{}
for i in 0..v.arrays.len {
if arr_mark[i] {
arrays << v.arrays[i]
}
}
v.arrays = arrays
mut structs := []StructVal{}
for i in 0..v.structs.len {
if struct_mark[i] {
structs << v.structs[i]
}
}
v.structs = structs
mut floats := []f64{}
for i in 0..v.floats.len {
if float_mark[i] {
floats << v.floats[i]
}
}
v.floats = floats
mut closures := []Closure{}
for i in 0..v.closures.len {
if closure_mark[i] {
closures << v.closures[i]
}
}
v.closures = closures
}
// mark_value traces a value and everything it references using an explicit
// worklist (arrays of arrays can nest deeply; recursion could overflow).
fn (mut v Vm) mark_value(x i64, mut str_mark []bool, mut arr_mark []bool, mut struct_mark []bool, mut float_mark []bool, mut closure_mark []bool) {
mut work := []i64{}
work << x
for work.len > 0 {
val := work.pop()
match v.tag(val) {
tag_str {
h := v.hand(val)
if h >= v.const_strs && h < str_mark.len && !str_mark[h] {
str_mark[h] = true
}
}
tag_arr {
h := v.hand(val)
if h >= 0 && h < arr_mark.len && !arr_mark[h] {
arr_mark[h] = true
for el in v.arrays[h] {
work << el
}
}
}
tag_struct {
h := v.hand(val)
if h >= 0 && h < struct_mark.len && !struct_mark[h] {
struct_mark[h] = true
for f in v.structs[h].fields {
work << f.val
}
}
}
tag_float {
h := v.hand(val)
if h >= 0 && h < float_mark.len && !float_mark[h] {
float_mark[h] = true
}
}
tag_closure {
h := v.hand(val)
if h >= 0 && h < closure_mark.len && !closure_mark[h] {
closure_mark[h] = true
}
}
else {}
}
}
}
// remap translates a handle to its post-compaction index, leaving integers
// and uncollected values untouched.
fn (mut v Vm) remap(x i64, str_new []int, arr_new []int, struct_new []int, float_new []int, closure_new []int) i64 {
match v.tag(x) {
tag_str {
h := v.hand(x)
if h >= v.const_strs && h < str_new.len && str_new[h] >= 0 {
return v.mkstr(str_new[h])
}
}
tag_arr {
h := v.hand(x)
if h >= 0 && h < arr_new.len && arr_new[h] >= 0 {
return v.mkarr(arr_new[h])
}
}
tag_struct {
h := v.hand(x)
if h >= 0 && h < struct_new.len && struct_new[h] >= 0 {
return v.mkstruct_handle(struct_new[h])
}
}
tag_float {
h := v.hand(x)
if h >= 0 && h < float_new.len && float_new[h] >= 0 {
return v.mkfloat(float_new[h])
}
}
tag_closure {
h := v.hand(x)
if h >= 0 && h < closure_new.len && closure_new[h] >= 0 {
return v.mkclosure(closure_new[h])
}
}
else {}
}
return x
}