wip(0.S1, 0.S2): state records, mut and read-only state parameters, entry injection, module var refused; ludic migrate state

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 13:45:09 +03:00
parent 63a1fa1378
commit 1e8b5b0523
35 changed files with 1227 additions and 30 deletions

View file

@ -951,6 +951,7 @@ function emit_call(e: Node) -> Val {
let rtname = `rt_{name}`
fn2 = find_fn(rtname)
if (fn2 == null) { perr(`unknown function {name}`) }
state_inject(e, fn2) # 0.S: a runtime built-in's states, supplied
cname = rtname
}
vis_check(fn2, name)

View file

@ -166,6 +166,8 @@ function emit_test_runner() -> void {
let rn1 = emit_bind(`add i32 {rn}, 1`)
emit(` store i32 {rn1}, ptr %ran\n`)
emit(" store i32 0, ptr @L_test_fail\n")
# 0.S: every state fresh for each test - the states are what changes, and they are made here
if i > 0 { emit(" call void @L_init_globals()\n") }
emit(` call void @fn__test_{itoa(i)}()\n`)
let f = emit_bind("load i32, ptr @L_test_fail")
let isbad = emit_bind(`icmp ne i32 {f}, 0`)

View file

@ -7,11 +7,14 @@ function is_fn_type(t: pointer) -> bool {
return len(t) > 3 and t[0] == 'f' and t[1] == 'n' and t[2] == '('
}
# the function type of a declared function, in ptype's own spelling
# 0.S: a function value is the function with its leading states supplied (state.ludic), so its
# type leaves them out
function fn_sig_of(d: Node) -> pointer {
var out = "fn("
var i = 0
var i = state_lead(d)
let first = i
while i < len(d.kids) {
if i > 0 { out = out + "," }
if i > first { out = out + "," }
out = out + d.kids[i].ty
i += 1
}
@ -63,8 +66,53 @@ function emit_fnref(e: Node) -> Val {
let d = find_fn(e.s)
if d == null { perr(`fn {e.s}: no function called {e.s}`) }
vis_check(d, e.s)
if state_lead(d) > 0 { return val(emit_inj_thunk(d), fn_sig_of(d)) }
return val(`@fn_{e.s}`, fn_sig_of(d))
}
# how many of a function's parameters, from the first, are states
function state_lead(d: Node) -> int {
var n = 0
while n < len(d.kids) and d.kids[n].kind == N_PARAM and is_state_ty(d.kids[n].ty) { n += 1 }
return n
}
# @fn_<name>$inj: the function with its leading states loaded from their instances - what a
# function value of it calls, so whoever calls the value (a system runner, a port, a package)
# supplies none of them. Written once per function, beside the rest.
var g_inj_done: []pointer = new []pointer
function emit_inj_thunk(d: Node) -> pointer {
let name = `@fn_{d.s}$inj`
var i = 0
while i < len(g_inj_done) {
if (g_inj_done[i] == d.s) { return name }
i += 1
}
push(g_inj_done, d.s)
let k = state_lead(d)
var rt = "void"
if d.ty != null { rt = llty(d.ty) }
var params = ""
var args = ""
var body = ""
i = 0
while i < len(d.kids) {
let p = d.kids[i]
if i < k {
body = body + ` %s{itoa(i)} = load ptr, ptr @g_state${p.ty}\n`
if len(args) > 0 { args = args + ", " }
args = args + `ptr %s{itoa(i)}`
} else {
if len(params) > 0 { params = params + ", " }
params = params + `{llty(p.ty)} %p{itoa(i)}`
if len(args) > 0 { args = args + ", " }
args = args + `{llty(p.ty)} %p{itoa(i)}`
}
i += 1
}
var call = ` call {rt} @fn_{d.s}({args})\n ret void\n`
if not (rt == "void") { call = ` %r = call {rt} @fn_{d.s}({args})\n ret {rt} %r\n` }
emith(`define {rt} {name}({params}) {{\nentry:\n{body}{call}}}\n\n`)
return name
}
# a call through a value of a function type
function emit_indirect_call(fv: Val, e: Node) -> Val {
let ptys = fn_ty_params(fv.ty)